掲棋(ジエチー)エンジンへの監査を検証する
要約
- Mistboard の掲棋ボットを動かすエンジンへの外部レビューが、評価関数の本物のバグを見つけた。片方の色の伏せ駒が一度も数えられていなかった。
- 修正すると出荷版のエンジンに対して 0.475。修正に合わせて関連する 43 項目を再調整すると 0.507。レビューが批判する駒の正体の平均化を置き換えると 0.370。
- ボットは今のままにする。手作業で調整された評価関数は自分のバグを吸収してしまい、200 局の関門では +10 Elo が見えず、このエンジンから派生したものではこのエンジンに勝てない。
iwestlin/jieqi-ai は Pikafish の jieqi_old ブランチ、つまり Mistboard の掲棋ボットを動かしているエンジンを取り込み、そのソースレビュー ai分析.md を同梱している。五つの指摘のうち二つは既知の制約、一つはルールの読み違いで、残る二つはマッチで確かめる価値があった。評価関数の本物のバグと、探索の中で伏せ駒の正体を平均するのは不健全だという主張だ。
jieqi_old は alpha-beta に手書きの評価関数を組み合わせたもので、ニューラルネットはない。すでに自分の評価値から蒸留した十個のネット(最高 0.43)にも、人間が勝った一局で探索時間を十五倍にすること(指し手は同じ)にも耐えている。以下の三つのテストはすべて同じやり方で行った。変更したエンジン対出荷版、先後を交互に、一手あたりの時間は同じ、審判プログラムが駒の正体を配るのでどちらのエンジンにも伏せ駒は見えない。
バグ:片方の色の伏せ駒が数えられていない
Evaluation::initialize<Us>() は白と黒について一回ずつ呼ばれる。呼ばれるたびに DarkPieces 配列全体を消してから自分の側だけを埋め直すので、脅威の項目がそれを読む時点では白の伏せ駒が消えている。
memset(DarkPieces, 0, sizeof DarkPieces); // both colours, every call
for (PieceType i = ROOK; i <= BISHOP; ++i) {
Bitboard b = pos.pieces(Us, i); // only Us is refilled
...
エンジンの eval トレースがこれを裏づける。黒の兵が紅の伏せ駒を攻撃しているとき、修正前は Threats の行が両者で同じになり、修正後は紅が黒を攻める場合の鏡像になる。紅は伏せ駒を攻撃すると加点され、黒は一度も加点されていなかった。
テスト 1:直す
黒は紅が持っている項目なしで指していたのだから、それを黒にも与えれば強くなるはずだ。
一手 500 ミリ秒で 200 局:91-101-8、スコア 0.475 ± 0.068。 紅番で 0.525、黒番で 0.425。
新たに加点を受けた側のほうが弱くなったので、この重みは数え方だけでなく値そのものも間違っている。ソースにある半端な値(S(-11, 2)、S(39, -26))は、バグを抱えたまま一度実戦で調整されたことを示している。式が正しく重みが古いままなら、それは調整されていない別のエンジンだ。
テスト 2:修正に合わせて再調整する
重みがバグのある式に合わせて調整されていたのなら、直した式に合わせて調整し直せば、テスト 1 で失った分を取り戻し、バグが隠していたものも見つかるはずだ。
バグが関わる 43 項目を Pikafish の TUNE 機能で UCI オプションとして公開し、ノートパソコンで fishtest 式の SPSA を回した。10,000 ペア、一手 100 ミリ秒、六時間。43 項目中 41 項目が動き、最も大きく動いたのは伏せ駒の手のスコアの上限(2862 から 2745)だった。調整版対出荷版、一手 500 ミリ秒で 200 局:96-93-11、スコア 0.507 ± 0.067。
互角の誤差の範囲内で、今度は反対側に出た。修正のみで 0.475、修正と再調整で 0.507 という結果は、これらの項目がどちらに振っても大した棋力を持たないことを示していて、調整不足を示しているのではない。借りた CPU で 50,000 ペアを回しても数ドルで済むが、この二つの結果はそれを後押ししない。
テスト 3:正体の平均化を置き換える
探索が伏せ駒を動かすとき、その側の駒の山に残っている正体ごとに一回ずつ分岐し、結果をまとめる。上限を設けた加重平均で、平均が最悪の場合を大きく上回るときは最悪の場合に戻す。これが情報集合探索ではないというレビューの指摘は正しく、私にも疑う理由があった。7 月、この式は真の期待勝率 64% の伏せ駒の手を、勝率 97% の安全な手より選んだ。
この式が賭けの値付けを誤っているなら、正しく値付けすればもっと良い手を選べるはずだ。エンジンの第一候補が自分の伏せ駒を動かす手のとき、上位四つの候補を取り、各賭けの各正体を別々に探索し、勝率を平均し、平均が最も高い手を指す。
ラッパーとして実装した。ルートの持ち時間 400 ミリ秒、正体ごとに 100 ミリ秒で 100 局:30-56-14、スコア 0.370、素のエンジンの 2.2 倍の時間。正体ごとに 400 ミリ秒をまるごと与えて 50 局:17-26-7、スコア 0.410、七倍の時間。
どの予算でも負けるので、手法が間違っているのであって、資源が足りないのではない。別々の探索どうしは、一つの木がそれ自身と食い違う以上に食い違う。レビューが問題視する最悪の場合への退避は、役に立つ仕事をしている。7 月の局面は本物の誤りだったが、この治療法は取り除くより多くの誤りを生む。
手作業で調整された評価関数が部分的な修理を受けつけない理由
評価関数は全体として調整されている。どの重みも、バグを含む他のすべての重みとの対局を通じて決められたので、一つの項目を直すと弱くなり、一群を調整し直しても直した分の損失を取り戻すだけだ。改善するには全体を調整し直すしかなく、それは元の作者が一度やった仕事だ。
関門が粗い。200 局のマッチは 95% で ± 0.07、およそ ± 25 Elo の幅がある。+10 の変更は見えず、+30 の変更でも五分五分だ。より細かい関門は 1,000 局で、ノートパソコンでは候補一つに一日かかる。
外部の物差しがない。暗棋(バンチー)には比較できるオープンなエンジンがあったが、掲棋にはない。だから出荷版から蒸留したものや、出荷版に対して調整したものは、作りからして出荷版と同じところに落ち着く。教師に欠けている信号は深い自己対局で、それこそが jieqi ブランチ用のネットに必要なものだ。
何が変わるか
出荷中のボットは何も変えない。固定版を動かすには、新しいブラウザ版のビルド、新しいエンジン ID、キャッシュ済みのすべての掲棋レビューの再計算が要り、ここにある結果はどれもそれに値しない。バグ修正は、それが入っている評価関数が置き換わるときまで待つ。
固定版には今、Linux のバイナリとブラウザ版のビルドが付属し、上のすべての数字の裏にあるトレーナー、特徴量セット、審判プログラム、マッチ用ハーネス、チューナーが公開されている。出荷中のボットに 200 局で 0.55 を取るネットが、次のボットになる。