LTC 回帰テスト

前提知識SPSASPRT

このページの要点

  • LTC(Long Time Control)回帰テストは、短時間チューニングで得たパラメータが長時間対局でも有効かを検証する
  • 一定回数の SPSA 更新ごとに自動的に実行され、SPRT またはスコアベースの基準でパス/フェイルを判定する
  • フェイルした場合、パラメータは前回のパス時点まで自動リバートされる
  • 「短時間では有効だが長時間では無効」な局所最適への陥落を防ぐ安全装置

なぜ LTC 回帰テストが必要か

SPSA チューニングは通常、短い持ち時間(STC: Short Time Control)で実行されます。 対局のスループットを最大化するためです。

しかし、STC と LTC では探索の性質が変わるため、STC で最適なパラメータが LTC でも最適とは限りません。

STC の特性:
- 読みの深さが浅い → 評価関数の粗い調整が有効
- 時間切れ負けのリスク → 高速な指し手生成が有利
- ノイズが大きい → 偶然の勝ちが多い

LTC の特性:
- 読みが深い → 精密な評価が重要
- 時間に余裕がある → 探索効率が重要
- ノイズが小さい → 真の実力差が現れやすい

LTC 回帰テストは、STC でのチューニング結果が LTC でも性能劣化していないことを定期的に確認します。

動作の流れ

SPSA 更新ループ
  │
  ├─ 更新 1 ... 49
  │
  ├─ 更新 50 (every_n_updates=50)
  │     │
  │     └─► LTC 回帰テスト実行
  │           │
  │           ├─ パス → 現在のパラメータを基準点として保存
  │           │
  │           └─ フェイル → 前の基準点までリバート
  │
  ├─ 更新 51 ... 99
  │
  ├─ 更新 100
  │     └─► LTC 回帰テスト実行
  │           ...
  └─ ...

設定

class LtcRegressionConfig(BaseModel):
    enabled: bool = False
    every_n_updates: int = 0        # 何回の更新ごとに実行
    total_pairs: int = 0            # 実行するゲームペア数
    time_control: TimeControlLimits | None = None  # LTC 用タイムコントロール
    pass_criteria: LtcPassCriteria | None = None   # パス判定基準

パス判定基準

class LtcPassCriteria(BaseModel):
    min_winrate: float | None = None    # 最低勝率(例: 0.55)
    max_elo_drop: float | None = None   # 最大 Elo 低下
    sprt: SprtConfig | None = None      # SPRT による判定

3 種類の基準を組み合わせ可能です。max_elo_drop は正値と負値のどちらでも同じ許容低下量として扱います。

設定例

ltc_regression:
  enabled: true
  every_n_updates: 50        # 50 更新ごとに実行
  total_pairs: 200           # 最大 200 ペア
  time_control:
    type: "byoyomi"
    main_time: 60            # 本時間 60 秒
    byoyomi_time: 1          # 秒読み 1 秒
    byoyomi_periods: 30      # 秒読み 30 回
  pass_criteria:
    min_winrate: 0.50         # 勝率 50% 以上
    sprt:
      elo0: 0.0
      elo1: 5.0
      alpha: 0.05
      beta: 0.05

判定ロジック

LTC 対局の実行

チューニング済みパラメータ vs ベースラインパラメータで対局します。

async def run_ltc_regression(
    orchestrator, *,
    tuned_params: list[ParamEntry],
    baseline_params: list[ParamEntry],
    ...
) -> dict[str, Any]:
    if sprt_config is not None:
        sprt = Sprt(
            elo0=float(sprt_config.elo0),
            elo1=float(sprt_config.elo1),
            alpha=float(sprt_config.alpha),
            beta=float(sprt_config.beta),
        )

    for pair_idx in range(total_pairs):
        score, gi_b, gi_w = await orchestrator._run_game_pair(...)

        # SPRT に結果を提出
        _submit_to_sprt(gi_b, tuned_as_black=True)
        _submit_to_sprt(gi_w, tuned_as_black=False)

        # SPRT が判定に到達したら早期終了
        if sprt_decision != SprtDecision.CONTINUE:
            break

パス/フェイルの判定

status = "passed"

# 勝率チェック
if min_winrate is not None and winrate < min_winrate:
    status = "failed"

# SPRT チェック
if sprt_decision == SprtDecision.ACCEPT_H0:
    status = "failed"     # 改善なしと判定
elif sprt_decision == SprtDecision.CONTINUE:
    status = "pending"    # 判定未確定

パス時の処理

if status == "passed":
    # 現在のパラメータを新しい基準点として保存
    self._store_ltc_baseline(post_update_snapshot, update_idx)

フェイル時の処理

elif status == "failed":
    # 前の基準点までパラメータをリバート
    params.clear()
    params.extend(baseline_params)
    write_params(params_path, params)

判定基準の選び方

SPRT ベース(推奨)

SPRT は判定に必要なだけ対局を続け、結論が出た時点で打ち切ります。 固定の対局数を先に決めなくてよいぶん、無駄な LTC 対局を減らせます。

pass_criteria:
  sprt:
    elo0: -5.0    # 5 Elo の劣化まで許容
    elo1: 0.0     # 改善なしが帰無仮説
    alpha: 0.05
    beta: 0.05

:LTC 回帰では「劣化していないこと」を確認するため、elo0 と elo1 の設定が通常の SPRT と逆になることがあります。

勝率ベース

設定は単純ですが、必要な対局数を統計的に決めないため、total_pairs が小さいと偶然の勝率で判定が揺れます。

pass_criteria:
  min_winrate: 0.50   # 最低でも 50% 以上

複合基準

複数の基準を組み合わせると、いずれか一つでも劣化を捉えた時点でフェイルにできます。

pass_criteria:
  min_winrate: 0.48        # 最低勝率
  max_elo_drop: 10.0       # 最大 10 Elo の劣化まで許容
  sprt:
    elo0: -5.0
    elo1: 0.0
    alpha: 0.05
    beta: 0.05

設計上のトレードオフ

every_n_updates の設定

LTC 頻度メリットデメリット
10高い劣化を早期に検出チューニング全体が遅くなる
50バランスが良い中程度の遅延
200低いチューニングが速い劣化の検出が遅れる

total_pairs の設定

精度時間推奨用途
50低い短いスクリーニング
200一般的なチューニング
500+高い長い重要なリリース前の検証

リバートの影響

LTC 回帰テストでフェイルしてリバートが発生した場合:

  1. パラメータは前回のパス時点に戻る
  2. SPSA 更新は次の反復から継続する
  3. ゲインスケジュールはリセットされない(\(k\) は増加し続ける)

ゲインスケジュールを据え置くことで、フェイル後のチューニングは以前より小さいステップサイズで進みます。 リバート直前と同じ経路をそのままなぞる可能性は下がり、同じ局所最適へ再突入しにくくなります。

実装リファレンス

ファイル関数/クラス役割
_core/contexts/spsa/adapters/runtime/ltc_regression.pyrun_ltc_regression()LTC 回帰テストの本体
_core/contexts/game_session/adapters/orchestration/config_spsa_models.pyLtcRegressionConfigLTC 設定
_core/contexts/game_session/adapters/orchestration/config_spsa_models.pyLtcPassCriteriaパス判定基準

次に読む

分散削減テクニック:CRN、ペアゲーム、バッチ処理といった推定精度を高める手法。