コンテンツにスキップ

調査メモ: ISMCTS蒸留の結果

更新日: 2026-06-28

Note: この文書は v4/v5/v6 周辺の実験履歴である。現在の本線は ../architecture/current-method.md の v12 方針を優先する。特に、checkpoint-only 蒸留を提出本命にするのではなく、Policy/Value Net と online ISMCTS を組み合わせる。

目的

Belief-Guided Neural ISMCTS の初期実装について、現在までの実験結果を整理し、元の提案手法をどのように修正すべきかを記録する。

特に、次の観察が重要だった。

Online ISMCTS is strong.
Distilled ISMCTS checkpoint is weak.

つまり、ISMCTS そのものが弱いのではなく、探索結果を Policy/Value model に蒸留する設計がまだ不十分である。

実装済みの要素

現在までに実装済みの主な要素:

  • CABT observation / legal action encoder
  • action-conditioned Policy/Value model
  • Belief model
  • belief prior による hidden state sampling
  • determinized search
  • single-tree ISMCTS
  • ISMCTS root visit distribution の search_policy 保存
  • self-play JSONL 収集
  • Docker 上での CABT self-play / evaluation
  • 複数デッキ pool
  • train / holdout deck split
  • --player-index による p0 / p1 フィルタ学習
  • --init-checkpoint による warm start fine-tuning
  • own_deck_card_ids / deck context 入力
  • 古い JSONL では use_deck_context=False に自動切替

主要な実験結果

v4チェックポイント

policy_value_v4_best.pt は、v3 に対して改善した。

代表結果:

{
  "games": 66,
  "player0_wins": 37,
  "player1_wins": 18,
  "unfinished": 11
}

holdout でも v3 に勝ち越しており、現時点の比較基準として v4 を使う。

オンラインISMCTS

online ISMCTS(v4) を checkpoint-only v4 と比較した結果:

{
  "avg_finished_steps": 56.93333333333333,
  "avg_steps": 56.93333333333333,
  "avg_unfinished_steps": 0.0,
  "draws": 0,
  "games": 15,
  "player0_wins": 14,
  "player1_wins": 1,
  "policy0_avg_seconds": 0.10250765617772196,
  "policy1_avg_seconds": 0.00678636884250172,
  "unfinished": 0
}

解釈:

  • online ISMCTS は v4 checkpoint-only より明確に強い。
  • 1手あたりの推論時間は約 0.10 秒で、checkpoint-only の約 15 倍。
  • 提出時の time budget 内で制御できるなら、本命として有望。

root Q ではなく root visit distribution で実戦 action を選ぶよう修正した後、holdout 5 decks に対して 10 games/deck の評価を行った。

Belief on:

{
  "avg_finished_steps": 89.12244897959184,
  "avg_steps": 91.34,
  "avg_unfinished_steps": 200.0,
  "draws": 0,
  "games": 50,
  "player0_wins": 31,
  "player1_wins": 18,
  "policy0_avg_seconds": 0.08618834630450507,
  "policy1_avg_seconds": 0.007461924810810076,
  "unfinished": 1
}

Belief off:

{
  "avg_finished_steps": 61.86,
  "avg_steps": 61.86,
  "avg_unfinished_steps": 0.0,
  "draws": 0,
  "games": 50,
  "player0_wins": 34,
  "player1_wins": 16,
  "policy0_avg_seconds": 0.06095710751981967,
  "policy1_avg_seconds": 0.006085639763570958,
  "unfinished": 0
}

解釈:

  • visit selection 修正後も online ISMCTS は v4 checkpoint-only に勝ち越す。
  • 現時点では belief prior ありより belief prior なしの方が勝率・速度ともに良い。
  • belief model は hidden state sampling に有効な情報を足せておらず、むしろノイズになっている可能性がある。
  • ただし 2 回目の評価は出力パスが belief-on のままだったため、JSON/CSV は belief off 結果で上書きされている。今後は belief on/off の出力ファイル名を分ける。

deck context の後方互換修正後に、50 games で再評価した。

Checkpoint-only v4 vs checkpoint-only v4:

{
  "avg_finished_steps": 97.96,
  "avg_steps": 97.96,
  "avg_unfinished_steps": 0.0,
  "draws": 0,
  "games": 50,
  "player0_wins": 0,
  "player1_wins": 50,
  "policy0_avg_seconds": 0.005896524545983639,
  "policy1_avg_seconds": 0.005766353330779205,
  "unfinished": 0
}

この評価は同じ checkpoint 同士だが、player0 は Mega Abomasnow 固定、player1 は holdout decks のため、純粋なミラーではなく「Mega Abomasnow checkpoint-only の holdout matchup baseline」と解釈する。

Online ISMCTS(v4, belief off) vs checkpoint-only v4:

{
  "avg_finished_steps": 76.94,
  "avg_steps": 76.94,
  "avg_unfinished_steps": 0.0,
  "draws": 0,
  "games": 50,
  "player0_wins": 43,
  "player1_wins": 7,
  "policy0_avg_seconds": 0.056226882939112856,
  "policy1_avg_seconds": 0.006321686258706605,
  "unfinished": 0
}

デッキ別:

garchomp:       6-4
mega-sharpedo: 10-0
slowking:       9-1
terastal-box:   9-1
zoroark:        9-1

解釈:

  • Mega Abomasnow checkpoint-only は holdout decks に 0-50 と大きく負ける。
  • しかし同じ v4 prior を使う online ISMCTS は 43-7 と大きく勝ち越す。
  • これは online search がデッキ相性/方策弱点をかなり補えていることを示す。
  • 現時点の精度向上の主戦場は、蒸留 checkpoint ではなく online ISMCTS の探索品質・速度・安定性である。

ISMCTS simulation / depth の比較も行った。

simulations=16, depth=1:

{
  "avg_finished_steps": 64.6,
  "avg_steps": 64.6,
  "avg_unfinished_steps": 0.0,
  "draws": 0,
  "games": 50,
  "player0_wins": 45,
  "player1_wins": 5,
  "policy0_avg_seconds": 0.11109392767108138,
  "policy1_avg_seconds": 0.006843767677203257,
  "unfinished": 0
}

デッキ別:

garchomp:       10-0
mega-sharpedo:   9-1
slowking:        9-1
terastal-box:    9-1
zoroark:         8-2

simulations=16, depth=2:

{
  "avg_finished_steps": 45.84,
  "avg_steps": 45.84,
  "avg_unfinished_steps": 0.0,
  "draws": 0,
  "games": 50,
  "player0_wins": 45,
  "player1_wins": 5,
  "policy0_avg_seconds": 0.16408650294318164,
  "policy1_avg_seconds": 0.005867193118003096,
  "unfinished": 0
}

デッキ別:

garchomp:        9-1
mega-sharpedo:  10-0
slowking:        8-2
terastal-box:    8-2
zoroark:        10-0

解釈:

  • sim16 depth1sim16 depth2 はどちらも 45-5。
  • depth2 は平均手数が短く、勝ち方が変わっている可能性はある。
  • ただし policy0_avg_seconds0.111s -> 0.164s と約 1.48 倍に増える。
  • 現時点の既定候補は belief off, simulations=16, depth=1。depth2 は追加比較対象として保持する。

simulations=32, depth=1:

{
  "avg_finished_steps": 54.76,
  "avg_steps": 54.76,
  "avg_unfinished_steps": 0.0,
  "draws": 0,
  "games": 50,
  "player0_wins": 42,
  "player1_wins": 8,
  "policy0_avg_seconds": 0.2173536580426543,
  "policy1_avg_seconds": 0.006767519065414533,
  "unfinished": 0
}

デッキ別:

garchomp:        9-1
mega-sharpedo:   8-2
slowking:        7-3
terastal-box:    8-2
zoroark:        10-0

解釈:

  • sim32 depth142-8 で、sim16 depth145-5 より悪い。
  • 1手あたり時間も 0.111s -> 0.217s とほぼ 2 倍。
  • simulation 数を増やせば必ず強くなるわけではない。
  • 現時点では sim16 depth1 が速度と勝率の最良バランス。

深い探索の smoke test も行った。各 holdout deck 2 games、合計 10 games のため、勝率は参考値として扱う。

simulations=8, depth=8:

{
  "avg_finished_steps": 52.8,
  "avg_steps": 52.8,
  "avg_unfinished_steps": 0.0,
  "draws": 0,
  "games": 10,
  "player0_wins": 9,
  "player1_wins": 1,
  "policy0_avg_seconds": 0.0953256295410355,
  "policy1_avg_seconds": 0.006078884390144645,
  "unfinished": 0
}

デッキ別:

garchomp:        2-0
mega-sharpedo:   2-0
slowking:        2-0
terastal-box:    1-1
zoroark:         2-0

simulations=8, depth=12:

{
  "avg_finished_steps": 77.8,
  "avg_steps": 77.8,
  "avg_unfinished_steps": 0.0,
  "draws": 0,
  "games": 10,
  "player0_wins": 7,
  "player1_wins": 3,
  "policy0_avg_seconds": 0.13088816634686437,
  "policy1_avg_seconds": 0.008193155309504806,
  "unfinished": 0
}

デッキ別:

garchomp:        1-1
mega-sharpedo:   2-0
slowking:        2-0
terastal-box:    1-1
zoroark:         1-1

解釈:

  • sim8 depth8 は smoke では 9-10.095s/action と有望。
  • sim8 depth127-30.131s/action で、深くしすぎると悪化する可能性がある。
  • 次に確認すべき本評価候補は sim8 depth8

ISMCTS蒸留: 追加学習なし・先攻

ISMCTS で集めた self-play records を使い、p0 のみを学習した checkpoint は弱かった。

代表結果:

{
  "games": 15,
  "player0_wins": 0,
  "player1_wins": 15,
  "unfinished": 0
}

初期段階では、online ISMCTS の強さが checkpoint policy に移らなかった。

Visit target 修正後

ISMCTS の root visit distribution を PolicyDecision.target_policy として保存し、search_policy に使うよう修正した。

ただし、古い JSONL には own_deck_card_ids が無く、deck context が未学習のまま評価時だけ使われる問題があった。この問題は、古い JSONL では use_deck_context=False にすることで修正した。

修正後の結果:

{
  "avg_finished_steps": 95.66666666666667,
  "avg_steps": 95.66666666666667,
  "avg_unfinished_steps": 0.0,
  "draws": 0,
  "games": 15,
  "player0_wins": 2,
  "player1_wins": 13,
  "policy0_avg_seconds": 0.006177727177448678,
  "policy1_avg_seconds": 0.006265385281939371,
  "unfinished": 0
}

解釈:

  • deck context の誤使用バグは外せた。
  • それでも checkpoint policy としては v4 に大きく負ける。
  • ISMCTS visit target だけで小さな policy に蒸留する設計はまだ不安定。

v4から開始した追加学習

v4 checkpoint から warm start し、ISMCTS visit data で fine-tune した。

結果:

{
  "avg_finished_steps": 109.33333333333333,
  "avg_steps": 109.33333333333333,
  "avg_unfinished_steps": 0.0,
  "draws": 0,
  "games": 15,
  "player0_wins": 3,
  "player1_wins": 12,
  "policy0_avg_seconds": 0.006612762360284585,
  "policy1_avg_seconds": 0.006174431762816142,
  "unfinished": 0
}

デッキ別:

garchomp:       0-3
mega-sharpedo:  3-0
slowking:       0-3
terastal-box:   0-3
zoroark:        0-3

解釈:

  • warm start により 2-13 から 3-12 へわずかに改善。
  • しかし v4 の汎化性能を上回れていない。
  • 少量の ISMCTS data で v4 を fine-tune すると、特定相手には良くなるが、全体性能を壊す可能性がある。

turn_value を value target に混ぜる実験も行った。

{
  "avg_finished_steps": 98.1,
  "avg_steps": 98.1,
  "avg_unfinished_steps": 0.0,
  "draws": 0,
  "games": 50,
  "player0_wins": 0,
  "player1_wins": 50,
  "policy0_avg_seconds": 0.006113164606258061,
  "policy1_avg_seconds": 0.005991529428377721,
  "unfinished": 0
}

解釈:

  • turn_value_weight=0.5 の p0 fine-tune は改善しなかった。
  • checkpoint-only baseline と同じく 0-50 で、online ISMCTS の強さは蒸留できていない。
  • turn_value 自体が無意味とは限らないが、現在の少量 p0 data / one-shot fine-tune では効果が出ていない。

データ観察

使用した ISMCTS self-play data:

data/selfplay/ismcts-visits-fixed-v4-abomasnow-50.jsonl

概要:

records: 4431
p0 records: 2081
p1 records: 2350
finished games: 49/50

p0 records の分布:

wins:       1116
losses:      845
draw/timeout:120
actions mean: 19.13
max policy prob mean: 0.784
max policy prob median: 1.0
one-hot-like targets: 1255 / 2081

重要な観察:

  • p0 data は約 2000 records と少ない。
  • 約 60% の target がほぼ one-hot。
  • simulations=8 の visit distribution は粗く、教師信号として尖りすぎている。
  • 自分側デッキは Mega Abomasnow 固定で、分布が狭い。

なぜ ISMCTS 蒸留が負けるのか

現在の仮説:

  1. 教師は v4 + online ISMCTS だが、生徒は 1 回の forward だけで判断する。
  2. ISMCTS の判断は hidden state sampling と先読み込みなので、単純な policy head に圧縮しづらい。
  3. simulations=8 では visit target が粗く、偶然の影響を含みやすい。
  4. target が one-hot 寄りで、v4 の柔らかい汎化能力を壊しやすい。
  5. p0 records が少なく、train opponent に偏っている。
  6. value target は最終勝敗で、ISMCTS の root value とはズレがある。
  7. 蒸留 checkpoint は探索なしで評価されるため、online ISMCTS の強さをそのまま再現できない。

結論:

ISMCTS is not the failure point.
Naive ISMCTS distillation is the failure point.

修正後の提案方針

元の提案である Belief-Guided Neural ISMCTS は維持する。ただし、主役を「ISMCTS 蒸留 checkpoint」ではなく「online Belief-Guided ISMCTS」に置く。

修正版:

Belief-Guided Online ISMCTS with Iteratively Refined Neural Priors

役割分担:

  • Policy/Value model
  • ISMCTS の action prior
  • rollout / leaf value
  • 時間切れ時の fallback policy
  • Belief model
  • opponent hand / deck / prize の prior
  • hidden state sampling の重み
  • Online ISMCTS
  • 提出時の主意思決定器
  • 終盤や重要局面で厚く使う
  • ISMCTS distillation
  • one-shot の単体 checkpoint 化ではなく、iterative self-play の中で prior を改善するために使う
  • 通常 self-play data と混ぜて使う
  • target smoothing を入れる

補助蒸留には 2 種類の用途があるため、混同しない。

Fallback distillation:
  時間切れ時に 1 forward で打つための checkpoint。
  現状は v4_best で十分。

Prior refinement:
  ISMCTS の中で使う policy/value prior を改善するための蒸留。
  こちらが本来の AlphaZero-style iterative loop の目的。

次に試すべき方針

優先度順:

  1. P0: Online ISMCTS の提出時安全性
  2. time budget controller を入れる
  3. 実戦の action selection を root Q ではなく root visit argmax にする
  4. belief を使う/使わない A/B 評価を行い、belief prior が本当に効いているか確認する
  5. 現時点の A/B では belief off が優勢なため、提出候補は belief off を基本にする

  6. Online ISMCTS の time budget 最適化

  7. simulation 数の動的制御
  8. 序盤は shallow、終盤は厚く
  9. fallback policy の確実化

  10. 高品質 ISMCTS data の再収集

  11. simulations=8 の大量データより高品質 target を優先する
  12. ただし evaluation では sim32 depth1sim16 depth1 より悪化したため、現時点の候補は sim16 depth1
  13. visit target が one-hot 化しすぎていないかを先に確認する
  14. max depth も 1 から 2-4 へ増やした場合の速度を測る

  15. Mixed replay training

  16. v4 の通常 self-play data と ISMCTS data を混ぜる
  17. ISMCTS data だけで学習しない
  18. v4 の一般性を保持する

  19. Visit target smoothing

  20. temperature を上げる
  21. one-hot target を平滑化する
  22. base policy mixing を使う場合は改善余地を消さないよう小さくする
  23. 例:
target = 0.95 * ismcts_visit_policy + 0.05 * base_policy
  1. Value target の改善
  2. final result だけでなく root value / rollout value を保存する
  3. 初期案:
value_target = 0.5 * turn_value + 0.5 * final_result
  • policy target と value target の意味を揃える
  • training/train.py では --turn-value-weight によりこの blending を指定できる
  • ismcts-visits-fixed-v4-abomasnow-50.jsonlturn_value coverage が 100% のため、この実験に使える

  • Deck-aware model の再検証

  • 新しい self-play data には own_deck_card_ids を保存する
  • 古い JSONL では使わない
  • 複数自分デッキで再収集してから効果を見る

追加レビューからの実装メモ

2026-06-22 の追加レビューを受けて、以下を反映した。

  • src/pca/search/ismcts.py
  • online ISMCTS の実戦 action selection を root visit distribution ベースに変更。
  • 以前は target は visit distribution だったが、実際の selected action は root Q score 由来だった。
  • src/pca/submission/main.py
  • PCA_TIME_BUDGET_SECONDS / PCA_SEARCH_RESERVE_SECONDS / PCA_SEARCH_REDUCE_SECONDS による time budget controller を追加。
  • 残り時間が少ない場合は checkpoint-only fallback に落とす。
  • PCA_DISABLE_BELIEF=1 で belief prior を無効化できるようにし、belief A/B 評価をしやすくした。

次に必要な評価:

# Belief on
bash scripts/evaluate_docker.sh \
  --deck0 /app/decks/own/mega-abomasnow/deck.csv \
  --deck1-dir /app/decks/opponents/holdout \
  --policy0 search-ismcts \
  --checkpoint0 /app/checkpoints/policy_value_v4_best.pt \
  --belief-checkpoint0 /app/checkpoints/belief_v5_ismcts_best.pt \
  --policy1 checkpoint \
  --checkpoint1 /app/checkpoints/policy_value_v4_best.pt \
  --games 10 \
  --max-steps 200 \
  --ismcts-simulations 8 \
  --output /app/data/eval/online-ismcts-v4-belief-on-vs-v4-holdout.json \
  --csv-output /app/data/eval/online-ismcts-v4-belief-on-vs-v4-holdout.csv

# Belief off
bash scripts/evaluate_docker.sh \
  --deck0 /app/decks/own/mega-abomasnow/deck.csv \
  --deck1-dir /app/decks/opponents/holdout \
  --policy0 search-ismcts \
  --checkpoint0 /app/checkpoints/policy_value_v4_best.pt \
  --policy1 checkpoint \
  --checkpoint1 /app/checkpoints/policy_value_v4_best.pt \
  --games 10 \
  --max-steps 200 \
  --ismcts-simulations 8 \
  --output /app/data/eval/online-ismcts-v4-belief-off-vs-v4-holdout.json \
  --csv-output /app/data/eval/online-ismcts-v4-belief-off-vs-v4-holdout.csv

実験上の注意

  • 古い JSONL には own_deck_card_ids が無い。
  • そのデータで学習するときは use_deck_context=False でなければならない。
  • use_deck_context=True の checkpoint を古いデータで作ると、評価時だけ未学習 deck encoder が使われて性能を壊す。
  • 古い checkpoint は model_config={} のため、モデル既定値は後方互換として use_deck_context=False にする。deck-aware model は checkpoint 側に use_deck_context=True を明示保存する。
  • online ISMCTS と distilled checkpoint は別物として評価する。
  • distilled checkpoint が弱くても、online ISMCTS の有効性は否定されない。
  • v4 と同じ構造で conservative fine-tune したい場合は、JSONL に own_deck_card_ids が含まれていても training/train.py --disable-deck-context を使う。

Unknown-deck 評価への修正

追加レビューで、online ISMCTS 評価における重要な前提を見直した。

これまでの holdout 評価では、CABT の実対戦に使う deck1 がそのまま探索側の opponent_prior_deck として使われていた。つまり結果は以下の条件を含んでいた。

Known-deck prior:
  相手の正確な60枚デッキを探索が知っている。

これは online ISMCTS の上限性能を見るには有用だが、実戦条件としては甘い。実際の対戦では相手の正確なデッキリストは分からないため、評価を以下の2種類に分ける。

Known-deck evaluation:
  実デッキを prior に渡す。
  探索手法の上限性能を見る。

Unknown-deck evaluation:
  実デッキは CABT の battle_start にだけ使う。
  探索には train/meta deck pool だけを prior として渡す。
  未知デッキへの実戦寄り性能を見る。

このため evaluation/tournament.py に以下を追加した。

  • --opponent-prior-deck-dir
  • policy0 の探索用 opponent prior deck pool。
  • 実際の対戦相手デッキ --deck1-dir とは分離される。
  • --policy0-opponent-prior-deck-dir
  • policy0 用の明示指定。
  • --policy1-opponent-prior-deck-dir
  • policy1 が探索policyの場合の opponent prior。

unknown-deck 評価例:

bash scripts/evaluate_docker.sh \
  --deck0 /app/decks/own/mega-abomasnow/deck.csv \
  --deck1-dir /app/decks/opponents/holdout \
  --opponent-prior-deck-dir /app/decks/opponents/train \
  --policy0 search-ismcts \
  --checkpoint0 /app/checkpoints/policy_value_v4_best.pt \
  --policy1 checkpoint \
  --checkpoint1 /app/checkpoints/policy_value_v4_best.pt \
  --games 10 \
  --max-steps 200 \
  --ismcts-simulations 8 \
  --search-rollout-depth 8 \
  --unknown-card-rate 0.1 \
  --output /app/data/eval/online-ismcts-v4-unknown-prior-sim8-depth8-vs-v4-holdout.json \
  --csv-output /app/data/eval/online-ismcts-v4-unknown-prior-sim8-depth8-vs-v4-holdout.csv

解釈:

  • --deck1-dir /app/decks/opponents/holdout は実際に対戦する相手デッキ。
  • --opponent-prior-deck-dir /app/decks/opponents/train は探索が信じてよい候補デッキ群。
  • holdout の正確なカードリストは sample_hidden_state に渡さない。
  • --unknown-card-rate は候補デッキ外カードへの保険。まず 0.0 / 0.1 / 0.2 / 0.3 を比較する。

情報非対称性と strategy fusion

追加レビューでは、ISMCTS の古典的な問題である strategy fusion / 情報非対称性も指摘された。

整理:

  • encoder は相手にこちらの手札を直接露出していない。
  • 本当の問題は「探索内の相手」が sampled hand を見て動く一方、本番の相手は本物の hand を見て動くこと。
  • さらに、同じ公開局面を異なる hidden state で共有するかどうかにより、strategy fusion と計算効率の trade-off が出る。

現状の information_set_keyencoded.state_tokens を含むため、相手手番では sampled hand の違いで key が分かれる可能性がある。

メリット:
  sampled hand ごとの条件付き行動を表現しやすい。

デメリット:
  tree 共有が弱く、simulation 効率が悪い。
  single-tree ISMCTS というより PIMC 寄りの hybrid になる。

次の研究タスク:

  1. current-key 方式のまま simulations / depth を増やす。
  2. node-key 専用の public-view encoding を追加し、真の single-tree ISMCTS と比較する。
  3. BeliefNet の hand/prize/deck 精度を測定し、belief on/off を unknown-deck 条件でも再評価する。

v6 prior 失敗の再解釈

policy_value_v6_prior_best.ptpolicy_value_v4_best.pt からの単純な fine-tune ではなかった。

checkpoint の差分:

v4:
  model_config = {}
  state_dict keys = 37

v6:
  model_config = {"use_deck_context": True}
  state_dict keys = 45

つまり v6 は deck_encoder / context_fusion を有効化した deck-aware 新構造であり、v4 からロードできない新規パラメータを少量の p0 ISMCTS data で学習していた。

結果:

online ISMCTS(v6 prior) vs checkpoint v4:
  29-20-1 / 50 games

online ISMCTS(v6 prior) vs online ISMCTS(v4 prior):
  4-19-2 / 25 games

解釈:

v6 prior が弱い、というより
少量データで deck-aware 新構造へ移行したことが弱化要因。

次の切り分け:

PYTHONPATH=src uv run python -m pca.training.train \
  --input data/selfplay/ismcts-v4-prior-rate00-sim8-depth8-100g.jsonl \
  --init-checkpoint checkpoints/policy_value_v4_best.pt \
  --output checkpoints/policy_value_v6b_samearch_final.pt \
  --best-output checkpoints/policy_value_v6b_samearch_best.pt \
  --epochs 1 \
  --batch-size 64 \
  --learning-rate 0.00001 \
  --player-index 0 \
  --turn-value-weight 0.0 \
  --disable-deck-context

この v6b は use_deck_context=False のままになるため、v4 と同じ構造で conservative prior refinement を試せる。

v6b same-arch の結果:

online ISMCTS(v6b same-arch prior) vs checkpoint v4:
  49-1 / 50 games
  avg policy0 seconds: 0.101 sec/action

online ISMCTS(v6b same-arch prior) vs online ISMCTS(v4 prior):
  23-2 / 25 games
  avg policy0 seconds: 0.094 sec/action

online ISMCTS(v6b same-arch prior) vs online ISMCTS(v4 prior):
  98-2 / 100 games
  avg policy0 seconds: 0.092 sec/action
  avg policy1 seconds: 0.095 sec/action

100 games のデッキ別結果:

garchomp:       19-1
mega-sharpedo:  20-0
slowking:       20-0
terastal-box:   19-1
zoroark:        20-0

結論:

v6 deck-aware:
  失敗。構造変更 + 少量データが主因。

v6b same-arch:
  成功。v4 と同じ構造で conservative fine-tune すると online ISMCTS prior が改善。

以降の本命 prior は checkpoints/policy_value_v6b_samearch_best.pt とする。

次の self-play teacher:

policy: search-ismcts
checkpoint: checkpoints/policy_value_v6b_samearch_best.pt
opponent prior: decks/opponents/train
simulations: 8
rollout depth: 8
unknown_card_rate: 0.0
belief: off

v7 Set Transformerによるデッキ対応の事前分布

v6 の deck-aware 化は、平均 pooling の deck context をいきなり有効化したため、少量データでは prior を壊しやすかった。v7 では同じ反省を踏まえ、デッキ情報を入れるが初期挙動は v6b に近く保つ。

実装方針:

state_tokens -> state_encoder -> state_vec
deck card ids + static card features -> CardFeatureEncoder -> SetTransformerDeckEncoder -> deck_vec
state_vec + deck_gate * fusion(state_vec, deck_vec) -> policy/value heads

重要な制約:

  • deck_context_modenone / mean / set_transformer
  • Set Transformer はデッキを順序なし集合として扱う。
  • init_deck_gate=0.0 から始めるため、学習開始時は deck branch が policy/value に影響しない。
  • pokemon-tcg-ai-battle/EN_Card_Data.csv 由来の既存静的特徴だけを使う。
  • 効果文解析、手書き役割タグ、heuristic reranker は次段階に残す。
  • checkpoint には card_data_hashdeck_context_mode を保存する。

全デッキ pilot JSONL をまとめて v7 を学習する例:

PYTHONPATH=src uv run python -m pca.training.train \
  --input data/selfplay/pilots/*.jsonl \
  --init-checkpoint checkpoints/policy_value_v6b_samearch_best.pt \
  --output checkpoints/policy_value_v7_settransformer_final.pt \
  --best-output checkpoints/policy_value_v7_settransformer_best.pt \
  --epochs 1 \
  --batch-size 64 \
  --learning-rate 0.00001 \
  --player-index 0 \
  --turn-value-weight 0.0 \
  --deck-context-mode set_transformer \
  --card-data pokemon-tcg-ai-battle/EN_Card_Data.csv \
  --init-deck-gate 0.0

評価では checkpoint-only ではなく、online ISMCTS prior として v6b と比較する。

bash scripts/evaluate_docker.sh \
  --deck0 /app/decks/own/mega-abomasnow/deck.csv \
  --deck1-dir /app/decks/opponents/holdout \
  --policy0 search-ismcts \
  --checkpoint0 /app/checkpoints/policy_value_v7_settransformer_best.pt \
  --policy1 search-ismcts \
  --checkpoint1 /app/checkpoints/policy_value_v6b_samearch_best.pt \
  --policy0-opponent-prior-deck-dir /app/decks/opponents/train \
  --policy1-opponent-prior-deck-dir /app/decks/opponents/train \
  --games 20 \
  --max-steps 200 \
  --ismcts-simulations 8 \
  --search-rollout-depth 8 \
  --unknown-card-rate 0.0 \
  --output /app/data/eval/online-ismcts-v7-settransformer-vs-v6b-holdout.json \
  --csv-output /app/data/eval/online-ismcts-v7-settransformer-vs-v6b-holdout.csv

判定基準:

短期:
  v6b online ISMCTS に大きく負けないこと。

中期:
  複数デッキ pilot で、リプレイ上の明らかなミスが減ること。

長期:
  この deck-aware prior を土台に、カード役割タグと heuristic reranker を追加する。

ヒューリスティックによる再順位付け v1

リプレイ確認では、重要カードの discard、攻撃準備の遅れ、育ったポケモンの出し方などに課題が残った。ただし「高HP / ex / 進化後 = メインアタッカー」といった固定ルールは、デッキタイプをまたぐと危険である。

そのため v1 の heuristic reranker は、カード役割を決め打ちせず、局面改善量だけを薄く補正する。

実装済みの補正:

attack:
  legal attack を少し加点し、終盤の膠着を減らす。

evolve:
  進化行動を少し加点する。

attach:
  対象ポケモンの最短攻撃必要エネに近づくなら加点。
  すでに攻撃可能な対象への追加添付は減点しない。
  逃げエネ、エネルギー枚数依存打点、将来の攻撃、特殊エネルギー効果があるため、過剰と決めつけない。
  残り HP が少ない対象への投資は減点。

promote:
  攻撃可能 / 逃げやすいポケモンを前に出す行動を少し加点。

discard:
  低採用枚数カード、進化ラインの一部、ACE SPEC の discard を減点。
  基本エネルギーも攻撃不能につながるため減点を残す。
  ただし進化ラインや ACE SPEC ほどは重くしない。

deck-out emergency:
  自分の deckCount が 0〜2 のときは、追加 draw / deck search を強く減点し、attack を加点する。
  相手の deckCount が 0 のときだけ END を強く加点する。
  相手の deckCount が少ないだけでは attack/待ちを加点しない。
  自分の deckCount が 0 かつ相手 deck が残っている場合、END は減点する。

最初の評価は、同じ v7 checkpoint の policy0 だけに reranker を入れる。

bash scripts/evaluate_docker.sh \
  --deck0 /app/decks/opponents/train/dragapult-citytop.csv \
  --deck1-dir /app/decks/opponents/holdout \
  --policy0 search-ismcts \
  --checkpoint0 /app/checkpoints/policy_value_v7_settransformer_best.pt \
  --policy1 search-ismcts \
  --checkpoint1 /app/checkpoints/policy_value_v7_settransformer_best.pt \
  --policy0-opponent-prior-deck-dir /app/decks/opponents/train \
  --policy1-opponent-prior-deck-dir /app/decks/opponents/train \
  --games 20 \
  --max-steps 200 \
  --ismcts-simulations 8 \
  --search-rollout-depth 8 \
  --unknown-card-rate 0.0 \
  --heuristic-reranker0 \
  --heuristic-weight 0.05 \
  --output /app/data/eval/online-ismcts-v7-rerank005-vs-v7-holdout.json \
  --csv-output /app/data/eval/online-ismcts-v7-rerank005-vs-v7-holdout.csv

判定基準:

勝率:
  v7 reranker が v7 baseline に大きく負けないこと。

手数:
  unfinished と avg_steps が減るかを見る。

deck-out:
  JSON/CSV の `player0_deck_out_wins`, `player1_deck_out_wins`, `deck_out_finishes` を見る。
  deck-out 勝ちが増えるのはよいが、deck-out 負けが増えるなら draw/search penalty を強める。

リプレイ:
  エネ添付先、進化、攻撃選択、重要カード discard が自然になるかを見る。

ヒューリスティックによる再順位付け v2: エネルギーの色、サイドレース、自己対戦の安定性

2026-06-23 に、v1 reranker のレビューとリプレイ確認を受けて次の修正を行った。

実装したこと

target policy safety:

  • reranked_policyapplicable_player_index を追加した。
  • --heuristic-reranker0 は policy0 の実手番だけに効く。
  • 探索内の相手手番まで policy0 用 heuristic がかかる問題を避ける。
  • rerank 後は古い target_policy を破棄する。
  • これにより、self-play record の selectedtarget_policy が不整合になる事故を防ぐ。

energy attach:

  • 攻撃コストを {P}{P} / {R}{C} などの色付き要求として parse する。
  • Basic {P} Energy のようなカード名、energy_type、カードDB由来の特徴からエネルギー色を推定する。
  • 色が合わないエネ添付は「攻撃準備が進んだ」とみなさない。
  • 特殊エネルギーは、効果文までは解釈せず、まずは任意色 1 個ぶんの wildcard として扱う。
  • すでに最短攻撃コストを満たしている対象への追加添付は減点しない。

prize race:

  • legal attack に基本加点するだけでなく、相手バトル場を KO できそうなら追加加点する。
  • 取れるサイド枚数が多いほど加点を大きくする。
  • 推定サイド枚数は、通常ポケモン 1、ex / V 2、VMAX / TAG TEAM 3 とする。
  • 残りサイド枚数より多くは加点しない。

deck-out:

  • 自分の deckCount が 0〜2 のときの draw / search penalty は維持する。
  • 相手 deckCount が 0 のときだけ END を加点する。
  • 相手 deckCount が少ないだけでは、攻撃しない/待つ行動を加点しない。

調べたこと

CABT observation / replay には、攻撃時の attackId が含まれる。

ただし、attackId は CSV の攻撃行番号と直接対応していないように見える。リプレイでは attackId: 1047 のような内部 ID が出ており、EN_Card_Data.csvMove Name / Damage 行と単純には join できない。

そのため、v2 のサイド取得加点は「その攻撃 ID の厳密なダメージ」ではなく、カードDBの damages 候補から推定している。これは conservative ではない可能性があるため、将来的には CABT 側の攻撃 ID とカードDBの技情報を対応づける調査が必要。

エネルギー添付によって発生する特性・効果について:

  • 現状の heuristic は理解していない。
  • CABT の合法手生成・search rollout には反映されるため、online ISMCTS が探索深度内で見ることはできる。
  • reranker は、添付前の局面で「このエネ添付により特性が発動する」とは評価できない。
  • カード固有効果を heuristic に入れるには、効果文解析または手書き効果タグが必要。
  • 雑なタグは逆に弱くするリスクが高いため、v2 では入れない。

学んだこと

  • エネルギー添付は単純な「必要枚数に近づく」だけでは足りない。
  • 色が合わない添付を加点すると、リプレイ上の不自然な育成を悪化させる。
  • 一方で、過剰エネ添付を即減点するのも危険。
  • 逃げエネに使う場合がある。
  • エネルギー枚数依存の打点がある。
  • 将来の攻撃や特性につながる場合がある。
  • サイド取得は勝ち筋そのものなので、attack heuristic に明示的に入れる価値がある。
  • ただし、厳密なダメージ計算は CABT の search に任せ、reranker は薄く補正するに留める。

テスト

追加したテスト:

  • 相手手番では applicable_player_index により rerank されない。
  • 自分手番では rerank され、古い target_policy は残らない。
  • 違う色のエネ添付は攻撃準備として加点しない。
  • 特殊エネルギーは wildcard 1 個ぶんとして扱う。
  • KO で取れるサイド枚数が多いほど attack score が高い。

確認:

PYTHONPATH=src .venv/bin/python -m unittest tests.test_reranker
# Ran 16 tests OK

PYTHONPATH=src .venv/bin/python -m unittest discover -s tests
# Ran 69 tests OK

v8 self-play 方針

v2 reranker は、提出エージェントに直接入れるだけでなく、teacher policy として self-play データ生成に使う。

狙い:

  • 色エネを意識したエネ添付
  • サイド取得を意識した攻撃
  • deck-out 負け回避
  • 重要カード discard の抑制

まずは Dragapult Citytop 固定で 100 games の小規模データを作る。

bash scripts/collect_selfplay_docker.sh \
  --deck0 /app/decks/opponents/train/dragapult-citytop.csv \
  --opponent-deck-dir /app/decks/opponents/train \
  --policy search \
  --checkpoint /app/checkpoints/policy_value_v7_settransformer_best.pt \
  --games 100 \
  --workers 4 \
  --max-steps 200 \
  --search-mode ismcts \
  --ismcts-simulations 8 \
  --search-rollout-depth 8 \
  --unknown-card-rate 0.0 \
  --heuristic-reranker \
  --heuristic-weight 0.05 \
  --card-data /app/pokemon-tcg-ai-battle/EN_Card_Data.csv \
  --output /app/data/selfplay/v8-dragapult-citytop-heuristic-100g.jsonl

学習:

PYTHONPATH=src uv run python -m pca.training.train \
  --input data/selfplay/v8-dragapult-citytop-heuristic-100g.jsonl \
  --init-checkpoint checkpoints/policy_value_v7_settransformer_best.pt \
  --output checkpoints/policy_value_v8_dragapult_heuristic_final.pt \
  --best-output checkpoints/policy_value_v8_dragapult_heuristic_best.pt \
  --epochs 1 \
  --batch-size 64 \
  --learning-rate 0.00001 \
  --player-index 0 \
  --turn-value-weight 0.0 \
  --deck-context-mode set_transformer \
  --card-data pokemon-tcg-ai-battle/EN_Card_Data.csv \
  --init-deck-gate 0.0

評価:

bash scripts/evaluate_docker.sh \
  --deck0 /app/decks/opponents/train/dragapult-citytop.csv \
  --deck1-dir /app/decks/opponents/holdout \
  --policy0 search-ismcts \
  --checkpoint0 /app/checkpoints/policy_value_v8_dragapult_heuristic_best.pt \
  --policy1 search-ismcts \
  --checkpoint1 /app/checkpoints/policy_value_v7_settransformer_best.pt \
  --games 10 \
  --max-steps 200 \
  --ismcts-simulations 8 \
  --search-rollout-depth 8 \
  --unknown-card-rate 0.0 \
  --heuristic-reranker \
  --heuristic-weight 0.05 \
  --card-data /app/pokemon-tcg-ai-battle/EN_Card_Data.csv \
  --output /app/data/eval/v8-dragapult-heuristic-vs-v7-holdout.json \
  --csv-output /app/data/eval/v8-dragapult-heuristic-vs-v7-holdout.csv

評価時に見る指標:

  • 勝率
  • unfinished
  • avg_steps
  • player0_deck_out_wins
  • player1_deck_out_wins
  • リプレイ上のエネ添付、攻撃、サイド取得、discard の自然さ

残課題

  • attackIdEN_Card_Data.csv の技行を対応づける。
  • 効果文解析または手書きタグで、エネ添付トリガー特性を扱う。
  • 特殊エネルギーを一律 wildcard ではなく、カードごとに正確に扱う。
  • サイド取得 heuristic が過大評価になっていないか、リプレイで確認する。

v8 初回評価からの修正

v8 初回評価では次の結果になった。

{
  "games": 50,
  "player0_wins": 20,
  "player1_wins": 19,
  "unfinished": 11,
  "deck_out_finishes": 26,
  "player0_deck_out_wins": 20,
  "player1_deck_out_wins": 6
}

この結果は「deck-out 勝ち筋をうまく拾った」とは解釈しない。

Pokémon TCG では相手のドローを heuristic レベルでコントロールする手段はほぼなく、deck-out は「両者とも prize を取りに行かず受動的にターンが進んだ副作用」である。能動的に相手を deck-out させた戦略ではなく、攻撃しないまま試合が長引いた結果、たまたま相手が先に山札を引き切っただけ。真の問題は「そもそも攻撃するための準備 (エネ添付・進化) が進まない」ことである。

そのため、次の修正を行った。

  • self-play JSONL の meta.result_reason に終了理由を保存する。
  • 学習時に --deck-out-win-value を指定できるようにする。
  • --deck-out-win-value 0.0 を使うと、deck-out 勝ちを通常勝ちとして学習しない (= 受動的な deck-out 勝ちを +1 として吸い込まない)。
  • self-play 収集側に --heuristic-reranker-player を追加し、teacher として policy0 にだけ reranker を当てられるようにする。

なお、当初 codex が入れた「攻撃可能な局面で END を減点する」(passive_end_with_attack_penalty) は、attack option が legal にならない局面では発火しないため根本治療にならず、v9 で削除した。代わりに preparation 側 (attach / evolve / bench 展開) の加点を強める方針に切り替えた。

今後の v8b 以降では、古い v8-dragapult-citytop-heuristic-100g.jsonl は使わず、再度 self-play を収集する。古い JSONL には result_reason がないため、deck-out 勝ちの価値を下げる学習ができない。

v8b 用 self-play 再収集 (policy0 のみ reranker を teacher として適用):

bash scripts/collect_selfplay_docker.sh \
  --deck0 /app/decks/opponents/train/dragapult-citytop.csv \
  --opponent-deck-dir /app/decks/opponents/train \
  --policy search \
  --checkpoint /app/checkpoints/policy_value_v7_settransformer_best.pt \
  --games 100 --workers 4 --max-steps 200 \
  --search-mode ismcts --ismcts-simulations 8 --search-rollout-depth 8 \
  --unknown-card-rate 0.0 \
  --heuristic-reranker --heuristic-reranker-player 0 --heuristic-weight 0.05 \
  --card-data /app/pokemon-tcg-ai-battle/EN_Card_Data.csv \
  --output /app/data/selfplay/v8b-dragapult-citytop-heuristic-100g.jsonl

学習時には --deck-out-win-value 0.0 を指定して deck-out 勝ちラベルを 0 に潰す:

PYTHONPATH=src uv run python -m pca.training.train \
  --input data/selfplay/v8b-dragapult-citytop-heuristic-100g.jsonl \
  --init-checkpoint checkpoints/policy_value_v7_settransformer_best.pt \
  --output checkpoints/policy_value_v8b_dragapult_heuristic_final.pt \
  --best-output checkpoints/policy_value_v8b_dragapult_heuristic_best.pt \
  --epochs 1 --batch-size 64 --learning-rate 0.00001 \
  --player-index 0 --turn-value-weight 0.0 \
  --deck-context-mode set_transformer \
  --card-data pokemon-tcg-ai-battle/EN_Card_Data.csv \
  --init-deck-gate 0.0 \
  --deck-out-win-value 0.0

v9 方針: サイド取得距離ベースの board development

v8b 評価後、問題は「攻撃できるのに END を選ぶ」ことよりも、そもそも攻撃できるアタッカーを育成できていないことだと整理した。

また、「攻撃ダメージが高い」「必要エネが少ない」は単体では目的ではない。重要なのは、その行動がサイド取得までの距離を短くするかである。

そのため、heuristic は次の方針に変更する。

よい proxy:
  その行動後、相手 active を倒すまでの距離が短くなる。

避ける proxy:
  高打点だから加点。
  低エネだから加点。
  高HP / ex / 進化後だから加点。

実装した board development 補正:

  • エネ添付:
  • 添付後に、相手 active を倒す route の不足エネ / 必要攻撃回数が減るなら加点。
  • 添付後にサイド取得 route が ready に近いなら追加加点。
  • 進化:
  • 進化後に相手 active を倒す route が短くなるなら加点。
  • 進化そのものの固定加点だけに依存しない。
  • ベンチ展開:
  • 場に出すポケモンが相手 active へのサイド取得 route を持つなら小さく加点。
  • 単に高HPや高打点だからではなく、サイド取得 route があるかを見る。
  • バトル場選択:
  • 前に出すポケモンがサイド取得 route に近いなら加点。

直接攻撃のサイド加点については、引き続き option-level damage がある場合だけ「その攻撃でサイドを取れる」とみなす。CABT の attackIdEN_Card_Data.csv の技行が未対応なため、直接攻撃の取得サイド数をカードDBの最大打点だけで断定しない。

一方で、育成・進化・ベンチ展開のような board development では、カードDB の damagesattack_costs を使って「将来サイドを取る route」を推定する。これは厳密なルール評価ではなく、探索 prior を少し整えるための弱い補正である。

END について:

  • 「攻撃できるのに END を選ぶ」ことは主因ではないため、legal attack があるだけでは END を直接減点しない。
  • 相手 deck が 0 でも END を加点しない。
  • 自分 deck が 0 で負ける END だけは引き続き減点する。

テスト:

PYTHONPATH=src .venv/bin/python -m unittest tests.test_reranker
# Ran 23 tests OK

PYTHONPATH=src .venv/bin/python -m unittest discover -s tests
# Ran 78 tests OK