$$ \def\bra#1{\mathinner{\left\langle{#1}\right|}} \def\ket#1{\mathinner{\left|{#1}\right\rangle}} \def\braket#1#2{\mathinner{\left\langle{#1}\middle|#2\right\rangle}} $$

◇◇UTTT006:教師あり学習編⑤:いろいろちゃんとする◇◇


年末年始はいろいろ忙しくてサボっていたらかなり期間が開いてしまった。

前回の記事を読むと、MCTSエージェントで作った棋譜を学習したネットワークを作って手を指すエージェントを作ったものの弱かったので、不利局面の学習が足りてないのではないかと言って終わっていた。本当だろうか?

原因を調べるついでに、適当にやっていたところをいろいろちゃんとしたのでメモしていく。

〇教師データについて

様々な局面の棋譜を集めるためにMixedAgentというものを作ってみた。 MixedAgentはランダム、MCTS(1000プレイアウト)、MCTS(5000プレイアウト)、MCTS(10000プレイアウト)の4つの人格(?)をもっていて、これらが手番ごとにランダムに交代するエージェントである。 今回は、このMixedAgentどうしを対戦させて、そのうち10000プレイアウトのMCTSエージェントの人格が着手した盤面と手を教師データとした(訓練データ539316局面、テストデータ59924局面)。また、訓練データとテストデータに同じ局面が含まれないように分割した。 結果としては、教師データを入れ替えただけではDeepLearningエージェントの強さは特に改善しなかった。局面の偏りをなくせば改善するだろうという考えは誤りだったらしい。

〇特徴量について

前回までは、ニューラルネットワークの入力として、自分のマーク、相手のマーク、合法手を結合して1枚の画像として入力していた。 これを結合せずに、9×9の画像3枚として入力するように変更した。それに伴って、マスの配置のindexの対応も実際のゲームの盤面と同じになるように変更した(以前は順番が変わっていた) また、すでに取られているローカルボードを取った側のマークで塗りつぶすように前処理を行った。

〇ネットワークについて

前回まではMNISTの延長で3層の全結合ネットワーク(!)を用いていたが、いくつかの試行錯誤の結果、これを10層のCNNに変更した。ここで、入力の3チャンネルをそれぞれ50個のフィルタで150チャンネルに拡張して学習した。AlphaGOdlshogiにならって13層210チャンネルのCNNなども試したが、初期値依存性が高く収束も遅かった(そして性能も低かった)のでモデルのサイズを少し小さくした。特徴の数が少ないので、モデルが小さくてよいのは納得できる。

                
class Network(nn.Module):
def forward(self, x):
    # CNN
    b = F.relu(self.conv1(x))
    b = F.relu(self.conv2(b))
    b = F.relu(self.conv3(b))
    b = F.relu(self.conv4(b))
    b = F.relu(self.conv5(b))
    b = F.relu(self.conv6(b))
    b = F.relu(self.conv7(b))
    b = F.relu(self.conv8(b))
    b = F.relu(self.conv9(b))
    b = F.relu(self.conv10(b))
    
    h = b.view(-1, self.kernel_coef * self.channels_num * 81)
    h = F.relu(self.fc1(h))
    return h


def __init__(self, channels_num):
    super(Network, self).__init__()
    self.channels_num = channels_num
    self.kernel_coef = 50
    k = self.kernel_coef * channels_num

    self.conv1 = nn.Conv2d(in_channels=channels_num, out_channels=k, kernel_size=(3, 3), padding=(1, 1)).cuda()
    self.conv2 = nn.Conv2d(in_channels=k, out_channels=k, kernel_size=(3, 3), padding=(1, 1)).cuda()
    self.conv3 = nn.Conv2d(in_channels=k, out_channels=k, kernel_size=(3, 3), padding=(1, 1)).cuda()
    self.conv4 = nn.Conv2d(in_channels=k, out_channels=k, kernel_size=(3, 3), padding=(1, 1)).cuda()
    self.conv5 = nn.Conv2d(in_channels=k, out_channels=k, kernel_size=(3, 3), padding=(1, 1)).cuda()
    self.conv6 = nn.Conv2d(in_channels=k, out_channels=k, kernel_size=(3, 3), padding=(1, 1)).cuda()
    self.conv7 = nn.Conv2d(in_channels=k, out_channels=k, kernel_size=(3, 3), padding=(1, 1)).cuda()
    self.conv8 = nn.Conv2d(in_channels=k, out_channels=k, kernel_size=(3, 3), padding=(1, 1)).cuda()
    self.conv9 = nn.Conv2d(in_channels=k, out_channels=k, kernel_size=(3, 3), padding=(1, 1)).cuda()
    self.conv10 = nn.Conv2d(in_channels=k, out_channels=k, kernel_size=(3, 3), padding=(1, 1)).cuda()
    self.fc1 = nn.Linear(in_features=k * 9 * 9, out_features=9 * 9).cuda()
                
            

以上のような変更を行ったところ、ある程度学習するようになった。テストデータに対するaccuracyは最大35%程度で、合法手を指す確率は98%程度となった。以下は200エポック学習する過程のtrain lossとaccuracyの変化をグラフにしたものである。train lossは下がり続けているが、accuracyは50エポック付近をピークとして減少に転じており、過学習の傾向があると考えられる。今回は50エポックで学習を打ち切った。

合法手を指す確率があまり高くないので、合法手のなかで最も評価が高い手を指すようにsupervised learningエージェントを改良して対戦させてみたところ、Randomエージェントには95%程度、100プレイアウトのMCTSエージェントには55%程度の勝率だった。ただし、1000プレイアウトのMCTSエージェントには勝率17%程度だった。

1000プレイアウトのMCTSエージェントに勝てるものができればよかったが、難しいようだ。加えて、推論の結果常に合法手を自分で選ばせたかったがこれも厳しい。合法手を選ぶ確率が単純に毎回98%だとしても、40手連続で指しきる確率は44%程度であり、序盤は合法手を指しやすいが終盤は確率が落ちるなど偏りがあればもっと落ちる可能性が高い。ゲームの仕様上合法手のリストを与えているので合法手率を100%にすることも可能だと思っていたのだが……。

一応推論の速度は100プレイアウトのMCTSエージェントよりもはるかに速いので、それの上位互換ではあるといえる(当初の目標よりスケールがかなり小さくなってしまった)。AlphaGOでは「取る手」や「当たりから逃げる手」などの情報を特徴として加えていたり、Ponanza Chainerやdlshogiでは王手を特徴として加えているらしく、そういったことを行えばもう少し精度は上がるのかもしれない。ぱっと思いつくのは「相手のマークが3つ揃うのを阻止する手」「自分のマークが3つ揃う手」「次の相手の番で3つ揃えられる状況を作ってしまう手」とか……?

戻る