0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【将棋AI開発日記】2. 棋譜集めで盛大にやらかして、進行度を学習させるまで

0
Last updated at Posted at 2026-06-27

はじめに

将棋AI「技巧(ぎこう)」を自分の手で訓練して強くしてみる開発日記、第2回です。
前回はソースからビルドして、評価関数も定跡もない「知識ゼロの脳みそ」を起動させて、score cp 0(評価値ずっと0)を眺めて終わりました。

ちなみに技巧の本家リポジトリはこちらです(オープンソース・GPL)。

今回はいよいよ学習の入口、学習用の棋譜データを集めて、--learn-progress(進行度の学習)までやります。……が、結論から言うとデータ集めで盛大にやらかしました。そのやらかしと、それをどう見つけて直したかが今回の本体です。

相変わらず Claude(Claude Code)と一緒に 進めています。というか今回の「やらかし発見」はほぼClaudeのお手柄でした。Pythonしか触ってこなかった人間なので、C++の事情も将棋AIの事情も手探りです。間違いはそのまま残していきます。

今回のゴール

  • 学習に使う棋譜データベース(kifu_db.txt)を用意する
  • 集めたデータがどんなものか軽くEDAする
  • --learn-progress で「進行度」を学習する

棋譜はどこから集める?

技巧の学習は、棋譜データベース(対局の記録の集まり) を教師にします。プロの棋戦DBを使うのが本来の想定ですが、大量に手に入れるのは大変。そこで定番の floodgate(コンピュータ将棋同士が24時間戦い続けている対局場)の棋譜を使うことにしました。

floodgateは1年分の対局を .7z アーカイブで配布してくれているので、それを落として、技巧が読める kifu_db.txt 形式に変換する――という流れです。この「CSA→kifu_db変換」を自作のskill(/fetch-kifu)にやらせました。

技巧の棋譜DBは「1局 = 2行」というシンプルな形式です。

<番号> <日付> <先手名> <後手名> <結果> <手数> <棋戦> <戦型>
<指し手をスペース区切りで1行>

<結果> は 先手勝ち=1 / 後手勝ち=2 / 引き分け=0。ここが今回の地雷でした。

やらかし① 後手勝ちが、1局も、ない

集めたデータ(約3万局)で、まず軽く中身を見てみようとClaudeに頼んだら、結果列の分布がこうなりました。

result=1 先手勝ち : 16,790
result=0 引き分け : 13,210
result=2 後手勝ち :      0   ← ???

後手勝ちがゼロ。 3万局やって後手が一度も勝たないとか、ありえません。将棋の先手勝率はせいぜい52〜54%なので、後手だって普通に半分近く勝つはずです。

原因を追ってもらったら、変換スクリプトのバグでした。CSAファイルの終局行('summary:)から勝者を取り出す正規表現が、

# 旧(バグ):"〇〇 win:△△ lose" の語順しか拾えない
re.search(r"'summary:\w+:(\S+) win:(\S+) lose", line)

となっていて、「win が先」の語順しかマッチしない。ところが実際のCSAは両方の語順がありました。

'summary:toryo:debugengine win:testengine lose   ← 先手勝ち。マッチする
'summary:toryo:debugengine lose:testengine win   ← 後手勝ち。マッチしない!

後手勝ちは必ず lose:...win の語順になるので、ことごとく取りこぼして「引き分け(0)」に倒れていた、と。だから先手勝ち16,790 + 謎の引き分け13,210(=拾えなかった後手勝ちの山)という分布になっていたわけです。

語順に依存せず「win タグが付いた側を勝者」と判定するように直しました。

# 新:win/lose の順番に関係なく勝者を判定
m = re.match(r"'summary:\w+:(\S+) (\w+):(\S+) (\w+)", line)
if m:
    name1, tag1, name2, tag2 = m.group(1), m.group(2), m.group(3), m.group(4)
    winner = name1 if tag1 == "win" else (name2 if tag2 == "win" else None)
    result = 0 if winner is None else (1 if winner == sente else 2)

千日手(sennichite)は両者 draw なので、ちゃんと引き分け=0に落ちます。

教訓:データは作ったら必ず分布を見る。 「なんとなく動いた」を信じてそのまま学習に突っ込んでいたら、「先手は絶対に負けない」という狂った評価関数が出来上がっていたはずです。こわい。

やらかし② --learn がfloodgateの棋譜を全部捨てる

ついでにもう1個見つかりました。評価関数の学習(--learn)のコードを読むと、棋譜を読み込む部分でこんなフィルタがかかっていました。

game_db.set_title_matches_only(true);  // 7大棋戦+順位戦だけ使う

竜王戦 名人戦 … といったプロのタイトル戦の棋譜しか採用しない設定です。ところが僕が集めたfloodgateの棋譜は棋戦名が全部 Floodgate。つまりこのままだと --learn が使える棋譜は実質0局。

本来の技巧が「プロ棋譜前提」で作られていた名残ですね。今回はトレース目的でfloodgateを使うので、ここは割り切ってフィルタを外しました(1行 true→false)。

// floodgateの棋譜を学習に使うため、棋戦の限定を解除
game_db.set_title_matches_only(false);

棋譜を集め直す

バグを直したので、改めて floodgate の2025年アーカイブ(約335MB)を落として、修正版スクリプトで全部変換し直しました。展開すると 128,843局 ものCSAファイルが出てきて、なかなか壮観です。

added=127778 skipped=1065  result0(draw)=5512 result1(sente)=65904 result2(gote)=56362

今度はちゃんと後手も勝っています。kifu_db.txt は約104MB、127,778局のデータベースになりました。

集めたデータを眺める(EDA)

Pythonで軽くEDAしてみます。Kaggleごっこで覚えたやつがこんなところで役に立つとは。

結果の分布:

result 意味 局数 割合
1 先手勝ち 65,904 51.6%
2 後手勝ち 56,362 44.1%
0 引き分け/千日手 5,512 4.3%

決着局だけで見た先手勝率は 53.9%。実際の将棋の先手勝率(52〜54%)とぴったり重なって、ようやく「まともなデータになった」と安心しました。

手数の分布:

min=1  max=512  mean=111.5  median=108
0-49    : 11.7% ####
50-99   : 31.8% ############
100-149 : 35.3% ##############   ← 最頻
150-199 : 16.0% ######
200-299 :  4.6% #
300+    :  0.6%

中央値108手のきれいな山。引き分け局だけ平均95.8手と短めで、「千日手は早めに成立しがち」というのが数字に出ていて面白い。

ほかにも、登場プレイヤーは997人、gikou2_1c や suisho5-1M みたいな有名エンジンがずらり。floodgateは強いエンジンも弱いエンジンも混ざっているので、棋譜の質はピンキリ。本来プロ棋譜を使う理由はここ(質の担保)なんだな、と腑に落ちました。質を上げたいなら「強いエンジン同士の対局だけ」に絞る手もありそう。これは将来の課題に。

進行度を学習する(--learn-progress)

データができたので、いよいよ学習1段目。

./bin/release --learn-progress

そもそも「進行度」って?

進行度は、いまの局面が「序盤(0.0)〜終盤(1.0)」のどのへんかを表す1つの数字です。技巧の評価値は「序盤用」「終盤用」など複数の重みを同時に持っていて、この進行度を使って序盤の評価と終盤の評価をブレンドします。同じ「金」でも、序盤なら囲いの価値、終盤なら寄せの価値、と局面段階で重みを変えるための土台ですね。

どうやって学習する?

仕組みを読んでみると、思っていたよりシンプルでした。盤面から進行度を出すまでの流れはこんな感じ。

そして教師ラベル(正解) は「その局面が対局の何手目か」を正規化しただけ、というのが面白いところ。

教師ラベル = 今の手数 ÷ その対局の総手数

開始局面                                            投了
 │1手目                                         最終手│
 ▼                                                  ▼
0.0 ──── 0.25 ──── 0.5 ──── 0.75 ──── 1.0
└── 序盤 ──┴── 中盤 ──┴── 中盤 ──┴── 終盤 ──┘
  • 入力:両方の玉の位置 × 盤上の各駒の位置(いわゆるKP=玉と駒の関係)
  • 出力:重みを全部足してシグモイドで0〜1に圧縮した値
  • 損失:(推定した進行度 − 正解の手数割合)² を小さくする

要するに「盤面の駒配置だけ見て、この対局はどのくらい進んだ局面か当てなさい」という回帰問題。なるほど、これなら勝敗ラベルがなくても学習できる(さっきのバグの影響を受けにくい)。

結果①:ちゃんと収束した

50,000局・約540万局面を使って2048回まわした結果がこちら。

iteration 交差検定の誤差(RMSE) 初期局面の進行度
1 0.263 0.443
256 0.110 0.040
2048(最終) 0.110 0.037

文字でも可視化してみます。

交差検定の誤差 RMSE(小さいほど良い)
iter    1 |████████████  0.263
iter  256 |█████         0.110
iter 2048 |█████         0.110   ← 256回で頭打ち=過学習なし

初期局面の進行度(学習が進むほど "序盤=0" に近づくのが正解)
iter    1 |████████████████████  0.443
iter  256 |██                    0.040
iter 2048 |█▌                    0.037   ← ほぼ0に張り付く=モデルが正しい

平手の開始局面はどう見ても「序盤=0」なので、推定値がほぼ0に張り付いたのはモデルがちゃんと学習できている証拠です。

結果②:学習した「駒の感覚」を覗いてみる

学習後、「どの駒がどのマスにいると終盤っぽいか」の重みが出力されます。これをヒートマップにしてみました(自玉を8八に固定したときの、各マスにその駒がいる場合の"終盤寄り度")。

濃いほど終盤寄り(+)、点(·)は序盤寄り(−)です。

【金】 自玉=8八に固定 / 上=敵陣 下=自陣
    9 8 7 6 5 4 3 2 1  (筋)
 一  ▓ ▓ ■ ■ ■ ▓ ▓ ░ ░     ← 敵陣に進出=終盤寄り(濃い)
 二  ▓ ▒ ▓ ▓ ▓ ▓ ▓ ▒ ▒
 三  ▓ ■ ▒ ▓ ▓ ▒ ▓ ■ ▒
 四  ▓ ▒ ▒ ▒ ▒ ▒ ▓ ▓ ▒
 五  ▒ ▒ ▒ ▒ ▓ ▓ ▓ ▒ ▒
 六  ▒ ░ · ░ ▒ ▒ ▓ ▓ ▓
 七  ░ · ░ ░ ░ ▒ ▓ ▓ ▓
 八  ░ ░ · · · · ▒ ■ ▓     ← 自玉の周り=序盤寄り(点)=囲いの金
 九  · · ░ · · ░ ▒ ▓ ░
 凡例 ■≥.13 ▓≥.07 ▒≥.02 ░≥-.03 ·<-.03

金は 自玉の周り(左下)に居ると序盤寄り=囲いの金、前に出る・敵陣に進出すると終盤寄り。完全に人間の感覚どおりです。

飛車はもっとはっきり出ました。

【飛】 自玉=8八に固定 / 上=敵陣 下=自陣
    9 8 7 6 5 4 3 2 1  (筋)
 一  ▓ ▓ ▒ ▓ ▓ ▓ ▓ ▓ ▒     ← 敵陣まで成り込む=終盤寄り
 二  ▒ ▓ ▓ ▒ ▓ ▓ ▓ ▓ ▓
 三  ▒ ▒ ▒ ▒ ░ ▒ ▓ ▒ ▒
 四  ░ ░ ░ ░ ░ ░ ░ · ░
 五  · ░ ░ ░ ░ ░ ░ ░ ░
 六  ░ ░ · ░ ░ ░ ░ ░ ░
 七  ░ ▒ ▒ · · · · ░ ░
 八  · · ░ · ░ · · · ·     ← 自陣の定位置=序盤寄り
 九  · · · ░ · · · · ·
 凡例 ■≥.13 ▓≥.07 ▒≥.02 ░≥-.03 ·<-.03

4段目から下(自陣側)はほぼ序盤寄り、敵陣の1〜2段目だけ終盤寄り。「飛車が敵陣に成り込む=終盤が進んでいる」 が一目瞭然です。

ただの二乗誤差の最小化なのに、出てきた重みが将棋の直感そのままで、ちょっと感動しました。モデルがちゃんと将棋を理解しているように見える。

今回のまとめ

Gikou
├─ プログラム本体        ✓
├─ USI通信              ✓
├─ 棋譜DB(kifu_db.txt)  ✓ ← 今回(やらかし修正済み)
├─ 進行度(progress.bin) ✓ ← 今回
├─ 評価関数(params.bin) ✗ ← 次回の本丸
└─ 定跡(book.bin)       ✗
  • floodgateから12.8万局を集めて、変換バグ(後手勝ち消失)と棋戦フィルタを直して、ようやくまともな棋譜DBが完成
  • 「データは必ず分布を見る」を身をもって学習
  • 進行度学習は成功。初期局面0.037、誤差0.110、駒の重みも将棋の直感と一致

次にやること

いよいよ評価関数の本体(--learn) の学習です。これは各局面で実際に探索を回すので、僕のマシンでも十数時間コースの長丁場。実はこの記事を書いている裏で既に回し始めていて、score cp 0 から卒業できるかどうか、次回お見せできればと思います。乞うご期待。

(余談:「GPU(RTX4090)使えば速くなる?」とClaudeに聞いたら「技巧の評価関数は手作りの線形モデルでCPU並列だけ、GPUの出番はゼロ」とバッサリ。GPUが効くのはNNUEやdlshogi系の話で、技巧は別物なんですね。勉強になった。)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?