TadaoYamaokaの開発日記

個人開発しているスマホアプリや将棋AIの開発ネタを中心に書いていきます。

大規模言語モデルで将棋AIを作る その3(出力層の構成)

前回、単純なTransformerで学習を試した。
出力層を全てのトークンの全結合としていたため、パラメータ数の半分近くを占めており効率が悪かった。
今回は、出力層の全結合の前に、カーネルサイズ1の畳み込み層を追加することで、チャンネル方向に圧縮を行い、学習が効率化できるか試した。

トークンの方向に圧縮する場合と、埋め込みの次元方向に圧縮する場合の2パターンを試した。

トークン方向に圧縮

方策と価値の出力層の全結合の前に、カーネルサイズ1のConv1dを追加し、トークンを16に圧縮する。

    def __init__(self, ntoken=96, d_model=256, nhead=8, dim_feedforward=256, num_layers=8, dropout=0.1):
        ...
        self.policy_conv = nn.Conv1d(ntoken, 16, 1, bias=False)
        self.policy_norm = nn.BatchNorm1d(d_model * 16)
        self.policy_fc = nn.Linear(d_model * 16, 1496)
        self.value_conv = nn.Conv1d(ntoken, 16, 1, bias=False)
        self.value_norm1 = nn.BatchNorm1d(d_model * 16)
        self.value_fc1 = nn.Linear(d_model * 16, 256, bias=False)

    def forward(self, src):
        ...
        policy = self.policy_conv(x)
        policy = F.relu(self.policy_norm(policy.flatten(1)))
        policy = self.policy_fc(policy)
        value = self.value_conv(x)
        value = F.relu(self.value_norm1(value.flatten(1)))
        value = F.relu(self.value_norm2(self.value_fc1(value)))
        value = self.value_fc2(value)
        return policy, value

埋め込みの次元方向に圧縮

方策と価値の出力層の全結合の前に、カーネルサイズ1のConv1dを追加し、埋め込みの次元を1/8に圧縮する。
forwardで、Conv1dの前にtransposeを行い、埋め込みの次元をチャンネルの次元にする。

    def __init__(self, ntoken=96, d_model=256, nhead=8, dim_feedforward=256, num_layers=8, dropout=0.1):
        ...
        self.policy_conv = nn.Conv1d(d_model, d_model // 8, 1, bias=False)
        self.policy_norm = nn.BatchNorm1d(ntoken * d_model // 8)
        self.policy_fc = nn.Linear(ntoken * d_model // 8, 1496)
        self.value_conv = nn.Conv1d(d_model, d_model // 8, 1, bias=False)
        self.value_norm1 = nn.BatchNorm1d(ntoken * d_model // 8)
        self.value_fc1 = nn.Linear(ntoken * d_model // 8, 256, bias=False)

    def forward(self, src):
        ...
        x = x.transpose(1, 2)
        policy = self.policy_conv(x)
        policy = F.relu(self.policy_norm(policy.flatten(1)))
        policy = self.policy_fc(policy)
        value = self.value_conv(x)
        value = F.relu(self.value_norm1(value.flatten(1)))
        value = F.relu(self.value_norm2(self.value_fc1(value)))
        value = self.value_fc2(value)
        return policy, value

結果

トークン方向に圧縮するパターン(ntoken)と、埋め込みの次元方向に圧縮するパターン(d_model)で、それぞれ条件を変えて比較した。
d model : 埋め込む次元
dim FF : TransformerのFF層の次元
n head : MultiHeadAttensionのヘッド数
num layers : Transformerのレイヤー数

データは前回と同じで、1エポックだけ学習した。

パターン out channels d model dim FF n head num layers val loss policy acc. value acc.
resnet20 2.677 0.391 0.665
baseline 96 256 256 8 8 3.088 0.336 0.652
ntoken 16 256 256 8 8 3.599 0.305 0.655
ntoken 16 512 256 8 8 3.414 0.323 0.657
ntoken 16 768 256 8 8 3.338 0.326 0.660
d_model d_model//8 256 256 8 8 3.213 0.333 0.640
d_model d_model//8 512 256 8 8 3.072 0.345 0.657
d_model d_model//8 512 512 8 8 3.067 0.346 0.655
d_model d_model//8 512 512 4 8 3.071 0.345 0.657
d_model d_model//8 768 512 8 8 3.063 0.345 0.644
d_model d_model//8 768 512 12 8 3.051 0.347 0.644
d_model d_model//8 768 512 12 10 3.024 0.348 0.659

Conv1Dで次元を圧縮することで、ベースラインに比べて、精度が下がっている。
埋め込みの次元方向に圧縮する方が、若干精度が良い。

他の条件を変えてみたが、いずれもResnet20ブロックのモデルに比べると精度が低い。

まとめ

Transformerの出力のすべてのトークンを全結合するよりも、カーネルサイズ1のConv1Dで次元を圧縮することで学者が効率化できること期待したが精度は上がらなかった。
トークンの方向に圧縮した場合と、埋め込みの次元方向に圧縮した場合では、埋め込みの次元方向に圧縮する方が精度は若干高くなった。

次回は、Transformerの前段をResNetにして、ResNetの特徴マップをTransformerの入力とすることを試してみたい。