はじめに
将棋AI「技巧(ぎこう)」を自分の手で訓練して強くしてみる開発日記、第3回です。
前回は棋譜データ集めで盛大にやらかしつつ、--learn-progress(進行度の学習)まで終えました。
今回はいよいよ本丸の「評価関数」の学習(--learn) です。前々回の最後に眺めていた score cp 0(評価値ずっと0)から、ついに卒業できるか――という回。結論を先に言うとちゃんと強くなったのですが、その直後に**「初手から投了」**という心臓に悪いイベントが発生したので、その顛末も込みでお届けします。
相変わらず Claude(Claude Code)と一緒に やっています。技巧の本体はこちら。
今回のゴール
- 評価関数の学習(
--learn)を回す - 学習中の指標を可視化して、ちゃんと学習が進んでいるか見る
- 学習前(
score cp 0)と学習後で、指し手がどう変わるか確認する
評価関数の学習って何をしてる?
進行度のときは「盤面から手数を当てる」回帰でしたが、評価関数はもう少し本格的です。技巧が採用しているのは、いわゆるBonanza型の教師あり学習。ざっくり言うと「プロ(今回は強いソフト)が指した手を、自分も指せるようになれ」という方向で重みを調整します。
ポイントは2つ:
- 棋譜の手との一致:その局面で棋譜の手が一番良く見えるように重みを動かす(指し手の再現)
- 勝敗との一致:その局面の評価値が、最終的な勝敗と矛盾しないように動かす(勝率予測)
第2回で「後手勝ちが消えるバグ」を直したのは、まさにこの 2 の「勝敗」を正しく使うため。あのバグを放置していたら、ここで全部の局面が「先手勝ち」扱いになって学習が崩壊していました。直しておいて本当に良かった。
実行(13時間コース)
./bin/release --learn
各局面で実際に探索を回すので、とにかく重い。僕のマシン(14スレッド)で約13時間かかりました。100イテレーションごとに params.bin(評価関数の本体、約259MB)が保存されるので、夜通し放置して朝に回収する運用です。
全部で 10,000イテレーション、最後に無事こう出ました。
Congratulations! Learning is successfully finished!
学習中の指標を見る(軽い分析)
学習ログには、100イテレーションごとに「テスト用の棋譜(学習に使っていない1000局)で、どれだけ棋譜の手と一致したか」が記録されます。これを可視化するとこんな感じ。
◆ 棋譜一致率(交差検定)の推移
iter 100 |██████░░░░░░░░░░░░░░░░░░░░░░░░░░ 43.6%
iter 1000 |███████████████████████░░░░░░░░░ 47.6%
iter 3000 |████████████████████████████░░░░ 48.6%
iter 6000 |██████████████████████████████░░ 49.0%
iter 10000 |██████████████████████████████░░ 49.1%
43.6% → 49.1% まで上がって頭打ち。約11.8万局面でテストして、ほぼ半分は教師(強いソフト)と同じ手を選べるようになった、ということです。1局面で合法手が80〜100手ある中での「ピタリ一致率」が約50%なので、素人目にはけっこうすごい数字に見えます。
もう一つ、「その局面の評価値が最終的な勝敗とどれだけ合っているか」の誤差(wr_e)も下がっています。
◆ 勝率予測の誤差 wr_e(小さいほど良い)
iter 1 |██████████████████████████████ 0.477
iter 1000 |██████ 0.425
iter 10000 |██ 0.416
序盤でガクッと下がって、あとはじわじわ改善。指し手の再現(一致率↑)と勝敗予測(誤差↓)が同時に良くなっているので、学習は健全に進んだと判断できます。
事件:学習が終わったエンジン、初手から投了する
意気揚々と、出来立ての評価関数でエンジンを起動して初手を指させたら――
position startpos
go depth 6
→ bestmove resign
投了。 開始局面でいきなり投了です。「13時間かけた評価関数、まさかの崩壊か…?」と一気に青ざめました。
Claudeと一緒に原因を追っていくと、デバッグビルド(エラーチェック有効版)でこんなのが出ました。
Assertion `score != kScoreNone' failed. (search.cc:398)
「探索が評価値を返せていない(nodes 0)」という状態。さらに調べると、犯人は評価関数ではなく、僕らのテストの叩き方でした。
エンジンに命令を送るとき、go(思考開始)と quit(終了)をまとめて一気に送っていたのです。すると技巧くんは、思考を始める前に quit を受け取って「あ、終わりね」と探索を0手で打ち切る。その結果、評価値が未確定のまま返り、製品版(エラーチェック無効)ではそれがたまたま「詰み(mate -2)」として表示され、投了スコア扱いになっていた、という流れ。
quit を後から送るように直したら、何事もなかったように動きました。評価関数は最初から無事。お騒がせしました…。
教訓:AIが「失敗した」ように見えても、まず自分の動かし方を疑え。 今回は完全に計測側のミスでした。
(ついでに --bench というベンチ機能も落ちるのですが、これは別件=まだ作っていない probability.bin(次回の宿題)が無いせいで、通常の対局には影響なし。次回への伏線ということで。)
結果:score cp 0 から卒業
テストの叩き方を直して、改めて初期局面を深く読ませた結果がこちら。
Before(第1回・学習前)
go depth 3
score cp 0 ... bestmove 1g1f ← 評価値ゼロ、端歩しか指せない
After(今回・学習後)
go depth 16
score cp 120 pv 7g7f 8c8d 6i7h 8d8e 8h7g 3c3d 7i6h ...
bestmove 7g7f ponder 8c8d
- 評価値が
cp 0→cp 120(=約+1.2、先手やや良し)と、ちゃんと動くようになった - 初手が 7六歩(7g7f)。将棋で最も自然な一手の一つで、続く読み筋も「8四歩→7七銀…」と真っ当な序盤
- 中盤の局面を読ませても
score cp 148と、局面に応じた評価値が出る
端歩マンだった技巧くんが、たった一晩でちゃんと「将棋を指す人」になりました。score cp 0 卒業、おめでとう。
今回のまとめ
Gikou
├─ プログラム本体 ✓
├─ USI通信 ✓
├─ 棋譜DB(kifu_db.txt) ✓
├─ 進行度(progress.bin) ✓
├─ 評価関数(params.bin) ✓ ← 今回完成!(13時間)
├─ 実現確率(probability.bin) ✗ ← 次回
└─ 定跡(book.bin) ✗ ← 次回
- 評価関数の学習(Bonanza型)を完走。一致率 43.6%→49.1%、勝率予測誤差も低下と、健全に学習
- 「初手から投了」は評価関数のせいではなく、こちらのテスト方法のミスだった
- 学習後はちゃんと7六歩を指し、評価値も動く。
score cp 0から無事卒業
次にやること
残りは 実現確率の学習(--learn-probability) と 定跡の作成(--create-book)。実はここでも早速メモリ不足でプロセスが強制終了する事件が起きていて……という話は次回に。やらかしのネタが尽きません。