前回の続き。
今回は、入力特徴量の作成処理を実装し、ベースラインとして単純なTransformerモデルを学習させた。
入力特徴量
盤上の駒と駒の種類ごとの効き、効き数、持ち駒、王手をトークンに埋め込んで表現する。
盤
盤上の駒は、各マスを1トークンに対応させて表現する。
駒の種類を単語とし、先手と後手の駒は別の単語とする。
駒がないマスは、ないことを示す単語を割り当てた方がよいかもしれないが、一旦なしとする。
駒の種類ごとの効きは、効きのあるマスのトークンにEmbeddingBagで埋め込む。
駒の種類ごとに別の単語とし、先手と後手の駒は別の単語とする。
効きのあるマスに、駒がある場合は、その駒の単語と、効きを表す単語の両方が1つのトークンに埋め込まれる。
効き数は、マスごとに効きのある駒の数を最大3までカウントする。
効き数は、それぞれ別の単語とする。先手と後手の利き数は別の単語とする。
効き数が2の場合、効き数1と効き数2の単語の両方がEmbeddingBagで1つのトークンに埋め込まれる。
スカラー値にしない理由は、効き数1と効き数2により、判断が全く変わる場合があるため、量として扱うには適していないためである。
持ち駒
持ち駒は、持ち駒の種類を1トークンに対応させて表現する。
先手と後手の持ち駒は別のトークンとする。
持ち駒の数は、各数値を別の単語とし、EmbeddingBagで1つのトークンに埋め込む。
持ち駒の数が2の場合、持ち駒1と持ち駒2の単語の両方が1つのトークンに埋め込まれる。
スカラー値にしない理由は、持ち駒の数1と持ち駒の数2により、判断が全く変わる場合があるため、量として扱うには適していないためである。
歩は最大8枚までカウントする。
また、駒の種類と、駒の数を別のトークンに分けて表現することも考えたが、トークン長が計算量に2乗で効いてくるため、1つのトークンに埋め込むことでトークン長を節約する。
王手
王手がかかっているかを1トークンに対応させて表現する。
位置エンコーダ
位置エンコーダは、位置ごとに学習可能なエンコーダとする。
Transformerモデル
ベースラインとして、PyTorchの標準のTransformerEncoderLayerとTransformerEncoderを使用したシンプルなモデルとした。
import torch import torch.nn as nn import torch.nn.functional as F class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len=96): super(PositionalEncoding, self).__init__() self.pos_encoding = nn.Parameter(torch.zeros(1, max_len, d_model)) def forward(self, x): return x + self.pos_encoding[:, :x.size(1)] class PolicyValueNetwork(nn.Module): def __init__(self, ntoken=96, d_model=256, nhead=8, dim_feedforward=256, num_layers=8, dropout=0.1): super(PolicyValueNetwork, self).__init__() self.encoder = nn.EmbeddingBag(2892, d_model, mode="sum", padding_idx=0) self.pos_encoder = PositionalEncoding(d_model, ntoken) transformer_layer = nn.TransformerEncoderLayer(d_model, nhead, dim_feedforward, dropout, activation="gelu", batch_first=True) self.transformer = nn.TransformerEncoder(transformer_layer, num_layers) self.policy = nn.Linear(d_model * ntoken, 1496) self.value_fc1 = nn.Linear(d_model * ntoken, 256, bias=False) self.value_norm = nn.BatchNorm1d(256) self.value_fc2 = nn.Linear(256, 1) self.ntoken = ntoken self.d_model = d_model def forward(self, src): x = self.encoder(src.type(torch.int64).view(-1, 35)) x = x.view(-1, self.ntoken, self.d_model) x = self.pos_encoder(x) x = self.transformer(x) x = x.flatten(1) policy = self.policy(x) value = F.relu(self.value_norm(self.value_fc1(x))) value = self.value_fc2(value) return policy, value
学習結果
floodgateの2019年から2013年のR3800以上の棋譜から作成した8,872,952局面を訓練データ、2017年~2018年6月のfloodgateのR3500以上の棋譜からサンプリングした856,923局面を評価データとして、バッチサイズ1024で4エポック学習した。
比較のために、ResNet20ブロック256フィルタのモデルも同条件で学習した。
パラメータ数
TransformerとResNetのパラメータはそれぞれ以下の通り。
Transformer | ResNet |
---|---|
46,990,553 | 24,363,714 |
Transformerのパラメータ数の内訳は以下の通り。
=============================================================================================== Layer (type:depth-idx) Output Shape Param # =============================================================================================== PolicyValueNetwork [1, 1496] -- ├─EmbeddingBag: 1-1 [96, 256] 740,352 ├─PositionalEncoding: 1-2 [1, 96, 256] 24,576 ├─TransformerEncoder: 1-3 [1, 96, 256] -- │ └─ModuleList: 2-1 -- -- │ │ └─TransformerEncoderLayer: 3-1 [1, 96, 256] 395,776 │ │ └─TransformerEncoderLayer: 3-2 [1, 96, 256] 395,776 │ │ └─TransformerEncoderLayer: 3-3 [1, 96, 256] 395,776 │ │ └─TransformerEncoderLayer: 3-4 [1, 96, 256] 395,776 │ │ └─TransformerEncoderLayer: 3-5 [1, 96, 256] 395,776 │ │ └─TransformerEncoderLayer: 3-6 [1, 96, 256] 395,776 │ │ └─TransformerEncoderLayer: 3-7 [1, 96, 256] 395,776 │ │ └─TransformerEncoderLayer: 3-8 [1, 96, 256] 395,776 ├─Linear: 1-4 [1, 1496] 36,767,192 ├─Linear: 1-5 [1, 256] 6,291,456 ├─BatchNorm1d: 1-6 [1, 256] 512 ├─Linear: 1-7 [1, 1] 257 =============================================================================================== Total params: 46,990,553 Trainable params: 46,990,553 Non-trainable params: 0 Total mult-adds (Units.MEGABYTES): 114.13 =============================================================================================== Input size (MB): 0.00 Forward/backward pass size (MB): 0.41 Params size (MB): 175.30 Estimated Total Size (MB): 175.71 ===============================================================================================
精度比較
Transformer | ResNet | |
---|---|---|
訓練損失 | 2.4075677 | 2.1040008 |
評価損失 | 2.9794911 | 2.5547059 |
方策正解率 | 0.3583973 | 0.4136466 |
価値正解率 | 0.6371848 | 0.6714451 |
Transformerよりも、ResNetの方が精度が高いという結果になった。
学習時間
TransformerとResNetの学習時間はそれぞれ以下の通り。
Transformer | ResNet |
---|---|
1:26 | 1:37 |
(h:mm)
考察
Transformerは、ResNetに比べて帰納バイアスが弱いため、大量のデータがないと性能が上がらないことが知られている。
比較的少ないデータで実験したため、Transformerの精度が上がらなかった可能性がある。
また、方策の出力を全結合層にしたため、パラメータ数の半分くらいが全結合層になってしまっている。
トークンをチャンネル方向に連結して、1x1の畳み込みで、チャンネル方向に畳み込みを行うなどして効率化した方がよさそうである。
位置エンコーダも、将棋では絶対的な位置よりも相対的な位置関係が重要なため、Relative Position Representationsなどを使用した方がよさそうである。
Leela Chess Zeroでは、チェスの駒の関係をとらえたSmolgenという独自の位置エンコーダを実装している。こちらも参考にしたい。
まとめ
入力特徴量の作成処理を実装して、ベースラインとして単純なTransformerで学習を試した。
結果、Transformerよりも、ResNetの方が精度が高いという結果になった。
次回は、出力層の構成を見直して実験を行いたい。