![将棋AIで学ぶディープラーニング](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.amazonaws.com%2F0%2F233744%2Fded117b9-37c7-1b41-033d-960e63ca5fe1.jpeg?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&s=935ba98476ebc61c2169e698c737d5cf)
###解説したいこと
(1)USIプロトコルについて
(2)価値ネットワーク「1手」探索プログラム
(3)Policy vs. Value
###(1)USIプロトコルについて
USI:Universal Shogi Interface
【参考】
・ホーム>USIプロトコルとは
上記参考サイトから
対局における通信の具体例が示されています。
本書では以下のような簡単なやり取りとして示されています。
※USIエンジンがコマンドの応答を返す際は、最後に必ず改行コード(¥n)を追加します
また、座標系等は以下のとおりです。
9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | - |
---|---|---|---|---|---|---|---|---|---|
9a | 8a | 7a | 6a | 5a | 4a | 3a | 2a | 1a | 一 |
9b | 8b | 7b | 6b | 5b | 4b | 3b | 2b | 1b | 二 |
9c | 8c | 7c | 6c | 5c | 4c | 3c | 2c | 1c | 三 |
9d | 8d | 7d | 6d | 5d | 4d | 3d | 2d | 1d | 四 |
9e | 8e | 7e | 6e | 5e | 4e | 3e | 2e | 1e | 五 |
9f | 8f | 7f | 6f | 5f | 4f | 3f | 2f | 1f | 六 |
9g | 8g | 7g | 6g | 5g | 4g | 3g | 2g | 1g | 七 |
9h | 8h | 7h | 6h | 5h | 4h | 3h | 2h | 1h | 八 |
9i | 8i | 7i | 6i | 5i | 4i | 3i | 2i | 1i | 九 |
将棋所の上記参考サイトから引用します。
「USIで使用される、SFEN(Shogi Forsyth-Edwards Notation)表記法による局面と指し手の表記を解説します。
駒の種類は、それぞれ1文字のアルファベットで表され、先手の駒は大文字、後手の駒は小文字になります。
先手の玉:K、後手の玉:k (Kingの頭文字)
先手の飛車:R、後手の飛車:r (Rookの頭文字)
先手の角:B、後手の角:b (Bishopの頭文字)
先手の金:G、後手の金:g (Goldの頭文字)
先手の銀:S、後手の銀:s (Silverの頭文字)
先手の桂馬:N、後手の桂馬:n (kNightより)
先手の香車:L、後手の香車:l (Lanceの頭文字)
先手の歩:P、後手の歩:p (Pawnの頭文字)
駒が成った状態を表記するには、駒の文字の前に+をつけます。先手のと金は+Pとなります。
盤面を表記するとき、1段目の左側(9筋側)から駒の種類を書いていきます。空白の升は、空白が続く個数の数字を書きます。
平手初期局面の場合、1段目は、左から後手の駒が香桂銀金玉金銀桂香と並んでいるので、lnsgkgsnlとなります。2段目は、空白が1升、後手の飛車、空白が5升、後手の角、空白が1升というように並んでいるので、1r5b1となります。
各段がそのように表記され、1段目から9段目まで、それぞれの段の表記を/(半角スラッシュ)でつなげて書くと、盤面の表記になります。平手初期局面であれば
lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL
ということになります。」
以下、実際にコマンドをたたいてみると、上記のやり取りが再現できます。
>python -m pydlshogi.usi.usi_policy_player
usi
id name policy_player
option name modelfile type string default C:\Users\tosio\fastText\path\to\corpus\AB\KerasExample\dlshogi\model\model_policy
usiok
setoption name modelfile value C:\Users\tosio\fastText\path\to\corpus\AB\KerasExample\dlshogi\model\model_policy
isready
readyok
position startpos
lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1
go
info string 9g9f : 0.00476
info string 8g8f : 0.00001
info string 7g7f : 0.45925
info string 6g6f : 0.00017
info string 5g5f : 0.00244
info string 4g4f : 0.00074
info string 3g3f : 0.00005
info string 2g2f : 0.51143
info string 1g1f : 0.00026
info string 9i9h : 0.00000
info string 1i1h : 0.00000
info string 7i7h : 0.00087
info string 7i6h : 0.00048
info string 3i4h : 0.00265
info string 3i3h : 0.00581
info string 6i7h : 0.00145
info string 6i6h : 0.00000
info string 6i5h : 0.00012
info string 4i5h : 0.00601
info string 4i4h : 0.00009
info string 4i3h : 0.00000
info string 2h7h : 0.00009
info string 2h6h : 0.00006
info string 2h5h : 0.00003
info string 2h4h : 0.00001
info string 2h3h : 0.00000
info string 2h1h : 0.00000
info string 5i6h : 0.00133
info string 5i5h : 0.00166
info string 5i4h : 0.00001
bestmove 2g2f
ここで2g2fは「2六歩」であり、上記の確率は7g7fつまり「7六歩」が確率が高く飛車か角の道を開ける手が大きな確率となっています。
###(2)価値ネットワーク「1手」探索プログラム
import numpy as np
import chainer
from chainer import serializers
from chainer import cuda, Variable
import chainer.functions as F
import shogi
from pydlshogi.common import *
from pydlshogi.features import *
from pydlshogi.network.value import *
from pydlshogi.player.base_player import *
ここまで必要なものをimportします。
def greedy(logits):
return np.argmax(logits)
def boltzmann(logits, temperature):
logits /= temperature
logits -= logits.max()
probabilities = np.exp(logits)
probabilities /= probabilities.sum()
return np.random.choice(len(logits), p=probabilities)
greedyは一番確率の高いものを選びます。
boltsmannは温度ゆらぎを導入して乱数要素で確率的に選ぶ戦略です。
class Search1Player(BasePlayer):
def __init__(self):
super().__init__()
#self.modelfile = r'H:\src\python-dlshogi\model\model_value'
self.modelfile = r'C:\Users\tosio\fastText\path\to\corpus\AB\KerasExample\dlshogi\model\model_value'
self.model = None
def usi(self):
print('id name search1_player')
print('option name modelfile type string default ' + self.modelfile)
print('usiok')
def setoption(self, option):
if option[1] == 'modelfile':
self.modelfile = option[3]
def isready(self):
if self.model is None:
self.model = ValueNetwork()
self.model.to_gpu()
serializers.load_npz(self.modelfile, self.model)
print('readyok')
ここまでは先ほどのusiプロトコルのためのモデュールです。BasePlayerを継承しています。
def go(self):
if self.board.is_game_over():
print('bestmove resign')
return
# 全ての合法手について
legal_moves = []
features = []
for move in self.board.legal_moves:
legal_moves.append(move)
self.board.push(move) # 1手指す
features.append(make_input_features_from_board(self.board))
self.board.pop() # 1手戻す
x = Variable(cuda.to_gpu(np.array(features, dtype=np.float32)))
# 自分の手番側の勝率にするため符号を反転
with chainer.no_backprop_mode():
y = -self.model(x)
logits = cuda.to_cpu(y.data).reshape(-1)
probabilities = cuda.to_cpu(F.sigmoid(y).data).reshape(-1)
for i, move in enumerate(legal_moves):
# 勝率を表示
print('info string {:5} : {:.5f}'.format(move.usi(), probabilities[i]))
上記が1手探索のメインルーチンです。すべての手について1手指して、その時の盤面をfeaturesに取得して随時蓄積します。そしてモデルにより、確率を予測しています。
# 確率が最大の手を選ぶ(グリーディー戦略)
selected_index = greedy(logits)
# 確率に応じて手を選ぶ(ソフトマックス戦略)
#selected_index = boltzmann(np.array(logits, dtype=np.float32), 0.5)
bestmove = legal_moves[selected_index]
print('bestmove', bestmove.usi())
最後に確率に応じて手を選ぶルーチンがあります。どちらの戦略を選ぶかは状況によるようです。通常は確率最大の手を選びます。
###(3)Policy vs. Value
いよいよ対戦です。
ここでは方策ネットワークと価値ネットワークの1手探索プログラムで対戦したいと思います。
ちなみにディレクトリ構成は以下のとおり
PJのディレクトリ
| setup.py
| ...
| kiflist_train_1000.txt
| kiflist_test_100.txt
├── model
| model_policy
| model_value
|── pydlshogi
| |common.py
| |features.py
| |read_kifu.py
| └── network
| | policy.py
| | value.py
| └── usi
| | usi.py
| | usi_policy_player_.py
| | usi_value_player_.py
| └── player
| base_player.py
| policy_player.py
| vlue_player.py
└── utils
| filter_csa.py
| ...
└── bat
policy_player.bat
value_player.bat
しかし、なぜか価値ネットワークの方が弱いんだよね。
###まとめ
・将棋所で対戦するためのUSIプロトコルについて説明した
・価値ネットワークの「1手」探索プログラムを説明した
・方策ネットワークと価値ネットワークの「1手」探索プログラムで対戦した
・なぜか価値ネットワークの方が弱い