【Claude AI】軍儀対局AIを作る_2

 Claude AIでボードゲーム対局強化学習AIを作る。(経緯:https://weblog.2410.dev/2026/06/claude-aiai.html)

さて、Claude AIに軍儀のルールを教え、学習環境を作らせた。
この記事では、その試行錯誤をメモしておく。

------

実行環境

CPU: AMD Ryzen 9 9950X3D 16-Core
グラフィック:NVIDIA GeForce RTX 4090 (24 GB), AMD Radeon(TM) Graphics (2 GB)
OS: Windows 11
第2OS: Ubuntu (22.04.5 LTS (GNU/Linux 6.18.33.1-microsoft-standard-WSL2 x86_64))
言語: Python 3.10.12

------

LLM (Claud AI)関連 知見

■ Claude Code(Windows用App)は使わない。

怪しいし何するか信じられないから、ブラウザ上だけで操作している。
会社の人たちの様子からは、権限の広いLLMをローカルで動かしたいならそれ専用のPCを用意するべきだっぽい。なんならネットワークすら切り分けるべきかも。

ブラウザはブラウザで閉じてるので安心。

■ プロジェクトの説明書を作る。

会話のログが溜まると、回答の精度が落ちる。
これまでLLMをちゃんと使ってなかったから分かってなかったけど、ログ膨大で前の会話が徐々に圧縮され、それまでの対話の内容があいまいになる。キャラクターを演じさせるにはそこまで問題ないかもしれないけど、厳密なルールや仕様、実装について雰囲気で対応されると困る。適度に乗り換えていかなければならないっぽい。
会話をリセットするたびに、毎回ルール説明してたんじゃやってらんない。知らんゲームのルール説明は大変で、この説明だけでかなりログが溜まる。
なので、軍儀のルール説明をするための説明書と、軍儀Webの方針を説明するドキュメントを作らせた。また、対話の最初に出す指示をメモして、反応を見ながらコピペですぐ即戦力に仕上げられるようにする。
「ここまでのルール理解をテキストにまとめて。できるだけ簡潔に、LLM向けにトークン減らして」
って言ってまとめさせた。(これも結構大変だった)。

ドキュメントが完成したら、対話開始前の全LLMに共有してよさそう。

SNSを見ると、「git管理させてないやつおる?w」みたいな雰囲気だが、俺は強いから、そんなのはいらない。

■ 成果物に混ざる嘘説明

成果物であるところのプログラムには、実装の経緯や懸念点、予定などがコメントで残されている。
対話をリセットしてLLMに成果物を引き継ぐ際にここに嘘コメントが混じってると、嘘コメントを前提にした挙動を見せる。
「"弓"は正面には飛び越えられない」とか大嘘コメントが混ざってたりして、ただでさえ弓は説明が難しいのに、これに気づかずLLMはそう思い込んでいるから修正が困難になる。こっちがいくら「弓は正面に飛び越えられます。ルール説明に書いてますよね?」とか言っても、コメントを信じてこっちの指示を無視するので本当に最悪。

なんかおかしいなと思ったら「渡したファイルのどこでそれを判断したの?」って聞いて丁寧に"毒"を除去するようにした。

------

環境作成/実装関連(時系列)

■ 方針

ほぼVIBEコーディングでやる。できるだけ手は出さない。(現時点で振り返ると、対話リセットのタイミングである程度は手を出して方向修正したほうが楽かも。)

■ 立ち上げ

ゲームルール説明、学習方針相談、ネットワーク入力値相談、対局用clientの作成
回して「バグがある、バグがある、こういうログ出してほしい、バグがある…」を繰り返す。バグの指摘は「怒られた!」って委縮して慌てて適当な修正をしはじめるから疲れる。
この辺は特別なことはなく、とにかく動くかの確認と、今後のデバッグなどのためのログ出力などをただただ依頼する。最低限の枠組みは、無料枠のSonnet 4.6[低]~[中]で十分できた。

■ ドキュメント整理

2回目のリセット時点で、「これこの先何回リセットするかわからんな」と理解した。そこまでの成果物を渡して「このコード読んで」「ゲームルールわかった?説明してみて」「じゃあルールをドキュメントにまとめて」って頼んだ。
「弓」の挙動…基本動作も難しいけど、特に飛び越えルールが全く伝わらなくてかなり大変だった。3世代くらいにわたって「弓」の説明をし続けた。Sonnet 4.6[中]相手にしてたからだと思うけど、これはもっと賢いLLMバージョンでやらせるべき作業だった。本当に大変だった。何回説明しても、説明したばかりの直前の内容が抜け落ちるくらい、こいつは「弓」がダメらしい。あとこっちが丁寧な甘い言葉を使ってるからかナメてきて、依頼した仕事サボってる感じがする。
この作業は1回で終わらないから、ほかの作業もさせながら、ブラッシュアップしたほうが良い。なんか対話リセットのたびに同じ勘違いしててここ毎回訂正しなきゃいけないのか…と思ってたら、そう誤解するような表現が混ざってた、とかがあった。
これはプログラムのコメントも同じ。コメントがあると、コード読解をサボってコメントを信じるから、嘘コメントが混ざってると最悪。
勘違いを発見するたびに、「それ間違ってるから消して」って依頼して潰して回った。(「嘘コメント消しました!」って言ったのに消してないことがあってありえないと思った、嘘つき野郎がよ)。

■ トラブル:符号反転バグ

軍儀は、将棋やチェス、オセロみたいな完全情報公開の2人用ボードゲームだ。
このため、将棋のモンテカルロ木探索(MCTS)の考え方をそのまま当てはめることができる(実はそうではない側面があるが、ここでは省く)。
とある自分の手番のノードを評価したとき、その子ノードの評価は相手手番なので、評価値が逆転する。相手にとって「良い」場面とは、自分にとって「悪い」盤面のはずだからだ。
5代目との対話から、LLMのバージョンをOpus 4.8[高]にした。[高]にしたらどうなるの、と思ったのと、このへんから課金を始めたので、SonnetがOpusに変わったことには気づかなかった。
さてこの能力が高いモデルの第5世代にこれまでの成果物をレビューさせたところ、「MCTSの評価符号が反転してる」とのことだった。
白黒で入力テンソルの向きを統一するため、y軸をそろえていて、その影響がどうこうで、後手番のときに評価が逆転"していない"、という感じだった(軸の反転の影響についてあまり納得してないのでよくわからない)。
コードは動いて回ってはいるけど、学習はうまく進んでおらず、評価系が壊れてるんだからそれはそうでしょという感じであった。

■ トラブル:自滅手バグ

駒を動かしたとき、次の番、相手に「帥」が取られるような操作はしてはいけない。軍儀のルール上では規定がないが、モデルには学習してほしいルールである。
AlphaZeroは、余計なことを言わずともただとにかく大量に学習させればモデルはルールを理解し強くなる、ということだが、個人開発で規模の大きなことはできない。
学習効率化のため、様々な小細工を弄する必要がある。入出力を制限もそのひとつだ。(つまり「帥」を晒すと危ない、という学習ができなくなるし、それが悪影響を及ぼすかもしれないが、その影響を確認するのも大変である)。
この方針から、「自殺手」はそもそも選べないようにコード上で制限していた。はずなのだ。
しかし自殺手が止まらない。ありえない。
調査の結果、
「この手を指したら、自分の帥は相手の次の手番で取られるか(チェックがかかっているか)」を実装するべきところ、「この手を指したら、"相手"の帥は相手の次の手番で取られるか」がフィルタされていたらしい。
これは符号反転バグを直したせいで起きた副作用らしかった。

■ トラブル:投了

自滅手バグでは、「本当は投了したいんだけど、投了はギリギリまで制限されてる…帥を相手に差し出すのが、投了の次に評価が高い手だ!」みたいな状態だったらしい。
そもそも、学習の選択肢に投了を入れているのは「だらだら対局を長引かせるよりは投了したほうがいい」と教えたかったためで、できれば投了しないでほしい。
ここでどう対処したのか覚えてない。

■ 実行環境問題

Windows11 + Ubuntu on WSL2 + Python3 でプログラムを動かしている。
このどこかでforkやプロセスに制限がかかっているようで、並列処理ができなくて効率が悪かった。
調査用のプログラムを書かせ、実行結果を返し……を繰り返して対応させた。
この調査のついでに、マシン側のステータスを確認しながら今の自分の環境でどれくらい負荷をかけてもいいのかも確認した。

■ メモ:

6代目「精査の結果、明確なバグはありません(ほかの修正点をいくつか提示)」

■ 調整:引き分けばっかり

駒が無意味な往復を繰り返し、対局が引き分け(千日手)ばかりになる。
また、千日手にならなくともゲームが長引いて、学習時間も長くなるし、データにもよくない。
ゲームに手番上限を設けて、その手番に達したら評価-1とか-0.5を与えよう、みたいな方針をとっていた。
だらだらした手回しにペナルティを与えましょう、みたいな話だ。
いつそういう設計にしたのかわからないが、冷静に考えて盤面評価に手数はないし、ネットワークにも入力してないので無理だ。意味のない評価を与えていた。

■ メモ:

7代目と、まだ弓の動きについて話している。
ドキュメントもかなり仕上がってきて、LLMの間違いを指摘する際に根拠を探させたり、LLMに伝わりやすい表現があるっぽいな、などと感じる。

■ トラブル:見えない自殺手探索(枝刈り)

自殺手自体は結果に出力はされないが、ルートノードで枝刈りされてるだけで、その先の子ノードからは枝刈りせずに自殺手を探索してたらしい(そんなことある?)。

    ```自殺手ノードはq_value=+1.0(ノード手番=相手視点で「相手の勝ち」)。親(白の手番、root)から_select_childがucb_scoreを計算するとき、ucb_scoreは符号反転しないので、このq=+1.0が「親にとって最高に良い子」に見える。_select_childis_prunedを無視するので、毎回この+1.0の自殺手ノードを選ぶ。選んだ先はis_terminal=Trueなので即座にterminal_valueを返して_backpropするだけ——展開も推論も起きず、ただ予算を1消費して終わる。これが300回繰り返される。```

なので探索はほぼ自殺手がいいと言っているのに、コード側で自殺手は制限されているので、ほぼランダム、みたいな手しか打ってなかったらしい。
「自殺手はなくなったけど、学習出来てんのかなこれ? 軍儀は盤面での選択肢が多すぎるからもっと時間がかかるのかな?」とか思ってた。

■ トラブル:符号"非"反転バグ

反転バグを直したのに非反転バグってなんだよという感じだが、前段の「見えない自殺手」の引用に「ucb_scoreは符号を反転しない」と記載がある。
ノードの高評価は、その親ノードの低評価。この点が反映されていなかったらしい。
つまり……どういうこと?反転バグと同じこと言ってない?
この符号(評価)を先手後手で反転させたことで、自殺手の探索は消えた(多分。少なくともこの時点ではデバックコードでも検出しなかった)。

■ トラブル:アーキテクチャの出力を確認していなかった

説明や他の指示がたくさんあったこともあって、学習の結果何を出力するのかを把握しないまま作業を進めていた。
なんか学習コード自体は回ってるし、よしなにやってくれてるんだろうと思っていた。
しかしどうも、特定の場所にだけ手を打ちたがる傾向があり、何を学習したらこうなるんだろうという感じだった。
調査のため、モデルと儀譜を与えて、その時その手をどう評価していたか、みたいなモデルの考えをはかるプログラムを作成させた。

$ python3 inspect_policy.py --score-file g.scrs --line 1 --upto 1 --model Models/model.pt --candidates-only
=== 1手目まで再生した局面 ===
手番: 黒(後手)  phase=main  帥手を受けている=False
この局面で実際に指された手(行動手): 1b4a  ▲ 9-三-1 大 新
[Model] Loaded from Models/model.pt (step=65000, config={'num_res_blocks': 10, 'num_channels': 128, 'policy_channels': 64, 'value_channels': 256})
value(手番側視点) = +0.0669  [+1=手番side勝勢, -1=敗勢]
合法手 124 手・合法手のみで正規化した policy 上位10:
   1.   7.09%  2a4a  ▲ 9-三-1 兵 新
   2.   7.09%  214a  ▲ 9-三-1 槍 新
   3.   7.09%  1d4a  ▲ 9-三-1 小 新
   4.   7.09%  1b4a  ▲ 9-三-1 大 新  (行動手)
   5.   3.79%  2a40  ▲ 8-二-1 兵 新
   6.   3.79%  2140  ▲ 8-二-1 槍 新
   7.   3.79%  1d40  ▲ 8-二-1 小 新
   8.   3.79%  1b40  ▲ 8-二-1 大 新
   9.   2.83%  2a48  ▲ 9-一-1 兵 新
  10.   2.83%  2148  ▲ 9-一-1 槍 新

実行結果がこれだ。上位手がすべて同率%で、違う駒で同じ位置を指している。
下位手も、同じ場所に指す手は、違う駒でもみんな同じ評価だ。
どうもおかしい気がする。
探索をランダムに行わず、なにか偏ったノード選択をしているのではないか?
原因を調べてくれと頼んだところ、探索の問題ではないらしい。
出力Policyが「座標(9x9x3)x移動分類(4種)=972次元」らしい。
ありえない。たとえば座標9,9,3に駒を置くには、9,9,2に駒が置いてある必要がある…などの制約もあるからほぼ2/3くらい死んでいるし、何より駒種がない。
つまりこれまで何を学習していたのかというと「盤面のどこに置くか」だ。「どの駒を」は考えていない。
駒を置く座標しか出力しないので、そこに置く手ならどの駒でも同じ評価というわけだ。
学習ではここから探索が走るわけだが、だから何である。
12世代は「どう?直す?このまま進める?」みたいなことを言っており、このまま進めるわけねえだろとやんわり伝えた。見つけてくれてありがとうというところだったのに、なに頓珍漢を言っている?
致命的なトラブルであり、あらためてアーキテクチャの見直しを行った。
今回の軍儀AI作成前からアーキテクチャは検討していたので、そのころのメモを引っ張り出して相談した。
把握してなかった知見として、「大」「中」で必要表現が増えるから、将棋を見習って、移動方向だけにしましょうとかアドバイスをもらった。
出力を12,085次元にして、ちょっと大きめだけど許容範囲だろうということでそれにした。
これによって、多様な場所に手を打つようになった。

■ トラブル:無限往復 千日手問題

多様な手を打てるようになったところ、どこにでも打てるので、同じ駒を同じ場所で往復させる手を打つようになった。
これまでは打てる場所が実質限定されていたので、このあたりからモデルは自由に千日手を量産している。
まったく学習がうまくいっていない。
この千日手、往復手問題には長く苦しめられ、複数の対策を講じた。
- 千日手検出:まず、アーキテクチャを見直して、同じ盤面が何回出ているか伝え、千日手を避ける学習を促した。
- 往復状態の盤面評価:手番が伸びると千日手になるわけだから、手数が増えることにもペナルティを与えようか検討した。入力が増えると面倒だから、アーキテクチャでは操作しない。手数が伸びている盤面は多分価値の低い盤面になっているはずだから、手数上限まで到達した対局は、評価を負けと同じにした。
- 往復の価値:「千日手(引き分け)」と「負け」では引き分けのほうがよいのだが、現状勝てないので「手数上限(負けと等価)or 引き分け」しかなく、だらだら引き分けたほうがマシ、みたいな学習になる恐れがあった。そのため、一時的のつもりで千日手も評価を負けと等価にした。これは、のちに、千日手と負けの対局は(勝ちを学習するまでは)学習させない、という方針にしたので今のところ効果は消えている。
- 手数上限:「手数が伸びてたらダメ盤面」は、学習が進まない状況では間違いないはずなので、代わりに1ゲームあたりの手数上限を下げた。千日手問題とは直接関係ない箇所の判断だが、この状態のモデルにフルゲームは荷が重そうだったため、より限定的な駒と状況を設定した。選択肢を狭めることで、勝ち負けにつながりやすくなることと、早く対局が終わることを期待した。これの影響で、手数上限はかなり減った。上限180手から、段階的に様子を見て、結局、上限50手まで減らした。
- 学習記録:どの設定でどれくらいまわしてどうだった、みたいなメモをつけるようにした。Claudeが「やったほうがいいよ」って言ったやつで、コーディングとか実装とは別方面で役に立つアドバイスだった。ランごとの記録を見返すと、役に立つ部分もある。

■ トラブル:枝刈り失敗バグ

「帥」にチェックがかかっている際、それを回避する手以外は探索しなくていいはず。
だからそこの枝刈りは理屈としては簡単なんだけど、なぜか枝刈りできていなかったらしい。
前述の調査プログラムの結果を眺めていて気付いた。次の手番で負ける状況でありえない手が評価されてるから、これって探索もされてるのかなって気になった。
学習方針に迷っていた時期に、「かかっているチェックを回避するだけの合法手生成」と「チェックを回避しない全合法手生成」のロジックをスイッチできるようにしていて、ノードの中でこのスイッチが切り替わっていなかったらしい。

■ トラブル:合法手増殖バグ

前述の調査プログラムを走らせて眺めていたら、ノード内で同じ手が複数回出現していることに気が付いた。
Claudeに調査させたところ、探索の際の合法手生成にバグがあった。同じ手が2~3倍生成されているため、仮に有力な手でも探索が分散して無駄。
時間かけて探索回してるのにそんなことしてたの。

■ トラブル:盤面ではなく手番を見ている問題

結論としては、これは実在したトラブルなのか不明。
ただ、今どっちの番か、ということを重視しているだけで、盤面を全然みてないんじゃないか、という疑いがあって、調査したり対策を考えたりした。
千日手に限らず、他の問題も並列して発生していたので切り分けが難しく、これは特によくわからなかった。
テンソル入力の見た目(手番による上下)をそろえましょう、みたいなことをやった気がする。

■ メモ:

13世代には成果物のリファクタリングと、クライアントの通信プロトコルについて相談した。通信プロトコルについては、軍儀AIとはちょっとレイヤの違う話なので今回は記載しない。
学習を回しながら、14~17世代はこのプロトコル整理をやった。4~5日かけてたらしい。

■ 方針変更:データ量産

2週間程度の試行錯誤で、何か進んでいる感はあるものの、学習自体はほとんどうまくいっていなかった。
そこで、データを量産する方針に切り替えた。
学習は二の次で、発散しなければOK。
学習用の限定的状況から始めるデータをとにかく増やす。
30時間かけて1000局対局させて、50局くらい有効なデータが回収できればまあまあといった状況。
人間が一人でデータを用意するよりはましだ。質は低いし時間もかかるが、対局のデータづくりだけなんてやりたくない。(といいつつぼちぼちやっているけど…)。

■ トラブル:イテレーションが増えると投了も増える問題

「そもそも投了って制限したんじゃないんだっけ…?」と思いながら、対応することがいっぱいあって後回しにしていた。
投了関連で、期待する学習は今のところ望めない。
そこで、コード上で投了を制限した。投了は選択肢ではなく、「ほかに選択肢がない場合に選ばれる」ようにした。実質禁止だ。

■ トラブル:バッチ探索が死んでいたバグ

19代目にOpus 4.8 [Ex高]にして、思考をONにした。
回答に時間がかかり、トークンもやたら使うので全然話が進まない。
しかし、既存のコードを調査させたところかなり致命的なバグが(また)見つかった。
「デフォルトでは使ってなさそうだけど、オプションの--mcts-mode batch使うなら探索できなさそうだよ」
みたいなことを言われた。
え?もう、ずっとそれだけ使ってるんだけど?(早いし)
と思って詳しく聞いたら、mcts-batch が32なら、1回の探索で32バッチとも同じノードを探索してるらしい。探索時間が減っても、探索効率が1/32になっている。
ありえない、マジで。
探索量をちょっと増やしてみても様子が変わらないわけだ。mcts探索回数を200にしていたので、6~7個の子ノードしか探索していなかったらしい。
うまくいくわけないだろ。
これを修正して何回か様子見を重ねたところ、学習用の小さい対局は止めて、人間の初心者向け通常対局でも時々決着がつくようになった。
本当にありえない!なんだったんだ!一体!!
試行錯誤すら無意味だったんじゃないのかと思う。見返すとそんなことはないのだが、しかし学習自体はほぼ全くしていないといっていい状況だった。
トライ安堵エラーの都合で何度かモデルを捨てているが、崩壊したり完全にダメになったモデルはまだない。それが不思議なくらいだ。

■ 微調整:

ここまでのデバッグで、あらかた潰せて、学習が回るようになっただろう、と期待している。まあ質が低くてもデータがぼちぼち手に入るようにはなった。
他は今は微調整、ログの出力を詳細にして調査したり、データをせくせく集めたり、ハイパーパラメータはあまりいじらず、前後のランと比較しやすい状況を維持して学習を回している。

------

まだ書き切れてないことがあるけど、大きなものはだいたい記載したんじゃないだろうか。

学習以外の面でトラブルも別途抱えているから書くことには困らないが……。

https://ai-heartland.com/explain/claude-thinking-lever-effort-level-guide/
Anthoripic(Claude開発元)が使い方のコツとかのセッションをして、それをまとめた記事。基本新しいモデルのほうがいい、とかクセとか。

たぶんやっと学習コードが動くようになったから、このへんでgithubにpushしようかな。

コメント