山岡さんの本を参考にtensorflowを用いてDeepLearningを用いた将棋ソフトを作ってみています。
- 作者: 山岡忠夫
- 出版社/メーカー: マイナビ出版
- 発売日: 2018/03/14
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
ResidualNetを実装しているところなのですが、実装がまずいのかブロック数を増やすと上手く学習してくれないという事態が起こりました。現在の実装は以下の通りです。山岡さんの本をそのまま模倣しようとしているので、そうなっていない部分があったら指摘していただけると幸いです。
ch = 192 kernel = 3 def ResidualBlock(inputs): conv1 = tf.layers.conv2d(inputs=inputs, filters=ch, kernel_size=kernel, padding="same", use_bias=False) bn1 = tf.layers.batch_normalization(inputs=conv1, training=True) relu1 = tf.nn.relu(bn1) conv2 = tf.layers.conv2d(inputs=relu1, filters=ch, kernel_size=kernel, padding="same", use_bias=False) bn2 = tf.layers.batch_normalization(inputs=conv2, training=True) return tf.nn.relu(inputs + bn2) def policy_value_func(features): # Input Layer # featuresのshapeは9×9マスが104チャネル ([-1, 9, 9, 104]) input_layer = features # ResidualBlock conv1 = tf.layers.conv2d(inputs=input_layer, filters=ch, kernel_size=3, padding="same", activation=tf.nn.relu) res1 = ResidualBlock(conv1) res2 = ResidualBlock(res1) res3 = ResidualBlock(res2) res4 = ResidualBlock(res3) res5 = ResidualBlock(res4) res6 = ResidualBlock(res5) res7 = ResidualBlock(res6) res8 = ResidualBlock(res7) res9 = ResidualBlock(res8) res10 = ResidualBlock(res9) # policy_network用 conv13p = tf.layers.conv2d(inputs=res10, filters=MOVE_DIRECTION_LABEL_NUM, kernel_size=1, padding="same", activation=tf.nn.relu) policy = tf.reshape(conv13p, shape=[-1, 9 * 9 * MOVE_DIRECTION_LABEL_NUM]) # value_network用 conv13d = tf.layers.conv2d(inputs=res10, filters=MOVE_DIRECTION_LABEL_NUM, kernel_size=1, padding="same", activation=tf.nn.relu) # 1次元化する flat = tf.reshape(conv13d, [-1, 9 * 9 * MOVE_DIRECTION_LABEL_NUM]) # 全結合層 dense = tf.layers.dense(inputs=flat, units=256, activation=tf.nn.relu,) # 出力値はスカラ1つ value = tf.layers.dense(inputs=dense, units=1) # サイズ1のベクトルになっているのでsqueezeで直す value = tf.squeeze(value, axis=1) # policyとvalueの二つを返す return policy, value
学習はfloodgateの2016年の棋譜を山岡さんの本の通りに学習用3,711,167局面、テスト用414,677局面に分け、early stoppingをpatience = 3でやっています。
5ブロックで行った場合の結果が以下
epoch | Loss | Move Accuracy | Value Accuracy |
---|---|---|---|
1 | 3.6427 | 0.3086 | 0.6702 |
2 | 3.2474 | 0.3481 | 0.6825 |
3 | 3.1120 | 0.3646 | 0.6899 |
4 | 3.0099 | 0.3765 | 0.6882 |
5 | 2.9835 | 0.3807 | 0.6913 |
6 | 2.9768 | 0.3865 | 0.6861 |
7 | 2.9753 | 0.3879 | 0.6853 |
8 | 3.0051 | 0.3903 | 0.6839 |
9 | 2.9959 | 0.3927 | 0.6816 |
10 | 3.0267 | 0.3927 | 0.6815 |
10ブロックで行った場合の結果が以下のようになります。
epoch | Loss | Move Accuracy | Value Accuracy |
---|---|---|---|
1 | 5.0812 | 0.2416 | 0.6547 |
2 | 4.6445 | 0.2853 | 0.6804 |
3 | 4.4765 | 0.3042 | 0.6877 |
4 | 4.3976 | 0.3153 | 0.6901 |
5 | 4.3842 | 0.3237 | 0.6883 |
6 | 4.3915 | 0.3265 | 0.6866 |
7 | 4.3952 | 0.3267 | 0.6836 |
8 | 4.4427 | 0.3301 | 0.6837 |
5ブロックのときと比べて損失が大きい段階で学習が終了しています。指し手の一致率も6%以上低いです。今冷静に見返すとやはりもっとpatienceを大きくするべきなのでしょうか……。
上の二つの実験はoptimizerをNesterovありのmomentum
optimizer = tf.train.MomentumOptimizer(learning_rate=0.01, momentum=0.9, use_nesterov=True)
で行っており、普通のSGD
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)
にしたところ5ブロックの場合にやや劣る程度になりました。
epoch | Loss | Move Accuracy | Value Accuracy |
---|---|---|---|
1 | 3.9261 | 0.2829 | 0.6452 |
2 | 3.4671 | 0.3243 | 0.6741 |
3 | 3.2671 | 0.3472 | 0.6791 |
4 | 3.1414 | 0.3597 | 0.6859 |
5 | 3.0936 | 0.3697 | 0.6810 |
6 | 3.0394 | 0.3727 | 0.6882 |
7 | 3.0323 | 0.3789 | 0.6845 |
8 | 3.0575 | 0.3811 | 0.6798 |
9 | 3.0550 | 0.3833 | 0.6842 |
10 | 3.0771 | 0.3852 | 0.6813 |
表にして最終結果をまとめると以下のようになります。
model | Loss | Move Accuracy | Value Accuracy |
---|---|---|---|
5ブロック(Nesterov) | 2.9753 | 0.3879 | 0.6853 |
10ブロック(Nesterov) | 4.3842 | 0.3237 | 0.6883 |
10ブロック(SGD) | 3.0323 | 0.3789 | 0.6845 |
patienceが小さいと鞍点にハマってしまうかどうかに大きく左右され、運次第になってしまうということでしょうか。それぞれ1回の実験だけではなんとも言えないという気もします。optimizerとの相性の問題だとするとかなり大変なことだなと思えるのですが、何はともあれもっと実験を重ねてみるしかなさそうです。