Bonanzaの評価関数はどのようなものか?【KKP編】

■ Bonanzaの評価関数はどのようなものか?【KKP編】


前回までの記事で、Bonanzaの評価関数は
・駒割 -- material関数
KKP(二つの王と1駒による3駒相対) -- make_list関数
・KPP(王と2駒による3駒相対) -- evaluate関数
の3つから構成されていることがわかった。


また駒割についての説明はすでに終わっている。
今回はBonanzaKKPについて説明する。


■ BonanzaKKPとmake_listとの関係


BonanzaではKKPは、make_list関数のなかで行なわれる。


make_listはKPP用で使うための駒リストを列挙するための関数であるが、駒リストを列挙したついでにKKPを評価している。


make_listの実行には、プロファイラで測定したところ、CPU時間の5%程度を消費していた。


KKP自体は、王を動かさない限り差分計算により大幅に計算を簡略化できるが、Bonanzaではそれをやっていない。まあ、そちらはうまくやってもCPU時間全体の1%程度高速化されるだけなのでたいした問題ではないのかも知れない。


ともかく、今回はこれ以上、差分計算の問題には触れないことにする。


■ BonanzaKKPの持ち駒の扱い


KKP自体は、2玉と1駒だが、手駒自体も駒であるかのように扱う。すなわち、先手の手駒が歩5,香3,桂0,銀1,金2,角0,飛0だとしたら、7枚の駒を持っているかのように扱う。枚数が0の駒もひとつの駒のように扱っていることに注意が必要である。


具体的には、evaluate.cのmake_list関数のなかの次の部分が、先手の手駒の歩の枚数と、先手玉(sq_bk0 = square black king = 先手の玉の盤上の座標)と後手玉(sq_wk0 = square white king = 後手の玉の盤上の座標)のKKPを評価している部分である。

  score += kkp[sq_bk0][sq_wk0][ kkp_hand_pawn + I2HandPawn(HAND_B) ];

同様に、先手の7種類の手駒、後手の7種類の手駒で計14回、上のような評価を行なう。


■ BonanzaKKPの盤上の駒の扱い


盤上の駒については、普通にkkpを調べているだけなので特に説明するまでもないが、注意すべき点は、盤上の「金,と,成香,成桂,成銀」は先手ならばBB_BTGOLD(bitboard black total goldの略か?)というbitboardに格納されており、ここから1枚ずつ盤上の座標取り出してKKPを評価してある。


すなわち、「と」も「金」も金と同じ価値(KKP,KPP)でmaterial(駒割)の値だけが違うというのは実に興味深い。このように簡略化しておいたほうが、学習のときにパラメータがうまく収束しやすいのだろうか。


BB_BTGOLD,BB_WTGOLD(こちらは後手用)から座標を取り出してKKPを評価しているのはmake_listの以下の部分である。ただし、私はこの部分、高速化のために少し書き換えている。

  // 金,と,成香,成桂,成銀は、駒割に影響を与えるだけであとは同じ評価

  bb = BB_BTGOLD;
  foreach_bitboard_firstone(bb,sq,
		{
			list0[nlist] = f_gold + sq;
			list1[nlist] = e_gold + Inv(sq);
			score += kkp[sq_bk0][sq_wk0][ kkp_gold + sq ];
			nlist++;
	  }
	  );

  bb = BB_WTGOLD;
  foreach_bitboard_firstone(bb,sq,
		{
			list0[nlist] = e_gold + sq;
			list1[nlist] = f_gold + Inv(sq);
			score -= kkp[sq_bk1][sq_wk1][ kkp_gold + Inv(sq) ];
			nlist++;
		}
		);

foreach_bitboard_firstoneは私が書き換えた、bitboardから1つずつ座標を取り出すのを高速化するマクロで、このマクロを使用することで思考ルーチンの1〜2%の高速化を実現できている。


このマクロは以下のように私は定義している。

// Core2Duoではbsrのlatencyは2で、Core2Quadでは1。
// Core2Duoではbitboardが空かどうかを判定するのにbsr×3を
// 行なうよりは、先頭でbitwise ORをとって空なら抜けるように
// しておいたほうが、bitboardが空であることが多い場合は
// 有利に作用することに留意せよ。
#define foreach_bitboard_firstone(bb,sq,XXX) \
{\
/**/ if (bb.p[0]|bb.p[1]|bb.p[2]) /**/ \
  {\
		unsigned long _index_; \
		while ( _BitScanReverse( &_index_, bb.p[0] ) ) \
		{ \
			bb.p[0] ^= 1<< _index_; \
			sq = 26 - _index_; \
			XXX; \
		} \
		while ( _BitScanReverse( &_index_, bb.p[1] ) ) \
		{ \
			bb.p[1] ^= 1<< _index_; \
			sq = 53 - _index_; \
			XXX; \
		} \
		while( _BitScanReverse( &_index_, bb.p[2] ) ) \
		{ \
			bb.p[2] ^= 1<< _index_; \
			sq = 80 - _index_; \
			XXX; \
		}	\
  }\
}

■ まとめ


今回はKKPについて詳しく説明した。


また、bitboardから1つずつ盤上の座標を取り出す私の書いたマクロについても紹介した。