Ryfamateが採用しているモデル構造の工夫の一つである位置エンコーディングについて検証する。
位置エンコーディング
畳み込みニューラルネットワークは、位置不変性という特性を持っており、画像の位置をずらした場合でも同じように認識される。
将棋では、位置不変性という特定は局所的には活かすことができる(たたえば、桂馬で両取りする形は盤のどこであっても似た意味を持つ)が、大局的には位置の考慮も必要である。
畳み込みニューラルネットワークを複数の層で構成する場合、局所的な特徴を段階的により広く捉えていくため、大局的に駒の配置を捉えることができる。
また、プーリングを行わない場合、各座標がその位置の情報を保持するため、入力に位置の情報を与えなくても位置を考慮して盤を認識できる。
しかし、将棋では局所的な特徴でも自陣と相手陣によって大きく意味が変わるものがある(例えば、駒が成れる場合と成れない場合で意味が大きく変わる)。
そのような場合、局所的な特徴を捉える際に絶対的な位置を考慮できる方がよい可能性がある。
したがって、入力に位置をエンコーディングした特徴量を加えることで、位置に依存した特徴量を学習することには意味があるかもしれない。
ネットワーク構成
学習可能な位置エンコーディングを入力に加える際、活性化関数の前に加えるか、後に加えるか2パターンが考えられる。
活性化関数の前
活性化関数の後
どちらでも学習できると思われるが、2パターン検証する。
実験条件
ラージカーネルの検証で使用した、20ブロック256フィルタのResNetに対して9x1と9x1と1x1のブロックを5ブロック間隔で入れ替えたモデルをベースのモデルとする。
訓練と評価の条件は、ラージカーネルの検証と同じである。
結果
モデル | 方策損失 | 価値損失 | 方策正解率 | 価値正解率 |
---|---|---|---|---|
ベースモデル | 1.4281 | 0.4636 | 0.5259 | 0.7615 |
位置エンコーディング(活性化関数前) | 1.4282 | 0.4631 | 0.5263 | 0.7616 |
位置エンコーディング(活性化関数後) | 1.4272 | 0.4638 | 0.5263 | 0.7610 |
位置エンコーディングを活性化関数後に加えたモデルの方策損失が少し下がっているが、価値の損失はわずかに高くなっており、精度にはっきりした違いは見られない。
推論速度
推論時は固定値を加算するのみであるため、推論速度はほとんど変わらないと予測できるが、実際に測定して確認した。
floodgateから抽出した100局面で4回測定した平均NPSは、以下の通り。
100局面平均値について、4回の測定結果をベースモデルとの間でt検定した際のp値も記載する。
RTX 4090 1枚を使用した。
モデル | NPS | p値 |
---|---|---|
ベースモデル | 47323 | - |
位置エンコーディング(活性化関数前) | 46922 | 0.0118 |
位置エンコーディング(活性化関数後) | 47359 | 0.7839 |
活性化関数の後に位置エンコーディングを加えたモデルは、推論速度に差はない。
活性化関数の前に加えた場合は、有意に遅くなっている。
そこで、TensorRTの最適化の結果に違いがないか確認した。
TensorRTでモデルを最適化する際に、以下の処理を追加してトレース出力するように設定して、ログを比較した。
config->setProfilingVerbosity(nvinfer1::ProfilingVerbosity::kDETAILED);
以下の箇所で差分が確認できた。
ベースモデル
Layer(CaskGemmConvolution): /l1_2/Conv + /Add_1 + /act/Relu, Tactic: 0x0000000000020765, Reformatted Input Tensor 0 to /l1_2/Conv + /Add_1 + /act/Relu (Half[-1,57:8,9,9]), /Add_output_0 (Half[-1,256:8,9,9]) -> /act/Relu_output_0 (Half[-1,256:8,9,9])
活性化関数の前
Layer(CaskGemmConvolution): /l1_2/Conv + /Add_1 + /act/Relu, Tactic: 0x000000000002091f, Reformatted Input Tensor 0 to /l1_2/Conv + /Add_1 + /act/Relu (Half[-1,57:8,9,9]), /Add_output_0 (Half[-1,256:8,9,9]) -> /act/Relu_output_0 (Half[-1,256:8,9,9]) Layer(Scale): pos + /Add_2, Tactic: 0x0000000000000000, /act/Relu_output_0 (Half[-1,256:8,9,9]) -> /Add_2_output_0 (Half[-1,256:8,9,9])
活性化関数の後
Layer(CaskConvolution): /l1_2/Conv + /Add_1, Tactic: 0xa1a20ea714d420f4, Reformatted Input Tensor 0 to /l1_2/Conv + /Add_1 (Half[-1,57:8,9,9]), /Add_output_0 (Half[-1,256:8,9,9]) -> /Add_1_output_0 (Half[-1,256:8,9,9]) Layer(Scale): pos + /Add_2 + /act/Relu, Tactic: 0x0000000000000000, /Add_1_output_0 (Half[-1,256:8,9,9]) -> /act/Relu_output_0 (Half[-1,256:8,9,9])
活性化関数の前に加えた場合、ベースモデルと同じLayer(CaskGemmConvolution)の後に、Layer(Scale)が追加されている。
活性化関数の後に加えた場合、Layer(CaskConvolution)とLayer(Scale)で構成されており、活性化関数はLayer(Scale)にある。
最適化の結果が異なることが確認できた。
その違いが推論速度の差につながったと考える。
活性化関数の後に加えた方が、ベースモデルから推論速度が下がらず良いようである。