TadaoYamaokaの開発日記

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

将棋でディープラーニングする その9(王手を入力特徴に追加)

以前の日記に書いたがPonanza Chainerでは王手かどうかを入力特徴に入れているようだ。
そこで自分のニューラルネットワークにも王手を入力特徴に追加してみた。

現在の局面が王手かどうかを判定し、2値画像1チャネルを入力チャネルに追加する。
王手かどうかは、python-shogiではboard.is_check()で調べられるので、数行追加するだけで実装できた。

測定結果

入力特徴に王手を追加して学習した結果を王手なしの場合と比較した。

train loss test acuraccy
王手なし 2.15 0.41
王手あり 2.19 0.42

結果はほとんど変わらなかった。
そもそも、学習しているプロ棋譜3744局に王手の局面はそれほど多くなく、差が出るとも思いにくい。
もっと大規模な局数で学習しないと効果があるかわからないが、とりあえず王手は入力特徴に加えておく。

github.com

将棋でディープラーニングする その8(出力に移動元を追加)

前回までのニューラルネットワークでは、差し手の移動先のみを出力して、移動元については考慮していなかった。
移動元をどのように出力するかは悩んでいたのでとりあえず移動先のみで検証を行った。

移動元クラス数:盤のマス数(9×9)+持ち駒の種類(7)
移動先クラス数:駒の種類(14)×盤のマス数(9×9)
であるため、
移動元と移動先の組み合わせると、(9×9+7) × (14×9×9) = 642978 のクラス分類となる。

分類数が増えすぎると精度を上げるのは難しいので、移動元を移動先と別々に出力することを考えていた。

こちらの方も別々に出力している。
http://www2.computer-shogi.org/wcsc27/appeal/GANShogi/wcsc27.pdf

しかし、移動元を移動先と別々に出力すると、移動元と移動先が矛盾した結果になる可能性がある。
また、指し手のオーダリングする際、上位数手を使用すると、移動元と移動先を組み合わせて使用する必要があり、それは出力で考慮できていない。

というように悩んでいたが、解決方法が見つかった。
といっても自分で発見したわけではなく、こちらの方が昨日公開したソースの実装方法が参考になった。

この実装では、移動元を移動方向で表現している。
移動元から移動先への方向が分かれば、どの駒を移動したかを一意に決めることができる。
自分では気づきませんでした。素晴らしいアイディアです。

上記の実装では、移動方向を10方向(8方向+桂馬の動き)と成りと持ち駒で分類している。
よって、分類クラス数は(10×2+7)×9×9 = 2187となる。
どの駒を移動したかは移動方向からロジックで一意に決まる。

参考にして、前回までの指し手の駒の種類は残して、移動方向を加えることにした。
移動方向のみの方が分類数は減らせるが、駒の種類を残すのは、駒の種類は指し手の意味として重要な情報なので残した方がよいだろうと考えたためである。
どちらがよいかは実験してみないとわからない。

駒の移動は、以下を考慮して分類数を減らす。

  • 駒ごとに移動できない方向がある
  • 持ち駒にならない駒がある
  • 成れない駒がある

駒ごとの移動できる方向を考慮すると、下記表の〇の個所のみに絞られ、分類数は71となる。

左上 右上 左下 右下
と金
成香
成桂
成銀
竜馬
竜王

また、成りができるのは上記表の「飛」までなので、方向と成りの組み合わせで分類数は、71+23=94となる。
また、持ち駒にできるも上記表の「飛」までなので、方向と成りの組み合わせと持ち駒の合計で、分類数は94+7=101となる。
これと、盤の位置の組み合わせで、最終的な分類数は101×9×9 = 8181となる。

これくらいの分類クラス数であれば、それなりの精度になりそうである。
クラス数が絞られることで、予測した指し手が合法手になる確率も上がる。
また、移動元が一意に決まるため、そのままオーダリングに使用可能である。

測定結果

前回のコードに移動元(移動方向)の考慮を出力に加えて、測定を行った。
測定方法は、前回までと同様、プロの棋譜422852局面で3エポック学習した。

train loss test accuracy
移動元考慮なし 2.09 0.40
移動元考慮あり 2.15 0.41

分類クラス数が増えているが、移動元(移動方向)を考慮してもほぼ同じ精度で予測できている。

学習時間は以下の通り少しだけ増えた。

学習時間
移動元考慮なし 0:47:34
移動元考慮あり 0:49:15

GitHubソースコードを公開しました。
github.com

将棋でディープラーニングする その7(最適化手法の変更)

前回に続き、学習の改善を試します。
今回は、最適化手法を変えて収束性、精度を測定します。

最適化手法は、はじめAdamではうまく学習できなかったためAdaGradとしていました。
Batch Normalizationを入れたことで、Adamを含めた他の手法でも学習できるようになった可能性があるので試してみます。

手法は、SGD、AdaGrad、RMSprop、Adamを試しました。
学習率ははじめ大きめの値として、学習できない場合は1/10にして測定しました。

測定結果

手法 学習率 train loss test accuracy
SGD lr=0.1 2.85 0.34
SGD lr=0.01 2.09 0.40
SGD lr=0.001 3.38 0.29
AdaGrad lr=0.1
AdaGrad lr=0.01 2.58 0.38
AdaGrad lr=0.001 2.71 0.35
RMSprop lr=0.01 9.36 0.02
RMSprop lr=0.001 3.23 0.31
Adam alpha=0.01
Adam alpha=0.001 2.73 0.34

※-は学習が進まないため中断

Adamなどの比較的新しい手法が良いと思っていたが予想に反して、SGD(lr=0.01)が一番収束が速く、精度が高くなった。
AlphaGoの論文でもモーメントは使用しないでSGDで学習したと記述されている。
学習率の初期値は0.003で8千万ステップごとに半分にしている。

上記の測定結果で良い結果となったSGDの学習率は0.01でAlphaGoの論文の値より大きいが、Batch Normalizationを入れているので、大きめの初期値でも学習できていると思われる。

測定結果を受けて、最適化手法はSGD(lr=0.01※Chainerのデフォルト)にすることにします。

GitHubにソースを公開しました。
github.com

elmoのアピール文書を読む

世界コンピュータ将棋選手権で優勝したelmoのアピール文書を読んでいますが、結構難しいです。

勝率が二項分布に従う場合、評価値はロジスティック分布に従う(※1)だろう、
ということでロジスティック回帰を適用しています(※2)。

この部分は、ある局面の勝敗は評価関数の値によって決まる確率分布に従うという仮定を置き、評価値が与えられた時の事後確率をロジスティック関数で表すということだと思います。
勝敗が評価値によって決まる確率に従うならば、ベルヌーイ試行に従うので勝率は二項分布になります。

ロジスティック関数は、以下の式で与えられる値の範囲が0~1の関数で、入力値が大きければ1に近づき、入力値が小さければ0に近づきます。

\displaystyle
\sigma(a) = \frac{1}{1+\exp(-a)}
\bf aは評価値で、3駒関係の重みを\bf w、学習データの3駒関係の入力ベクトルを\bf xとしたときの線形和{\bf w}^T{\bf x}
※多くの将棋ソフトでは歩1枚の価値がだいたい100点になるように横軸をスケーリングしているようです。
f:id:TadaoYamaoka:20170506123454p:plain:w300

人間が評価値が1000を超えたあたりからほぼ勝ちだと判断する感覚とも一致するので、評価値による勝率がロジスティック関数に従うと仮定するのは納得がいきます。

局面の勝敗結果から、機械学習最尤推定という手法で、損失関数(交差エントロピー誤差関数)を最小化するように学習することで、評価関数のパラメータを最適化できます。

交差エントロピー誤差関数は以下の式で表されます。
\displaystyle
L({\bf w}, {\bf X}, {\bf t}) = -\sum_{i=1}^{N}(t_i{\bf w}^T{\bf x_i} - \log(1+\exp({\bf w}^T{\bf x_i}))
\bf Xは学習データの集合({\bf x_1}, {\bf x_2}, \cdots)\bf tは勝敗(0か1)の教師データです。
交差エントロピー誤差関数をパラメータ\bf wについて偏微分すると
\displaystyle
\frac{\partial L({\bf w}, {\bf X}, {\bf t})}{\partial {\bf w}} = \sum_{i=1}^{N}{\bf x_i}(\sigma({\bf w}^T{\bf x_i}) - t_i)
となり、計算がしやすい式となります。

アピール文書では、これに正則化項を加えています。

単純に最尤推定のロジスティック回帰を適用するのではではなく、
正則化項として深い探索結果を浅い探索結果にフィードバックする手法(※3)を採用しています。

交差エントロピー誤差関数に、その局面の深い探索結果による評価値を加えることで、その評価値に近づける正則化を行っています。
\displaystyle
L'({\bf w}, {\bf X}, {\bf t}) = L({\bf w}, {\bf X}, {\bf t}) + \lambda R({\bf X})
\lambda正則化係数、R({\bf x})が浅い探索による評価値と深い探索による評価値の誤差

さらに、正則化項に以下の変形を加えています。

正則化項には、第4回電王戦トーナメントの†白美神†さんが利用していた同様交差エントロピー(※4)を利用しています。
これは単にロジスティック回帰の損失項が交差エントロピーを使うのが普通なので
両項のオーダーを合わせる意味で利用しています。計算簡単で直感的に値が分かりやすい点も良いです。

つまり、以下の式になります。
\displaystyle
\begin{eqnarray*}
L'({\bf w},{\bf X}, {\bf t}) &=& L({\bf w},{\bf X}, {\bf t}) + \lambda H({\bf p}, {\bf q}) \\
H(p, q) &=& - \sum_t p(t) \log q(t) \\
 &=& -p \log q - (1 - p) \log(1-q)
\end{eqnarray*}
pは評価値からの勝率、qは深い探索の評価値からの勝率、H(p, q)は2確率変数の交差エントロピー

H(p, q)の偏微分は、
\displaystyle
\frac{\partial H(p, q)}{\partial {\bf w}} = {\bf x_i}(q - p)
となる。
H(p, q)の微分の式の展開の詳細は、引用されている資料参照
ロジスティック関数の微分\sigma'(a)=\sigma(a)\sigma(1-a)であることを利用している。

学習データに使用するのは、ある局面からの浅い探索結果と深い探索結果の評価値と勝敗のデータになります。

1エポック分パラメータを更新した後、自己対戦により再度学習データを生成する必要があるので、何エポックも学習を回すのはかなり大変そうです。

elmoは、アピール文書では探索深さ6、50億局面弱で1回だけ最適化したということです。(対局時はもっと回していたかもしれません。)

2017/5/11 追記

elmoのソースコードが公開されました。
github.com

上記の説明と対応する箇所は以下の通りです。

src/usi.cpp (706~711行目)
const double eval_winrate = sigmoidWinningRate(eval);
const double teacher_winrate = sigmoidWinningRate(teacherEval);
const double t = ( (hcpe.isWin) ? 1.0 : 0.0 ); // tkzw: 勝っていれば1, 負けていれば0

const double LAMBDA = 0.5; // tkzw: 適当に変えてください。
const double dsig = (eval_winrate -t) + LAMBDA * (eval_winrate - teacher_winrate);

eval_winrateが浅い探索の評価値による予測勝率、teacher_winrateが深い探索の評価値による予測勝率、tが勝敗データです。
LAMBDAが正則化係数で、0.5が使われています。

dsigに、上記説明の「勝敗の交差エントロピー誤差の勾配」+「正則化係数」×「正則化項の勾配」を代入しています。

後の処理でdsigに学習率を掛けた値で、入力ベクトルの要素に値がある項のパラメータを更新しています。

なお、元々のAperyのソースでは、浅い探索と深い探索の評価値による予測勝率の2乗誤差が損失関数になっていました。

将棋でディープラーニングする その6(BatchNormalizationを追加)

前回は、手番を入力特徴に加えても効果がないことを確認した。

今回は、ニューラルネットワークの畳み込み層の後にBatch Normalizationを追加して精度への影響を確認する。

Batch Normalizationを適用することで以下のメリットがある。

  • 学習を速く進行させることができる(学習係数を大きくすることができる)
  • 初期値にそれほど依存しない
  • 過学習を抑制する(Dropoutの必要性を減らす)

13層のCNNのどの層までBatch Normalizationを入れるのがよいかわからなかったので、1層目から順番にBatch Normalizationを入れる層を増やして測定した。
ベースの条件は以前の持ち駒のフィルターサイズを変更したバージョンとした。

測定結果

f:id:TadaoYamaoka:20170506093538p:plain
f:id:TadaoYamaoka:20170506093558p:plain
※横軸のラベルは、BNの後の数値がBatch Normalizationを入れた層数

測定結果を見ると、9層目までBatch Normalizationを入れた方がtrain loss、test accuracyともに精度が上がる。
10層目より深い層に入れると精度が落ちてくる。

また、Batch Normalizationを入れる程、学習時間が延びる傾向がある。学習の効率を上げるには必要以上に入れない方が良い。

この測定結果からは、9層目までBatch Normalizationを入れるのがよさそうだ。

9層目までBatch Normalizationを入れたことで、test accuracyは35%になった。

次回はBatch Normalizationを入れたことで学習率をどこまで増やせるか検証したい。

GitHubにソースを公開しました。
github.com

2017/5/7 追記

Batch Normalizationを活性化関数(ReLU)の後に入れた場合、どうなるか試してみました。
上記の測定は最適化手法をAdaGradにしていましたが、その後SGDに変更したので、SGDでBatch Normalizationを活性化関数の前に入れた場合と、後に入れた場合を比較します。
9層目までにBatch Normalizationを入れた条件での比較です。

train loss test accuracy
ReLUの前 2.09 0.40
ReLUの後 2.13 0.40

結果はほとんど変わりませんでした。

将棋でディープラーニングする その5(入力特徴に手番を追加)

世界コンピュータ将棋選手権を参加者の生放送の方で見ていました。
開発者の話が聞けて、大変面白かったです。

Ponanza Chainerの手法については、後日公開予定ということなので、公開されたら拝見させていただきたいと思います。
生放送でもだいぶ中身について言及されていて大変参考になりました。
私の理解では以下の通りです。

  • CNNを使用して、ネットワーク構成はAlphaGoを参考にした
  • レイヤー数は13(19とうろ覚えのようでしたが、AlphaGoを参考にしたということなので13だと思われる)
  • 将棋は遠くまで効く駒があるのでその点考慮した(フィルターサイズの話と思われる)
  • 合法手の情報は入れていない
  • 駒の効きの情報は入れていない
  • 入力特徴は局面の盤面情報と、将棋の知識で唯一入れているのは王手があるかだけ
  • ディープラーニングは指し手のオーダリングに使用している(手の候補の順番リストを用意する)
  • 探索は既存のPonazaベース+クラスタリング
  • 評価関数部分は既存のPonanzaベース
  • ディープラーニングのみだと序盤が強い、終盤が弱い
  • オーダリングだけで初段は超えてる
  • ディープラーニングでオーダリングすると既存のPonanzaより6割くらい強くなる
  • 学習はPonanzaの自己対戦データ5億局面を使い、1GPUで1.5か月学習したものを使用した
  • 128GPUで並列で学習したものより1GPUで長時間学習の方が強かった

個人的には、フィルターサイズについて詳しく知りたかったが、後日公開されると思うので待つことにします。

Ponanza Chainerは結果は優勝を逃しましたが、今回導入したのは、AlphaGoで言うSL Policy networkの部分のみで、優勝したelmoの方は勝敗の結果を評価関数に反映する強化学習を行っていたようなので、Ponanza ChainerもAlpahGoで言うRL Policy networkで勝敗の結果から方策改善を行うことでさらに強くなる余地があると思います。

さて、私が検証を行っている将棋のディープラーニングについて、前回変更したネットワーク構成の入力特徴に手番を加えて精度が上がるか試してみました。
本日他にも試して効果があったことがありますが、記事を分けて書きます。

手番の入力特徴について

AlpahGoでは手番(Turns since)を8枚の入力特徴としている。
将棋では1手から200以上の手数があるため、8枚で表すには手数を分割して割り振る必要がある。
分割する手数を10、15、20としてOneHotエンコーディングした場合と、1枚の入力特徴で0~1の連続値で入力した場合、先手・後手を入力特徴とした場合で実験を行った。

実験結果

プロの棋譜422852局面を3エポック学習した結果
train loss test accuracy
手番なし 3.35 0.26
10ごと 3.39 0.27
15ごと 3.37 0.29
20ごと 3.43 0.27
連続値 3.42 0.26
先手・後手 3.34 0.27

どれも誤差の範囲であり、手番の情報は結果には影響がありませんでした。
この結果からは手番は入力特徴には不要のようです。

2017/5/7 追記

以下のNDFのアピール文書を読んでいると、今の将棋プログラムの機械学習で使われている「手番」の意味は、上記の手番(Turns since)とは異なり、探索時の先手か後手かという意味合いのようで、「手番」を持っている側へのボーナスを評価するようです。
http://www.computer-shogi.org/wcsc24/appeal/NineDayFever/NDF.txt

将棋プログラムの経験が浅く十分に理解しきれていませんが、ここで言う「手番」であれば効果的かもしれません。
しかし、学習しているのは方策であるためここで言う「手番」の考慮は不要そうです。

また、NDFの相対KPPや相対PPの考えは、未知の局面への対応力を増やすためあえて関数の表現力を落として、汎化性能を上げています。(※KPPやPPのK、PはそれぞれKing、Pieceの略)
DCNNは表現力の高い関数なので、学習局面が増やせない入玉のような局面への汎化性能が得られるか、今後実験する上で興味があります。

将棋でディープラーニングする その4(ネットワーク構成の変更)

本日から世界コンピュータ将棋選手権が始まりましたね。
一次予選を参加者の方の生放送で見ていました。

今回からPonanza Chainer以外にもディープラーニングを取り入れて参加している方がちらほらいるようです。
こちらの方のアピール文章に、ネットワーク構成について記述がありました。
http://www2.computer-shogi.org/wcsc27/appeal/GANShogi/wcsc27.pdf

私が以前に試したネットワーク構成と似ていますが、フィルターサイズを複数種類用意しているようです。
また、出力を駒の移動先だけではなく、移動元も予測しているようです。

上記のアピール文書では一致率が50%程度になっているが、私の検証用ネットワークによる実験では3エポックで一致率が25%程度にしかなっていない。
入力特徴が最小限であること、学習のエポック数が少なくプラトー状態から抜け出ていない可能性があることが考えらる。
手番などの効果的な入力特徴を加えて、さらに学習することで50%程度の一致率まで上げられそうである。

さて、以前に将棋でディープラーニングを途中まで試した後しばらく放置していましたが、ニューラルネットワークの構成について気になる点があったので以下の通り変更を試してみました。

変更内容

前回DNNで実装した指し手を予測するモデルについて、持ち駒を値が2値の9×9の画像として入力していたが、全て同じ値の画像の畳み込みを行ってもすべて同じ値の結果になるため、計算に無駄がある。
また、エッジ部分ではパディングを行うため、悪影響がある。

そのため、持ち駒の入力の与え方を9×9ではなく、1×1の画像とし畳み込みの結果を9×9に引き延ばすように変更した。
画像を同じ値で引き延ばすのは、Chainerのunpooling_2dで行った。

このように変更した理由は、今後入力特徴に手番を加えたいが、手番の数値をOneHotエンコーディングすると、入力画像の枚数がかなり増えてしまうため省力化が必要と考えたためである。
効果の確認のためまずは持ち駒について変更を行った。

ネットワーク構成
class MyChain(Chain):
    def __init__(self):
        super(MyChain, self).__init__(
            l1_1=L.Convolution2D(in_channels = None, out_channels = k, ksize = 3, pad = 1),
            l1_2=L.Convolution2D(in_channels = None, out_channels = k, ksize = 1), # pieces_in_hand
            l2=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l3=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l4=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l5=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l6=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l7=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l8=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l9=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l10=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l11=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l12=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l13=L.Convolution2D(in_channels = k, out_channels = len(shogi.PIECE_TYPES), ksize = 1, nobias = True),
            l13_2=L.Bias(shape=(9*9*len(shogi.PIECE_TYPES)))
        )

    def __call__(self, x1, x2):
        u1_1 = self.l1_1(x1)
        u1_2_1 = self.l1_2(x2)
        u1_2_2 = F.unpooling_2d(u1_2_1, 9, outsize=(9, 9))
        h2 = F.relu(self.l2(h1))
        h3 = F.relu(self.l3(h2))
        h4 = F.relu(self.l4(h3))
        h5 = F.relu(self.l5(h4))
        h6 = F.relu(self.l6(h5))
        h7 = F.relu(self.l7(h6))
        h8 = F.relu(self.l8(h7))
        h9 = F.relu(self.l9(h8))
        h10 = F.relu(self.l10(h9))
        h11 = F.relu(self.l11(h10))
        h12 = F.relu(self.l12(h11))
        h13 = self.l13(h12)
        return self.l13_2(F.reshape(h13, (len(h13.data), 9*9*len(shogi.PIECE_TYPES))))

効果確認

変更前後で、前回プロの棋譜から学習した結果と比較したところ、以下の通りとなった。

422852局面を3エポック学習した結果
学習時間 train loss test accuracy
変更前 0:37:48 3.578415275 0.24140625
変更後 0:37:51 3.35968709 0.27265625

※train lossとtest accuracyは最後20イテレーション(100ミニバッチごと)の平均

計算量は減っているはずだが計算時間はわずかに増えている。
1×1の畳み込みの計算と、unpooling_2dの計算で2ステップ必要になるため実行時間に影響したと思われる。

train lossは減って、test accuracyが増えているので、パディングの悪影響が減らせたかもしれない。

変更その2

1×1の畳み込みの計算と、unpooling_2dの計算で2ステップ必要になるのが計算時間のロスになるので、入力は9×9として、フィルターサイズを1×1としてみた。
このようにすることで、多少計算時間を改善して、パディングの悪影響を抑えることができる。

ネットワーク構成
class MyChain(Chain):
    def __init__(self):
        super(MyChain, self).__init__(
            l1_1=L.Convolution2D(in_channels = None, out_channels = k, ksize = 3, pad = 1),
            l1_2=L.Convolution2D(in_channels = None, out_channels = k, ksize = 1), # pieces_in_hand
            l2=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l3=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l4=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l5=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l6=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l7=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l8=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l9=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l10=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l11=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l12=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l13=L.Convolution2D(in_channels = k, out_channels = len(shogi.PIECE_TYPES), ksize = 1, nobias = True),
            l13_2=L.Bias(shape=(9*9*len(shogi.PIECE_TYPES)))
        )

    def __call__(self, x1, x2):
        u1_1 = self.l1_1(x1)
        u1_2 = self.l1_2(x2)
        h1 = F.relu(u1_1 + u1_2)
        h2 = F.relu(self.l2(h1))
        h3 = F.relu(self.l3(h2))
        h4 = F.relu(self.l4(h3))
        h5 = F.relu(self.l5(h4))
        h6 = F.relu(self.l6(h5))
        h7 = F.relu(self.l7(h6))
        h8 = F.relu(self.l8(h7))
        h9 = F.relu(self.l9(h8))
        h10 = F.relu(self.l10(h9))
        h11 = F.relu(self.l11(h10))
        h12 = F.relu(self.l12(h11))
        h13 = self.l13(h12)
        return self.l13_2(F.reshape(h13, (len(h13.data), 9*9*len(shogi.PIECE_TYPES))))

効果確認

学習時間 train loss test accuracy
変更前 0:37:48 3.578415275 0.24140625
変更後(unpooling_2d) 0:37:51 3.35968709 0.27265625
変更後(ksize=1) 0:36:36 3.346870267 0.26328125

計算時間は少しだけ短くなっている。
unpoolingを行った場合より、train lossは減って、test accuracyも減っているが、行っていることは同じであるため、誤差の範囲と思われる。
変更前にくらべたらパディングの悪影響が減らせていると思われる。

以上の実験より、持ち駒の入力層の畳み込みは9×9の2値画像として入力し、カーネルサイズ1とすることで、計算時間を短縮できパディングの悪影響を減らすことができる。

手番の特徴を加える場合も、同じようにカーネルサイズを1とした畳み込みにするのがよいと考えられる。
手番は1~200以上の値をとるが、入力特徴の枚数が増えすぎると計算時間も増えるため、序盤、中盤、終盤を数分割するくらいがよいと思う。
AlpahGoの論文でも手番の特徴は8枚となっている。
8枚をどのような手番の範囲に割り振っているかは残念ながら論文には記述されていない。

GitHubのソースにも反映しました。
github.com