コンテンツにスキップ

2026-06-26 Journal

今日の結論

今日は v9 / v10 周りの実験結果を見直し、いったん方針を整理し直した。

現時点の判断は次の通り。

  • v9 の heuristic-heavy な方向は本線にしない。
  • v7 以降の構造差と学習差が混ざっているため、短期の本線は policy_value_v4_best.pt を起点に戻す。
  • NN-only はまだ弱い。実戦評価では当面 search-ismcts を使う。
  • 学習データは、Belief-guided ISMCTS で実際に選んだ行動と search visit distribution を集める。
  • Belief Model は ISMCTS を置き換えるものではなく、相手 hidden state のサンプリング分布を作るために使う。
  • deck-out 勝ちを狙わせるのではなく、deck-out 負けを避ける方向に評価を寄せる。
  • まともな通常勝ち、サイド取得、攻撃回数、攻撃可能状態の増加を主要な品質指標にする。

方針の変更

NN-only 提出方針の保留

6/24 時点では「提出時は NN-only、online ISMCTS は教師・評価用」という方針だった。

しかし NN-only の smoke evaluation では、攻撃も準備もほとんどできず、deck-out / pass に寄った。

例:

player0_attack_count = 0
player1_attack_count = 0
player0_no_progress_pass_count = 43
player1_no_progress_pass_count = 47
deck_out_finishes = 1

このため、現段階で NN-only を本命にするのは早い。AlphaGo Zero と同様に、少なくとも評価・実戦候補では MCTS/ISMCTS を併用する。

中期的には search teacher を蒸留して NN-only を強くしたいが、今は先に teacher search の品質を上げる。

v4 起点に戻す

v7 / v9 では Set Transformer deck context や heuristic reranker など構造差が入っている。

性能差が学習データの差なのか、モデル構造の差なのか、heuristic の差なのか切り分けづらくなった。そのため、次の実験は v4 起点に戻す。

当面の起点:

policy checkpoint: checkpoints/policy_value_v4_best.pt
belief checkpoint: checkpoints/belief_v10_general_best.pt
deck context: disabled
teacher: Belief-guided ISMCTS

実装したこと

ISMCTS の計算量改善

src/pca/search/ismcts.py を修正した。

  • leaf value fallback で余計な NN forward を呼ばないようにした。
  • root node / root encoding を simulation ごとに再計算しないようにした。
  • nn_policy_calls / ismcts_simulations がほぼ 1.0 以下になるようにした。
  • tree_encode_calls / simulations を下げた。

これにより、探索の主なボトルネックが Python glue ではなく NN forward であることが見えやすくなった。

self-play / eval のログ改善

src/pca/training/selfplay.pysrc/pca/evaluation/tournament.py のログを見やすくした。

今は self-play で次のような複数行ログを出す。

[selfplay][game] id=27 result=p0_win reason=1 steps=86 turns=18 avg_step_turn=4.78
  board taken=6-0 remain=0-6 deck=25-15 hand=15-8 atk=8-1 ready=13-3 no_pass=0-0 mill=0-0
  perf elapsed=151.1s speed=23.8g/h records/s=0.6
  search begin=5378 step=11905 end=86 obs_conv=86 full_obs=0 t=3.6/6.8s
  nn calls=5352 total=121.0s fwd=116.7s enc=2.5s tensor=1.4s
  ismcts dec=86 sim=5376 hidden=674/674 time=148.8s tree_enc=17281/8.6s nodes=5264/12017
  stops leaf=5250 term=126 no_action=0 no_select=0 fail=0 depth=0

重要な見方:

  • taken: 取得済みサイド枚数
  • remain: 残りサイド枚数
  • atk: 攻撃回数
  • ready: 攻撃可能状態になった回数
  • no_pass: 何も進展せず END した回数
  • nn calls: NN policy/value を呼んだ回数
  • sim: ISMCTS simulation 数
  • hidden: determinization / hidden sample 数
  • stops depth: max-depth で止まった回数。探索深さが 0 という意味ではない

depth=0 という表示は紛らわしいので、後で max_depth=0 のように改名したい。

Torch CPU thread 制御

Docker on Mac で worker を増やしたときに CPU oversubscription が起きやすいため、self-play / eval の main path と worker path で torch thread を 1 に固定した。

torch.set_num_threads(1)
torch.set_num_interop_threads(1)

デッキベクトルのキャッシュ

Set Transformer deck context が有効な場合のために、deck context vector を再利用できるようにした。

  • ActionConditionedPolicyValueNet.compute_deck_vector()
  • neural_policy(..., deck_context_cache=...)

ただし、今日の v4 起点 smoke では deck_context=none なので、ログ上の deck=0.0s は妥当。

PublicKnowledgeTracker

PublicKnowledgeTracker を追加し、公開ログから確定できる opponent hidden knowledge を belief sampling に反映する方向にした。

意図:

  • 相手が手札に加えたことが公開されたカードを known hand として扱う。
  • そのカードが後で play / discard された場合は known hand から外す。
  • 同じ rolling log を繰り返し読んでも二重加算しない。
  • known hand / known deck / known prize / known not in hand のような制約を sample_hidden_state 側に渡す。

これにより、BeliefNet の確率だけでなく、公開情報から論理的に確定する事実を sampling に混ぜられる。

今日見た実験結果

30 game smoke self-play

Wrote 4336 records from 30 games to data/selfplay/v10-v4-belief-ismcts-d8s8-depth12-30g.jsonl
Finished games: 19/30
compute nn=7962.7s ismcts=9700.4s
elapsed=1627.7s speed=66.4g/h records/s=2.7

これは smoke としては使えるが、学習データとしては少ない。

データの問題:

  • unfinished record が多い。
  • reason=None が多く、通常勝ちの教師信号が薄い。
  • 30 games では 17 train decks に対してサンプルが偏る。

ニューラルネットワーク単体の評価

policy_value_v10_smoke_d8s8_best.pt を checkpoint-only で v4 と評価したところ、攻撃・育成がほぼできなかった。

結論:

  • NN-only はまだ提出候補にできない。
  • 検索済みの強い手を蒸留するには、より強い teacher data が必要。
  • checkpoint-only で見える弱さは、学習データ量と teacher quality の不足が大きい。

v10 ISMCTS対v4 ISMCTSの動作確認

dragapult-citytop vs garchomp の 1 game では unfinished だったが、v10 側は攻撃とサイド取得をしていた。

player0_attack_count = 14
player0_prizes_taken = 2
player0_first_attack_step = 10
player0_first_prize_step = 27
player0_no_progress_passes = 0
unfinished = 1

結論:

  • search-ISMCTS を使えば、NN-only よりかなりまともに動く。
  • ただし 180 steps で終わらない試合がまだあり、teacher としてはさらに改善が必要。

d8s16-depth18 self-play のログ

次の設定で、かなり battle-like なログが出た。

--ismcts-determinizations 8
--ismcts-simulations-per-determinization 16
--search-rollout-depth 18

例:

result=p0_win reason=1 steps=93
board taken=6-0 remain=0-6 deck=22-17 atk=5-0 ready=15-1

別例:

result=p1_win reason=3 steps=138
board taken=4-6 remain=2-0 deck=31-12 atk=21-16 ready=24-31

良い点:

  • サイドを取り切る試合が出ている。
  • 攻撃回数が増えた。
  • ready count が増え、攻撃可能ポケモンの準備が進んでいる。
  • no_pass はほぼ 0。

懸念:

  • 1 step あたり 2.5 秒から 3.5 秒程度かかることがある。
  • NN forward が支配的で、Docker CPU 実行では重い。
  • stops depth=0 なので、max depth まで読み切る前に leaf / terminal で止まっている。これは異常ではないが、表示名は改善したい。

現在の推奨 self-play 設定

短期の teacher data collection:

bash scripts/collect_selfplay_docker.sh \
  --deck0-dir /app/decks/opponents/train \
  --opponent-deck-dir /app/decks/opponents/train \
  --policy search \
  --checkpoint /app/checkpoints/policy_value_v4_best.pt \
  --belief-checkpoint /app/checkpoints/belief_v10_general_best.pt \
  --games 100 \
  --workers 8 \
  --max-steps 180 \
  --search-mode ismcts \
  --ismcts-determinizations 8 \
  --ismcts-simulations-per-determinization 16 \
  --search-rollout-depth 18 \
  --dirichlet-alpha 0.3 \
  --dirichlet-epsilon 0.25 \
  --visit-temperature 1.0 \
  --unknown-card-rate 0.05 \
  --progress-every 1 \
  --output /app/data/selfplay/v10-v4-belief-ismcts-d8s16-depth18-100g.jsonl

補足:

  • --search-rollout-depth は名前が悪い。実際には random rollout ではなく、ISMCTS の max tree depth。
  • self-play では exploration のため Dirichlet noise を入れる。
  • eval では --dirichlet-epsilon 0.0 にする。
  • heuristic reranker は今は入れない。teacher の純度を保つため。

現在の推奨 training 設定

100 games が集まった後に、v4 から 1 epoch だけ慎重に fine-tune する。

PYTHONPATH=src uv run python -m pca.training.train \
  --input data/selfplay/v10-v4-belief-ismcts-d8s16-depth18-100g.jsonl \
  --init-checkpoint checkpoints/policy_value_v4_best.pt \
  --output checkpoints/policy_value_v10_d8s16_100g_final.pt \
  --best-output checkpoints/policy_value_v10_d8s16_100g_best.pt \
  --epochs 1 \
  --batch-size 64 \
  --learning-rate 0.00001 \
  --policy-target-source search \
  --turn-value-weight 0.0 \
  --deck-out-win-value 0.0 \
  --deck-out-loss-value -1.0 \
  --deck-out-policy-weight 0.25 \
  --unfinished-policy-weight 0.2 \
  --long-game-policy-decay 0.5 \
  --long-game-reference-steps 180 \
  --exclude-passive-deck-out-wins \
  --disable-deck-context

方針:

  • まず構造差を減らすため --disable-deck-context
  • v4 起点で、teacher search の差だけを見る。
  • unfinished は policy weight を下げる。
  • passive deck-out wins は過大評価しない。
  • deck-out loss は強く悪い value として扱う。

現在の推奨 evaluation 設定

新旧モデルを両方 search-ismcts で比較する。

bash scripts/evaluate_docker.sh \
  --deck0-dir /app/decks/opponents/train \
  --deck1-dir /app/decks/opponents/holdout \
  --policy0 search-ismcts \
  --checkpoint0 /app/checkpoints/policy_value_v10_d8s16_100g_best.pt \
  --belief-checkpoint0 /app/checkpoints/belief_v10_general_best.pt \
  --policy1 search-ismcts \
  --checkpoint1 /app/checkpoints/policy_value_v4_best.pt \
  --belief-checkpoint1 /app/checkpoints/belief_v10_general_best.pt \
  --policy0-opponent-prior-deck-dir /app/decks/opponents/train \
  --policy1-opponent-prior-deck-dir /app/decks/opponents/train \
  --games 1 \
  --max-steps 180 \
  --ismcts-determinizations 8 \
  --ismcts-simulations-per-determinization 16 \
  --search-rollout-depth 18 \
  --unknown-card-rate 0.05 \
  --dirichlet-epsilon 0.0 \
  --visit-temperature 0.0 \
  --output /app/data/eval/v10-d8s16-100g-vs-v4-holdout.json \
  --csv-output /app/data/eval/v10-d8s16-100g-vs-v4-holdout.csv \
  --workers 8

見る指標:

  • normal_wins
  • deck_out_losses
  • unfinished
  • avg_prizes_taken
  • avg_attacks
  • avg_attack_ready
  • avg_first_attack_step
  • avg_first_prize_step
  • replay 上の attacker preparation / active selection / unnecessary pass

今日の性能面の学び

ボトルネック

ログ上、主な時間は NN forward。

例:

nn calls=11077 total=249.6s fwd=241.3s
ismcts time=308.1s

つまり、Python の observation conversion や tree encoding も無視はできないが、最大の重さは policy/value network の forward である。

Docker on Mac と GPU

Mac の MPS GPU は Linux Docker container から基本的に使えない。Docker 内 self-play / eval は CPU 前提になる。

MPS を使えるのは、macOS host 上で uv run する training / inference。CABT の Linux .so が必要な処理は Docker に寄りやすいため、現状の self-play / eval は CPU 最適化が中心になる。

速度感

d8s16-depth18 は 1 game が数十秒から数分かかる。

ただし 8 workers で並列に回すと、100 games は現実的な範囲。まずは 100 games で 1 iteration、問題がなければ 300 games に増やす。

次にやること

  1. d8s16-depth18 で 100 games を集める。
  2. v4 起点で 1 epoch 学習する。
  3. v10+ISMCTS vs v4+ISMCTS を holdout で評価する。
  4. normal wins / prizes / attacks / unfinished / deck-out losses を比較する。
  5. replay を数本見て、攻撃可能ポケモンを用意して前に出せているか確認する。
  6. 改善していれば 300 games に増やす。
  7. 改善しなければ、ISMCTS の leaf expansion / max-depth 到達 / terminal handling / legal action mapping をさらに監査する。

後で直したい表現・CLI

  • --search-rollout-depth--search-max-depth または --ismcts-max-depth に改名したい。
  • ログの stops depthstops max_depth に改名したい。
  • game ログと worker ログは内容が重複している。最終的には game summary を主にし、worker は aggregate だけにしてもよい。