コンテンツにスキップ

Belief-Guided Neural ISMCTS

更新日: 2026-07-05

このドキュメントは、Pokemon TCG AI Battle 向けに開発しているエージェント "Belief-Guided Neural ISMCTS" の設計を、実装方針とあわせて読み物としてまとめたものである。実験結果の時系列や特定 checkpoint の数値比較は docs/research/ に置き、ここでは「現在採用している構成」と「その背後にある考え方」を中心に説明する。

目次

  1. Summary
  2. なぜこの手法か (Background)
  3. 先行研究との関係 (Related Work)
  4. 全体構成 (System Overview)
  5. Policy / Value Model
  6. Belief Model と PublicKnowledgeTracker
  7. ISMCTS
  8. Leaf / Progress Value
  9. Self-Play データ収集
  10. Policy / Value Training
  11. Belief Training
  12. 評価 (Evaluation)
  13. 既知の課題 (Known Issues)
  14. 今後の計画 (Next Steps)
  15. 付録 A: ワークフローと実行コマンド
  16. 付録 B: 用語集 (Glossary)

Summary

エージェントは、CABT から渡される公開 observation と動的な合法手リスト (select.option) を入力に、UnifiedTokenPolicyValueNetBelief prior + PublicKnowledgeTrackerInformation 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 つにまとまる。

  1. 不完全情報下で、相手の hidden hand / deck / prize を推定する。
  2. 動的な合法手リストをそのままスコアリングする。
  3. サイド取得に向かう盤面作りを評価する。
  4. deck-out 負けや種切れ負けといった、ポケカ固有の負け筋を避ける。
  5. 複数デッキで使える policy を学習しつつ、自分のデッキリストに応じた動きも表現する。
  6. self-play で作る teacher search の質を保ち、弱い探索結果を強く模倣してしまわない。

(3)(4) はサイドというリソース固有の難しさ、(5)(6) は学習プロセス側の課題である。NN-only の policy は高速だが、探索なしでは数手先のサイド取得や種切れリスクを見落としやすい。逆に rollout を使うと、弱い playout で誤った leaf value が伝播し、NN の評価まで汚れてしまう。そこで本手法は leaf を rollout せず NN value で評価し、不完全情報側の問題は ISMCTS と Belief Model に任せる、というレイヤ分けを採る。

本手法は、完全情報ゲームの 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 をサンプリングする。

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 も評価指標として追っている。

全体構成 (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

実装:

これは 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_metadatasearch.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/textstatic_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_modenone/mean/set_transformer default は deck_integration=pooled で Set Transformer 圧縮、将来 tokens も比較可能
補助 head policy / value のみ policy / value に加えて aux prize heads と integrated belief heads
ロード model_class 未指定、legacyActionConditionedPolicyValueNet checkpoint/config の model_class=UnifiedTokenPolicyValueNet または CLI --model-class unified
非公開情報 相手手札正解、山札順、サイド正解は入れない 同左

旧 checkpoint は model_class が未指定でも legacy として扱う。新 checkpoint は保存時に top-level model_classmodel_config.model_classUnifiedTokenPolicyValueNet を書くため、学習・self-play・評価・提出 loader は factory 経由で自動的に新旧を振り分ける。

v13 の学習 config では card-data: jsonl を使い、search.card_metadatasearch.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_modenone | 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.pyhistory_context_mode=auto は、records に history_tokens があれば event_transformer、無ければ none を選ぶ。現在の unified model でも history token / object を shared encoder に入れる。

Structured object context

legacy path の structured_context_modenone | 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_idscard_ref_context_mode は後方互換として残している。ただし structured objects がある場合、resolve_model_configcard_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。
  • CardStaticFeaturescard_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 だけでなく、静的特徴、位置、所属、親子関係、動的状態を合わせて入る。

  1. flat token 経路
  2. turn / count / select context / log type などの summary 文脈を token として表現する。
  3. 後方互換のため card id token も残るが、カード関係の本線は structured object branch。
  4. object token 経路
  5. raw card id は card_id_embedding で identity を学ぶ。
  6. CardStaticFeaturescard_feature_embedding で汎化特徴を学ぶ。
  7. owner / zone / role / slot / parent zone / parent slot を context embedding として合わせる。
  8. それを 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.positionhistory.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_modenone | 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 で非対称: meanstate_vec = context_fusion(...) で完全に置き換える。set_transformerstate_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

実装:

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

実装: src/pca/search/mcts.py

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_completionE[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 で行う ことである。BeliefTrainingTargetoracle_policy は学習用ラベルにすぎず、決定経路には入らない。

  • --full-observation-targets は、CABT の full observation から belief 用 hidden labels を抽出するためのフラグ。
  • --oracle-policy-target は、完全情報 search で policy label だけ作る実験機能。

長時間 self-play での運用

長時間 run では pca selfplay-train を使う。これは configs/default.yamlselfplay-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 BeliefNetbelief_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_wins
  • deck_out_finishes, pokemon_out_finishes
  • player*_deck_out_losses, player*_pokemon_out_losses
  • unfinished

特に、deck-out や種切れによる 偶発勝ちを normal win と混ぜない のがポイントである。

戦況の進み方

  • player*_avg_prizes_taken
  • player*_avg_attacks
  • player*_avg_first_attack_step
  • player*_avg_first_prize_step

探索 / policy の質

  • policy*_attack_reached_rate
  • policy*_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)

  1. pca selfplay-train で 1,000 games 単位の収集・学習を反復する。
  2. seed-out / deck-out / unfinished を減らし、normal win とサイド取得を増やす。
  3. NN forward 回数の削減・cache・local batch inference を続ける。
  4. aux prize heads と integrated belief heads が leaf value / hidden sampling に効いているかを評価で見る。
  5. latest checkpoint alias を使い、self-play / eval / submission の開始点を安定させる。
  6. 履歴情報と object token の品質を上げ、カード効果文や attack text のより強い埋め込みを検討する。

付録 A: ワークフローと実行コマンド

現在の運用入口は、root package の pca コマンドである。以前は python -m pca.training.selfplayscripts/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.pt
  • checkpoints/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)

本文中に出てくる略語・固有名詞・ポケカ固有の言い回しをまとめておく。

  • 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 headsUnifiedTokenPolicyValueNet 内にある 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-readyattack は実際に攻撃する 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 まで含めた最終構成同士で対戦させる評価形態。本手法の主たる評価指標はこの形で取る。