囲碁の局面の形勢を、目数で返すNNを公開します。ここ最近も改良しようといろいろと試していたのですが、ほとんど進歩が無かったので、今回公開するのは最新のバージョンですが、数か月前に作成した、今現在white shadeで使っているものと性能はほとんど同じです。
http://www.perfectsky.net/misc/whiteshade_vn-20190902.h5
white shade – 囲碁ブラウザゲーム COSUMI
https://www.cosumi.net/whiteshade.html
13路盤より大きな碁盤サイズには対応していませんが、それより小さい碁盤サイズは、特になにも問題はないと思います(12路盤、10路盤、8路盤とかもOK)。公開するのは、KerasのHDF5形式のファイルですが、TensorFlow.jsのコンバータも私の環境では問題なく通ります。このNNを使っても、そんなに強い思考エンジンは作れないと思いますが、目数で形勢を返すNNは比較的珍しいと思うので、だれかが何か面白いものを作ってくれたら嬉しいです。
以下、使い方を簡単に説明していきます。わたしも本当によく分かっていないので、難しいこと聞かれても、どうせちゃんとお答えできませんので、この説明で分からなかったら、もう諦めちゃってください(笑)。
入力は「盤上」・「次の手番の石」・「相手の石」・「コウで打てない場所」の4面です。「次の手番の石」・「相手の石」は、「黒石」・「白石」ではないので注意してください。出力は「次の手番から見た目数単位での形勢(中国ルール・コミは無いとして)」です。「黒から見た目数単位での形勢」ではないので注意してください。入力は当てはまる所は1、そうでない所は0です。
例えば次の局面の形勢を知りたい場合は、
次のようなコードになります。
# score.py
from keras.models import Model, load_model
import numpy as np
MODEL = load_model('whiteshade_vn-20190902.h5')
TURN = 'WHITE' # or 'BLACK'
BOARD_SIZE = 9
BOARD = [
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', 'W', 'B', ' ', ' ', ' ', ' ', ' '],
[' ', 'W', 'B', 'K', 'B', ' ', 'B', ' ', ' '],
[' ', ' ', 'W', 'B', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
]
NUM_PREDICT = 1
MAX_BOARD_SIZE = 13
NUM_INPUT_LAYER = 4
ON_BOARD = 0
MY_STONE = 1
YR_STONE = 2
KO = 3
################################################################################
predict_input = np.zeros((NUM_PREDICT, MAX_BOARD_SIZE, MAX_BOARD_SIZE, NUM_INPUT_LAYER))
for i in range(BOARD_SIZE):
for j in range(BOARD_SIZE):
predict_input[0][i][j][ON_BOARD] = 1
if BOARD[i][j] == 'B':
if TURN == 'BLACK':
predict_input[0][i][j][MY_STONE] = 1
else:
predict_input[0][i][j][YR_STONE] = 1
elif BOARD[i][j] == 'W':
if TURN == 'BLACK':
predict_input[0][i][j][YR_STONE] = 1
else:
predict_input[0][i][j][MY_STONE] = 1
elif BOARD[i][j] == 'K':
predict_input[0][i][j][KO] = 1
predict_output = MODEL.predict(predict_input)
if TURN == 'BLACK':
score = predict_output[0][0]
else:
score = -(predict_output[0][0])
if score > 0:
print('B+' + str(score))
else:
print('W+' + str(abs(score)))
$ python ./score.py 2>/dev/null
B+18.516293
13路盤より小さい碁盤は、13×13のどこに描いても大丈夫だと思っていたのですが、四隅のどれかにぴたっとくっつけたデータでしか学習してなかったせいか、例えば9路盤を13×13のど真ん中に描いてpredictさせると、ちょっと精度が悪くなるような気がします。まあ普通に、0の0から使ってください。
このNNは、まず座標ごとにどちらの地になりそうなのかを予測し、それを集計したような構造になっています。これは、そういうふうにした方が、評価関数として性能が良かったからそうしただけなのですが、結果的にその集計前の途中の出力を、副産物として利用することも一応は可能です。ただ、本当に一応ですね。例えば9路盤をpredictさせると、盤外の座標にもスコアがしっかり付いていて(!)、でもどちらかに偏らないようになっていたりと、13×13全体で上手にバランスをとっている感じなので、まあ参考程度に見てください。例えば、次のようなコードで使えます。activation_103っていう名前のレイヤーの出力を使ってください。
# territory.py
from keras.models import Model, load_model
import numpy as np
OUTPUT_LAYER_NAME = 'activation_103'
MODEL = load_model('whiteshade_vn-20190902.h5')
MODEL_2 = Model(inputs = MODEL.input,
outputs = MODEL.get_layer(OUTPUT_LAYER_NAME).output)
TURN = 'WHITE' # or 'BLACK'
BOARD_SIZE = 9
BOARD = [
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', 'W', 'B', ' ', ' ', ' ', ' ', ' '],
[' ', 'W', 'B', 'K', 'B', ' ', 'B', ' ', ' '],
[' ', ' ', 'W', 'B', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
[' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']
]
NUM_PREDICT = 1
MAX_BOARD_SIZE = 13
NUM_INPUT_LAYER = 4
ON_BOARD = 0
MY_STONE = 1
YR_STONE = 2
KO = 3
################################################################################
predict_input = np.zeros((NUM_PREDICT, MAX_BOARD_SIZE, MAX_BOARD_SIZE, NUM_INPUT_LAYER))
for i in range(BOARD_SIZE):
for j in range(BOARD_SIZE):
predict_input[0][i][j][ON_BOARD] = 1
if BOARD[i][j] == 'B':
if TURN == 'BLACK':
predict_input[0][i][j][MY_STONE] = 1
else:
predict_input[0][i][j][YR_STONE] = 1
elif BOARD[i][j] == 'W':
if TURN == 'BLACK':
predict_input[0][i][j][YR_STONE] = 1
else:
predict_input[0][i][j][MY_STONE] = 1
elif BOARD[i][j] == 'K':
predict_input[0][i][j][KO] = 1
predict_output = MODEL_2.predict(predict_input)
for i in range(MAX_BOARD_SIZE):
for j in range(MAX_BOARD_SIZE):
if TURN == 'BLACK':
darkness = predict_output[0][i][j][0]
else:
darkness = -(predict_output[0][i][j][0])
if darkness > 0.9:
print ('B', end='')
elif darkness > 0.5:
print ('b', end='')
elif darkness > -0.5:
print ('.', end='')
elif darkness > -0.9:
print ('w', end='')
else:
print ('W', end='')
print ('\n', end='')
$ python ./territory.py 2>/dev/null
bw.bBBBBBBWBB
.WWw...wBBWWB
WWwbBBBbBBWWB
WWwbBBBBBBWWB
WWwbBBBBBBWWB
WWwbBBBBBBWWB
WWw.BBBbBBWWB
WWWw...wBBWWB
b..bBBBBBBWWB
BBBBBBBBBBWWB
WBBBBBBBBBBBB
WWWWWWWWWWWWB
WWWWWWWWWWWWW
盤上は、左上の9×9なんですけどね。盤外がなんだか凄まじいことに…(笑)
white shadeの方は、NNの入力に「ダメの数」追加するか、少し深く探索した方が良さそうです。そういったことは無しでも、レベル5までは問題なくいけるのですが、できればレベル6が作りたい…