Splendor AIを教師あり学習してみた: policyは伸びたがvalueが過学習した
Splendor をプレイする AI を作っている。
Splendor は、宝石トークンを集めてカードを購入し、カードの割引を使ってさらに高いカードを買い、得点を競うボードゲームだ。いま作っているのは2人対戦用で、最終的にはニューラルネットワークをゲーム木探索に組み込み、自己対局で強くしていきたい。
その前段階として、まずはルールベースの教師を使って、ニューラルネットワークに「どの手を選ぶか」と「この局面から勝てそうか」を同時に学習させるところまで進めた。
今回はその最初の学習結果について書く。
結論から言うと、手の選び方を予測する policy は学習を続けるほど少しずつ改善したが、勝敗を予測する value は途中から過学習した。最終的には8,000回学習したモデルではなく、途中の3,356回目のモデルを採用することになった。
教師データを作る
いきなり自己対局から学習を始めると、最初はニューラルネットワークが何も分からないため、探索もほぼランダムになる。そこで最初のモデルは教師あり学習で作ることにした。
教師には、これまで作っていたルールベースのロジックを使った。カードの得点や割引、手持ちの宝石などから局面を評価し、さらに最大3手先まで読む。全候補を深く読むと重いため、各段階では評価の高い4手程度に絞って先読みしている。
教師には最善手を1つだけ出させるのではなく、合法手ごとの確率分布を出させた。たとえば、ほぼ一択の局面なら1つの手に確率が集中し、どちらでもよさそうな局面では複数の手に確率が分かれる。
このルールベースの教師同士を対局させて学習データを生成した。
| 項目 | 数 |
|---|---|
| 初期配置 | 4,096 |
| 対局数 | 8,192 |
| 学習に使える局面数 | 478,194 |
同じ初期配置から作られた対局が学習用と評価用の両方に入ると、似た局面を暗記して高い精度が出る可能性がある。そのため初期配置単位でまとめたまま、18:1:1 の比率で学習用、モデル選択用、最終評価用に分けた。
学習用だけを見ると、3,675個の初期配置から7,350対局、429,066局面になった。
policy と value を1つのネットワークで学習する
モデルは1つのニューラルネットワークから2種類の出力を出す。
policy は、その局面で選べる各合法手に対する確率を出す。教師が出した確率分布に近づくように学習する。
value は、手番プレイヤーから見た最終結果を win / draw / loss の3クラスで予測する。将来的には探索の末端で「この局面はどれくらい良さそうか」を判断するために使う。
概念的には次のような構成になっている。
局面 ↓プレイヤー・宝石・場のカード・貴族などを埋め込み ↓2層の self-attention でオブジェクト間の関係を見る ↓局面全体の表現 ├─ policy head → 各合法手の確率 └─ value head → win / draw / lossSplendor では、単体のカードの情報よりも「自分の割引とこのカードのコスト」「相手の宝石と場のカード」「カードのボーナスと貴族の条件」といった関係が重要になる。そのため、各オブジェクトを独立に圧縮してからまとめるのではなく、先に self-attention で相互作用させてから局面全体をまとめる構成にした。
モデルサイズは約138万パラメータ。大きなモデルではない。
学習方法
policy は教師の確率分布との cross entropy、value は最終勝敗との cross entropy で学習する。value の重みは 0.5 にした。
policy では教師が選んだ1手だけを正解にするのではなく、教師が出した分布そのものを教師信号にしている。
value は win / draw / loss の3クラス分類として学習する。
今回の主な学習条件は以下。
| 項目 | 設定 |
|---|---|
| Optimizer | AdamW |
| Batch size | 512 |
| Learning rate | 0.0003 |
| Weight decay | 0.01 |
| LR schedule | 100 step warmup + cosine decay |
| Gradient clipping | max norm 1 |
| Precision | bfloat16 mixed precision |
| GPU | Tesla T4 |
| 最大更新回数 | 8,000 step |
学習途中のモデルを定期的に保存し、学習には使っていない validation データで一番 loss が小さいものを採用する。test データはモデルを選び終わった後の評価にだけ使う。
途中から value だけ悪くなった
8,000 step まで学習したところ、validation で選ばれたのは step 3,356 のモデルだった。
3,356 step と学習終了時の8,000 stepを比較するとこうなった。
| Checkpoint | 全体 loss | policy loss | value loss | value accuracy |
|---|---|---|---|---|
| 3,356(採用) | 1.2824 | 0.9303 | 0.7043 | 65.86% |
| 8,000(終了時) | 1.4900 | 0.9120 | 1.1560 | 61.82% |
policy loss は 0.9303 から 0.9120 に下がっているので、教師の手を真似する能力は後半も少し改善していた。
一方、value loss は 0.7043 から 1.1560 まで悪化した。約64%の悪化になる。学習データ上では value loss が下がり続けていたので、典型的な過学習だった。
最初は policy と value を同時に学習すれば、両方とも同じように改善していくと思っていたので、この分かれ方は面白かった。
原因として考えているのは、policy と value で実質的な教師信号の量がかなり違うことだ。
policy は42万以上の各局面で異なる「次の手の分布」を教師信号として持っている。一方で value は、1対局に含まれる数十局面すべてが同じ最終勝敗を共有する。学習用データは429,066局面あるが、独立した勝敗という意味では7,350対局、さらに初期配置は3,675個しかない。
そのため value 側は、見かけの局面数ほど独立した情報量がなく、policy より先に暗記へ寄った可能性がある。
test データで確認する
採用した step 3,356 のモデルを、モデル選択に使っていない test データで評価した。
policy では、モデルが最も高い確率を付けた手と、教師が最も高い確率を付けた手が一致した割合が 68.15% だった。教師が実際にサンプルして選んだ手との一致率は 65.38%。
ただし局面による差が大きい。教師がほぼ一択だと考えている局面では top-1 accuracy が97.8%だったのに対し、教師自身が複数の手で迷っているような局面では17.4%まで落ちた。
value の win / draw / loss accuracy は 63.26%。単純に学習データの勝敗比率だけを予測し続けた場合の49.74%よりは良かった。
ただし、ゲーム終了まで33手以上ある局面では52.7%しかなく、最後の1手では90.0%だった。終盤の勝敗は読めるが、探索で本当に欲しい長期的な局面評価はまだ弱そうだ。
今回分かったこと
今回の学習で、教師の手をある程度再現する policy は作れた。勝敗を読む value にも何らかの信号は入っている。
一方で、policy と value を同じネットワークで同時に学習しても、両者の学習速度や過学習の仕方はかなり違った。特に value は、局面数だけを見てデータ量が十分だと思わない方がよさそうだ。
また、最終 step のモデルをそのまま使わず validation で途中の checkpoint を選ぶ仕組みを入れておいたのは正解だった。これがなければ、policy が少し良くなった代わりに value がかなり悪くなった8,000 stepのモデルを採用していた。
まだ「Splendor が強くなった」とは言えない。今回やったのは教師データ上での学習なので、次はこのモデルを実際のゲーム木探索に組み込み、まずランダムに手を選ぶプレイヤーとの対局でどの程度勝てるかを測る。
その結果を見て、自己対局を始めるか、value の学習方法やデータ量を見直すかを決める予定。
あとから振り返って
そもそも教師あり学習から始めたのは、自分が探した範囲では Splendor のユーザーによる実プレイログをまとまって入手する方法が見つからなかったからだ。人間の対局データを教師にできないのであれば、まずは自分でルールベースの教師を作り、そこから学習データを生成するのが現実的だと考えた。
この問題自体は先行研究にかなり直接的に書かれている。2016年の AlphaGo 論文 では、同じゲームの局面は相関が強く、同じ最終勝敗を共有するため value が過学習しやすいとして、1ゲームから1局面だけを使って学習データを作っていた。
今回の自分のデータは、まさに逆で、1ゲームから数十局面を取り出して、その全部に同じ勝敗ラベルを付けていた。いま見ると value 側が先に暗記へ寄るのはかなり自然だった。今回の過学習を「実験して初めて分かった現象」と言うより、先行研究で知られていた落とし穴を、自分の実装でも踏んでしまったという方が正確だと思う。この点は、先行研究を実装に落とすところが少し劣後していた。
DL将棋の実装や開発記事を追っていたことを考えると、なおさらそう思う。dlshogi の学習則 では、value を最終的な勝敗だけで学習するのではなく、探索で得た局面評価と最終勝敗を混ぜるブートストラップを使っている。勝敗という遠い教師信号だけに依存せず、途中局面で得られる評価も使う考え方になっている。
一方で、policy と value を最初から同時に学習したことについては、先行研究を追っていたことがそのまま役に立った。
DL将棋の初期の開発ではまず policy network 単体の学習から始まっていたが、その後 policy と value のマルチタスク学習 が試され、共有ネットワークから両方を同時に出力して学習できることが確認されていた。自分はこのあたりの記事を参考にしていたので、「まず policy だけ作って、その後 value を足す」という順番を繰り返さず、最初から policy-value の同時学習を採用できた。
ここは先行研究をうまくショートカットとして使えたところだと思う。
この記事は、実装・実験記録をもとに、本文の大部分をLLMが執筆し、筆者が内容を確認・編集しています。