レベル2以上のすべてのレベルでサーバの負荷が高い時に対局が開始できなくなりました

COSUMIのサーバの負荷が高い時に、今までのように9路盤レベル5と11路盤と13路盤のレベル4だけではなく、レベル2以上のすべてのレベルで、高いレベル・大きな碁盤サイズから順に、対局の開始ができなくなるようになりました。

囲碁ブラウザゲーム COSUMI
http://www.cosumi.net/

実質的に今までとそれほど変わりはないですが、一応今後はこういう仕様にさせていただきます。

Keras/TensorFlowでDNNな囲碁の評価関数を作ってみる

「囲碁をディープラーニングするのは面白い」という噂なので(笑)、私も試しに一度やってみることにしました。作るならやっぱり評価関数。それも、その時の形勢を「目数」で教えてくれるやつがなんかいいですよね? とりあえず今回は19路盤用です。

まずは学習に使うデータについてです。とりあえず評価する局面は、COSUMIで打たれた19路盤互先の作り碁の棋譜から作りました。GNU Go、強い人、弱い人、意図した序盤早々の連続パス、意図しないクリックミスの混ぜ合わさった様々なよく分からない局面が出現しそうで、まあ良いのではないかと…(笑) まず、最後のパスパスを取り除き、1手から最終手の間の一様乱数にまで棋譜の手数を短くして、さらに対称形を考慮しない完全な重複分を取り除き、残った棋譜の最終局面を使うことにしました。

そして次に、その局面に付けるラベル、今回の場合は「目数単位の形勢判断」ですが、うーん、これが本当にどうするのが良いのか… とりあえず、今回の作成方法は以下のとおりです。

  • まず先ほど作った局面を、コミ6目でRayの2k playoutに考えさせます
  • 返ってきたwin rateが0.5に近づく方向にコミを10目ずらして、もう一度Rayに考えさせます
  • それをwin rateが0.5の反対側に行くまで、繰り返します
  • 0.5をまたいだ2点を結んで、0.5と交わるところを「大体の形勢」とします
  • 再度、コミを「大体の形勢」として、今度はRayの20k playoutに考えさせます
  • 返ってきたwin rateが0.5に近づく方向に、今度はコミを4目ずらして、もう一度Rayに考えさせます
  • 先ほどと同じように、それをwin rateが0.5の反対側に行くまで、繰り返します
  • 先ほどと同じように、0.5をまたいだ2点を結んで、0.5と交わるところを「最終的な形勢」とします

あまりにも素朴すぎる気はしますが、こんな感じで作りました。前半は消費リソースを減らすためにやっているだけなので、後半だけを行っても当然似たようなラベルができるはずです。

最初のころは、これを10,000局面分作っていろいろ試していたのですが、ちょっと遊んでみたいだけとはいえ、それではあまりにも少なすぎたので、50,000局面分まで増やしました。そしてそれを対称形に8倍して、ここでもう一度重複分を除去し、きりの良い数字にまで少し減らして399,000局面分できました。今回は、その内80%の319,200局面分を学習用に、残りの20%の79,800局面分を検証用に使用します。

ここまで、学習データは用意できましたので、次に実際に学習を始めます。

今回の実行環境は、

  • Amazon EC2 p2.xlarge
  • Ubuntu 16.04 LTS
  • CUDA 8.0
  • cuDNN 5.1

です。最初、手元のGPUなしのWindowsマシン(CPU:Intel Core i5-3470S メモリ:16GB)でいろいろ試していたのですが、実際に学習が動き始めると、さすがにやはりちょっと遅すぎるので、EC2使いました。学習内容によって結構変わってくるみたいですが、だいたい12倍ほど速かったです。もう少し速いとうれしいのですが、しかたないでしょうか?

DNNのフレームワークには、バックエンドにTensorFlowを使ったKerasを使ってみました。

Keras
https://keras.io/

TensorFlow
https://www.tensorflow.org/

Kerasはとても分かりやすくて、私のような素人には本当にありがたい。かなりおすすめです。TensorFlowもですが、本家のドキュメントがしっかりしているのがいいですよね。例えば、今回のケースだと、こんな感じのコードになります。

import numpy as np
from keras.models import Sequential
from keras.layers import Activation, AveragePooling2D, Conv2D, Flatten
from keras.layers.advanced_activations import LeakyReLU
from keras.layers.normalization import BatchNormalization
from keras.optimizers import Adam

BATCH_SIZE   = 200
EPOCHS       = 20

x_train = np.load('x_train.npy');
y_train = np.load('y_train.npy');
x_test  = np.load('x_test.npy');
y_test  = np.load('y_test.npy');

model = Sequential()

model.add(Conv2D(32, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(AveragePooling2D(pool_size=(13, 13)))
model.add(Flatten())

model.summary()

model.compile(loss      = 'mean_absolute_error',
              optimizer = Adam())

model.fit(x_train, y_train,
          batch_size      = BATCH_SIZE,
          epochs          = EPOCHS,
          verbose         = 1,
          validation_data = (x_test, y_test))

驚くほどシンプルに書けます。

今回は、以下すべての場合において(ただし、追記に関してはこの限りではありません)、

バッチサイズ 200
エポック数 20
損失関数 平均絶対誤差(Mean Absolute Error)
最適化アルゴリズム Adam(パラメータはKerasのデフォルト)

です。バッチサイズは、実行速度などに影響がかなり大きいです。エポック数は、収束していなくても、過学習していても、なにがあっても、今回は一定でいきたいと思います。

ネットワークへの入力は、とりあえず最初、「次の手番のプレーヤーの石」と「相手のプレーヤーの石」の2面(19,19,2)、数値は0と1です。ちなみにですが、今回の学習データのラベルは、平均5.7、標準偏差32.9、平均偏差21.5ぐらいです。なので、とりあえず盤面見ないで「黒5.7目形勢が良い」って答えておけば、Lossは21.5にはなりますので(どちらが黒か教えませんので、実際はもう少し難しいはずですが)、最終的にその数字がどれくらい0に近づくのか、っていう感じで見てもらうと良いと思います。

それではいってみましょう。まず最初に考えたのはこんなネットワーク構成でした。

model.add(Conv2D(32, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(Activation('relu'))
model.add(AveragePooling2D(pool_size=(13, 13)))
model.add(Flatten())

model.summary()が吐いてくれるネットワークの要約がこちら。

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 17, 17, 32)        608       
_________________________________________________________________
activation_1 (Activation)    (None, 17, 17, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 15, 15, 32)        9248      
_________________________________________________________________
activation_2 (Activation)    (None, 15, 15, 32)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 13, 13, 32)        9248      
_________________________________________________________________
activation_3 (Activation)    (None, 13, 13, 32)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 13, 13, 1)         33        
_________________________________________________________________
activation_4 (Activation)    (None, 13, 13, 1)         0         
_________________________________________________________________
average_pooling2d_1 (Average (None, 1, 1, 1)           0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 1)                 0         
=================================================================
Total params: 19,137.0
Trainable params: 19,137.0
Non-trainable params: 0.0

パタパタパタと畳んで、ペタンを押しつぶして、フワーと見るイメージなんですが(笑)、ところがこれ、全く学習してくれません。

Epoch 1/20
319200/319200 [==============================] - 29s - loss: 22.3445 - val_loss: 21.9808
Epoch 2/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 3/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 4/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 5/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 6/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 7/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 8/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 9/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 10/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 11/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 12/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 13/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 14/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 15/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 16/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 17/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 18/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 19/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808
Epoch 20/20
319200/319200 [==============================] - 25s - loss: 22.3445 - val_loss: 21.9808

試しに、活性化関数をtanhに変更してみます。

model.add(Conv2D(32, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(Activation('tanh'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('tanh'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('tanh'))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(Activation('tanh'))
model.add(AveragePooling2D(pool_size=(13, 13)))
model.add(Flatten())
Epoch 1/20
319200/319200 [==============================] - 29s - loss: 22.2877 - val_loss: 21.9071
Epoch 2/20
319200/319200 [==============================] - 26s - loss: 22.2663 - val_loss: 21.8958
Epoch 3/20
319200/319200 [==============================] - 26s - loss: 22.2548 - val_loss: 21.8842
Epoch 4/20
319200/319200 [==============================] - 26s - loss: 22.2462 - val_loss: 21.8796
Epoch 5/20
319200/319200 [==============================] - 26s - loss: 22.2417 - val_loss: 21.8763
Epoch 6/20
319200/319200 [==============================] - 26s - loss: 22.2386 - val_loss: 21.8739
Epoch 7/20
319200/319200 [==============================] - 26s - loss: 22.2370 - val_loss: 21.8727
Epoch 8/20
319200/319200 [==============================] - 26s - loss: 22.2341 - val_loss: 21.8692
Epoch 9/20
319200/319200 [==============================] - 26s - loss: 22.2325 - val_loss: 21.8677
Epoch 10/20
319200/319200 [==============================] - 26s - loss: 22.2305 - val_loss: 21.8665
Epoch 11/20
319200/319200 [==============================] - 26s - loss: 22.2290 - val_loss: 21.8653
Epoch 12/20
319200/319200 [==============================] - 26s - loss: 22.2273 - val_loss: 21.8669
Epoch 13/20
319200/319200 [==============================] - 26s - loss: 22.2259 - val_loss: 21.8618
Epoch 14/20
319200/319200 [==============================] - 26s - loss: 22.2245 - val_loss: 21.8624
Epoch 15/20
319200/319200 [==============================] - 26s - loss: 22.2234 - val_loss: 21.8621
Epoch 16/20
319200/319200 [==============================] - 26s - loss: 22.2221 - val_loss: 21.8590
Epoch 17/20
319200/319200 [==============================] - 26s - loss: 22.2208 - val_loss: 21.8604
Epoch 18/20
319200/319200 [==============================] - 26s - loss: 22.2197 - val_loss: 21.8587
Epoch 19/20
319200/319200 [==============================] - 26s - loss: 22.2191 - val_loss: 21.8621
Epoch 20/20
319200/319200 [==============================] - 26s - loss: 22.2178 - val_loss: 21.8557

ちょびっとだけ数字が動いた…(笑) 今度はLeakyReLUに。

model.add(Conv2D(32, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(LeakyReLU(alpha=0.1))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(LeakyReLU(alpha=0.1))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(LeakyReLU(alpha=0.1))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(LeakyReLU(alpha=0.1))
model.add(AveragePooling2D(pool_size=(13, 13)))
model.add(Flatten())
Epoch 1/20
319200/319200 [==============================] - 34s - loss: 21.7510 - val_loss: 20.9956
Epoch 2/20
319200/319200 [==============================] - 30s - loss: 21.1638 - val_loss: 20.7552
Epoch 3/20
319200/319200 [==============================] - 30s - loss: 20.8117 - val_loss: 20.4443
Epoch 4/20
319200/319200 [==============================] - 30s - loss: 20.5938 - val_loss: 20.1891
Epoch 5/20
319200/319200 [==============================] - 30s - loss: 20.3966 - val_loss: 19.9154
Epoch 6/20
319200/319200 [==============================] - 30s - loss: 20.1918 - val_loss: 19.6722
Epoch 7/20
319200/319200 [==============================] - 30s - loss: 20.0177 - val_loss: 19.7719
Epoch 8/20
319200/319200 [==============================] - 30s - loss: 19.8868 - val_loss: 19.3948
Epoch 9/20
319200/319200 [==============================] - 30s - loss: 19.7355 - val_loss: 19.4068
Epoch 10/20
319200/319200 [==============================] - 30s - loss: 19.5322 - val_loss: 19.0625
Epoch 11/20
319200/319200 [==============================] - 30s - loss: 19.3279 - val_loss: 19.1659
Epoch 12/20
319200/319200 [==============================] - 30s - loss: 19.1512 - val_loss: 18.8860
Epoch 13/20
319200/319200 [==============================] - 30s - loss: 18.8963 - val_loss: 18.6369
Epoch 14/20
319200/319200 [==============================] - 30s - loss: 18.6399 - val_loss: 18.4589
Epoch 15/20
319200/319200 [==============================] - 30s - loss: 18.4826 - val_loss: 18.1423
Epoch 16/20
319200/319200 [==============================] - 30s - loss: 18.3363 - val_loss: 18.1451
Epoch 17/20
319200/319200 [==============================] - 30s - loss: 18.1859 - val_loss: 18.0372
Epoch 18/20
319200/319200 [==============================] - 30s - loss: 18.0898 - val_loss: 17.8348
Epoch 19/20
319200/319200 [==============================] - 30s - loss: 18.0122 - val_loss: 17.7273
Epoch 20/20
319200/319200 [==============================] - 30s - loss: 17.9057 - val_loss: 17.9030

おお、がっつり動き始めました! ここで、なんとなく分かりましたよ。現在のネットワーク構成では、一番最後の活性化関数の後ろに、もう畳み込み層や全結合層がありません。活性化関数がひとつ余分なんですね。ReLUは正の値しか出力しないので、それを平均してもまた正の値の出力しか出てきませんが、ラベルの方には負の値(次の手番側が形勢悪い)もあります。その時にパラメータの更新ができないとか、たぶんそういう話です(合ってるかな?)。ということで、活性化関数をReLUに戻して、一番最後のは削ります。

model.add(Conv2D(32, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(AveragePooling2D(pool_size=(13, 13)))
model.add(Flatten())
Epoch 1/20
319200/319200 [==============================] - 29s - loss: 21.7389 - val_loss: 20.8649
Epoch 2/20
319200/319200 [==============================] - 25s - loss: 20.8634 - val_loss: 20.4426
Epoch 3/20
319200/319200 [==============================] - 25s - loss: 19.7709 - val_loss: 19.0222
Epoch 4/20
319200/319200 [==============================] - 25s - loss: 19.1506 - val_loss: 18.5475
Epoch 5/20
319200/319200 [==============================] - 25s - loss: 18.7009 - val_loss: 18.1697
Epoch 6/20
319200/319200 [==============================] - 25s - loss: 18.3530 - val_loss: 17.9657
Epoch 7/20
319200/319200 [==============================] - 25s - loss: 18.1615 - val_loss: 17.7496
Epoch 8/20
319200/319200 [==============================] - 25s - loss: 18.0063 - val_loss: 17.8551
Epoch 9/20
319200/319200 [==============================] - 25s - loss: 17.9094 - val_loss: 17.5887
Epoch 10/20
319200/319200 [==============================] - 25s - loss: 17.8051 - val_loss: 17.4792
Epoch 11/20
319200/319200 [==============================] - 25s - loss: 17.7149 - val_loss: 17.4250
Epoch 12/20
319200/319200 [==============================] - 25s - loss: 17.6149 - val_loss: 17.3268
Epoch 13/20
319200/319200 [==============================] - 25s - loss: 17.5354 - val_loss: 17.7732
Epoch 14/20
319200/319200 [==============================] - 25s - loss: 17.4814 - val_loss: 17.6514
Epoch 15/20
319200/319200 [==============================] - 25s - loss: 17.3799 - val_loss: 17.4220
Epoch 16/20
319200/319200 [==============================] - 25s - loss: 17.3349 - val_loss: 17.0786
Epoch 17/20
319200/319200 [==============================] - 25s - loss: 17.2229 - val_loss: 17.1846
Epoch 18/20
319200/319200 [==============================] - 25s - loss: 17.1549 - val_loss: 16.9264
Epoch 19/20
319200/319200 [==============================] - 25s - loss: 17.1092 - val_loss: 17.0422
Epoch 20/20
319200/319200 [==============================] - 25s - loss: 17.0327 - val_loss: 18.2891

OKのようです。

LeakyReLUってなんとなく好きなんですが、ReLUの方がやはり軽いみたいなので、ここから先はひとまずReLUを使います。

次に、ネットワークを深くしていきます。3×3の畳み込み層を全部で4層に。

model.add(Conv2D(32, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(AveragePooling2D(pool_size=(11, 11)))
model.add(Flatten())
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 17, 17, 32)        608       
_________________________________________________________________
activation_1 (Activation)    (None, 17, 17, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 15, 15, 32)        9248      
_________________________________________________________________
activation_2 (Activation)    (None, 15, 15, 32)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 13, 13, 32)        9248      
_________________________________________________________________
activation_3 (Activation)    (None, 13, 13, 32)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 11, 11, 32)        9248      
_________________________________________________________________
activation_4 (Activation)    (None, 11, 11, 32)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 11, 11, 1)         33        
_________________________________________________________________
average_pooling2d_1 (Average (None, 1, 1, 1)           0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 1)                 0         
=================================================================
Total params: 28,385.0
Trainable params: 28,385.0
Non-trainable params: 0.0

そして5層、6層、7層、8層、と増やしていって、最後に全部で9層。今回はパディングを入れていないので、どんどん畳み込まれていって、3×3の畳み込みのみで1×1のサイズに。そうなると、最後の平均プーリングはもう意味がありませんので削除します。1×1の畳み込み層も、実質、ただの全結合になってしまいました。

model.add(Conv2D(32, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(Flatten())
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 17, 17, 32)        608       
_________________________________________________________________
activation_1 (Activation)    (None, 17, 17, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 15, 15, 32)        9248      
_________________________________________________________________
activation_2 (Activation)    (None, 15, 15, 32)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 13, 13, 32)        9248      
_________________________________________________________________
activation_3 (Activation)    (None, 13, 13, 32)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 11, 11, 32)        9248      
_________________________________________________________________
activation_4 (Activation)    (None, 11, 11, 32)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 9, 9, 32)          9248      
_________________________________________________________________
activation_5 (Activation)    (None, 9, 9, 32)          0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 7, 7, 32)          9248      
_________________________________________________________________
activation_6 (Activation)    (None, 7, 7, 32)          0         
_________________________________________________________________
conv2d_7 (Conv2D)            (None, 5, 5, 32)          9248      
_________________________________________________________________
activation_7 (Activation)    (None, 5, 5, 32)          0         
_________________________________________________________________
conv2d_8 (Conv2D)            (None, 3, 3, 32)          9248      
_________________________________________________________________
activation_8 (Activation)    (None, 3, 3, 32)          0         
_________________________________________________________________
conv2d_9 (Conv2D)            (None, 1, 1, 32)          9248      
_________________________________________________________________
activation_9 (Activation)    (None, 1, 1, 32)          0         
_________________________________________________________________
conv2d_10 (Conv2D)           (None, 1, 1, 1)           33        
_________________________________________________________________
flatten_1 (Flatten)          (None, 1)                 0         
=================================================================
Total params: 74,625.0
Trainable params: 74,625.0
Non-trainable params: 0.0

3層~9層すべてのTrain Lossをグラフにしてみます。カッコ内の秒数は、2エポック目に掛かった時間です。だいたいこれが、1エポックあたりの平均の実行時間になります。

ネットワークが深くなるにつれ、どんどん賢くなっていくのがよく分かります。しかし、小さく畳み込まれたのをさらに畳み込んでいっているので、学習時間はあまり増えていきません。とはいえ、パラメータ数はどんどん増えていくので、過学習しやすくなったりはしてそうです。

パディングを入れれば、3×3の畳み込みをもっと重ねていくことは可能ですが、ここから先はひとまず9層で続けていきます。

次は、畳み込み層のフィルターの数を増やしていきたいと思います。まずは48に。

model.add(Conv2D(48, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(48, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(48, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(48, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(48, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(48, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(48, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(48, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(48, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(Flatten())
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 17, 17, 48)        912       
_________________________________________________________________
activation_1 (Activation)    (None, 17, 17, 48)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 15, 15, 48)        20784     
_________________________________________________________________
activation_2 (Activation)    (None, 15, 15, 48)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 13, 13, 48)        20784     
_________________________________________________________________
activation_3 (Activation)    (None, 13, 13, 48)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 11, 11, 48)        20784     
_________________________________________________________________
activation_4 (Activation)    (None, 11, 11, 48)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 9, 9, 48)          20784     
_________________________________________________________________
activation_5 (Activation)    (None, 9, 9, 48)          0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 7, 7, 48)          20784     
_________________________________________________________________
activation_6 (Activation)    (None, 7, 7, 48)          0         
_________________________________________________________________
conv2d_7 (Conv2D)            (None, 5, 5, 48)          20784     
_________________________________________________________________
activation_7 (Activation)    (None, 5, 5, 48)          0         
_________________________________________________________________
conv2d_8 (Conv2D)            (None, 3, 3, 48)          20784     
_________________________________________________________________
activation_8 (Activation)    (None, 3, 3, 48)          0         
_________________________________________________________________
conv2d_9 (Conv2D)            (None, 1, 1, 48)          20784     
_________________________________________________________________
activation_9 (Activation)    (None, 1, 1, 48)          0         
_________________________________________________________________
conv2d_10 (Conv2D)           (None, 1, 1, 1)           49        
_________________________________________________________________
flatten_1 (Flatten)          (None, 1)                 0         
=================================================================
Total params: 167,233.0
Trainable params: 167,233.0
Non-trainable params: 0.0

次は64に。

model.add(Conv2D(64, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(Flatten())
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 17, 17, 64)        1216      
_________________________________________________________________
activation_1 (Activation)    (None, 17, 17, 64)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 15, 15, 64)        36928     
_________________________________________________________________
activation_2 (Activation)    (None, 15, 15, 64)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 13, 13, 64)        36928     
_________________________________________________________________
activation_3 (Activation)    (None, 13, 13, 64)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 11, 11, 64)        36928     
_________________________________________________________________
activation_4 (Activation)    (None, 11, 11, 64)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 9, 9, 64)          36928     
_________________________________________________________________
activation_5 (Activation)    (None, 9, 9, 64)          0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 7, 7, 64)          36928     
_________________________________________________________________
activation_6 (Activation)    (None, 7, 7, 64)          0         
_________________________________________________________________
conv2d_7 (Conv2D)            (None, 5, 5, 64)          36928     
_________________________________________________________________
activation_7 (Activation)    (None, 5, 5, 64)          0         
_________________________________________________________________
conv2d_8 (Conv2D)            (None, 3, 3, 64)          36928     
_________________________________________________________________
activation_8 (Activation)    (None, 3, 3, 64)          0         
_________________________________________________________________
conv2d_9 (Conv2D)            (None, 1, 1, 64)          36928     
_________________________________________________________________
activation_9 (Activation)    (None, 1, 1, 64)          0         
_________________________________________________________________
conv2d_10 (Conv2D)           (None, 1, 1, 1)           65        
_________________________________________________________________
flatten_1 (Flatten)          (None, 1)                 0         
=================================================================
Total params: 296,705.0
Trainable params: 296,705.0
Non-trainable params: 0.0

フィルター数の違いを、グラフにしてみます。

これも増やせば増やすほど、賢くなっていきますが、学習時間の増え方もすごいですね。フィルター数64の時のTotal paramsは30万近くに… 身の丈に合っていないような気がするので(笑)、ここから先はひとまずフィルター数は32で続けていきます。

ここまでは、ネットワーク構成をいろいろ試してきましたが、ここで一度、ネットワークに対する入力を変更してみたいと思います。今現在は、石の配置の2面だけですが、これに「その場所の石のダメの数」を加えた3面にしてみました。数値はtanh(ダメの数*0.05)して0と1の間に収めました(0~1に正規化するのは、Kerasのサンプルがそうなっていたので)。ダメの数/256min(1, ダメの数/32)など、まあ何でもいいような気はします。ところで19路盤の最大ダメ数っていくらなんでしょう?

ダメなしとダメありとでの違いを、グラフにしてみます。

うーん、ちょっと効果が薄いですね。実は劇的に良くなるかと期待していたのですが… ダメの数は特に必要な情報でないからなのか、石の配置を見ればそんなことは分かるからなのかちょっとはっきりしませんが、ネットワークへの入力でがんばれることは、意外とあんまり無いのかもしれません。とはいえ、効果が全く無いわけではないので、ここから先はひとまず入力はダメありの3面で続けていきます。

次は、みんな大好き(笑)Batch Normalizationです。私は当初、Batch Normalizationって畳み込み層や全結合層の前に置くものだと、完全に思い込んでいたのですが、どうやら活性化関数の前に置くのが正しい? そのあたりも含めて調べてみます。

まずは、活性化関数の前にBatch Normalizationを置くバージョン。

model.add(Conv2D(32, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(BatchNormalization())
model.add(Activation('relu'))
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(Flatten())
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 17, 17, 32)        896       
_________________________________________________________________
batch_normalization_1 (Batch (None, 17, 17, 32)        128       
_________________________________________________________________
activation_1 (Activation)    (None, 17, 17, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 15, 15, 32)        9248      
_________________________________________________________________
batch_normalization_2 (Batch (None, 15, 15, 32)        128       
_________________________________________________________________
activation_2 (Activation)    (None, 15, 15, 32)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 13, 13, 32)        9248      
_________________________________________________________________
batch_normalization_3 (Batch (None, 13, 13, 32)        128       
_________________________________________________________________
activation_3 (Activation)    (None, 13, 13, 32)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 11, 11, 32)        9248      
_________________________________________________________________
batch_normalization_4 (Batch (None, 11, 11, 32)        128       
_________________________________________________________________
activation_4 (Activation)    (None, 11, 11, 32)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 9, 9, 32)          9248      
_________________________________________________________________
batch_normalization_5 (Batch (None, 9, 9, 32)          128       
_________________________________________________________________
activation_5 (Activation)    (None, 9, 9, 32)          0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 7, 7, 32)          9248      
_________________________________________________________________
batch_normalization_6 (Batch (None, 7, 7, 32)          128       
_________________________________________________________________
activation_6 (Activation)    (None, 7, 7, 32)          0         
_________________________________________________________________
conv2d_7 (Conv2D)            (None, 5, 5, 32)          9248      
_________________________________________________________________
batch_normalization_7 (Batch (None, 5, 5, 32)          128       
_________________________________________________________________
activation_7 (Activation)    (None, 5, 5, 32)          0         
_________________________________________________________________
conv2d_8 (Conv2D)            (None, 3, 3, 32)          9248      
_________________________________________________________________
batch_normalization_8 (Batch (None, 3, 3, 32)          128       
_________________________________________________________________
activation_8 (Activation)    (None, 3, 3, 32)          0         
_________________________________________________________________
conv2d_9 (Conv2D)            (None, 1, 1, 32)          9248      
_________________________________________________________________
batch_normalization_9 (Batch (None, 1, 1, 32)          128       
_________________________________________________________________
activation_9 (Activation)    (None, 1, 1, 32)          0         
_________________________________________________________________
conv2d_10 (Conv2D)           (None, 1, 1, 1)           33        
_________________________________________________________________
flatten_1 (Flatten)          (None, 1)                 0         
=================================================================
Total params: 76,065.0
Trainable params: 75,489.0
Non-trainable params: 576.0

長い…(笑) 次は、活性化関数の後にBatch Normalizationを置くバージョン。

model.add(Conv2D(32, (3, 3), padding='valid', input_shape=x_train.shape[1:]))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Conv2D(32, (3, 3), padding='valid'))
model.add(Activation('relu'))
model.add(BatchNormalization())
model.add(Conv2D(1, (1, 1), padding='valid'))
model.add(Flatten())
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 17, 17, 32)        896       
_________________________________________________________________
activation_1 (Activation)    (None, 17, 17, 32)        0         
_________________________________________________________________
batch_normalization_1 (Batch (None, 17, 17, 32)        128       
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 15, 15, 32)        9248      
_________________________________________________________________
activation_2 (Activation)    (None, 15, 15, 32)        0         
_________________________________________________________________
batch_normalization_2 (Batch (None, 15, 15, 32)        128       
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 13, 13, 32)        9248      
_________________________________________________________________
activation_3 (Activation)    (None, 13, 13, 32)        0         
_________________________________________________________________
batch_normalization_3 (Batch (None, 13, 13, 32)        128       
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 11, 11, 32)        9248      
_________________________________________________________________
activation_4 (Activation)    (None, 11, 11, 32)        0         
_________________________________________________________________
batch_normalization_4 (Batch (None, 11, 11, 32)        128       
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 9, 9, 32)          9248      
_________________________________________________________________
activation_5 (Activation)    (None, 9, 9, 32)          0         
_________________________________________________________________
batch_normalization_5 (Batch (None, 9, 9, 32)          128       
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 7, 7, 32)          9248      
_________________________________________________________________
activation_6 (Activation)    (None, 7, 7, 32)          0         
_________________________________________________________________
batch_normalization_6 (Batch (None, 7, 7, 32)          128       
_________________________________________________________________
conv2d_7 (Conv2D)            (None, 5, 5, 32)          9248      
_________________________________________________________________
activation_7 (Activation)    (None, 5, 5, 32)          0         
_________________________________________________________________
batch_normalization_7 (Batch (None, 5, 5, 32)          128       
_________________________________________________________________
conv2d_8 (Conv2D)            (None, 3, 3, 32)          9248      
_________________________________________________________________
activation_8 (Activation)    (None, 3, 3, 32)          0         
_________________________________________________________________
batch_normalization_8 (Batch (None, 3, 3, 32)          128       
_________________________________________________________________
conv2d_9 (Conv2D)            (None, 1, 1, 32)          9248      
_________________________________________________________________
activation_9 (Activation)    (None, 1, 1, 32)          0         
_________________________________________________________________
batch_normalization_9 (Batch (None, 1, 1, 32)          128       
_________________________________________________________________
conv2d_10 (Conv2D)           (None, 1, 1, 1)           33        
_________________________________________________________________
flatten_1 (Flatten)          (None, 1)                 0         
=================================================================
Total params: 76,065.0
Trainable params: 75,489.0
Non-trainable params: 576.0

これも、グラフにします。今回はValidate Loss付きです。

もう少したくさん学習させてみないと、最終的に収束した時のLossが低くなるのか、学習が速いだけなのか、よく分かりませんが、なんにせよ、とりあえずBatch Normalizationはすばらしい! Validate Lossも10をはっきりと切ってきました。みんな大好きBatch Normalization、僕も大好きです(笑)。1エポックあたりの学習時間は大幅に増えて、たしかに重いのは重いんですが、学習が速くなるのであれば、それも少なくともある程度はペイしそうです。

そして、先ほどの「Batch Normalizationは活性化関数の前なのか後なのか問題」ですが、今回のケースでは、「活性化関数の後」が良さそうです。平均した数字だけ見てもそうなのですが、「活性化関数の前」のValidate Lossの上下にバタバタする感じがちょっと気持ち悪い… ただ、今回は検証用データの量が絶対的に少ないので、もう少しちゃんと調べないと、はっきりしたことは言えません。

今後は、ひとまず「活性化関数の後」にBatch Normalizationを置く形で続けていきます。

いやあ、それにしても長い記事になりました。しかも、まだぜんぜん終わってない… たぶん、大量に追記することになります。

ここまで、やってきて一番思うのは、「学習データって大事」ってことです。これは量も質もですね。最初始めた時、「学習データなんてなんでもいいよ。俺はディープなラーニングがしたいだけなんだよ」って思ってた自分を引っ叩いてやりたい(笑)。ただ、当初はここまで良い数字が出るとは、正直思ってなかったから、ということもあります。例えば、今現在のようなネットワーク構成でも、もう少しネットワークを深くして、もう少しフィルターを増やして、学習データ増やして、ワンコインぐらい課金すれば、Validate Lossが7ぐらいまでいけそうですが、そこまでいければ、今回の評価関数は(も?)同じような盤面は同じように形勢判断を間違えるのだろうと思うので、深さ1の全幅と組み合わせて、GNU Goぐらいならなんとか勝てないでしょうか? もし仮にそれができたら、Keras.js使って、もうこれは何て言うか一丁上がりなんですが、なかなか事前にそこまで夢みることはできませんでした。

そういう訳で、とにかく学習データです。今現在、COSUMIのサーバを使って学習データを大量に作成中です(負荷が低くなったら、自動的に作り始めるようにした)。それができあがったら、また引き続きいろいろ試してみたいと思います。

[追記 2017/5/2]
あの後、学習データをたくさん作りました。まずは、前回使用したデータと今回分との、形勢の分布のグラフを。比較しやすいように、スケールは調整してあります。

前回分のデータの分布を最初に見た時、いくらなんでもこれは分散が足らないんじゃないかと思ったので、今回は平均近辺を適当に間引きながらデータを作成したのですが、あんまりきれいな間引き方になっていないような気がして、少しもったいなかったのですが、思いきって新規作成分の平均近辺をスクエアにぱすっと捨てて、それに前回作成分を足しました。えっと、なに言っている分からないと思いますが(笑)、とにかくグラフのような感じに、真ん中減らしました。前回、21.5だった平均偏差は27.8に。そこだけでいうと、今回の方が厳しいデータセットになっていると思います。

今回使用分は15万局面分+α。これを対称形に8倍して、切り良く120万局面分に減らしました。そして、前回と同じく、その内80%を学習用に、残りの20%を検証用に使用します。

この新しい学習データで、前回最後のネットワーク構成から試してみたいと思います。現在地をおさらいすると、

  • 入力は「手番のプレーヤーの石の配置」「相手の石の配置」「その場所にある石のダメの数」の3面(19,19,3)
  • 3×3の畳み込み層が9層、1×1の畳み込み層が1層、フィルター数は最後以外32
  • 活性化関数は全部ReLU
  • ReLUの後にBatch Normalization

です。さらに今回はここから、入力層の所にパディング付の3×3の畳み込み層を追加していく形で、どんどん深くしてみました。最後は3×3の畳み込み層が18層です。今回はエポック数は固定にしないで、Validate Lossが下げ止まったら、学習を止めるようにしました。話が少しそれますが、Kerasには、そういう時に使うkeras.callbacks.EarlyStoppingというコールバック関数が用意されているのですが、それのpatienceという引数についてのまともな説明が、ウェブ上にほとんど無い! これは本家のドキュメントも一緒で、例えば、日本語版ではこうなってます。

patience: トレーニングが停止し,値が改善しなくなった時のエポック数.

なにを言っているのか、まじでぜんぜん分からん…(笑) 次に本家英語版。

patience: number of epochs with no improvement after which training will be stopped.

私の英語力が確かなら、これも間違っています。例えば、patience=1の時は、最高なり最低なりを1回でも更新できなかったらそこですぐに止まるわけではなく、2回連続して更新できなかったら止まるんです。patience=2の時は、3回です。1回でも更新できなかったらすぐ止まるpatience=0を基準に、さらに何回待つかっていうのがこのpatienceですよ。みなさん気をつけてください。で、今回は最初の「3×3の畳み込み層が9層」の時のみpatience=2(ちょっと少なかった。加減が結構難しい…)、10層からはpatience=3に設定してみました。

うーん、もうネットワークを深くすれば良いだけなのかな? 簡単に数字が良くなっていきます。もっと早くサチるかと思っていたのに、なかなか止まらないので、気持ちよくお金が溶けていきました…(泣) 「こんな風に深くするだけでいいんであれば、小細工なしにResNetってやつをやれば終了じゃない?」ということで、前半部分にショートカットを入れたバージョンの14層と18層も追加で試してみました。先ほどのpatienceは4にしました。14層ならコードはこんな感じ。本当にこれで良いのか、かなり不安ですが…

input = Input(shape=x_train.shape[1:])

fork = Conv2D(32, (3, 3), padding='same')(input)

main = Activation('relu')(fork)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)
main = Activation('relu')(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)

fork = add([main, fork])

main = Activation('relu')(fork)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)
main = Activation('relu')(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)

main = add([main, fork])

main = Activation('relu')(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='valid')(main)
main = Activation('relu')(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='valid')(main)
main = Activation('relu')(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='valid')(main)
main = Activation('relu')(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='valid')(main)
main = Activation('relu')(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='valid')(main)
main = Activation('relu')(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='valid')(main)
main = Activation('relu')(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='valid')(main)
main = Activation('relu')(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='valid')(main)
main = Activation('relu')(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='valid')(main)
main = Activation('relu')(main)
main = BatchNormalization()(main)
main = Conv2D(1, (1, 1), padding='valid')(main)

output = Flatten()(main)

model = Model(inputs=input, outputs=output)

ショートカットあるなしで比較してみます。

「18層の時は、もしかしたらショートカットが効いてるのかな?」ってぐらいですね。そもそも、この程度ではまだぜんぜんネットワークが深すぎるっていうほどのものじゃないのかもしれません。深くしたからサチったのではなくて、そろそろ学習データの精度の問題かも…

ここで一度、現在作っている評価関数が実際にどんな形勢判断を返してくるのか確認することにしてみました。使った評価関数のバージョンは、先ほどの「3×3の畳み込み層が18層/ショートカットあり」。これに、検証用データの先頭200局面分を予測させた時の数値のグラフがこちらです。

当たり前の話なんですが、本当に形勢判断できるんですね(笑)。感動します。ただ、ちょっと気になることもあって、この検証用データのラベルが8つずつ全く同じのが続くのは、同じ局面の対称形が連続して並んでいるからなんですが、評価関数の出力の方はなかなかきれいに揃わないですね。これを「まだ伸び代がある」とか、「対称形8つ全部を順番に評価関数に入れて平均とったら精度上がるんじゃない?」とか、ポジティブに捉えることもできなくはないかもしれませんが、個人的にはこういうのはただただ気持ち悪いです… こうなる理由として考えられるのは、「3×3の畳み込み層のフィルタの初期値が対称形でないから」とか、「学習用データに対称形がすべて含まれているけど、学習するタイミングが前後するので、先に学習した、後に学習したでモデルに与える影響が変わってくるから」とかあたりでしょうか? これはまた調べてみたいですね。

今回の200局面分の予測の中で検証用データのラベルと一番食い違っているのが、グラフ中央右寄りにある100を超えているやつなので、この局面を探して実際に盤面を見てみることにしました。それがこちら。


Sorry, your browser doesn’t support WGo.js.

検証用データのラベルは約138.61。これは、「コミがないとすれば次の手番である白が138.61目勝っている」の意味です。最初に盤面で確認しておかないといけないのは、下辺の黒の大石の死活ですが、これはセキにはなりますが生きてますね(見損じしてないよね?)。だとすると、形勢は白100目弱勝ちぐらいでしょうか? 自作評価関数の出力は対称形8つの平均で87.31。おお、自作評価関数の方がだいぶ近い… 先生より正確とはやるじゃん!(笑) まあ、「石いっぱいあるから強ーい」ぐらいに思っているだけで(笑)、Ray先生のような高度な判断をしている訳ではないような気がしますが、とはいえ、こういった不正確なラベルのせいで、40目も余分に間違えていることにされるようなことがちょくちょくあったら、下がるはずのLossも下がりません。「機械学習では質の良い学習データを大量に用意することが肝心」、という結論にまた落ち着いてしまいますね。ということで、現在、学習データの精度を上げるべく、COSUMIのサーバをまたぶん回しております。いつか、学習データの作成自体を、この評価関数にやらせたいですね。それができれば、量の問題は一発で解決なんですが…

[追記 2017/6/11]
あの後、ASUSのSTRIX-GTX1060-DC2O6GっていうGTX1060・メモリ6GBなビデオカード買いました。EC2への課金が100ドルを超えてきたので、EC2使い続けるのか、別の方法を取るのか、今決めてしまわないといけないと思い、かなりいろいろ考えて、結局GPU買っちゃいました。最初は、GPU買うなら中途半端はだめで、1080ti一択だなと思い込んでいたのですが、そうなってくると電源ユニットの買い直しが確定するので、それがちょっとなあと思っていました。けれども、よく調べてみると、その下のグレードでも十分実用性がありそうですし、なによりはるかに安いので、こういう選択肢になりました。1070でも良かったけど、電源が100%自信が持てなかったので1060に。使っているマザーはASUSのP8H77-Vで、H77と最近のビデオカードとでは動かない時がある、という話を見て少し心配していたのですが、全く問題ありませんでした。このビデオカードは、温度が低い時にファンが完全に止まる静音設計で、それも購入にあたって重視していた点なのですが、そもそもファンが回っていても、めちゃくちゃ静かです。良い買い物でした。こんな高価なビデオカードを買うのは、もちろん初めてですし、ビデオカード自体、一番最後に買ったのはいつのことだろう… Rage Fury MAXX(笑)が最後かな?(一番最後まで使っていたのは、たぶんG400) ちなみに、今現在のメインメモリは16GBなんですが、GPU買ってしまうと、今度はこれを32GBに増やしたくて仕方がない…(笑) ただ、4年半前に買った時は5,880円だった物が、今現在、値段が倍以上する感じで萎えまくりです。うーん、どうしたものか…

そして、学習データもこの前使っていたものを、さらに50k playoutで2目ずつずらしていく形でラベル付け直して精度を上げてみました(この前までは、20k playoutで4目ずつ)。量も少し増やして、計159万局面分。今までと同じく、その内80%を学習用に、残りの20%を検証用に使用します。

ということで、新しいGPUと新しいデータでいろいろ試してみましたが、結局一番数字が良くなるのは、次のようなパディングとショートカットを入れながら、ひたすら3×3の畳み込み層を重ねるだけというシンプルなやつでした。

input = Input(shape=x_train.shape[1:])

fork = Conv2D(32, (3, 3), padding='same')(input)

main = Activation("relu")(fork)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)
main = Activation("relu")(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)

fork = add([main, fork])

main = Activation("relu")(fork)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)
main = Activation("relu")(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)

fork = add([main, fork])

main = Activation("relu")(fork)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)
main = Activation("relu")(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)

fork = add([main, fork])

main = Activation("relu")(fork)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)
main = Activation("relu")(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)

fork = add([main, fork])

main = Activation("relu")(fork)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)
main = Activation("relu")(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)

fork = add([main, fork])

main = Activation("relu")(fork)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)
main = Activation("relu")(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)

fork = add([main, fork])

main = Activation("relu")(fork)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)
main = Activation("relu")(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)

fork = add([main, fork])

main = Activation("relu")(fork)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)
main = Activation("relu")(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)

fork = add([main, fork])

main = Activation("relu")(fork)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)
main = Activation("relu")(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)

fork = add([main, fork])

main = Activation("relu")(fork)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)
main = Activation("relu")(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)

fork = add([main, fork])

main = Activation("relu")(fork)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)
main = Activation("relu")(main)
main = BatchNormalization()(main)
main = Conv2D(32, (3, 3), padding='same')(main)

main = add([main, fork])

main = Activation("relu")(main)
main = BatchNormalization()(main)
main = Conv2D(1, (3, 3), padding='valid')(main)
main = AveragePooling2D(pool_size=(17, 17))(main)

output = Flatten()(main)

model = Model(inputs=input, outputs=output)

ショートカットなし版との比較がこちら。

ショートカットは、はっきり効果があるようです。そして問題は、深くするのが良いのか広くするのが良いのかなんですが、まずはフィルタ数を32で固定して、3×3の畳み込み層が24層、32層、40層の比較がこちら。

そして次に、3×3の畳み込み層を24層に固定して、フィルタ数が32、48、64の比較がこちら。

それ以外にもいろいろ試した結果としては、

  • ReLUとBatch Normalizationの順番は、BN -> ReLU -> ConvよりReLU -> BN -> Convの方が、やはり良さそう
  • 入力は、「だめの数なし」より「だめの数あり」の方が、やはり少し数字が良い
  • オプティマイザにNesterov MomentumなSGDを少し試してみたけど、特に良さそうには見えない

といった感じでしょうか。

数字はだいぶ良くなってきたので、本当に何か使い道も考えてみたいですね。

[追記 2017/6/14]
今のデータ量で行けるところまでやってみようと、3×3の畳み込み層が30層、フィルタ数が48で50エポック回してみました。さらに、その30エポック目からAdamの学習率をKerasのデフォルト(そしてそれは論文の推奨値だそうです)の1e-3から1e-4に小さくしたのと、またさらに、その40エポック目から学習率を1e-5に小さくしたのとのグラフがこちら。

この学習率を下げるのは手動でやっているのですが、本当はこのあたり、コンピュータにスマートによろしくやってもらわないといけないのでしょうね。keras.callbacks.LearningRateScheduler()使ったり、keras.optimizers.Adam()decayを設定すれば良いのかなと、少し試してみたりもしましたが、結局どのくらいずつ下げていけば良いのか事前にはっきり分からないので、もう手動でもいいかな…

それと、学習率下げてはっきりしましたが、最後はほんの少し過学習ぎみですね。対称形に8倍して1,272,000局面分のデータ量では、Trainable params586,513の今回の大きさのネットワークあたりが限界かな、という気がしてます。

しかしそれにしても、数字がかなりよくなってきて、Validate Loss3.6(!)を切ってきました。そんなのもう、ほとんどRay由来のノイズじゃないのかと思ってしまいます。というより、データ作成で何かやらかしていないか、心配になるレベルなのですが…(笑)

ワールド碁チャンピオンシップ

2017.03.20  |  Zen, 囲碁  |  Comments (0)

明日21日から、ワールド碁チャンピオンシップが開催されます。

ワールド碁チャンピオンシップ | -世界最強棋士決定戦-【公式】
http://www.worldgochampionship.net/

Zenの成績の事前予想を簡単にしてみます。とりあえず囲碁電王戦の時のバージョンは13.0でいいのかな? CGOSからいくと、1c1g1c0gが混ざっているのでちょっとややこしいですが、Zen-13.0-1c0gZen-13.3-1c0gとの差46と、Zen-13.3-1c1gZen-14.2-1c1gとの差133との合計179ぐらい進歩したってことでよいでしょうか?(だとしたら、思ったよりかなり強くなっているな…) で、これが一番はっきりしないところですが、囲碁電王戦時点で、治勲先生に対して勝率33.3%ぐらいだったとして120の差。ここまでの分をGo Ratingsのレートでそろえると、

朴廷桓 3575
芈昱廷 3554
井山裕太 3526
DeepZenGo 3301(趙治勲3242-120+179)

Zenの期待勝率は、それぞれ

朴廷桓 17.1%
芈昱廷 18.9%
井山裕太 21.5%

ということなので、3連敗の確率が52.8%。なかなか厳しいですね。ただ、囲碁電王戦の時がかなり不出来だった可能性もそれなりに考えられます。一つ勝ってくれると面白いのですが… ハードとか対局時間とかは、もうよく知りません。とりあえず明日の初戦の対戦相手は、芈昱廷九段です。

[追記 2017/3/21]
Zenは中押し負け。なんだかなあってところもいろいろありましたけど、実力的には十分戦えそうで良かった。そして井山さんも中押し負け。あの碁はなんとか勝ってもらいたかった…

話し変わりますけど、こんなイベントを平日の昼間にやるってどうなんでしょう?

[追記 2017/3/22]
今日もZenと井山さんの負け。井山さんの負けは、ちょっとこたえるなあ…(泣) このままいけば、明日がおそらく最終日ですよね? Zenには悪いけど、井山先生を応援することにします。

[追記 2017/3/24]
井山先生…

Rnやばい

Masterやらなんやらで大騒ぎだったここ一ヶ月間ほどのコンピュータ囲碁界でしたが、そんな中、ひっそりと、しかし何気に本格的にやばいなと思うのが、CGOSでのRayのニューラルネットワーク強化版、Rn(と呼べばいいのか?)の強さです。現在最新のRn.3.6-4cは、アンカーとして(?)以前からずっと居続けているZen-12.0n-1c追いついた(この表現は誤解を生む…)たどり着いた感じです。

(CGOS) 19×19 Computer Go Server
http://www.yss-aya.com/cgos/19×19/standings.html

逆にちょっとだめそうなのがZenで、ここ最近あまりレート上がってないですよね? CGOSだけで判断していいのかは分かりませんが、今度のワールド碁チャンピオンシップは3連敗の可能性が一番高いのでは…

一番直近のRnとZenの最新版の対局を一局どうぞ。


Sorry, your browser doesn’t support WGo.js.

そろそろCOSUMIの19路盤も強くできそうな気がしてきました。AlphaGoがオープンソースにならないかな?(笑)

「Ray 囲碁」をGoogleで検索すると、うちのブログ記事が一番上に来るのが気に食わないので、最後にRay関係のリンクをいろいろ置いておきます。

Rayの本家サイト
http://computer-go-ray.com/

RayのGitHub
https://github.com/koban6/Ray/

RnのGitHub
https://github.com/zakki/Ray/

Rayの中の人のTwitter
https://twitter.com/goraychan

Rnの中の人のTwitter
https://twitter.com/k_matsuzaki

[追記 2017/2/7]
Rnの中の人、松崎さんがこの記事を見てくださったみたいです。

一応念のために書いておくと、CGOSで動いているRnとZen(1c0g)はハードがだいぶ違うというのは理解しています。でもまあ、あれだけガチで開発しているZenが強いのはある意味当然ですし、毎日毎日、計52コアでGNU GoとFuegoを動かし続けている私からすると、自分はものすごく間違ったことをしているのではという不安に襲われ(笑)、「Rnやばい」って感想になります。

コメントがスパム判定されるっていうのは、他の方にも言われました。コメントしようとして下さったみなさん、申し訳ありません。また時間のある時に調べます。

[追記 2017/2/19]
Rn.3.9-4cが14戦全敗だったZen-13.3-1c1gに、Rn.3.10-4cがいきなり土をつけてちょっとびっくり。しかし、すべては偶然の産物か…


Sorry, your browser doesn’t support WGo.js.

それにしても、碁のレベルが高すぎ…

13路盤レベル4の棋譜を公開します

COSUMIの13路盤レベル4の棋譜がある程度溜まってきたので、公開します。

囲碁ブラウザゲーム COSUMI
http://www.cosumi.net/

作り碁になった対局のみの、全部で72,498局分です。COSUMIが黒番のも、白番のも、勝ったのも、負けたのも、まぜこぜになったひとつのテキストファイルが圧縮してあります。解凍してから、適当に切り分けてお使いください。

http://www.perfectsky.net/misc/cosumi_13x13level4.zip

以前公開した9路盤レベル5の棋譜も、よければどうぞ。

http://www.perfectsky.net/misc/cosumi_9x9level5.zip

なんかの役に立ちますかね?

趙治勲 vs Zen

第2回囲碁電王戦が開催されることになりました。

第2回囲碁電王戦
http://denou.jp/go/

記者発表会が行われると発表された時に、ある程度予想できた内容と言えるでしょうか。前回の囲碁電王戦の時も、発表から開催までの間隔が非常に短かったのですが、こうした方が注目を集めやすいという判断なんでしょうね。それにしても、世間一般的に囲碁は全く話題になりませんね。ただ、AlphaGoの時も一回始まってからがすごかったので、まだ今のところはっきりとは言えませんが…

ZenはKGSで10dですから、もちろん勝機は十分にあるでしょう。記者発表会で出てくる12.4というバージョンから、どれだけ上積みがあるのかはちょっと分かりませんが、CGOSとか見てる限り、むちゃくちゃな進歩は無さそうでしょうか?

おそらく、来年の柯潔-AlphaGo戦はすでに決まっているのだと思いますが、ここまでくるとGoogleは一番手直りでやりたいぐらいでしょうから、柯潔先生にそれを受けてもらえなければ、とりあえず今回Zenにがんばってもらって、その後Zenと一番手直り十番勝負ぐらいやればいいんじゃないかと思います。

[追記 2016/11/20]
ということで、第一局は趙治勲名誉名人の中押し勝ちでした。実力が拮抗してそうなので、残り2局もかなり楽しめるんじゃないかと… ただの勘で本当に適当なことを書きますが、右下を黒にコスミできっちり取りきっててもらえれば、Zenが勝てたような気がしないでもないです。できれば、Zenにも一つ勝ってもらいたいですね。

しかし、COSUMIには本当に人が来ないですねえ。第1回囲碁電王戦の時は、(たしかものすごい突貫工事で作った)囲碁のルール説明の動画を流してたのに、なんで今回はそういうの流さないんだろうと思います。考えがあってそうしているのならまあなんですが、ただうっかりしているだけっぽいのがなんとも…

[追記 2016/11/20]
第二局はDeepZenGoの中押し勝ちでした。対局内容が、なんだかものすごくZenな感じでした。これで両者とも一つは勝てて、良かったのではないでしょうか。第三局も楽しみです。

[追記 2016/11/24]
第三局は趙治勲名誉名人の中押し勝ちとなり、第2回囲碁電王戦は趙治勲名誉名人の勝利となりました。おめでとうございます。

ここからは、少し話題になっているZen開発者の加藤さんの判断による投了について、私の考えを書いてみたいと思います。

まず第一に、あんな局面で投了するなんてありえません。趙先生も井山先生も全然しっかりとした見通しが立っている感じではないですし、なによりもZen自身の白番のwinrateが50.1%のタイミングで投げるなんて、正気の沙汰ではない(ちなみに、手元の天頂の囲碁6だと62%ぐらい、山下さん曰くAyaは72%!)。しかし、以前、将棋電王戦で話題になった時にも感じたことですが、そもそも大会レギュレーションで開発者判断の投了が許可されているのがおかしな話で、「本当にドワンゴは学習能力ないわ。五目ナカデで死ね」って最初思ったのですが(笑)、一応念のために、今回の第2回囲碁電王戦の対局ルールを確認してみると、これって開発者判断による投了を認めていない気が…

囲碁電王戦 趙治勲名誉名人 vs DeepZenGo 対局ルール
http://denou.jp/go/pdf/denou_go_rule201611.pdf

ゆっくりと順番に読んでいきますと、まず今回の大会では対局ルールを

対局は日本囲碁規約に準ずるものとする。

としています。日本囲碁規約とはこれのことです。

日本囲碁規約(全文) | 棋戦 | 囲碁の日本棋院
http://www.nihonkiin.or.jp/match/kiyaku/zenbun.html

この日本囲碁規約の第十一条でこのように定められています。

第十一条(投了)
対局の途中でも、自らの負けを申し出て対局を終えることができる。これを「投了」という。その相手方を「中押勝」という。

素直に読めば、負けを申し出ることができるのは対局者のみ、今回の場合はDeepZenGo(と趙治勲名誉名人)のみです。そして、先ほどの「囲碁電王戦 趙治勲名誉名人 vs DeepZenGo 対局ルール」にはこれを上書き、または補完するような規定が見つかりません。このようなレギュレーションで行われていた対局にもかかわらず、開発者の判断で勝手に投了するのは無理があると思われます。

開発者の判断で投了することの是非が、過去に大きく話題になったのは、なんといっても将棋電王戦FINALでの、AWAKE開発者、巨瀬亮一さんによる21手投了でしょう。あの投了に対して様々な議論があるのは致し方ないことですが、ただ間違いなく言えるのは、レギュレーション的には問題なかったということです。

将棋電王戦FINAL 対局ルール
http://ex.nicovideo.jp/img/denou/final/pc/Rules_denousen_final.pdf

着手確定について」の項目には、こう書かれています。

ソフト開発者には「投了する」の権利を認める。

ドワンゴはしっかりとした意図を持って、このような権利を開発者に付与することを今回は止めたのではないかと推測するのですが、もし仮にそうだとしたら、運営としてレギュレーション違反だとちゃんと指摘しようよ…

長くなりましたが、まとめます。

  • 開発者の判断による投了は、レギュレーション違反。運営も含めて、なぜみんなそれを指摘しない…
  • 仮にレギュレーション的に問題なくても、あんな所で投げるな!(怒)

以上です。

13路盤にレベル4を追加しました

COSUMIの13路盤に、レベル4の強さ設定を追加しました。

囲碁ブラウザゲーム COSUMI
http://www.cosumi.net/

ただし、今回もサーバの負荷が高い時に対局が開始できず、9路盤レベル5や11路盤レベル4よりもさらに早い段階で、対局開始不可能になりますのでご了承ください。今現在は、一日の内、1/5ぐらいは動かない感じなんですが、今後は正直ちょっとよくわかりません。「打てればラッキー」ぐらいでお願いします。棋力は、囲碁クエだと1700にはちょっと足りないくらいでしょうか? 自分でも何局か打ちましたが、まだ少し調整が必要な気はしています。

今回の13路盤レベル4ははっきり言って重いです… あまりに重いので、一局あたりサーバ代がいくらになるのかを試しに計算してみると、サーバのリソースをきっちり使い切ったとして、0.15円ぐらいでした。最初、「意外と安いな」と思ってしまった自分がいて怖かったのですが、いやいやぜんぜん安くないから!(笑) でも、サーバの負荷が高い時間帯に止めれば、実質タダであります。

今回はRay先生のお世話になろうかと、当初考えていたのですが、とりあえず実績のあるFuegoでいってみます。あと、今回の13路盤レベル4の棋譜がある程度たまったら、後日まとめて公開したいと思います。

[追記 2017/1/8]
棋譜を公開しました。

http://www.perfectsky.net/blog/?p=339

Amazon EC2でDarkForestを動かしてみた

Amazon EC2のg2.2xlargeでDarkForestを動かしてみました。G2はGPUなインスタンスファミリーです。基本的にドキュメントどおりで、あまり中身のある内容ではありませんが、以下、簡単に手順を書いていきたいと思います。

まずはともあれ、DarkForestのドキュメントに目を通しておきます(g2.2xlargeはそんなに安くありませんので(笑)、インスタンス立ち上げる前に準備をしっかりしとかないとね!)。

darkforestGo/README.md at master ・ facebookresearch/darkforestGo ・ GitHub
https://github.com/facebookresearch/darkforestGo/blob/master/README.md

EC2は初めて使ったのですが、インスタンスの使用数に制限があって、私の場合は、なんとg2.2xlarge0(!)でした。制限緩和のリクエストは可能ですが、承認されるのに私の場合で半日ほど掛かりましたので、使用の予定がある時は、早めに確認しておくことをお勧めします。

OSは、Ubuntu 16.04で今回はいきたいと思います。Ubuntu初めて触りました。初めてだらけです。こちらのページで、Version16.04 LTSを選び、Instanch Typehvm:ebs-ssdを選びして(他との違いがよく分かりませんが…)、最後にZoneus-west-2(オレゴン)なami-191fd379に決定しました。

インスタンスを起動したら、まず最初にこちらに書かれていることを全部やります。ただし、cuDNNはcudnn-7.5-linux-x64-v5.1-rc.tgzを使用してみました。cuDNNのダウンロードには、NVIDIAのAccelerated Computing Developer Programへの登録が必要です。

Ubuntu 16.04へのCUDAインストール方法 – Qiita
http://qiita.com/yukoba/items/3692f1cb677b2383c983

次にTorchです。こちらを参考にします。

Torch | Getting started with Torch
http://torch.ch/docs/getting-started.html

言われるようにやっていきます。

$ git clone https://github.com/torch/distro.git ~/torch --recursive
$ cd ~/torch
$ bash install-deps
$ ./install.sh
$ source ~/.bashrc
$ luarocks install class
$ luarocks install image
$ luarocks install tds
$ luarocks install cudnn

そして、本題のDarkForest。まずはコンパイル。

$ git clone https://github.com/facebookresearch/darkforestGo.git ~/darkforest --recursive
$ cd ~/darkforest
$ sh ./compile.sh

次に、モデルファイルを用意します。

$ mkdir ~/darkforest/models

作ったmodelsディレクトリにこちらのファイルを(よく分からんから全部)入れておきます。

Dropbox – df_models
https://www.dropbox.com/sh/6nm8g8z163omb9f/AABQxJyV7EIdbHKd9rnPQGnha?dl=0

次に、pipeファイル用のディレクトリをどこか適当な場所に作ります。

$ mkdir ~/df_pipe

ここまでで、準備は完了です。そして、実際にDarkForestを動かすためには、まずGPUサーバを動かします。ここで、先ほどのpipeファイル用のディレクトリを指定してください。

$ cd ~/darkforest/local_evaluator
$ sh cnn_evaluator.sh 1 ~/df_pipe

そして、本体を動かします。再度、先ほどのpipeファイル用のディレクトリを指定してください。

$ cd ~/darkforest/cnnPlayerV2
$ th cnnPlayerMCTSV2.lua --pipe_path ~/df_pipe

これでGTPコマンドを受け付けてくれるようになります。cnnPlayerMCTSV2.luaにはオプションがいろいろあるので確認してみてください。ただ、MCTSではないPure-DCNN playerCNNPlayerV3.luaっていうのもあるのですが、こいつが動いてくれません(本当は、こっちに興味があったのですが…)。df.binは、代わりにdf2.binとかを使えばいいのかもしれませんが、value_model.binっていうのがどこにも見当たりません。残念です。

最後に、gogui-twogtpで取った棋譜を3局載せておきます。3局とも、黒が--time_limit 10で、白が--time_limit 20です(実際の消費時間は、白が黒の約1.37倍)。


Sorry, your browser doesn’t support WGo.js.

Sorry, your browser doesn’t support WGo.js.

Sorry, your browser doesn’t support WGo.js.

はっきりとは棋力が分かりませんが、とりあえず私よりは間違いなく強そう…(笑) とはいえ、私はこの3局以外にも何局か棋譜を確認しましたが、あきらかにおかしな手が結構あります。一種の攻め合いのような時が多いように思えますが、例えば、3局目の291手目(同じく292手目、293手目、ついでに295手目!)とかやばすぎる… 「MCTSは攻め合いが…」とかそんなレベルではないと思うし、というか、これはもうただのアタリアタリですしね。なんか致命的なのが、コードに残っているような気がします。

あと、部分部分でDarkForestがものすごく好む形っていうのがいろいろありますね。いくつかの棋譜を続けて見ていたら、「あれっ、これ今さっき見たやつじゃない?」ってなるぐらい、部分的に同じような形のオンパレードになります。ひとつだけ例をあげると、星に小ゲイマに掛かられた時、ほぼ例外なくケイマか一間に受けて、周りの状況がどうであれ、ハサミ返すことはしません(私がざっと見たかぎり、約20回中0回でした)。モデル、ひいてはそれを作成するのに使った棋譜によるところが大きいのだろうし、これをDarkForestの特徴とは言っていいのかよく分かりませんが、Fuegoなどでは、あまり感じない傾向だと思います。

先ほどの3局は、並列ではなく一局ずつ打たせてて、全部で3時間以上掛かっています。そして、あまりよく分からないですが、GPUはだいたい使い切っているように見えます。もちろん、消費リソースをもっと絞って打たせることはできますが、やっぱりお金が掛かりますね。「DarkForestを、なんらかの形でCOSUMIで使えたら…」と思ったのですが、簡単ではないなあ… また、しばらく考えておきます。

天頂の囲碁6 Zenを使って置き石の価値を調べる

2016.07.02  |  Fuego, Zen, 囲碁  |  Comments (0)

HiraBotの中の方が、「HiraBotにたくさん対局させることによって適切なコミ(置き石の価値)を調べる」ということをされています。

適切なコミを求める
http://kiyoshifk.dip.jp/kiyoshifk/apk/komi-V4.pdf

私もこれを見て、最近購入した天頂の囲碁6 Zenを使って、同じく置き石の価値を調べてみたくなったので、試してみました。と言っても、たくさん対局させるのはかなり大変なので、コミをいろいろ変えて考えさせて評価値(win rate)が50になるところを探ってみる、という非常に簡易的な方法です(要するに、最新版の天頂の囲碁を手に入れたのがうれしくて、ちょっと浮かれてなにかやってみたくなっただけです(笑))。

今回は2子、3子、4子を調べてみました。次のグラフのY軸の評価値は、置き石を置いた開始局面で「検討」を使って次の手を考えさせ、その時に探索回数が一番多かった手の評価値です(「検討」はある程度までいくと自動的に止まりますが、そこまで考えさせてからの数値です)。天頂の囲碁は、それ自体では、コミが9目半までしか設定できない(と思う)ので、あらかじめコミを設定しておいたSGFファイルを作成し、それを読み込ませて考えさせています(たぶん、これで特に問題は無いと思います)。

ということで、見ていただいたとおり、だいたい2子で15目3子で25目4子で35目という結果でした。やり方が雑なのはよく分かっていますが、それにしても置き石の価値がちょっと小さすぎですよね。うーんなんでかな… 少し気になるのは、今回のように置き碁の初期局面を白に考えさせると、時間が経つにつれ評価値がすうっと下がっていくんですよね。長い時間考えさせると、探索回数が増えるにつれ形勢の良い悪いがだんだんとはっきりしてきて、評価値が50から離れていく傾向っていうのは常に少しあるかなと思うのですが、今回は、50を超えていてもほぼ例外なく時間とともに数値が下がってきます。これは、黒が置き石を上手に利用する手順が、探索するにつれ見つかってくるってことなんでしょうか? 「豚に真珠」ならぬ「へぼに置き石」的な…(笑) しかし、それだけではこの数字の少なさはちょっと説明ができないような気もします。

ということで、最後にFuego先生のご意見もお伺いすることにしました。1000k playoutで4子のみ、以下のグラフは先ほどの天頂の囲碁の4子との比較です。

Fuegoですら、38.5目って言ってます。それでも少なすぎる気がしますが、どっちにしろ天頂の囲碁の方はやっぱりなんかおかしいですよね。たぶん、私が何か勘違いしているんだと思います。

DarkForestもソースコードが公開されました

2016.06.11  |  DarkForest, 囲碁  |  Comments (1)

あのDarkForestもソースコードが公開されています。

GitHub – facebookresearch/darkforestGo: DarkForest, the Facebook Go engine.
https://github.com/facebookresearch/darkforestGo

なんかもうすごいですね。びっくりしました。これはさくっと動かせるんでしょうか?

将棋の方も、技巧とかnozomiとか驚くことばかりですけど、囲碁もなんか時代が変わった感があります。

[追記 2016/8/26]
Amazon EC2でDarkForestを動かしてみました。

http://www.perfectsky.net/blog/?p=323

強豪囲碁ソフト「Ray」のソースコードが公開されました

先日の第9回UEC杯コンピュータ囲碁大会でも7位になった、とても強い囲碁ソフト、「Ray」のソースコードが公開されています。

Ray – Computer Go Program
http://computer-go-ray.com/

すばらしすぎる話です。現時点で、オープンソース最強は間違いないところでしょうか?

ということで、とりあえずFuegoとたくさん対局させてみました。まずは19路盤、Ray 5,000playoutとFuego 5,000playoutで先後を換えて50局ずつ計100局やってみました。Windows用のバイナリも公開されていて、ただなぜか動作がすごく遅いらしいのですが、今回はLinuxで動かしてます。

Rayは--playout 5000というオプションと付けただけ。Fuegoはこれだけ。

uct_param_search number_threads 1
uct_param_player ignore_clock 1
uct_param_player max_games 5000

結果は、

Ray 5,000po(443.5sec) 89勝 – 11勝 Fuego 5,000po(211.5sec)

カッコ内は一局あたりの平均消費時間です。この設定だとRayがFuegoの2倍ほど時間を使うので、次にRayのみ2,500playoutにしてさらに試してみます。結果は、

Ray 2,500po(210.9sec) 81勝 – 19勝 Fuego 5,000po(198.9sec)

Ray強い…

次に13路盤で、Fuego 10,000playoutに対してRay 5,000playout、3,000playout、2,000playoutでも試してみました。結果は、

Ray 5,000po(69.0sec) 76勝 – 24勝 Fuego 10,000po(59.5sec)
Ray 3,000po(42.1sec) 56勝 – 44勝 Fuego 10,000po(58.7sec)
Ray 2,000po(27.8sec) 50勝 – 50勝 Fuego 10,000po(57.7sec)

13路盤では、Fuegoの倍ほど速そうです。COSUMIでは、Fuegoを動かすためだけに、だいたい年50万円ぐらい掛かっていると思うので、それが半分になる計算に…(ニヤリ) などという皮算用をついしてしまいますが、実際はまたいろいろ大変かな? さらに強くなったバージョンを、いつか公開していただけたら、かなり真剣に考えると思います。

それにしても、このRayに勝てる方って囲碁ファン全体の何パーセントぐらいなんでしょうか? たぶん、大半の人はもう勝てないと思うのですが、そんなソフトが自由に使える訳で、なんかすごいですね…

「天頂の囲碁6 Zen」と「最強の囲碁 Deep Learning」

2016.05.21  |  Zen, 囲碁  |  Comments (1)

「天頂の囲碁」と「最強の囲碁」の最新版が発売されます。

『天頂の囲碁6 Zen』特設ページ | マイナビブックス
https://book.mynavi.jp/tencho6/

最強の囲碁 Deep Learning
http://www.unbalance.co.jp/igo/sigoDL/

名前に「Zen」とか「Deep Learning」とか入れたほうが売れると考えられているのが、なんだか今時な感じがしますね。どちらかを購入したいのですが、天頂の囲碁は3を持っているので、今回は最強の囲碁にしようかな?

[追記 2016/7/3]

最強の囲碁は発売延期なったようです。英語版の方はまだ売っているように見えますが、違うのかな? とりあえず、私は天頂の囲碁を購入しました。

天頂の囲碁6 Zenは、相変わらず6つ入っている石音がどれも気に入らないので、COSUMIのに差し替えました。もし良ければ、ここに置いておきますのでご自由にお使いください。

http://www.perfectsky.net/misc/sound/stone.wav

天頂の囲碁6 Zenがインストールされたフォルダ(私の環境では、C:\Program Files (x86)\Mynavi\天頂の囲碁 6 Zen)の中のWavフォルダの中に、Stone1.wavStone6.wavがありますので、どれかひとつと差し替えれば使えます。元のファイルは一応バックアップ取っておいた方がいいですね。石音の設定は[表示]-[オプション][碁石の効果音]です。

井山棋聖の七冠挑戦とCOSUMI

昨日の第54期十段戦第3局は、黒番伊田十段の中押し勝ちとなり、井山棋聖の七冠達成は次局以降へと持ち越しとなりました。私は、「井山七冠」を何とか一度見てみたいと思っている人間なんですが、昨日の結果はまあこれで良かったのかなと思っています。伊田先生の意地を強く感じましたし、なんだか盛り上がりに欠けるなあと思っていた今回の七冠挑戦ですが、一回世間の注目を大きく集めることができたような気がしてます。

ここでは、井山棋聖の七冠挑戦関連の話題にCOSUMIがどんな影響を受けたのか、書いていきたいと思います。

囲碁ブラウザゲーム COSUMI
http://www.cosumi.net/

とりあえず、昨日は言うほどたいしたことはなかったような気がしますが(AlphaGo騒動で感覚が麻痺しているだけかも(笑))、それでも一瞬凄いことに…

PM5:26、たぶんテレビ? 4桁は初めて見ました…

続きの情報は、おいおいここに追記していきます。

[追記 2016/4/24]
ということで、井山七冠が誕生したわけですが、COSUMIへの新たな人の流入はほとんど無いと言っていい状況です。いろんな意味でこれは結構まずいと思いますが、無駄に毒吐いてもしかたないので、もう別に何も言いません。一週間後ぐらいに、年始からのアクセスの推移を追記して、AlphaGoの時との比較なんかが分かるようにしたいと考えてますので、興味のある方はぜひご覧ください。

[追記 2016/5/2]
年始から4月末までのCOSUMIへの1日単位のアクセスの推移です。

次に、COSUMI開始時から4月末まで。

最後は、「AlphaGo vs セドル」の時と「井山七冠誕生」の時とでの、COSUMIへのアクセスの質的な違いが一番よく分かる気がしているグラフを載せておきたいと思います。3月1日から4月末までの、1時間単位の新規セッション率の推移です。

このグラフについて少しだけ補足しておくと、右側「井山七冠誕生」の方の一番大きな3つのスパイクは、左から順に、「新・情報7daysニュースキャスター」(ダイヤモンド囲碁サロンとかが出てた?)、「アッコにおまかせ」(碁的の話などをしてた?)、「プロフェッショナル 仕事の流儀」(約2年前の再放送。今日もまた放送してました)で間違いないと思います。一番最初の「新・情報7daysニュースキャスター」ですら、井山七冠誕生の瞬間から丸3日以上経っての話というのも、なんだか凄いです。これ以上のことは、もうノーコメントで…(笑)

[追記 2016/5/6]
PM10:42、報道ステーションです(報道ステーション多いなあ)。

これもまた、二度とはお目にかかれなさそうな数字です。この「現在★人のアクティブユーザーがサイトを訪問しています」ってやつは、その時たまたまGoogle Analyticsの管理画面を見ていないかぎり確認できないんですが、実は、自分の知らない時にもっとすごい数字になっていることがあるんでしょうか? ちなみにこういう数字になる時は、大半が囲碁のルール解説のページ(http://www.cosumi.net/learn.html)へのアクセスで、さらにいつもはけっして多くはない、スマホからのアクセスだらけになります。

サーバの負荷が高い時に9路盤レベル5と11路盤レベル4の対局が開始できなくなりました

COSUMIのサーバの負荷が高い時に、9路盤レベル5と11路盤レベル4の対局が開始できなくなりました。

囲碁ブラウザゲーム COSUMI
http://www.cosumi.net/

現時点では、これは予防的処置なので、実際に対局が開始できない時間帯はほとんどないはずです(井山七冠が誕生したら知りませんが…)。ただ、もし今後、対局ができない状態がある程度常態化しても、それがもうよっぽどではないかぎり、サーバの増強は控えさせていただきます。実は、この一年間にお支払いしたサーバ代が160万を超えていて、いくらなんでもさすがにやりすぎだったので、ちょっと自重します。私はここ何年もの間、「朝起きたら、GNU GoとFuegoが二子ずつ強くなっていないかな」と思いながら床に就く日々を送らせていただいております(笑)。

というマイナス方向の改変のお知らせでしたが、今回のような対策をすれば、今度は逆に今後13路盤レベル4が追加できるかもしれません。せっかく高いお金を出して買ったリソースなので、できるだけ全部使い切りたいですしね。状況見ながら考えておきます。

という訳で、井山七冠対策はこれにて終了。何とか間に合いました(結構早い段階からいろいろがんばっていたおかげで、AlphaGo騒動も乗り切ることができました。何もしていない時にあんなの始まったら、どうにもなんない(笑))。「ここまでやってだめならもう知らない」って感じなんですが、しかし、今のところなんだか盛り上がりに欠けますねえ。AlphaGoのインパクトが強すぎたせいもあるんでしょうか?

AlphaGoがKGSに来る?

2016.03.26  |  AlphaGo, 囲碁  |  Comments (0)

かもしれないし、来ないかもしれないそうです。

AlphaGo is going to play on KGS : baduk
https://www.reddit.com/r/baduk/comments/4bsbww/alphago_is_going_to_play_on_kgs/

とりあえずアカウントだけは出来ています。

https://www.gokgs.com/gameArchives.jsp?user=AlphaGoBot

ところでこれはマジなんでしょうか?

おそらく、左下に「自己対戦だから…」みたいなことが書かれているんだと予測しますが、それにしてもすご過ぎでしょ! こっちの方が、対セドル戦の結果よりよっぽど怖いよ…