前回 TensorFlowによる将棋ソフトの開発日誌(ゆっけさんの場合) #2
目次 TensorFlow将棋ソフト開発日誌 目次
将棋の盤面を見て勝敗を予測するマンを作ろう その2
引き続き勝敗予測モデルを作っていく。というか分量が多くなったので分けただけで前回を書いた直後にこれを書いているのだが。この記事では使用するネットワークモデルに投入する入力データ形式ついて記述する。
はじめに断っておくが「すべては勘でできている」。
入力データ形式
#1で書いた通り入力データの形式が重要だと考えている。これはニューラルネットワークの重要な研究分野だと思う(word2vecのインパクトよ)。なのであまり書きたくないのだけれど書かないと話が進まないので書いてしまう。論文書くわけでも仕事にするわけでもないしな。
将棋の駒ごとに位置チャンネルと動きチャンネルを考え、それを全駒分積み上げたものが入力データとなる。以下に詳しく書く。視覚的にわかりやすくするため実際の投入データの形式(numpy, tensorflowのshape)は異なるが概念的には等しい。
位置チャネル
駒の位置を表す。[9,9] の行列の駒位置に対応する要素に自分の駒であれば1、相手の駒であれば-1を設定する。初期盤面の王であれば以下のようになる。
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0 0
相手の玉は以下のようになる。
0 0 0 0 -1 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
動きチャネル
その盤面において次に打てる手、あるいは相手が(自分の手で変更がない限り)次に打てる手。駒の効きと言っても良い(考えたあとから気づいた)。初期盤面の王は以下になる(左右には金がいて動けないことに注意)。相手の駒は数値-1で同様に設定される。
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 1 1 1 0 0 0
0 0 0 0 0 0 0 0 0
入力ベクトル全体像
以下のようなテンソルになる。形式上、王も玉もフラットに扱う。自分の駒、相手の駒は要素に設定される数値で表されるので各チャネルには自分・相手の区別はない。ルールとして自分の位置・動きを前に詰め、相手の位置・動きを後ろに回す。
王1の位置チャネル
王1の動きチャネル
王2の位置チャネル
王2の動きチャネル
飛車1の位置チャネル
飛車1の動きチャネル
飛車2の位置チャネル
飛車2の動きチャネル
竜王1の位置チャネル
竜王1の動きチャネル
竜王2の位置チャネル
竜王2の動きチャネル
・
・
・
成歩18の位置チャネル
成歩18の動きチャネル
これで [148, 9, 9] のテンソルとして盤面を表す。実際には [9,9,148] にしてさらにバッチ化して [1,9,9,148] の形式で tensorflow のコンボリュージョンなどにそのまま渡せるようにしている(この形式だと上のようにテキストで説明できない)。
どうしてこんなことに・・・盤面そのまま食わせたらあかんの?
たとえば以下のような入力データではおそらくうまくいかないと予感した。
-7 -6 -5 -4 -1 -4 -5 -6 -7
0 -2 0 0 0 0 0 -3 0
-8 -8 -8 -8 -8 -8 -8 -8 -8
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
8 8 8 8 8 8 8 8 8
0 3 0 0 0 0 0 2 0
7 6 5 4 1 4 5 6 7
まずテンソルの各要素が複数の役割を持つのが良くないと感じた。具体的には上だとどの位置も王が位置することもあれば銀が位置する事もある。線形変換を繰り返して非線形変換に近似させるニューラルネットワークではこれは好まくないのではないか。1マスしかない版にに存在する駒が王(1)か金(2)か銀(3)かの2択のミニマムな問題として考える。
# 1マスに数の違いとして表現した場合の概念、matmulの左引数の数値がそれぞれ王、金、銀であり
# 異なる数値を与えると混同しない数値が得られて欲しい
# こんなん分離できるんですかね・・・。
argmax(matmul([1], [w_1, w_2, w_3]) + [b_1, b_2, b_3]) -> 0
argmax(matmul([2], [w_1, w_2, w_3]) + [b_1, b_2, b_3]) -> 1
argmax(matmul([3], [w_1, w_2, w_3]) + [b_1, b_2, b_3]) -> 2
# 駒ごとにチャネルを分けた場合の概念
# これなら単純には単位ベクトルでも分離できる
argmax(matmul([1,0,0], weight_9x9) + [b_1, b_2, b_3]) -> 0
argmax(matmul([0,1,0], weight_9x9) + [b_1, b_2, b_3]) -> 1
argmax(matmul([0,0,1], weight_9x9) + [b_1, b_2, b_3]) -> 2
というわけで駒の種類ごとにチャネルを分けたのである。
もうひとつ、動きチャネルを追加した理由は単純に「推定前からわかっている事実は推定の入力として与えたほうが良い」と考えたからだ。動きチャネルを追加しない場合、ネットワークモデルは駒に可能な動きを入力からのヒントなしで学習することになる。やればなんとかなるんだろうが収束が遅くなりそうなので入れることにした。
これで万全?課題は?
いくつかの不満、不安がある
- サイズが大きい。スパースな構造にしたせいだ。このせいで自分の作業マシンだとCPU/GPUのメモリが足りずあまり大きなネットワークが作れない。
- 状態が不安定。あまりちゃんと検証していないが「形が近い盤面はテンソルの表現も近い」というのがちゃんと実現できていないと思う。特に持ち駒の表現がうまくいっていない。相手の駒のチャネルを追加すること(サイズ2倍!)、持ち駒のチャネルを追加すること(さらに倍!)で安定するかもしれないがやはりサイズが・・・。
小さくて、各観点の情報を線形分離することが容易で、近いデータは近く、遠いデータは遠く、微小な変化で数値が大きく変わらない、そういう表現があれば最適。
次回
というか、これ書いたらすぐ書くが。ネットワークモデルと実験結果を書く。