stMind

about Tech, Computer vision and Machine learning

Multi Head Attentionの概要を掴む

DeepMindのResearch Scientistの方がツイートしていたMulti Head Attentionのスレッドの紹介。 全部で12個。英語だけど、日本語に翻訳すれば10分くらいで読めるし、コードサンプルと図もあって短い時間でMHAの概要が掴めると感じた。

以下は、自分が理解した内容のメモ書き。

2/n. Attentionとは

例として、sentiment analysisをしたいとする。「Attention is not too shabby.」 shabbyはネガティブを示唆しているけれど、not shabbyであればネガティブではなくポジティブ。正しく分類するためには、文中の全ての単語を考慮しないといけない。

3/n. 全ての単語を考慮する

全ての単語を考慮する最もシンプルな方法は、全ての単語をネットワークに入力すること。これで十分か?というと、そうではない。各単語を考慮するだけでなく、他の単語との関係も理解しないといけない。つまり、notはshabbyに注意を向けているということが重要。そこで出てくるのが、query, key, value(Q,K,V)。

4/n. Value

単語を線形層に通し、そこから得られた出力をValueと呼ぶ。

words.shape # (T,in_dim)
values = Linear(words)
values.shape # (T, D)

では、Value同士の関係をどの様にエンコードするか?それぞれのValueを混合(sumをする)することで、関係を見ることができる。ただし、これには問題がある。

out = np.ones((T, T)) @ values # (T, T) @ (T, D)
out.shape # (T, D)

5. 問題

単純な総和の問題は、全ての関係が等しいと想定していること。isとtoo、notとshabbyでいえば、明らかに後者の方が感情分類に重要。

6. QueryとKey

全てが等しい関係ではなく、word_jに対してword_iがどれだけ有用かを表す様にしたい。そこで、Valueと同じように単語を線形層に通して得られるQueryとKeyを導入する。QueryとKeyから求めた重みWeightsにおいて、w_ijはi番目のQueryに対して、j番目のKeyの間の内積に比例した値になっているはず。

queries.shape # (T, D)
keys.shape # (T, D)
weights = queries @ keys.T # (T, T)

7. Rescaling

QueryとKeyから得られたWeightsの各要素を \sqrt{D}でrescale。

8. Single Head Attention

Weightsの列ベクトルについてSoftmaxを適用して正規化する。直感的には、Qは単語Kに対してのどの程度有用かという質問で、内積が高いということは非常に有用、逆に内積が低い時はあまり役に立たないということ。これがAttention。

attention = softmax(Q @ K.T / sqrt(D), dim=1) @ Value # [T, D] = [T, T] @ [T, D]

9. Single Head Self Attention

10. Why Multi Head

なぜMulti Headなのか?Single Headだと学習データにオーバーフィットするかもしれない。過学習対策の一般的な戦略であるアンサンブルで、複数のAttentionによりロバストな結果を獲得する。(Multi Head Attentionは、Single Head Attentionの[T, D]をN個連結したもので、[T, NxD])

11. Multi Head Self Attention

forwardの入力xは、[T, D]をN個分concatした[B, T, ND](ただし、Bはバッチサイズで、ND = C)と想定している。

データセットをtrain/val/testに分割するコードをnumpyで簡潔に記述する

tl;dr

numpy.splitを使って、aryを3つのsubarrayに分割する。

import numpy as np
train, val, test = 
    np.split(ary, [int(len(ary) * .6), int(len(ary) * .8)])

簡単な説明

データをtrain/testに分割する時、scikit-learnのtrain_test_splitを使うことが多いと思いますが、 train/val/testと分割しようとすると、一度train/testと分けた後でtestに対して再度train_test_splitするなどが必要です。

numpy.split(ary, [a, b])は、第一引数に指定されたaryに対してary[:a], ary[a:b], ary[b:]と分割されるため、一回の処理でデータセットをtrain/val/testに分割することができます。

train/val/testで60/20/20に分割するときは、

np.split(ary, [int(len(ary) * .6), int(len(ary) * .8)])

と指定すればOKです。

ただし、np.splitは単純に分割するだけですので、各セットのラベルの分布を維持するtrain_test_splitのstratifyのような分割をしたい場合には、別途ケアする必要があります。

参照先

以上の内容は、下記stackoverflowの投稿を参照したものです。

stackoverflow.com

Pythonで行列から行と列を取得するコードを簡潔に記述する

tl;dr

アスタリスクによるアンパックとzipを組み合わせる。

for row_vals, col_vals in zip(matrix, zip(*matrix)):
    print(row_vals, col_vals)

簡単な説明

まず、matrixは次のようなリストとする。

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

行は難しいことはなく、問題は列の方。 ここで、アスタリスクでアンパック→アンパックされた各リストに対して、zipで一つずつ要素を取り出してタプルとして返す、という処理をすることで行列の転置と同じことが実現できる。

print(*matrix)
# [1, 2, 3] [4, 5, 6] [7, 8, 9]

for col_vals in zip(*matrix):
    print(col_vals)
# (1, 4, 7)
# (2, 5, 8)
# (3, 6, 9)

従って、最初のコードを実行すると、行と列を一つずつ取り出すことが出来る。

for row_vals, col_vals in zip(matrix, zip(*matrix)):
    print(row_vals, col_vals)
# [1, 2, 3] (1, 4, 7)
# [4, 5, 6] (2, 5, 8)
# [7, 8, 9] (3, 6, 9)

13年かけてApache Solrのコミッターになった人が語る、OSSコミッターになりたい人向けアドバイス

Apache SolrはOSS全文検索システムですが、そのApache Solrのコミッターになった人が、オープンソースプロジェクトのコミッターになりたい人向けにアドバイスをしているブログ記事のメモです。

opensourceconnections.com

最初にパッチを投稿したのが2007年7月12日、4662日後の2020年4月6日にコミッターになったということで、その期間は約13年! ご自身の経験をもとに、コミッターになるためのアドバイスが書いてあります。以下、その内容です。

  1. まず、そのプロジェクトの文化を知ることから始める。どのように意思決定されているのか?どんなツールを使っているのか?様々な頭字語は何を意味するのか?メーリングリストに参加し、すべてのコミットを読もう。
  2. 小さなことから始めて、徐々にやることを増やしていく
    • 既存のパッチを取り込み、テストする。 パッチを最新のコードベースに更新する。 学んだことを文書化する。
    • ドキュメントに新鮮な目を向ける。何かの仕組みについて頭を悩ませることがあったら、その都度、ドキュメントを修正してみる。これは、すぐに貢献できる強力な方法で、プロジェクトに参加するための最も早い方法であり、かつ、認知的な負荷が最も少ない方法。
    • メーリングリストで質問に答える。質問に対する妥当な回答を明確にできれば、どれだけ学んだかが分かる。
    • バグフィックスバグフィックスバグフィックス。 正しい解決法が簡単にわかり、答えが明らかなバグを選ぶ。正しい解決方法が非常に曖昧な場合は、おそらくあまり賛同を得られない。コミッターは、時間があるときに、あなたの修正を適用するということを忘れないように。
  3. コードを書き始める準備はできた?プロジェクトの中核となる基盤をリファクタリングするのはやめよう(少なくとも今は)。その代わりに、パイロットフィッシュのようになって、アクティブに活動しているコアコミッターの人と共に泳ぎましょう(意訳)。プロジェクトのビジョンを受け入れ、彼らが行っている大きなタスクの関連タスクを拾ってみよう。そして、ユニットテストを書いてみる、リファクタリングの機会を探す、複数のプラットフォームで手動テストを行う。あなたが貢献していること(プロジェクトを加速させていること)を彼らが理解したら、自分のチケットをいくつか割り当ててもらえるように働きかける。これが、コミッターに直結しているのを何度も見てきたし、もしこのようにしていたら、もっと早くコミッターになっていたかも。

13年間も貢献し続けたことが単純にすごいことだなと思いますし、そうやってずっと貢献してきた人をコミッターとして迎え入れたApache Solrのプロジェクトも良いコミュニティだなと、偉そうにも読んでいて思いました。

姿勢推定の関節ヒートマップデコードと標準シフト処理の有効性

姿勢推定モデルにおいては、関節毎のヒートマップ表現がよく使われており、OpenPoseを始め、多くの論文で有効性が確認されています。さらに、関節毎のヒートマップから、オリジナル画像における座標へ変換するデコード処理は、これまで十分に考察が行われてこなかったのに対して、DarkPoseと呼ばれる新しいデコード処理を提案する論文がCVPR2020で発表されました。

arxiv.org

論文では、様々な姿勢推定モデルにDarkPoseのデコード処理をアドオンすることで、検出精度が向上したことが報告されていたのですが、注目したのは、デコード処理を比較した以下の結果です。

f:id:satojkovic:20211107180907p:plain

No shiftingは、ヒートマップの最大値の座標をそのまま関節座標とするもので、Standard shiftingは、最大値の座標mと次に大きい値の座標sを求め、mからsへ0.25だけシフトする処理のことで、Stacked Hourglassの論文で使われたものです。このようなサブピクセルレベルのシフト処理は、ダウンサンプリングされたヒートマップ画像における位置が、オリジナル画像の正確な位置とは一致しないことを補うことが目的で、経験的な値として0.25とされた様ですが、大変な効果があることに驚きました。経験的な固定値ではなく、分布を考慮してシフトするのがDarkPoseで、さらに向上が見られているのですが、データセットにおける有効性は確認した上で、シンプルな実装としてStandard shiftingを使っても良いのかもしれません。

TF OD APIのpipeline.configの読み方(概要編)

Tensorflow Object Detection API(TF OD API)では、Protocol Buffersを使用して学習と評価のプロセスを設定しています。学習と評価用のスキーマは、tensorflow/models/research/object_detection/protos/pipeline.configに定義されており、ハイレベルでは5つのパートに分かれています。

  1. 特徴抽出器の指定など、モデルの詳細設定(model)
  2. バッチサイズやデータ拡張オプションなど、モデルの学習用の設定(train_config)
  3. 学習用の入力画像、正解ラベル、正解BoundingBoxなどのテンソルを生成するinput readerの設定(train_input_reader)
  4. バッチサイズやデータ数など、モデルの評価用の設定(eval_config )
  5. train_input_readerと同様で、評価用のinput readerの設定(eval_input_reader)

それぞれの詳細は、別途まとめをするとして、ここでは基本的な設定について触れることにする。

1. model configuration

モデルの詳細設定は、メタアーキテクチャにより異なり、指定可能なアーキテクチャは3種類。

  1. faster_rcnn
  2. ssd
  3. center_net

それぞれのアーキテクチャの中で、feature_extractorや入力画像の前処理のimage_resizerの設定など、細かく指定ができるようになっている。これは、それぞれtensorflow/models/research/object_detection/protosの中に、faster_rcnn.proto、ssd.proto、center_net.protoにて定義されている。

2. train_config

学習の設定で、29種類の項目がある。基本的な項目としては、以下のようなものがある。

  • batch_size
  • num_steps
    • 学習ステップ数。0とすると上限なしとなる。
  • fine_tune_checkpoint
    • リストア対象のチェックポイントへのパス。Tensorflow2 Detection Model Zooからダウンロードしたpretrainedモデルなどを使用。

3. train_input_reader

学習用の入力データ生成に関する設定。基本は、検出対象クラス名とインデックスの対応を記述したlabel mapと、学習データを記録したtfrecordを使った学習をすることが多いので、それらを指定するところとなる。その他、30種類ほど指定可能な項目がある。

  • label_map_path
    • label mapファイルのパス
  • tf_record_input_reader もしくは external_input_reader
    • tfrecordの場合は、tf_record_input_readerの中で、input_pathとして指定する。

4. eval_config

evaluationのmetricsなど、評価用の設定(35種類)。

5. eval_input_reader

3のtrain_input_readerと同じで、こちらはevaluation用。こちらもtfrecordを使うことが多いので、3と同じ様にlabel_map_pathとtf_record_input_readerを指定する。加えて、データの処理順序をshuffleしないようにする(shuffle: false)、num_epochsは1回だけ(num_epochs: 1)、などもよく使う。

最後に

見てみると、それぞれの大項目には結構な数の設定項目があり、基本的にはmodel zooの設定をそのまま使うことが多いけれど、自分のデータセットでチューニングする時には知っておくと良さそうなので、それぞれまた詳しく調べてみることにする。

TF OD APIにおいてどのモデルを使うのかは、どのように判断されるのか?

tl;dr

pipeline configに含まれるmodelのアーキテクチャ名から判断され、アーキテクチャに応じたbuildメソッドが使われる。 例えば、以下はfaster_rcnn_resnet50_v1_640x640_coco17_tpu-8のpipeline.configファイル。 f:id:satojkovic:20211010212331p:plain

具体的なところ

configはobject_detection/protos/pipeline.protoに定義され、model以外に5つのフィールドがある。

f:id:satojkovic:20211010211715p:plain

また、modelは対応している有効なmeta architectureとしてfaster_rcnn、ssd、center_netの三種類がobject_detection/protos/model.protoに定義されている。 f:id:satojkovic:20211010212524p:plain

そして、model_main_tf2.pyを使って、引数のpipeline_config_pathで使いたいモデルのpipeline configファイルを指定して実行すると、model_lib_v2.pyのeval_continuously()からmodel_builder.pyのbuild()が呼ばれて、アーキテクチャ名に応じたbuildメソッドを実行するようになっている。 f:id:satojkovic:20211010213240p:plain