[方策ネットワーク]
第7章1〜4
policy.py
フィルター数194個の意味
入力層で考えると入力は9×9の局面図(画像)が104個(=104ch)。
フィルター数は194。
1つ目のフィルターを使い1つの画像に対してフィルターをかけた9×9の1つの画像を得る。
それを104ch数分行い、104個の画像を得る。
104個の画像を1個の画像にまとめる。これが1chとなる。
2つ目のフィルターを使い同様のことを行う。
194個目のフィルターまで同様のことを行う。
これで194個の画像を得る。つまり出力ch数が194chになる。
1×1フィルターの意味
pointwise convolution
次元数の削減が目的
「1画素x1画素x層数」の細長いフィルターのイメージ
https://www.robotech-note.com/entry/2017/12/24/191936
入力:1~192chそれぞれのchのとある1画素、出力:1つの画素値
これを全画素について実施して1画面の画像を得る。
それをパラメータを変えて出力ch回数分実施する。
出力ch数分の出力画像を得る。
結局上の194個のフィルターのフィルターサイズが1×1になっているだけでやってることは同じ。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from chainer import Chain
import chainer.functions as F
import chainer.links as L
from pydlshogi.common import *
ch = 192
class PolicyNetwork(Chain):
# 入力104ch
# 入力は9x9の局面図が104個。
# フィルター数194。
# 入力chそれぞれに対してフィルターをかけた9x9の値を入力ch分加算して1chとして出力、
# それをフィルター数194個行うので出力ch数が194chになる。
def __init__(self):
super(PolicyNetwork, self).__init__()
with self.init_scope():
self.l1 = L.Convolution2D(in_channels = 104, out_channels = ch, ksize = 3, pad = 1)
self.l2 = L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
self.l3 = L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
self.l4 = L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
self.l5 = L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
self.l6 = L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
self.l7 = L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
self.l8 = L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
self.l9 = L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
self.l10 = L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
self.l11 = L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
self.l12 = L.Convolution2D(in_channels = ch, out_channels = ch, ksize = 3, pad = 1)
self.l13 = L.Convolution2D(in_channels = ch, out_channels = MOVE_DIRECTION_LABEL_NUM,
ksize = 1, nobias = True)
# フィルターサイズ1x1(ksize=1)の意味
# 入力:1~192chそれぞれのchのとある1画素、出力:1つの画素値
# これを全画素について実施して1画面の画像を得る。
# それをパラメータを変えて出力ch回数分実施する。
# 出力ch数分の出力画像を得る。
self.l13_bias = L.Bias(shape=(9*9*MOVE_DIRECTION_LABEL_NUM))
# MOVE_DIRECTION_LABEL_NUM=27。移動方向20個と持ち駒7個を示す。
def __call__(self, x):
h1 = F.relu(self.l1(x))
h2 = F.relu(self.l2(h1))
h3 = F.relu(self.l3(h2))
h4 = F.relu(self.l4(h3))
h5 = F.relu(self.l5(h4))
h6 = F.relu(self.l6(h5))
h7 = F.relu(self.l7(h6))
h8 = F.relu(self.l8(h7))
h9 = F.relu(self.l9(h8))
h10 = F.relu(self.l10(h9))
h11 = F.relu(self.l11(h10))
h12 = F.relu(self.l12(h11))
h13 = self.l13(h12)
return self.l13_bias(F.reshape(h13,(-1, 9*9*MOVE_DIRECTION_LABEL_NUM)))
# 出力にソフトマックス関数を記述していない理由は、
# 学習するときにF.softmax_cross_entropy関数を使うため。
# この関数はソフトマックス関数と交差エントロピー誤差の計算を同時に行う。
common.py
bb_rotate180()
引数:81桁の2進数。(piece_bbやoccupiedの1要素、つまりビットボードが入る)
出力:81桁の桁を反転して出力する。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import shogi
# 移動の定数
# UP=0、UP_LEFT=1、、、、UP_RIGHT_PROMOTE=19と定義している。
# 使うときは、例えば〇〇 = UPとすると〇〇という変数に0が代入される。
MOVE_DIRECTION = [
UP, UP_LEFT, UP_RIGHT, LEFT, RIGHT, DOWN, DOWN_LEFT, DOWN_RIGHT,
UP2_LEFT, UP2_RIGHT,
UP_PROMOTE, UP_LEFT_PROMOTE, UP_RIGHT_PROMOTE, LEFT_PROMOTE, RIGHT_PROMOTE,
DOWN_PROMOTE, DOWN_LEFT_PROMOTE, DOWN_RIGHT_PROMOTE,
UP2_LEFT_PROMOTE, UP2_RIGHT_PROMOTE
] = range(20)
# 成り変換テーブル
# **_PROMOTEという名前の変数はMOVE_DIRECTIONで定義済み。
# UP_PROMOTE=10,・・・,UP_RIGHT_PROMOTE=19と定義されている。
MOVE_DIRECTION_PROMOTED = [
UP_PROMOTE, UP_LEFT_PROMOTE, UP_RIGHT_PROMOTE, LEFT_PROMOTE, RIGHT_PROMOTE,
DOWN_PROMOTE, DOWN_LEFT_PROMOTE, DOWN_RIGHT_PROMOTE,
UP2_LEFT_PROMOTE, UP2_RIGHT_PROMOTE
]
# 指し手を表すラベルの数
MOVE_DIRECTION_LABEL_NUM = len(MOVE_DIRECTION) + 7 # 7は持ち駒の種類
# rotate 180degree
# shogi.I1は80、・・・、shogi.A9は0。つまりSQUARES_R180 = [80, 79, ・・・, 1, 0]
SQUARES_R180 = [
shogi.I1, shogi.I2, shogi.I3, shogi.I4, shogi.I5, shogi.I6, shogi.I7, shogi.I8, shogi.I9,
shogi.H1, shogi.H2, shogi.H3, shogi.H4, shogi.H5, shogi.H6, shogi.H7, shogi.H8, shogi.H9,
shogi.G1, shogi.G2, shogi.G3, shogi.G4, shogi.G5, shogi.G6, shogi.G7, shogi.G8, shogi.G9,
shogi.F1, shogi.F2, shogi.F3, shogi.F4, shogi.F5, shogi.F6, shogi.F7, shogi.F8, shogi.F9,
shogi.E1, shogi.E2, shogi.E3, shogi.E4, shogi.E5, shogi.E6, shogi.E7, shogi.E8, shogi.E9,
shogi.D1, shogi.D2, shogi.D3, shogi.D4, shogi.D5, shogi.D6, shogi.D7, shogi.D8, shogi.D9,
shogi.C1, shogi.C2, shogi.C3, shogi.C4, shogi.C5, shogi.C6, shogi.C7, shogi.C8, shogi.C9,
shogi.B1, shogi.B2, shogi.B3, shogi.B4, shogi.B5, shogi.B6, shogi.B7, shogi.B8, shogi.B9,
shogi.A1, shogi.A2, shogi.A3, shogi.A4, shogi.A5, shogi.A6, shogi.A7, shogi.A8, shogi.A9,
]
def bb_rotate_180(bb):
# bbに入るのはpiece_bbの1要素やoccupiedの1要素。つまり81桁の2進数。
bb_r180 = 0
for pos in shogi.SQUARES: #SQUARESはrange(0, 81)のこと
if bb & shogi.BB_SQUARES[pos] > 0:
# BB_SQUARESは[0b000・・・0001, 0b000・・・0010, 0b000・・・0100, ・・・, 0b100・・・0000]。要素81個。
# &はビット演算子のAND。
bb_r180 += 1 << SQUARES_R180[pos]
# a<<bはaのビットを左側にb桁シフトする演算子。
# なので1をSQUARES_R180[pos]個シフトするという意味。
# 例えばposが0ならSQUARES_R180[pos]は80なので
# 0b000000000000000000000000000000000000000000000000000000000000000000000000000000001
# を80個シフトして
# 0b100000000000000000000000000000000000000000000000000000000000000000000000000000000
# になる。
return bb_r180
features.py
make_input_features()
引数:piece_bb、occupied、pieces_in_hand
出力:先手の駒が有る場所、先手の持ち駒、後手の駒が有る場所、後手の持ち駒、で全部で9×9の行列が104個
[(9×9の行列), ・・・が14個(盤上の駒の種類),
(9×9の行列), ・・・が(18+4+4+4+4+2+2)個,
(9×9の行列), ・・・が14個(盤上の駒の種類),
(9×9の行列), ・・・が(18+4+4+4+4+2+2)個]
make_input_features_from_board()
boardを引数としてpiece_bb, occupied, pieces_in_hand, turnを取り出してmake_input_featuresを行う。
make_output_label()
引数:move(move_fromとmove_to), color(先手か後手)
colorはmove_directionを先手でも後手でも同じものを使用できるようにするために使う。
出力:move_direction(81の桁) + move_to(1の桁)
81進数のイメージ。move_directionが81の桁、move_toが1の桁。
81の桁・・・27個
1の桁・・・9×9個
つまりmoveを一つの数値に変換して出力するということ。
ちなみにこの出力の数値はNNの出力のインデックスにもなっている。NNの出力から所望の要素を取り出したい時に使う。
make_features()
引数:position
positionとはread_kifu()の出力。(「局面図」「指し手」「勝敗」)を局面ごとに算出し一つのリストとして出力したもの。
1局面のデータは下記5要素で構成。
-piece_bb:15要素
-occupied:2要素
-pieces_in_hand:2要素
-move_label:1要素
-win:1要素
最終的な出力は[([15要素], [2要素], [2要素], [1要素], [1要素]), (同じセット), ・・・が手数 x 対局数]
出力 : features, move, winの3つを出力する。
features : positionからpiece_bb, occupied, pieces_in_handを取り出してmake_input_featuresを行ったもの。
先手の駒が有る場所、先手の持ち駒、後手の駒が有る場所、後手の持ち駒、で全部で9×9の行列が104個
[(9×9の行列), ・・・が14個(盤上の駒の種類),
(9×9の行列), ・・・が(18+4+4+4+4+2+2)個,
(9×9の行列), ・・・が14個(盤上の駒の種類),
(9×9の行列), ・・・が(18+4+4+4+4+2+2)個]
move : positionのmove_labelをそのまま出力する
win : positionのwinをそのまま出力する
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import numpy as np
import shogi
import copy
from pydlshogi.common import *
def make_input_features(piece_bb, occupied, pieces_in_hand):
features = []
for color in shogi.COLORS:
# board pieces
for piece_type in shogi.PIECE_TYPES_WITH_NONE[1:]: #PIECE_TYPES_WITH_NONEはrange(0, 15)のこと
bb = piece_bb[piece_type] & occupied[color] #手番の側の駒の位置を駒ごとに取得(&はビット演算子)
feature = np.zeros(9*9)
for pos in shogi.SQUARES: #SQUARESはrange(0, 81)のこと
if bb & shogi.BB_SQUARES[pos] > 0: #BB_SQUARESは[0b1, 0b10, 0b100, ・・・, 0b1・・・0]。要素81個。
feature[pos] = 1 #bit boardの各bitを要素に分解。例えば1010・・・だったら[1,0,1,0,・・・]に分解。
features.append(feature.reshape((9, 9))) #bit boardの各bitを要素に分解したものを9x9の行列にして返す。
# pieces in hand
for piece_type in range(1, 8):
for n in range(shogi.MAX_PIECES_IN_HAND[piece_type]):
#shogi.MAX_PIECES_IN_HANDは持ち駒の数。インデックス1~7はおそらく下記のような感じ。
#shogi.MAX_PIECES_IN_HAND[1] = 18 :歩
#shogi.MAX_PIECES_IN_HAND[2] = 4 :香車
#shogi.MAX_PIECES_IN_HAND[3] = 4 :桂馬
#shogi.MAX_PIECES_IN_HAND[4] = 4 :銀
#shogi.MAX_PIECES_IN_HAND[5] = 4 :金
#shogi.MAX_PIECES_IN_HAND[6] = 2 :角
#shogi.MAX_PIECES_IN_HAND[7] = 2 :飛車
if piece_type in pieces_in_hand[color] and n < pieces_in_hand[color][piece_type]:
feature = np.ones(9*9)
else:
feature = np.zeros(9*9)
features.append(feature.reshape((9, 9)))
return features
# 出力:先手の駒が有る場所、先手の持ち駒、後手の駒が有る場所、後手の持ち駒、で全部で9x9の行列が104個
# [(9x9の行列), ・・・が14個(盤上の駒の種類),
# (9x9の行列), ・・・が(18+4+4+4+4+2+2)個,
# (9x9の行列), ・・・が14個(盤上の駒の種類),
# (9x9の行列), ・・・が(18+4+4+4+4+2+2)個]
def make_input_features_from_board(board): # boardを引数としてmake_input_featuresを行う。
if board.turn == shogi.BLACK:
piece_bb = board.piece_bb
occupied = (board.occupied[shogi.BLACK], board.occupied[shogi.WHITE])
pieces_in_hand = (board.pieces_in_hand[shogi.BLACK], board.pieces_in_hand[shogi.WHITE])
else:
piece_bb = [bb_rotate_180(bb) for bb in board.piece_bb]
occupied = (bb_rotate_180(board.occupied[shogi.WHITE]), bb_rotate_180(board.occupied[shogi.BLACK]))
pieces_in_hand = (board.pieces_in_hand[shogi.WHITE], board.pieces_in_hand[shogi.BLACK])
return make_input_features(piece_bb, occupied, pieces_in_hand)
def make_output_label(move, color):
move_to = move.to_square
move_from = move.from_square
# ■ Moveクラス
# from_square変数 :盤面を0~80の数値で表したときの移動元の値。
# 9で割ったときの商がy座標、余りがx座標となる。xy座標は0オリジン。
# to_square変数 :同上(移動先)。
#
# x座標
# 0 1 2 3 4 5 6 7 8
#
# 0 1 2 3 4 5 6 7 8 0 y座標
# 9 10 11 12 13 14 15 16 17 1
# 18 19 20 21 22 23 24 25 26 2
# 27 28 29 30 31 32 33 34 35 3
# 36 37 38 39 40 41 42 43 44 4
# 45 46 47 48 49 50 51 52 53 5
# 54 55 56 57 58 59 60 61 62 6
# 63 64 65 66 67 68 69 70 71 7
# 72 73 74 75 76 77 78 79 80 8
# 白の場合盤を回転
if color == shogi.WHITE:
move_to = SQUARES_R180[move_to]
if move_from is not None: # 持ち駒ではなく盤上の駒を動かした場合
move_from = SQUARES_R180[move_from]
# move direction
if move_from is not None: # 持ち駒ではなく盤上の駒を動かした場合
to_y, to_x = divmod(move_to, 9)
from_y, from_x = divmod(move_from, 9)
dir_x = to_x - from_x
dir_y = to_y - from_y
if dir_y < 0 and dir_x == 0:
move_direction = UP
elif dir_y == -2 and dir_x == -1:
move_direction = UP2_LEFT
elif dir_y == -2 and dir_x == 1:
move_direction = UP2_RIGHT
elif dir_y < 0 and dir_x < 0:
move_direction = UP_LEFT
elif dir_y < 0 and dir_x > 0:
move_direction = UP_RIGHT
elif dir_y == 0 and dir_x < 0:
move_direction = LEFT
elif dir_y == 0 and dir_x > 0:
move_direction = RIGHT
elif dir_y > 0 and dir_x == 0:
move_direction = DOWN
elif dir_y > 0 and dir_x < 0:
move_direction = DOWN_LEFT
elif dir_y > 0 and dir_x > 0:
move_direction = DOWN_RIGHT
# promote
if move.promotion:
move_direction = MOVE_DIRECTION_PROMOTED[move_direction]
else:
# 持ち駒
# len(MOVE_DIRECTION) は20
# move.drop_piece_typeは置いた持ち駒の種類?
# -1は数合わせ?
move_direction = len(MOVE_DIRECTION) + move.drop_piece_type - 1
move_label = 9 * 9 * move_direction + move_to
# 81進数のイメージ。move_directionが81の桁、move_toが1の桁。
# 81の桁・・・27個
# 1の桁・・・9x9個
return move_label
def make_features(position):
piece_bb, occupied, pieces_in_hand, move, win = position
features = make_input_features(piece_bb, occupied, pieces_in_hand)
return(features, move, win)
コメント