Belief-Guided Neural ISMCTS¶
更新日: 2026-07-05
このドキュメントは、Pokemon TCG AI Battle 向けに開発しているエージェント "Belief-Guided Neural ISMCTS" の設計を、実装方針とあわせて読み物としてまとめたものである。実験結果の時系列や特定 checkpoint の数値比較は docs/research/ に置き、ここでは「現在採用している構成」と「その背後にある考え方」を中心に説明する。
目次¶
- Summary
- なぜこの手法か (Background)
- 先行研究との関係 (Related Work)
- 全体構成 (System Overview)
- Policy / Value Model
- Belief Model と PublicKnowledgeTracker
- ISMCTS
- Leaf / Progress Value
- Self-Play データ収集
- Policy / Value Training
- Belief Training
- 評価 (Evaluation)
- 既知の課題 (Known Issues)
- 今後の計画 (Next Steps)
- 付録 A: ワークフローと実行コマンド
- 付録 B: 用語集 (Glossary)
Summary¶
エージェントは、CABT から渡される公開 observation と動的な合法手リスト (select.option) を入力に、UnifiedTokenPolicyValueNet・Belief
prior + PublicKnowledgeTracker・Information Set MCTS
の 3 つを組み合わせて 1 手を選ぶ。現在のメインモデルは UnifiedTokenPolicyValueNet
であり、1 つの shared Transformer trunk から policy logits、win value、aux prize heads、integrated
belief heads を同時に出す multi-head 構造を採用している。
以前は ActionConditionedPolicyValueNet を中心に、Policy/Value checkpoint と standalone BeliefNet
checkpoint を分けて運用していた。現在も旧 checkpoint / 比較実験 / 互換用に
ActionConditionedPolicyValueNet と standalone BeliefNet
は残しているが、本線の self-play、training、evaluation、submission bundle は unified
checkpoint を中心に動かす。--belief-source auto では、明示的な --belief-checkpoint
が無い場合、policy checkpoint 内の integrated belief heads を hidden-state
prior として使う。PublicKnowledgeTracker は公開ログから「確定できているカード」を hard
constraint として与え、belief prior だけでは保てない一貫性を担保する。
flowchart LR
PO["Public observation<br/>+ legal CABT options"]:::input
PVN["UnifiedTokenPolicyValueNet<br/>policy · value · aux · belief"]:::nn
BM["Belief prior +<br/>PublicKnowledgeTracker"]:::nn
ISMCTS["Information Set<br/>MCTS"]:::search
OUT["Selected legal<br/>option index"]:::output
PO --> PVN
PO --> BM
PVN -- "prior + value<br/>+ aux leaf hints" --> ISMCTS
PVN -- "integrated belief heads" --> BM
BM -- "hidden state samples" --> ISMCTS
ISMCTS -- "visit distribution" --> OUT
classDef input fill:#e3f2fd,stroke:#1976d2,color:#000
classDef nn fill:#fff3e0,stroke:#f57c00,color:#000
classDef search fill:#f3e5f5,stroke:#7b1fa2,color:#000
classDef output fill:#e8f5e9,stroke:#388e3c,color:#000
設計の方針を一行で並べると次のようになる。
- NN-only にしない。Policy/Value Network は MCTS の prior として使い、最終決定は tree search の visit distribution で行う。
- rollout は使わない。AlphaGo Zero と同様に、leaf では NN value と軽い progress value を合成して評価する。弱い rollout は探索を汚すリスクがあるため避ける。
- Belief Model は ISMCTS を置き換えない。役割はあくまで hidden state sampling の prior であって、相手手札を決め打ちに行く部品ではない。
- 完全情報 oracle target は実験用に残す。現在の通常学習では使わない。
- デッキ専用にしない。複数デッキを扱える汎用 policy を作り、デッキ固有の最適化は teacher search 側に閉じる。
- 評価は勝率だけで見ない。通常勝ち・サイド取得・攻撃回数・種切れ負け・deck-out 負け・unfinished を合わせて追う。
なぜこの手法か (Background)¶
Pokemon TCG AI Battle で扱う難しさは、完全情報のボードゲームに比べると一段多い。盤面の見た目だけでは状態が定まらず、手札・山札・サイドは hidden information を含む。サーチで公開されたカードや、公開ゾーンに出されたカードだけが部分的に判明する。さらに 1 ターンの中で、カードを使う / 進化する / エネルギーを付ける / 特性を使う / 逃げる / 攻撃する、といった複数段階の意思決定が連続する。
CABT はこの多段の意思決定を「各 decision point で動的な合法手 select.option
を返す」形で表現する。そのため、固定サイズのアクション分類で全行動を扱うより、その局面の合法 option を直接スコアリングする action-conditioned 構造の方が素直である。また、「いま見える盤面」だけでなく「このデッキは何を狙うデッキか」「相手の hidden
zone に何が残っていそうか」も意思決定に直接効いてくる。
これらをふまえると、エージェントが同時に扱うべき問題は次の 6 つにまとまる。
- 不完全情報下で、相手の hidden hand / deck / prize を推定する。
- 動的な合法手リストをそのままスコアリングする。
- サイド取得に向かう盤面作りを評価する。
- deck-out 負けや種切れ負けといった、ポケカ固有の負け筋を避ける。
- 複数デッキで使える policy を学習しつつ、自分のデッキリストに応じた動きも表現する。
- self-play で作る teacher search の質を保ち、弱い探索結果を強く模倣してしまわない。
(3)(4) はサイドというリソース固有の難しさ、(5)(6) は学習プロセス側の課題である。NN-only の policy は高速だが、探索なしでは数手先のサイド取得や種切れリスクを見落としやすい。逆に rollout を使うと、弱い playout で誤った leaf value が伝播し、NN の評価まで汚れてしまう。そこで本手法は leaf を rollout せず NN value で評価し、不完全情報側の問題は ISMCTS と Belief Model に任せる、というレイヤ分けを採る。
先行研究との関係 (Related Work)¶
本手法は、完全情報ゲームの neural MCTS、不完全情報ゲームの ISMCTS、カードゲームにおける belief / opponent modeling の 3 つを組み合わせる位置づけにある。参照リスト全体は references.md にまとめている。
AlphaZero 系¶
AlphaZero は、Policy/Value Network と MCTS を自己対戦で反復改善する代表的な手法である。policy head は MCTS の visit distribution を学習し、value head は勝敗を学習する。本手法でも、CABT の合法 option に対する policy logits と局面 value を出し、ISMCTS の探索結果を policy target として学習する。違いは、ポケカが不完全情報ゲームである点で、通常の完全情報 MCTS ではなく hidden state sampling を含む ISMCTS を使う。
- Silver et al. (2018) "AlphaZero"
- Schrittwieser et al. (2020) "MuZero"
- MiniZero
Information Set MCTS¶
不完全情報ゲームで hidden state を 1 つに固定した determinized search だけ回すと、strategy fusion などの問題が起きやすい。Information Set MCTS は、観測者から同じに見える状態を information set として 1 ノードにまとめ、hidden state をサンプリングしながら tree を共有することでこれを緩和する。本手法の ISMCTS は public information key で tree node を共有し、Belief Model と PublicKnowledgeTracker で hidden state をサンプリングする。
- Cowling, Powley, Whitehouse (2012) "Information Set Monte Carlo Tree Search"
- Whitehouse, Powley, Cowling "Determinization and Information Set Monte Carlo Tree Search for Dou Di Zhu"
- Cowling et al. (2015) "Information capture and reuse strategies in MCTS"
Belief / opponent modeling¶
ポーカーや麻雀の研究では、相手の hidden
information を分布として扱い、観測に応じて更新する考え方が重要になる。DeepStack は continual
re-solving と range / belief、Suphx は麻雀のような hidden
information と長い意思決定系列を扱う実例である。本手法では、現在は unified checkpoint の integrated
belief heads が相手 hand / deck /
prize の prior を出し、公開ログから確定できる情報は PublicKnowledgeTracker が hard
constraint として反映する、という分担にしている。standalone BeliefNet は互換・比較用に残している。
TCG / CCG AI¶
Hearthstone や Legends of Code and Magic などの CCG 研究では、カード固有効果・動的アクション・デッキ構築・リソース管理を含む意思決定が扱われている。ポケカも同様に、盤面・手札・山札・サイド・エネルギー・進化・攻撃準備が絡むため、単純な勝敗 reward だけでは学習が不安定になりやすい。本手法では、勝敗だけでなくサイド取得・攻撃回数・attack-ready・deck-out / pokemon-out も評価指標として追っている。
- Improving Hearthstone AI by Combining MCTS and Supervised Learning
- Legends of Code and Magic 系列
- A Taxonomy of Collectible Card Games from a Game-Playing AI Perspective
全体構成 (System Overview)¶
1 手の決定がどう流れるかを上から下に追うと、概ね以下のようになる。CABT
observation を encoder が tokenize し、その結果が UnifiedTokenPolicyValueNet に入る。unified
model は root / leaf の policy/value 推論に加えて、同じ public observation から hidden-state
prior も出せる。BeliefPrior は PublicKnowledgeTracker と合流して hidden-state
sampler に渡され、ここからサンプルされた hidden state と NN の prior/value/aux
hint を ISMCTS が消化する。最終的に visit distribution から 1 つの option index が選ばれる。
flowchart TD
CABT["CABT<br/>observation + select.option"]:::io
ENC["features.encoder<br/>state / history / action tokens"]:::io
subgraph NN["Neural Networks"]
direction LR
PV["UnifiedTokenPolicyValueNet<br/>action prior · value · aux prize"]:::nn
BN["Integrated belief heads<br/>or standalone BeliefNet<br/>hand / deck / prize prior"]:::nn
end
subgraph SAMPLING["Hidden-state sampling"]
direction LR
PKT["PublicKnowledgeTracker<br/>確定カード (hard)"]:::sampling
HSS["Hidden-state sampler"]:::sampling
end
ISMCTS["Neural ISMCTS<br/>PUCT with NN prior<br/>leaf = NN value + progress"]:::search
OUT["Selected option index"]:::io
CABT --> ENC
CABT -. "public log" .-> PKT
ENC -- "state / history / action tokens" --> PV
ENC -- "public state tokens" --> BN
BN -- "soft prior" --> HSS
PKT -- "hard constraints" --> HSS
PV -- "prior + value" --> ISMCTS
HSS -- "hidden state" --> ISMCTS
ISMCTS -- "visit distribution" --> OUT
classDef io fill:#e3f2fd,stroke:#1976d2,color:#000
classDef nn fill:#fff3e0,stroke:#f57c00,color:#000
classDef sampling fill:#fce4ec,stroke:#c2185b,color:#000
classDef search fill:#f3e5f5,stroke:#7b1fa2,color:#000
1 手分のシーケンス¶
上のフローチャートはモジュール間の依存だけを示している。実際の 時間方向の呼び出し順 をシーケンス図で書くと以下のようになる。外側の D 回 (determinization) と内側の S 回 (simulation) のループの中で、どのモジュールがいつ呼ばれるかが見やすい。
sequenceDiagram
autonumber
participant CABT
participant ENC as features.encoder
participant ISMCTS as Neural ISMCTS
participant PV as Policy/Value Net
participant BN as Belief Net
participant PKT as PublicKnowledgeTracker
participant HSS as Hidden-state sampler
CABT->>ENC: observation + select.option
ENC-->>ISMCTS: state_tokens / history_tokens / action_tokens
Note over ISMCTS: root node 作成
ISMCTS->>PV: forward(state, actions)
PV-->>ISMCTS: NN prior + state value
opt belief checkpoint is configured
ISMCTS->>BN: forward(public state)
BN-->>ISMCTS: BeliefPrior(hand / deck / prize)
end
loop d = 1..D (determinizations)
ISMCTS->>PKT: 現在の公開ログから hard constraints
PKT-->>ISMCTS: 確定カード集合
ISMCTS->>HSS: sample(BeliefPrior, hard constraints)
HSS-->>ISMCTS: hidden state d
loop s = 1..S (simulations)
Note over ISMCTS: PUCT で leaf までたどる
ISMCTS->>PV: leaf の NN value + prior
PV-->>ISMCTS: value + 子ノード prior
Note over ISMCTS: leaf_value = NN value<br/>+ progress value + action delta
Note over ISMCTS: backpropagate (visit++ / value avg)
end
end
Note over ISMCTS: visit distribution から argmax/sample
ISMCTS-->>CABT: selected option index
主な観察点:
- NN forward は 2 ヶ所で呼ばれる: root prior (1 回) と各 leaf 評価 (S × D 回程度)。後者が self-play 全体の最大ボトルネック。
- Belief prior provider は 1 decision につき最大 1 回呼ぶ。通常は policy checkpoint の integrated belief heads、互換運用では standalone BeliefNet を使う。得られた BeliefPrior を使って D 回の determinization をサンプリングする。深いノードでは新たに belief 推論を呼ばず、root でサンプルした hidden state を simulation ごとに clone して使う。
- PublicKnowledgeTracker は毎 determinization で hard constraints を返す。BeliefPrior サンプリング前に必ず通すことで、公開ログと矛盾する hidden は最初から排除される。
- leaf shaping (progress value / action delta) は backpropagate の直前に NN value に足し込まれる。
各モジュールの中身を、ここから順に説明していく。
Policy / Value Model¶
実装:
- current: src/pca/models/policy_value_unified/
(
UnifiedTokenPolicyValueNet) - legacy: src/pca/models/policy_value.py
(
ActionConditionedPolicyValueNet)
これは AlphaGo Zero における Policy/Value Network に対応する。ただし合法手が局面ごとに変わる CABT の都合で、固定アクション分類ではなく action-conditioned policy にしている。具体的には、encoded state と各 option の action vector を照合し、option ごとに 1 つの logit を返す。
現在の本線は UnifiedTokenPolicyValueNet である。以前の v10〜v12 では
ActionConditionedPolicyValueNet を中心に、structured object branch、deck context、history
branch を段階的に足していた。現在はそれらの考え方を 1 つの token
stream に整理し、カードの静的定義・盤面上の動的状態・位置/所属情報・履歴・デッキ文脈・合法手構造を unified
model 側で扱う。
現在採用している Unified Multi-Head Model¶
UnifiedTokenPolicyValueNet は、public observation から state sequence を作り、shared Transformer
encoder で 1 つの state representation を得る。その state representation を複数 head が共有する。
flowchart TD
CABT["CABT public observation<br/>+ legal options"]:::input
TOK["Unified tokenization<br/>global · board objects · history · deck · actions"]:::layer
STATIC["Static card / attack embeddings<br/>card_id + CardStaticFeatures<br/>attack_id + AttackStaticFeatures"]:::layer
ENC["Shared Transformer encoder<br/>[CLS] + global + deck + board + history"]:::nn
STATE["state_vec<br/>encoded CLS"]:::vec
ACT["action option vectors<br/>action flat token + action objects<br/>+ state-action cross attention"]:::vec
PH["policy head<br/>score(state_vec, action_vec_i)"]:::head
VH["value head"]:::head
AUX["aux prize heads"]:::head
BEL["integrated belief heads"]:::head
PI["policy_logits [B,A]"]:::output
V["value [B]"]:::output
PRIZE["this_turn_prize_gain<br/>own/opp prize completion"]:::output
BP["opponent hand/deck/prize<br/>next threat · KO threat"]:::output
CABT --> TOK
STATIC --> TOK
TOK --> ENC --> STATE
TOK --> ACT
STATE --> PH
ACT --> PH --> PI
STATE --> VH --> V
STATE --> AUX --> PRIZE
STATE --> BEL --> BP
classDef input fill:#e3f2fd,stroke:#1976d2,color:#000
classDef layer fill:#fff3e0,stroke:#f57c00,color:#000
classDef nn fill:#f3e5f5,stroke:#7b1fa2,color:#000
classDef vec fill:#f1f8e9,stroke:#689f38,color:#000
classDef head fill:#fffde7,stroke:#f9a825,color:#000
classDef output fill:#e8f5e9,stroke:#388e3c,color:#000
出力 head:
| Head | Output | 用途 |
|---|---|---|
| policy head | policy_logits [B, A] |
CABT が返した合法 option ごとの prior。ISMCTS の root / leaf prior と training target に使う。 |
| value head | value [B] |
現局面の勝敗期待値。leaf value の中心。 |
| aux prize heads | this_turn_prize_gain_logits [B, 7], own_prize_completion_logit [B], opp_prize_completion_logit [B] |
勝敗 value だけでは弱いサイド取得の近さを補助する。v13_aux_prize_race profile では leaf tie-break として小さく混ぜる。 |
| integrated belief heads | opponent hand / deck / prize logits、next threat logits、knockout threat | hidden-state sampling の soft prior。--belief-source auto では standalone belief checkpoint が無い場合にこれを使う。 |
入力の考え方:
state_tokens: サイド枚数、山札枚数、手札枚数、turn flags、select context など、カード 1 枚に紐づかない global summary。state_objects: 場、見えている手札、トラッシュ、スタジアム、見えているサイドなどの object row。card_idに加えて area / owner / slot / role / parent / damage / energy count などを持つ。history_tokens/history_objects: recent logs を event 列として表す。deck_tokens: 自分の 60 枚デッキ。標準では Set Transformer で pooled deck summary token にする。action_tokens/action_objects: CABT の合法 option。option type / select context に加え、actor、target、card、attack id などを object として持つ。
static embedding:
static_card_embedding =
card_id_embedding(card_id)
+ CardStaticFeatures(name/type/stage/hp/attack_count/retreat/... derived tokens)
static_attack_embedding =
attack_id_embedding(attack_id)
+ AttackStaticFeatures(cost/damage/text-derived feature tokens)
training は self-play JSONL に埋め込まれた search.card_metadata と search.action_metadata
から static feature table を復元する。これにより、大量学習時は JSONL 単体で unified model の card /
attack feature を再構築できる。
以前の構成からの変更点¶
以前の ActionConditionedPolicyValueNet は、flat state_tokens / history_tokens /
action_tokens を主入力にし、後から structured object branch や deck
context を足していた。これは v10〜v12 checkpoint 互換のため今も残している。
現在の unified model では、legacy branch で分かれていた情報を「同じ state
sequence に入る token」として扱う。つまり state_objects
は補助 branch ではなく、盤面理解の主入力である。policy/value と belief も別 checkpoint ではなく、同じ trunk を共有する multi-head
model になっている。
Legacy と Unified の違い¶
| 項目 | ActionConditionedPolicyValueNet |
UnifiedTokenPolicyValueNet |
|---|---|---|
| 目的 | 既存 checkpoint / evaluation / submission 互換 | v13 以降の入力層改善実験 |
| 盤面表現 | flat token branch + optional structured object branch | object token を主入力にする |
| カード情報 | card id embedding と static feature embedding を補助 branch で利用 | static card embedding を各 object/action token の素材として常時利用 |
| 技情報 | flat option token に attackId を含める |
self-play JSONL の search.action_metadata から attackId -> name/cost/damage/text を static_attack_embedding 化 |
| 位置情報 | structured object branch で owner/zone/role/parent を追加 | area + owner + slot + role + parent + dynamic を token 文脈として統合 |
| 合法手表現 | action token と optional action object mean | action / actor / target / attack を分け、action object attention と state-action cross attention で score |
| デッキ情報 | deck_context_mode で none/mean/set_transformer |
default は deck_integration=pooled で Set Transformer 圧縮、将来 tokens も比較可能 |
| 補助 head | policy / value のみ | policy / value に加えて aux prize heads と integrated belief heads |
| ロード | model_class 未指定、legacy、ActionConditionedPolicyValueNet |
checkpoint/config の model_class=UnifiedTokenPolicyValueNet または CLI --model-class unified |
| 非公開情報 | 相手手札正解、山札順、サイド正解は入れない | 同左 |
旧 checkpoint は model_class が未指定でも legacy として扱う。新 checkpoint は保存時に top-level
model_class と model_config.model_class に UnifiedTokenPolicyValueNet
を書くため、学習・self-play・評価・提出 loader は factory 経由で自動的に新旧を振り分ける。
v13 の学習 config では card-data: jsonl を使い、search.card_metadata と search.action_metadata
から static card / attack feature
table を復元する。つまり training は JSONL 単体で動く。一方で self-play 収集時は、その JSONL を作るためにカードDBと CABT
attack API を参照する。
参考: legacy path の全体構造¶
以下は旧 ActionConditionedPolicyValueNet
の構造である。現在の本線ではなく、旧 checkpoint の互換、A/B 比較、設計履歴の理解のために残している。
主入力は state_tokens / history_tokens / action_tokens / deck_tokens
である。これに加えて v12 では、盤面・履歴・合法手を固定幅 row で表す state_objects /
history_objects / action_objects を持つ。
state / history / action の flat token は、ターン状態・枚数・select
context などの summary 文脈を表す。カード本体とカード間の関係は structured object
branch に寄せる。deck と structured object branch は raw card id 用の card_id_embedding
と静的特徴 token 用の card_feature_embedding
を使い、concat(card_id_vec, static_feature_vec) -> card_feature_encoder で 1 枚ごとの card
vector を作る。その後、State / History / Action / Deck / Structured
Object の経路で集約してから、policy / value の 2 head に分かれる。
flowchart TD
ST["state_tokens<br/>[B, state_len]"]:::input
HT["history_tokens (optional)<br/>[B, H, history_len]"]:::input
AT["action_tokens<br/>[B, A, action_len]"]:::input
DT["deck_tokens (optional)<br/>[B, deck_len]"]:::input
SCR["state_objects (optional)<br/>[B, object_count, W]"]:::input
HCR["history_objects (optional)<br/>[B, H, object_count, W]"]:::input
ACR["action_objects (optional)<br/>[B, A, object_count, W]"]:::input
EMB[("Shared token Embedding<br/>state / history / action summary<br/>+ object context 用<br/>nn.Embedding(token_buckets, d_model)")]:::shared
CIDE[("card_id_embedding<br/>deck / state / history / action の raw card id 用")]:::shared
CFE[("card_feature_embedding<br/>CardStaticFeatures token 用")]:::shared
ST --> EMB
HT --> EMB
AT --> EMB
DT --> CIDE
DT --> CFE
SCR --> CIDE
SCR --> CFE
SCR --> EMB
HCR --> CIDE
HCR --> CFE
HCR --> EMB
ACR --> CIDE
ACR --> CFE
ACR --> EMB
subgraph STATE_PATH["State path"]
direction TB
SE["state_encoder<br/>TransformerEncoder × num_layers (nhead heads)"]:::layer
SP["mean pool<br/>(padding mask 適用)"]:::layer
SV["state_vec<br/>[B, d_model]"]:::vec
SE --> SP --> SV
end
subgraph HISTORY_PATH["History path"]
direction TB
HM["mean pool per event"]:::layer
HEV["history_event_encoder<br/>Linear → ReLU → LayerNorm"]:::layer
HOCTX["history object mean<br/>+ history_object_gate<br/>(event_vec に合流)"]:::layer
HPE["position embedding<br/>+ event TransformerEncoder"]:::layer
HP["mean pool over events<br/>(padding mask 適用)"]:::layer
HV["history_vec<br/>[B, d_model]"]:::vec
HF["history_fusion + history_gate<br/>state_vec + gate · delta"]:::layer
HSV["history-enriched state_vec<br/>[B, d_model]"]:::vec
HM --> HEV --> HOCTX --> HPE --> HP --> HV --> HF --> HSV
end
subgraph ACTION_PATH["Action path (per legal option)"]
direction TB
AM["mean pool per option<br/>(padding mask 適用)"]:::layer
AE["action_encoder<br/>Linear → ReLU → LayerNorm"]:::layer
AV["action_vec<br/>[B, A, d_model]"]:::vec
AM --> AE --> AV
end
subgraph STRUCTURED_OBJECT_PATH["Structured object path (optional)"]
direction TB
CRV["card_feature_vectors<br/>card_id_embedding + static features"]:::layer
OCTX["object context mean<br/>owner / zone / role / slot / parent..."]:::layer
OBJV["structured_object_encoder<br/>concat(card_vec, context_vec)"]:::layer
SCRP["state object SetTransformer<br/>+ state_object_gate"]:::layer
ACRP["action object mean<br/>+ action_object_gate"]:::layer
SVREF["structured-object enriched state_vec"]:::vec
AVREF["structured-object enriched action_vec"]:::vec
CRV --> OBJV
OCTX --> OBJV
OBJV --> SCRP --> SVREF
OBJV --> HOCTX
OBJV --> ACRP --> AVREF
end
subgraph DECK_PATH["Deck context (詳細は下図)"]
direction TB
DCONCAT["concat(card_id_vec, static_feature_vec)<br/>+ card_feature_encoder"]:::layer
DV["deck_vec<br/>[B, d_model]"]:::vec
CF["context_fusion + deck_gate<br/>(none/mean/set_transformer で挙動が変わる)"]:::layer
SV2["enriched state_vec<br/>[B, d_model]"]:::vec
DCONCAT --> DV --> CF --> SV2
end
EMB --> SE
EMB --> HM
EMB --> AM
CIDE --> DCONCAT
CFE --> DCONCAT
CIDE --> CRV
CFE --> CRV
EMB --> OCTX
SV --> HF
HSV --> SCRP
SVREF --> CF
subgraph POLICY["Policy head (option ごと)"]
direction TB
CAT["concat(state_vec, action_vec)<br/>broadcast → [B, A, 2·d_model]"]:::layer
PH["policy_head<br/>Linear(2d→d) → ReLU → Linear(d→1)"]:::layer
PL["policy logits<br/>[B, A]"]:::output
CAT --> PH --> PL
end
subgraph VALUE["Value head (state ごと)"]
direction TB
VH["value_head<br/>Linear(d→d) → ReLU → Linear(d→1) → Tanh"]:::layer
VAL["value<br/>[B] ∈ [-1, 1]"]:::output
VH --> VAL
end
SV2 --> CAT
SV2 --> VH
AV --> ACRP
AVREF --> CAT
classDef input fill:#e3f2fd,stroke:#1976d2,color:#000
classDef shared fill:#fffde7,stroke:#f9a825,color:#000
classDef layer fill:#fff3e0,stroke:#f57c00,color:#000
classDef vec fill:#f1f8e9,stroke:#689f38,color:#000
classDef output fill:#e8f5e9,stroke:#388e3c,color:#000
Deck path では、raw card id の identity ベクトルと、CardStaticFeatures 由来の静的特徴ベクトルを
concat してから card_feature_encoder
に通す。つまり「カード番号を覚える branch」と「カードの一般的な性質を覚える branch」を別々に作り、1 枚ごとの card
vector に合成してから deck encoder に渡す。
同じ card vector 化は、deck だけでなく state_objects / history_objects / action_objects
にも使う。これにより、盤面・履歴・合法手に出てくるカードも「raw card
id の identity」と「静的特徴」を同じ方法で扱える。さらに object row には owner / zone / role / slot
/ parent zone / parent slot が入るため、付属エネルギー・tool・進化元・action target の関係を flat
token から推測させずに渡せる。
History context¶
history_context_mode は none | event_transformer を切り替える。event_transformer では、recent
logs を event 単位に分けて history_tokens
として受け取り、各 event の token を平均 pool した後、event 列に position
embedding と TransformerEncoder をかける。最後に state_vec + history_gate · history_delta
の residual gate で盤面 state に合流する。
この branch は、盤面の現在値だけでは区別しにくい「直前に何が起きたか」「どの選択が続いているか」を Policy
/
Value が直接扱えるようにするためのもの。既存 checkpoint からの warm-start を壊さないよう、history_gate
の初期値は 0 である。
旧 v12 以降の self-play JSONL には search.history_tokens が保存される。train.py の
history_context_mode=auto は、records に history_tokens があれば event_transformer、無ければ
none を選ぶ。現在の unified model でも history token / object を shared encoder に入れる。
Structured object context¶
legacy path の structured_context_mode は none | objects | auto を切り替える。auto
では、self-play JSONL に search.state_objects / search.action_objects / search.history_objects
があれば objects を有効化する。現在の unified model では object
token が主入力なので、同じ情報を shared state sequence 側で扱う。
この branch は、flat token 列とは別に「その局面・履歴・合法手に含まれる object」を集め、deck
context と同じ card_id_embedding + card_feature_embedding -> card_feature_encoder で card
vector 化し、さらに owner / zone / role / parent などの context embedding を合わせる。
state_objects- active / bench / attached energy / attached tool / pre-evolution / visible hand / discard / stadium などを object row として持つ。
- attached energy や tool は
parent_zone/parent_slotで、どの Pokemon に付いているかを明示する。 - Set Transformer で順序なし集合として
state_object_vecにし、state_vec + state_object_gate · deltaで合流する。 history_objects- recent logs の
cardId/fromArea/toArea/ event type を event ごとに保持する。 - 各 event の object vector を平均 pool し、history event vector に
history_object_gateで合流する。 action_objects- CABT option の
cardId、action type、target area / index、select context を option ごとに保持する。 - option の action vector に
action_object_gateで合流する。
すべての gate は --init-structured-gate
で初期値を決める。標準は 0.05 で、旧 checkpoint から warm-start した直後も大きく壊さず、同時に structured
object branch に最初から勾配を流す。完全互換を優先する smoke では 0.0 に下げられる。
古い state_card_ids / history_card_ids / action_card_ids と card_ref_context_mode
は後方互換として残している。ただし structured objects がある場合、resolve_model_config は
card_ref_context_mode=none にし、unstructured card-ref branch は使わない。
Deck context の 3 モード¶
deck_context_mode
(src/pca/models/policy_value.py:90-93) で
none / mean / set_transformer を切り替える。実装は
compute_deck_vector / _deck_context / _card_feature_vectors / _set_transformer_deck_vector
を参照。
flowchart TD
DT["deck_tokens<br/>[B, deck_len]"]:::input
MODE{"deck_context_mode"}:::loop
DT --> MODE
MODE -- "none" --> NONE["state_vec をそのまま使う<br/>(deck 情報を混ぜない)"]:::muted
subgraph MEAN_MODE["mode: mean"]
direction TB
ME["Embedding → mean pool"]:::layer
DE["deck_encoder<br/>Linear → ReLU → LayerNorm"]:::layer
DVM["deck_vec"]:::vec
ME --> DE --> DVM
end
MODE -- "mean" --> ME
subgraph SET_MODE["mode: set_transformer"]
direction TB
EM["card_id_embedding<br/>(raw card id を直接 index として学習)"]:::layer
SF["static feature tokens<br/>(CardStaticFeatures から構築した<br/>card_feature_token_table)"]:::input
SFE["card_feature_embedding<br/>→ mean pool"]:::layer
CAT["concat(card_id_vec, feature_vec)"]:::layer
CFE2["card_feature_encoder<br/>Linear(2d→d) → ReLU → LayerNorm"]:::layer
STE["SetTransformerDeckEncoder<br/>TransformerEncoder × num_layers<br/>+ pool_token + MultiheadAttention pooling<br/>+ LayerNorm"]:::layer
DVS["deck_vec"]:::vec
EM --> CAT
SF --> SFE --> CAT
CAT --> CFE2 --> STE --> DVS
end
MODE -- "set_transformer" --> EM
MODE -- "set_transformer" --> SF
DVM --> FUSE
DVS --> FUSE
FUSE["context_fusion(concat(state_vec, deck_vec))<br/>= Linear(2d→d) → ReLU → LayerNorm"]:::layer
FUSE -- "mean: 置き換え" --> OUT1["state_vec ← fusion"]:::vec
FUSE -- "set_transformer: 残差ゲート" --> OUT2["state_vec ← state_vec + deck_gate · fusion<br/>(deck_gate は学習可能スカラー, 初期値 0)"]:::vec
classDef input fill:#e3f2fd,stroke:#1976d2,color:#000
classDef layer fill:#fff3e0,stroke:#f57c00,color:#000
classDef vec fill:#f1f8e9,stroke:#689f38,color:#000
classDef loop fill:#ede7f6,stroke:#5e35b1,color:#000
classDef muted fill:#eeeeee,stroke:#9e9e9e,color:#000
set_transformer モードのポイント:
- raw card id は
card_id_embeddingで直接覚える。これは「101 番のカードそのもの」の identity を学ぶ branch。 CardStaticFeaturesはcard_feature_token_tableに token 化し、card_feature_embeddingで平均 pool する。これは「Pokemon / HP / 攻撃数 / 逃げエネ」などの一般化しやすい特徴を学ぶ branch。- 2 つのベクトルを concat して
card_feature_encoderに通してから Set Transformer に渡す。card id と特徴 token を同じ embedding table に混ぜないため、カード identity と汎化特徴を分離して学習できる。 - Set Transformer 自体は順序非依存。
pool_tokenを 1 つ用意し、MultiheadAttention で deck 集合から pool する形 (SetTransformerDeckEncoder)。 - 学習初期に deck context が暴れて壊れないよう、合流は residual + 学習可能ゲート
(
deck_gate, 初期値 0) で行う。--init-deck-gateでこの初期値を変えられる。meanモードの方は単純な fusion 置き換えで gate なし。
テンソル形状チートシート¶
| 記号 | 値 | 説明 |
|---|---|---|
B |
batch_size | 同時に評価する局面数。 |
A |
action_count | その局面の合法 option 数。局面ごとに可変。 |
H |
history events | recent logs から作る時系列 event 数。 |
state_len |
〜 数百 | state_tokens の長さ (paddingあり)。 |
history_len |
数個〜十数 | 1 history event あたりの token 数。 |
action_len |
数十 | 1 option あたりの token 数。 |
deck_len |
60 | 自分のデッキカード id 列。 |
state_card_count |
可変 | state に含まれる観測可能 card id 数。 |
card_count |
可変 | history event / action option ごとの card id 数。 |
d_model |
例 256 | 共通の埋め込み次元。 |
token_buckets |
例 8192 | shared embedding の vocab サイズ。 |
Card id と静的特徴の共有¶
flat token 経路では、TokenVocab が有効な場合、card id は prefix をまたいで同じ embedding
row に解決される。
token("card", 101)
token("option.cardId", 101)
token("log.cardId", 101)
-> 同じ card_id block の同じ token id
これにより、盤面上のカード、合法手に含まれるカード、ログに出たカードは「同じ card
id」として共有される。zone / option_type / log_type / select_context
などの周辺 token が、そのカードがどの文脈で現れたかを区別する。
旧 structured path では、flat token とは別に state_objects / history_objects / action_objects
を保存し、deck context と同じ専用 card vector 化を通していた。現在の unified
model では、この object 情報が shared encoder の主入力になっている。つまりカード情報は card_id
だけでなく、静的特徴、位置、所属、親子関係、動的状態を合わせて入る。
- flat token 経路
- turn / count / select context / log type などの summary 文脈を token として表現する。
- 後方互換のため card id token も残るが、カード関係の本線は structured object branch。
- object token 経路
- raw card id は
card_id_embeddingで identity を学ぶ。 CardStaticFeaturesはcard_feature_embeddingで汎化特徴を学ぶ。- owner / zone / role / slot / parent zone / parent slot を context embedding として合わせる。
- それを state / history / action それぞれの branch に residual gate で合流する。
このため、「card id とカードの静的特徴が deck だけでしか使われない」状態ではない。deck はデッキリスト全体の集合文脈として使い、state / action / history は今まさに局面に出ているカードとその関係として使う。
入出力¶
state_tokensは、盤面・手札・トラッシュ・サイド枚数・山札枚数・ターン状態・ログ・選択 context などを token 化した列。盤面の位置・所属の文脈はtoken("zone", ...)やtoken("player", ...)などの prefix トークン自体に埋め込んでいる。互換性のため recent logs も当面ここに残している。history_tokensは、recent logs を「古い順の event 列」として別に token 化したもの。各 event にはhistory.positionとhistory.age_from_latestを入れ、History path 側で event TransformerEncoder に通す。history_context_mode=event_transformerのときだけ使う。action_tokensは、CABT option の type / context / target / card id などを token 化した列。state_objectsは、観測できる盤面・手札・トラッシュ等の object row 列。attached energy / tool / pre-evolution は parent 情報を持つ。history_objectsは、recent logs に含まれる object row を event ごとに保持する列。action_objectsは、各合法 option に含まれる source / target object row 列。- policy 出力は「その局面の合法 option 数だけの logits」。
- value 出力は「その局面から見た勝敗期待値 ∈ [-1, 1]」。
トークン化に関する既知の制約:
TokenVocabが有効な training / self-play / evaluation では、既知 prefix は衝突しない決定論的 vocab を使う。TokenVocabが無い bare import / smoke fallback ではTOKEN_BUCKETS = 8192の feature hashing に戻るため、衝突が起きうる。詳細は ../research/2026-06-28-token-collision-report.md、対応計画は ../research/2026-06-28-policy-value-model-refactor.md を参照。
デッキ文脈 (deck_context_mode)¶
deck_context_mode は none | mean | set_transformer を切り替えられる。Set Transformer 版 deck
encoder と card static features は実装済みで、v7 系 checkpoint では実際に使っていた。v10 /
v11 では teacher search 自体の問題とモデル構造差を切り分けるため、一時的に none
で学習を進めた経緯がある。
現在の unified model でも、標準はデッキリスト全体を Set Transformer で deck_vec に圧縮し、state
sequence の deck summary token として使う。旧 legacy path では deck_context_mode=set_transformer
が同じ役割を担っていた。
fusion 流儀が mode で非対称:
meanはstate_vec = context_fusion(...)で完全に置き換える。set_transformerはstate_vec = state_vec + deck_gate · context_fusion(...)の residual + 学習可能ゲート (gate 初期 0)。後者は warm-start 時の安全側 (deck context を学習で徐々に効かせる) として導入された経緯がある。意図せぬ非対称ではない。
拡張ポイント¶
belief_summary は、将来 belief-conditioned policy / value にする際の入力口として legacy
path に残してある。現在の unified path では、同じ shared state representation から integrated belief
heads を出す。ただし、相手 hidden の正解を policy/value 入力として直接入れるわけではない。推論時に使える public
observation だけから belief prior を出し、その prior は ISMCTS の hidden state sampling 側で使う。
カード静的特徴¶
pokemon-tcg-ai-battle/EN_Card_Data.csv から
CardStaticFeatures を作り、card id / カテゴリ / stage / kind / HP
/ 攻撃数 / 逃げエネ などを token 化して使えるようにしている。実装は
src/pca/cabt/card_db.py。この特徴は active hidden
sampling と empty-bench safety pruning における Pokemon 判定にも使われる。
Belief Model と PublicKnowledgeTracker¶
実装:
- src/pca/models/policy_value_unified/heads.py
(
IntegratedBeliefHeads) - src/pca/models/belief.py (
BeliefNet, standalone 互換用) - src/pca/search/belief.py
- src/pca/training/belief_train.py
Belief は、相手の hidden hand / deck /
prize を「1 つに固定する」ためのものではなく、ISMCTS が determinization のたびに hidden
state をサンプリングするための soft prior
である。現在の通常経路では、UnifiedTokenPolicyValueNet の integrated belief
heads がこの prior を出す。standalone BeliefNet
は、旧 checkpoint 互換、比較実験、独立更新が必要な場合のために残している。
確定情報は別に PublicKnowledgeTracker が hard constraint として扱う。つまり「NN が推測した分布」と「公開ログから確定できる事実」を分け、サンプリング時に必ず hard constraint を満たす。
flowchart LR
PST["public observation<br/>state/object/history tokens"]:::input --> TE["shared Transformer<br/>(unified model)"]:::layer --> SM["belief MLP"]:::layer
subgraph DIST["hidden 分布 heads"]
direction TB
OHL["opponent_hand_logits"]:::out_dist
ODL["opponent_deck_logits"]:::out_dist
OPL["opponent_prize_logits"]:::out_dist
end
subgraph AUX["補助 heads"]
direction TB
NTL["next_threat_logits"]:::out_aux
KT["knockout_threat"]:::out_aux
end
SM --> OHL
SM --> ODL
SM --> OPL
SM --> NTL
SM --> KT
classDef input fill:#e3f2fd,stroke:#1976d2,color:#000
classDef layer fill:#fff3e0,stroke:#f57c00,color:#000
classDef out_dist fill:#e8f5e9,stroke:#388e3c,color:#000
classDef out_aux fill:#fff9c4,stroke:#f9a825,color:#000
Belief prior の役割¶
- 相手の hidden hand / deck / prize のカード分布を推定する。
- ISMCTS の各 determinization で hidden state をサンプリングする際、PublicKnowledgeTracker の制約に従いつつ、残りを BeliefPrior で重み付きサンプリングする。
- 提出時の対戦相手の手札を覗くための仕組みではない。あくまで「自分の側から見えている公開情報」だけから推定する。
推論時の選択は --belief-source で切り替える。
--belief-source |
挙動 |
|---|---|
auto |
--belief-checkpoint があれば standalone BeliefNet、無ければ policy checkpoint の integrated belief heads を使う。 |
separate |
standalone BeliefNet のみ。--belief-checkpoint 必須。 |
policy |
policy checkpoint の integrated belief heads のみ。 |
学習¶
self-play 収集時に --full-observation-targets を付けると、CABT の full
observation から学習用にだけ相手 hand / deck /
prize のラベルが抽出され、JSONL に同梱される。推論時は public observation だけを入力にする。
現在の unified training では、policy/value loss と同じ checkpoint の中で integrated belief
loss も合成できる。standalone pca belief-train は、別 belief
checkpoint を比較したい場合や旧経路を使いたい場合に使う。
PublicKnowledgeTracker¶
PublicKnowledgeTracker は、公開ログから「相手の hidden 情報のうち、ここまで確実に決まっているもの」を hard constraint として保持する。たとえば、相手が公開サーチで手札に加えたカード、公開された山札 / サイド移動、公開ゾーンに出たカードなどが該当する。サンプリングではまずこの hard constraint を全部満たし、残った枠を BeliefPrior の確率で埋めていく。Belief が外しても、公開情報と矛盾するサンプルは作られない、という保証を担う。
ISMCTS¶
実装:
ISMCTS は、不完全情報下で「観測者から見て同じに見える状態 (information set)」をひとつのノードとして共有しながら探索する MCTS の派生である。本手法では、AlphaGo Zero と同じく rollout を使わず、leaf は NN value と軽い progress value で評価する Neural ISMCTS として扱う。
flowchart TD
START(["root decision"]):::io --> KEY["encode public<br/>information key"]:::setup
KEY --> ROOT["create root node<br/>with NN prior"]:::setup
ROOT --> DLOOP{{"for d in 1..D<br/>(determinizations)"}}:::loop
subgraph OUTER["■ outer loop: hidden state sampling"]
DLOOP -- next d --> SAMPLE["sample hidden state<br/>(belief + public constraints)"]:::sampling
SAMPLE --> SLOOP{{"for s in 1..S<br/>(simulations)"}}:::loop
subgraph INNER["■ inner loop: tree search"]
SLOOP -- next s --> SB["search_begin(obs, hidden)"]:::search
SB --> TRAV["traverse tree with PUCT"]:::search
TRAV --> STOP["stop at leaf /<br/>terminal / max depth"]:::search
STOP --> EVAL["evaluate leaf:<br/>NN value + progress"]:::search
EVAL --> BP["backpropagate"]:::search
BP --> SLOOP
end
SLOOP -- done --> DLOOP
end
DLOOP -- done --> SEL["select by<br/>visit distribution"]:::output
SEL --> END(["action"]):::io
classDef io fill:#e3f2fd,stroke:#1976d2,color:#000
classDef setup fill:#fff3e0,stroke:#f57c00,color:#000
classDef loop fill:#ede7f6,stroke:#5e35b1,color:#000
classDef sampling fill:#fce4ec,stroke:#c2185b,color:#000
classDef search fill:#f3e5f5,stroke:#7b1fa2,color:#000
classDef output fill:#e8f5e9,stroke:#388e3c,color:#000
外側のループで hidden state を D 回サンプルし、内側で各 hidden state に対して S 回 simulation を回す。tree は public information key で共有しているので、複数の hidden state でも同じノードに統計が積み上がる。
基本パラメータ¶
| フラグ | 役割 |
|---|---|
--ismcts-determinizations |
hidden state sample 数 D |
--ismcts-simulations-per-determinization |
各 hidden state での simulation 数 S |
--search-rollout-depth |
互換名で残してあるが、実質は ISMCTS の max tree depth |
--dirichlet-alpha, --dirichlet-epsilon |
self-play 用 root noise |
--visit-temperature |
self-play の visit distribution 温度 |
評価や提出では基本的に noise off / temperature 0 寄りの設定で動かす。
現在の探索改善¶
過去の v12 実験で「teacher search が浅いところで止まる」「重要 action が pruning で消える」という観察があり、現在の ISMCTS では以下の挙動を標準的に使う。
- root と non-root の candidate
cap 分離。root では広く張り、深部では絞る。
--ismcts-root-max-candidate-actions/--ismcts-nonroot-max-candidate-actions。 - progressive widening。ノードの訪問数に応じて候補数を
base + scale * sqrt(node_visits)で増やす。 - 重要 action の pruning 例外。attack action と choice / forced 系 action は cap や widening の対象から外し、必ず候補に含める。
- 空ベンチ safety。空ベンチ時にベンチへ出せる Pokemon 候補も pruning で落とさない。
- 探索ログの拡充。depth histogram、turn_advance_rate、attack_reached_rate、prize_reached_rate、candidate kept / total、selected action category などをログに出し、teacher search がどこで止まっているかを後から追えるようにしている。
Leaf / Progress Value¶
leaf 評価は単純な合成で行う。
leaf_value = model_value_weight * NN value
+ optional aux prize value
+ prize race progress value
+ immediate action deltas
第 1 項が NN value、第 2 項が v13 unified model の aux prize
heads から得る補助評価、第 3 項がサイドレースの進み具合、第 4 項がその手 (action
delta) による即時の変化分である。aux prize value は v13_aux_prize_race
profile のときだけ有効で、own_prize_completion - opp_prize_completion と
E[this_turn_prize_gain] / 6 を小さい重みで足す。第 2〜第 4 項は「NN
value が弱い段階で、明らかに悪い停滞や種切れ負けに探索が流れていくのを抑える」ための shaping であり、勝ち筋を手書きで決めにいくものではない。
v13_aux_prize_race プロファイルの方針¶
サイド取得や負け筋の重み付けは、現在は v13_aux_prize_race プロファイルを中心に使う。旧
v12_prize_race の方針を引き継ぎつつ、unified model の aux prize heads を leaf
tie-break として使う。考え方は次の通り。
- deck-out 勝ちは強い勝ちにしない。偶発的に山切れで勝つ展開を「上手い勝ち方」と学んでほしくない。
- deck-out 負けは強く悪い。これは確実に避けたいので大きめのペナルティを与える。
- サイドレースを重視する。サイド差・サイド取得 delta・相手 active のダメージ進行を重く見る。
- attack-ready は弱めの補助。攻撃準備が整っているだけで強く加点はしない。
- no-progress END は減点。何も進んでいないターン終了を選びにくくする。
- 空ベンチに関する安全策。空ベンチで END すること・空ベンチで倒されるリスクを減点し、空ベンチ時に Pokemon を出す手には小さい safety bonus を与える。
- 手書きの役割評価はしない。"このカードはサポート役" のような決め打ちは入れない。
Self-Play データ収集¶
実装:
self-play では belief-guided ISMCTS で実際に対戦させ、決定点ごとに 1
record の JSONL を吐く。各 record は、探索結果 (SearchTrainingTarget)、必要なら integrated belief
heads / standalone
BeliefNet 用ラベル (BeliefTrainingTarget)、ゲーム全体のメタ情報 (meta) からなる。
SearchTrainingTarget:
observation_tokens
action_tokens
search_policy
selected_action
final_result
turn_value optional
oracle_policy optional
min_count
max_count
belief_summary optional
BeliefTrainingTarget optional:
observation_tokens
opponent_hand_card_ids
opponent_deck_card_ids
opponent_prize_card_ids
next_threat_card_ids
knockout_threat
AuxPrizeTrainingTarget optional:
this_turn_prize_gain
own_prize_completion
opp_prize_completion
current_own_remaining_prize
current_opp_remaining_prize
meta:
game_id
step_index
your_index
result
result_reason
game_steps
game_attack_counts
game_attack_ready_counts
game_prizes_taken
game_final_prize_counts
game_no_progress_pass_counts
own_deck_card_ids
ここで重要なのは、実際の action 選択は常に public observation + belief-guided ISMCTS で行う
ことである。BeliefTrainingTarget や oracle_policy は学習用ラベルにすぎず、決定経路には入らない。
--full-observation-targetsは、CABT の full observation から belief 用 hidden labels を抽出するためのフラグ。--oracle-policy-targetは、完全情報 search で policy label だけ作る実験機能。
長時間 self-play での運用¶
長時間 run では pca selfplay-train を使う。これは configs/default.yaml の selfplay-train
定義から scripts/run_selfplay_train.sh を呼び、self-play chunk 収集、JSONL
merge、Policy/Value 学習、latest alias 更新までをまとめて行う。
- self-play JSONL は ゲーム単位で incremental に保存 する。
- chunk 単位で JSONL と summary CSV を作り、途中停止しても完了済み chunk を回収できるようにする。
--resumeで完了済み chunk と checkpoint を skip する。--total-games/--games-per-cycleで「N games ごとに学習して次 cycle は直前 best checkpoint から開始」を実行する。- 学習完了後に
checkpoints/policy_value_latest_best.ptなどの latest alias を更新する。 - self-play logs に
reason=prize|deck_out|pokemon_out|timeout、サイド取得、攻撃回数、attack-ready、探索 depth、NN cache hit、candidate pruning などを出し、後から teacher search の質を見られるようにする。
現在の代表的な self-play config:
configs/v13/selfplay-v13-ismcts-selfplay.yaml- smoke 用:
configs/v13/selfplay-unified-smoke.yaml
実行例:
pca selfplay-train \
--run-name ismcts-selfplay-1000g \
--chunks 10 \
--games-per-chunk 100 \
--workers 8 \
--train-device mps \
--resume
反復学習:
pca selfplay-train \
--checkpoint checkpoints/policy_value_latest_best.pt \
--run-name ismcts-iterative-10k \
--total-games 10000 \
--games-per-cycle 1000 \
--chunks 10 \
--workers 8 \
--train-device mps \
--resume
Policy / Value Training¶
実装:
学習の中心は AlphaZero 系と同じ policy/value loss だが、現在の unified model では aux prize loss と integrated belief loss も同じ training loop で合成できる。
policy loss = CE(search_policy, model_policy)
value loss = MSE(value_target, model_value)
turn value loss = optional auxiliary value
aux prize loss = optional CE/BCE from AuxPrizeTrainingTarget
integrated belief loss = optional multi-label BCE from BeliefTrainingTarget
このうえで、ポケカ固有の事情によるノイズを抑えるために、record 単位の weighting を入れる。方針は大きく 3 つに分かれる。
1. 何を target にするか¶
- policy target は
search_policy(ISMCTS の visit distribution)。 - value target は最終勝敗を基本にする。
--turn-value-weight 0.1程度でturn_valueを補助 target として少量混ぜる。--player-indexを指定せず、p0 / p1 両方の record を学習する。
2. deck-out 系の取り扱い¶
- deck-out 勝ちは value 上で強い勝ちとして扱わない。
- deck-out 負けは value 上で強く悪くする。
- passive な deck-out win は policy / value 学習から除外可能。
3. 弱い teacher record の扱い¶
teacher search 自体が弱い局面 (サイドが取れていない / 攻撃が出ていない / no-progress END が混じる、など) の record は、value は残しつつ policy imitation だけ弱める、という扱いにする。
- 上のような record では value 学習はそのまま使うが、policy 側の weight を下げる。
reason=3の種切れ負けも、value は負けとして残しつつ policy imitation を low-progress 扱いで弱める。- unfinished / long game も policy weight を下げる。
これは「強い teacher としては不適だが、value 推定の負例としてはまだ有効」な record を捨てずに使う発想である。
実行例¶
pca train \
--input data/selfplay/example.jsonl \
--init-checkpoint checkpoints/policy_value_latest_best.pt \
--output checkpoints/policy_value_example_final.pt \
--best-output checkpoints/policy_value_example_best.pt \
--device mps
CLI flag は YAML より優先されるので、入力 JSONL や出力 checkpoint だけを上書きする形で使える。
Belief Training¶
現在の本線では、standalone BeliefNet ではなく UnifiedTokenPolicyValueNet の integrated belief
heads を使う。BeliefTrainingTarget は unified training の integrated belief
loss に入り、1 つの policy/value checkpoint から opponent hand / deck / prize prior も出せる。
従来の standalone BeliefNet と belief_train.py は互換・比較・段階的更新のために残す。Policy /
Value とは別の更新リズムで belief だけ比較したい場合に使う。
推論時の選択は --belief-source で切り替える。
--belief-source |
挙動 |
|---|---|
auto |
--belief-checkpoint があれば standalone BeliefNet、無ければ policy checkpoint の integrated belief heads を使う。 |
separate |
standalone BeliefNet のみ。--belief-checkpoint 必須。 |
policy |
policy checkpoint の integrated belief heads のみ。 |
統合 belief head は public observation だけを入力にする。--full-observation-targets
で保存した hidden labels は学習 target にだけ使い、推論時には相手手札やサイドの正解を直接見ない。
pca belief-train \
--input data/selfplay/example.jsonl \
--output checkpoints/belief_example_final.pt \
--best-output checkpoints/belief_example_best.pt \
--device mps
評価 (Evaluation)¶
評価は checkpoint そのものを単体で見るのではなく、online ISMCTS 同士の対戦 で行うのを基本にしている。これは、Policy / Value だけでなく ISMCTS や leaf shaping を含めた最終エージェントとしての強さを見たいためであり、checkpoint 単体評価では拾いきれない振る舞い (探索の深さ・攻撃に届くか・サイドが取れるか) があるためでもある。
指標は「勝ったか」だけでなく、「どう勝ったか / どう負けたか」を分解して追う。代表的なものを大別すると次のようになる。
勝ち負けの種別¶
player*_wins,player*_normal_winsdeck_out_finishes,pokemon_out_finishesplayer*_deck_out_losses,player*_pokemon_out_lossesunfinished
特に、deck-out や種切れによる 偶発勝ちを normal win と混ぜない のがポイントである。
戦況の進み方¶
player*_avg_prizes_takenplayer*_avg_attacksplayer*_avg_first_attack_stepplayer*_avg_first_prize_step
探索 / policy の質¶
policy*_attack_reached_ratepolicy*_prize_reached_rate- replay 上の行動品質 (詳細は docs/research/ のレポート)
実行例¶
pca eval \
--checkpoint0 checkpoints/policy_value_latest_best.pt \
--games 100 \
--workers 8 \
--device cpu \
--output data/eval/latest-vs-rule.json \
--csv-output data/eval/latest-vs-rule.csv
既知の課題 (Known Issues)¶
- Teacher search はまだ十分に強くない。特に depth 平均が浅く、攻撃・サイド取得まで届かない局面がある。
- NN forward が self-play の最大ボトルネックになりやすい。現在は local multiprocessing queue による batch inference と cache を優先する。
- unified model は integrated belief heads を持つが、belief prior は hidden state sampling に使うものであり、相手 hidden の正解を policy/value 入力として直接入れない。
- legacy checkpoint は
ActionConditionedPolicyValueNetとしてロードできるが、現在の本線では unified checkpoint を使う。 history_tokens/history_objects/state_objects/action_objectsは unified model の主入力になっている。古い JSONL では欠ける field があるため、最新 training には新しい self-play JSONL を使う。- カード効果文の意味理解、手書きカード役割タグ、強い heuristic reranker は本線に入れていない。
- サーチ / ドロー後に公開情報として確定できるカード追跡は PublicKnowledgeTracker で一部対応済みだが、完全な履歴推論ではない。
今後の計画 (Next Steps)¶
pca selfplay-trainで 1,000 games 単位の収集・学習を反復する。- seed-out / deck-out / unfinished を減らし、normal win とサイド取得を増やす。
- NN forward 回数の削減・cache・local batch inference を続ける。
- aux prize heads と integrated belief heads が leaf value / hidden sampling に効いているかを評価で見る。
- latest checkpoint alias を使い、self-play / eval / submission の開始点を安定させる。
- 履歴情報と object token の品質を上げ、カード効果文や attack text のより強い埋め込みを検討する。
付録 A: ワークフローと実行コマンド¶
現在の運用入口は、root package の pca コマンドである。以前は python -m pca.training.selfplay や
scripts/collect_selfplay_local.sh を直接呼ぶ形が中心だったが、今は最新の default
config、chunk/resume、latest checkpoint alias、unified model の選択を pca 側に集約している。
| コマンド | 役割 |
|---|---|
pca selfplay |
1 回分の self-play JSONL を収集する低レベル入口。 |
pca selfplay-train |
chunked self-play、学習、latest checkpoint 更新をまとめた標準入口。 |
pca train |
unified Policy / Value / Aux / Belief heads を学習する。 |
pca belief-train |
standalone BeliefNet だけを学習する互換・比較用入口。 |
pca eval |
tournament 評価を実行する。 |
pca submission-bundle |
提出・配布用 bundle を作成する。 |
共通 default は configs/default.yaml に置く。個別の YAML は、実験用の差分だけを持つ。CLI
flag は YAML より優先されるため、通常は default を読みつつ
--run-name、--games、--workers、--device、checkpoint だけを上書きする。
全体ループ¶
flowchart LR
SP["pca selfplay-train<br/>chunked self-play"]:::data
TRAIN["pca train<br/>UnifiedTokenPolicyValueNet"]:::train
LATEST["policy_value_latest_best.pt<br/>policy_value_latest_final.pt"]:::ckpt
EVAL["pca eval<br/>online ISMCTS tournament"]:::eval
SUB["pca submission-bundle"]:::deploy
SP -- "JSONL records<br/>Search + Aux + Belief targets" --> TRAIN
TRAIN -- "checkpoint" --> LATEST
LATEST --> EVAL
EVAL -. "次 cycle の開始 checkpoint" .-> SP
LATEST --> SUB
classDef data fill:#e3f2fd,stroke:#1976d2,color:#000
classDef train fill:#fff3e0,stroke:#f57c00,color:#000
classDef ckpt fill:#ede7f6,stroke:#5e35b1,color:#000
classDef eval fill:#f3e5f5,stroke:#7b1fa2,color:#000
classDef deploy fill:#e8f5e9,stroke:#388e3c,color:#000
環境確認¶
uv sync
source .venv/bin/activate
pca --help
pca commands
pca が見えない場合は virtualenv が有効でない可能性が高い。uv run pca ...
でも同じ CLI を実行できる。
Smoke Test¶
小さい件数で self-play から train までを通す。
pca selfplay-train \
--run-name smoke-selfplay-train \
--games 4 \
--workers 2 \
--train-device cpu \
--resume
1,000 Games 単位の Self-Play + Train¶
pca selfplay-train \
--run-name ismcts-selfplay-1000g \
--chunks 10 \
--games-per-chunk 100 \
--workers 8 \
--train-device mps \
--resume
完了時には run 固有の checkpoint に加えて、次の alias が更新される。
checkpoints/policy_value_latest_best.ptcheckpoints/policy_value_latest_final.pt
次回の self-play は policy_value_latest_best.pt
を起点にすれば、直近で最も良い checkpoint から継続できる。
反復 Self-Play + Train¶
--total-games と --games-per-cycle
を使うと、「N 試合ごとに学習し、その checkpoint で次の N 試合を打つ」ループになる。
pca selfplay-train \
--checkpoint checkpoints/policy_value_latest_best.pt \
--run-name ismcts-iterative-10k \
--total-games 10000 \
--games-per-cycle 1000 \
--chunks 10 \
--workers 8 \
--train-device mps \
--resume
途中で止まった場合も、同じ --run-name と --resume を付けて再実行すれば、完了済み chunk /
cycle を再利用する。
個別学習¶
self-play JSONL を指定して unified checkpoint を学習する。
pca train \
--input data/selfplay/example.jsonl \
--init-checkpoint checkpoints/policy_value_latest_best.pt \
--output checkpoints/policy_value_example_final.pt \
--best-output checkpoints/policy_value_example_best.pt \
--device mps
現在の main path では UnifiedTokenPolicyValueNet を使う。standalone BeliefNet
を別 checkpoint として更新したい場合だけ、互換用に pca belief-train を使う。
pca belief-train \
--input data/selfplay/example.jsonl \
--output checkpoints/belief_example_final.pt \
--best-output checkpoints/belief_example_best.pt \
--device mps
評価¶
最新 checkpoint を baseline や rule-based agent と対戦させる。
pca eval \
--checkpoint0 checkpoints/policy_value_latest_best.pt \
--games 100 \
--workers 8 \
--device cpu \
--output data/eval/latest-vs-rule.json \
--csv-output data/eval/latest-vs-rule.csv
評価では player*_wins
だけでなく、*_normal_wins、*_avg_prizes_taken、*_attack_reached_rate、deck-out / seed-out /
unfinished を分けて見る。
提出・配布 Bundle¶
提出時もサーバー化は前提にせず、self-play / eval と同じ checkpoint loader を使う bundle として固める。
pca submission-bundle \
--checkpoint checkpoints/policy_value_latest_best.pt \
--card-data pokemon-tcg-ai-battle/EN_Card_Data.csv \
--deck decks/own/ \
--opponent-deck-dir decks/opponents \
--output submission.tar.gz < your-deck > /deck.csv
低レベル入口¶
デバッグ時だけ、Python module を直接呼ぶ。
PYTHONPATH=src uv run python -m pca.training.selfplay --help
PYTHONPATH=src uv run python -m pca.training.train --help
PYTHONPATH=src uv run python -m pca.evaluation.tournament --help
長時間実行の運用手順は server-training.md にまとめている。
付録 B: 用語集 (Glossary)¶
本文中に出てくる略語・固有名詞・ポケカ固有の言い回しをまとめておく。
探索 (Search)¶
- MCTS (Monte Carlo Tree Search) — 木探索の各ノードで「これまでの評価平均」と「まだ試していない手の余地」を天秤にかけながら、有望な枝に計算リソースを集中させていく探索手法。
- ISMCTS (Information Set MCTS) — 不完全情報ゲーム向けの MCTS 派生。観測者から見て区別できない状態 (information set) を 1 ノードとして共有し、hidden state はサンプリングする。
- PUCT — MCTS で子ノードを選ぶ際の評価式の 1 つ。「現在の平均価値」と「NN prior × √親訪問数 / (子訪問数+1)」を組み合わせる。AlphaGo / AlphaZero で使われたものと同型。
- Information Set — 観測者からは区別できない複数の真状態をまとめた集合。ポケカでは「相手の手札の組み合わせの集合」が典型例。
- Determinization — hidden state を 1 通り具体化したサンプル。本手法では、各 determinization ごとに ISMCTS の simulation を走らせる。
- Strategy fusion — 1 つの hidden state に固定して探索すると、「自分は本当はその hidden を知らないのに知っているかのような最適行動」を学んでしまう問題。ISMCTS はこれを情報集合の共有で緩和する。
- PUCT prior / NN prior — Policy Network が出した各 option の確率値。PUCT が「まだ訪問の少ない手」を選ぶ際の重みになる。
- Rollout — leaf からランダムに最後まで対戦してみて勝敗を leaf value にする MCTS の伝統的な手法。本手法では使わない。
- Leaf value — leaf ノードでの即時評価値。本手法では
NN value + progress value + action deltaの合成。 - Progress value — 勝敗とは別に、その時点までの進行度 (サイド取得・攻撃・盤面整備など) で leaf を補正する shaping 項。
- Progressive widening — ノードの訪問が増えるにつれて考慮する子ノードの数を増やしていく手法。広い候補と深い探索の両立を狙う。
- Dirichlet noise — self-play の root に加える noise で、policy を毎回少しだけ揺らして多様性を作る。AlphaZero と同じ仕掛け。
- Visit distribution / visit temperature — 探索後の各 option の訪問回数を確率分布にしたもの。temperature が高いほど分布が滑らかになり、self-play での探索性が増す。
- Teacher search — self-play 中に走る ISMCTS そのもの。Policy/Value Network 学習時の「教師」になる。
モデル (Model)¶
- Policy/Value Network — 局面ごとに「各合法手の prior 確率」と「局面の勝敗期待値」を同時に出す NN。AlphaGo Zero 由来の構造。
- Action-conditioned policy
— 「固定アクション分類」ではなく、option ごとに state と action を入力して logit を出す構造。現在の unified
model でも合法手ごとの action token / object token を使って動的な
select.optionを扱う。 UnifiedTokenPolicyValueNet— 現在のメインモデル。public observation、履歴、デッキ文脈、合法手を unified token stream として shared Transformer に通し、policy logits、win value、aux prize heads、integrated belief heads を同じ checkpoint から出す。ActionConditionedPolicyValueNet— 旧 checkpoint 互換・比較用の legacy Policy/Value Net。以前の本線だったが、現在の標準 self-play / train / eval / submission では unified checkpoint を使う。- Object token — 盤面・手札・トラッシュ・合法手などに含まれるカード実体を表す token。同じ card id でも、area / owner / slot / dynamic state が違えば別 token になる。
- Static card embedding — card id と CardStaticFeatures / AttackData 由来の静的特徴から作るカード定義ベクトル。試合中に変わらないカード固有情報を表す。
- Integrated belief heads —
UnifiedTokenPolicyValueNet内にある hidden hand / deck / prize 推定 head。現在の通常経路ではこの head が BeliefPrior を出す。 - BeliefNet — 相手の hidden hand / deck / prize の分布を public observation から推定する standalone NN。現在は旧 checkpoint 互換・比較・独立更新用。
- TransformerEncoder — token 列を文脈付きベクトルに変換する Transformer のエンコーダ部。state / action token を集約するのに使う。
- Set Transformer — 集合 (順序を持たないトークン群、ここではデッキリスト) を埋め込むための Transformer 派生。
- Deck context (
deck_context_mode) — Policy/Value Net に「自分のデッキは何か」を入力するための文脈。none | mean | set_transformerを切り替えられる。 - CardStaticFeatures — カード id / カテゴリ / stage / HP / 攻撃数 / 逃げエネ など、ゲーム中に変化しないカード固有特徴。
- Belief embedding — belief prior を Policy/Value 側にも入力する将来拡張のための表現。現在の本線では、hidden の正解や sampled hidden state を policy/value 入力として直接入れない。
不完全情報 (Imperfect Information)¶
- Hidden state / hidden information — 観測者から見えない情報。ポケカでは相手の手札・山札・サイドの中身などが該当。
- Public observation — 観測者に公開されている情報のみで構成された観測。
- Hidden state sampling — hidden を「もっともらしい」分布から具体化すること。本手法では BeliefPrior + PublicKnowledgeTracker から行う。
- BeliefPrior — integrated belief heads または standalone BeliefNet が出す、相手 hidden カードの soft な確率分布。
- PublicKnowledgeTracker — 公開ログを追跡し、「相手の hidden 情報のうち、ここまで確実に決まっているもの」を hard constraint として保持するモジュール。
- Soft prior — 確率分布として柔らかく与える事前情報。サンプリングを偏らせるが排除はしない。
- Hard constraint — 「絶対にこれと矛盾するサンプルは許さない」型の制約。本手法では公開ログから確定する情報に使う。
- Oracle / oracle target — 学習時にだけ使える完全情報。実戦・推論では覗かない。
ポケカ用語¶
- CABT — Pokemon TCG AI Battle で使用される対戦バックエンド (Card Action / Battle
Tree)。各 decision point で動的な合法手
select.optionを返す。 select.option— CABT が「いまこの局面で選べる合法手」を返す形式。固定アクション空間ではないため action-conditioned policy が必要になる。- サイド (Prize) — ポケカでバトル開始時に伏せておくカード。相手のポケモンを気絶させると 1〜2 枚取れ、6 枚取った側が勝つ。
- Normal win — サイドを 6 枚取り切る通常の勝利。後述の deck-out / pokemon-out とは区別する。
- Deck-out — 山札が尽きてドローできずに負ける負け筋。本手法では「狙って取りに行く勝ち方」とは扱わない。
- Pokemon-out / 種切れ — バトル場のポケモンが気絶した後に出せるポケモンがいなくなって負ける負け筋。
- Knockout / 気絶 — ポケモンの HP がゼロになって場から退場すること。
- Attack / Attack-ready —
attackは実際に攻撃する option、attack-readyは「次ターン以降に攻撃できる準備が整っている」状態。 - No-progress END — 何の盤面進行もないままターン終了を選ぶこと。本手法では leaf shaping で減点対象。
- 空ベンチ — ベンチに 1 体もポケモンがいない状態。次に倒されると即敗北のため危険度が高い。
- Holdout deck / Train deck — それぞれ「学習時に見せない評価用デッキ」と「学習に使うデッキ」の区別。decks/opponents/ に整理されている。
学習・運用¶
- AlphaZero / AlphaGo Zero — 自己対戦と MCTS で Policy/Value Network を反復改善する手法。本手法もこの系統。
- MuZero — モデルベース版の AlphaZero 派生。本手法は不完全情報のため別系統だが、neural search の組み立て方として参考。
- Self-play — エージェント同士に対戦させて学習データを作ること。
- Search policy — ISMCTS の visit distribution。Policy Network の学習 target になる。
- Final result / value target — 試合の最終勝敗 (基本的に value head の target)。本手法では deck-out 勝ち / 負けで値を調整する。
- Turn value — 各ターン時点の補助 value target。
--turn-value-weightで少量だけ混ぜる。 SearchTrainingTarget/BeliefTrainingTarget— self-play JSONL が持つ record の dataclass。前者が探索結果、後者が hidden 情報の学習ラベル。--full-observation-targets— self-play 収集時に hidden labels も JSONL に同梱するフラグ。提出時に hidden を見るわけではない。--oracle-policy-target— 完全情報 search で policy label を作る実験フラグ。現在の通常学習では未使用。- NN cache / NN forward — Policy/Value Net の推論呼び出し。self-play 最大のボトルネックなので cache や batch 化が効く。
- Checkpoint
— 学習途中のモデル重みファイル。現在は run 固有名に加えて、
policy_value_latest_best.pt/policy_value_latest_final.ptの alias で最新の開始点を管理する。 - Online ISMCTS 評価 — checkpoint 単体ではなく、ISMCTS + shaping まで含めた最終構成同士で対戦させる評価形態。本手法の主たる評価指標はこの形で取る。