Edited at

【将棋AI】「将棋AIで学ぶディープラーニング」を読む♪~価値ネットワークで対戦する

将棋AIで学ぶディープラーニング

第九夜は、避けては通れないということで、今回はUSIプロトコルと価値ネットワークの「1手」探索プログラムでの対戦を解説しようと思う。


解説したいこと

(1)USIプロトコルについて

(2)価値ネットワーク「1手」探索プログラム

(3)Policy vs. Value


(1)USIプロトコルについて

USI:Universal Shogi Interface

【参考】

ホーム>USIプロトコルとは

上記参考サイトから

対局における通信の具体例が示されています。

本書では以下のような簡単なやり取りとして示されています。

usi_engine.jpg

※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手」探索プログラム


search1_player.py

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手探索プログラムで対戦したいと思います。

policyvsvalue.jpg

ちなみにディレクトリ構成は以下のとおり

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手」探索プログラムで対戦した

・なぜか価値ネットワークの方が弱い