対戦シミュレーションの理論と分析
ランキング方式と評価指標を検証するために作った合成世界 (リポジトリの experiments/simlab/) について、世界の定義、大会の作り方、方式の実装、測り方、結果、うまくいかなかった試み、再現手順を書きます。目的は、真の強さを既知にしたうえで「指標は正しいか」「方式の優劣はどの条件で決まるか」「シードは結果をどう歪めるか」に答えることです。数式は最小限にし、毎回日本語の読み下しを添えます。
- 世界: 真の強さ \(\theta \sim N(0,1)\) の選手 2000 人、Bradley-Terry モデルの勝率、ダブルエリミネーションを再現した大会。調子・相性・強さの変化・参加頻度・地域・疲れ・平日は練習、などの現象とシードの出所をスイッチで足します。
- 方式: SPSP 本番 (ens / tj / bt) を本番コードのまま呼び、Glicko-2、Elo、Bradley-Terry 最尤、JJPR と比べます。真の品質は真の強さとのペア一致率で定義します。
- 結果: 現象のない世界では試合を直接学習する方式が強い (Glicko-2 0.841、SPSP 0.800)。順位評価が実データで強いのは、強い人が大きい大会に集まる構造のためです。シードは 3 種類の歪みを生み、順位系の指標を組んだ側へ \(t\) で +5〜9 傾けます。
- 追試: 乱数の種 3 つで再現、逆確率重み付けによる補正は失敗、自己シードの繰り返しは頭打ち、逐次検定は採用。限界は、真の強さが 1 つの数であることと、大会が最大 512 人であることです。
1. 世界の定義
1.1 選手と勝率
選手を 2000 人 (full 世界だけ 3000 人) 用意し、選手 \(i\) に真の強さ \(\theta_i\) を正規分布 \(N(0, 1)\) から引いて与えます。読み下し: 多くの選手は 0 の近くで、\(\pm 1\) を超える選手は 3 割ほど、\(\pm 2\) を超える選手は 5% ほどです。試合の勝率は Bradley-Terry モデルで決めます。 \[P(i \text{ が } j \text{ に勝つ}) = \frac{1}{1 + e^{-(\theta_i - \theta_j)}}\] 読み下し: 強さの差が 0 なら 50%、差が 1 なら約 73%、差が 2 なら約 88%。この目盛りの 1 は Elo 点で約 174 点に当たります。上位 8 人の試合は 3 本先取、それ以外は 2 本先取で、ゲームごとにこの確率で勝敗を引きます。
現象のオプションを入れると、上の式の \(\theta_i - \theta_j\) の部分に、調子 \(f\) や相性 \(\gamma\) などの項が足されます。
1.2 現象のオプション
設定ファイルのスイッチで、次の現象を足せます。括弧内は既定の強さです。
- 調子 (form): 大会ごとに \(N(0, 0.3^2)\) を強さに足す。
- 相性 (matchup): 選手ごとに 8 次元のベクトル \(A_i, B_i\) を持たせ、\(\gamma_{ij} = \kappa (A_i \cdot B_j - A_j \cdot B_i)\) を足す。読み下し: 「i は j に強いが j は i に弱い」が必ず対になる (\(\gamma_{ij} = -\gamma_{ji}\)) 相性。\(\kappa = 0.3/\sqrt{16}\) で、\(\gamma\) の標準偏差が 0.3 になるように正規化します。
- 強さの変化 (drift): 強さが時定数 720 日でゆっくり揺れ動く (平均に戻る性質を持つ確率過程。定常状態の標準偏差は 0.5)。
- 参加頻度の差 (participation): よく出る人と滅多に出ない人がいる (対数正規の重み、\(\sigma = 1\))。
- 地域 (regions): 8 地域。他地域の大会には出にくい (確率 0.03)。全国大会は 1 割の確率で開かれ、他地域からも出やすく (0.35)、規模が 2 倍。
- 疲れ (fatigue): 大会内の消化試合数 × 0.03 を強さから引く。
- 敗者側の気持ち (pressure): 敗者側の試合で、選手固有の \(\delta_u \sim N(0, 0.3^2)\) を足す (強くなる人も弱くなる人もいる)。
- 待ち時間 (waiting): グランドファイナルで勝者側で待っていた側に −0.3。
- 勢い (runform): 大会内の勝ち数 × \(\rho_u\)、\(\rho_u \sim N(0, 0.06^2)\)。
- キャラ相性 (charmu): 26 のキャラの組ごとの相性 (0.5 の大きさ)。
- 強い人ほど大きい大会に来る (attraction): 100 人以上の大会で、参加の重みに \(e^{0.6\,\theta}\) を掛ける。読み下し: 強さが 1 高い人は約 1.8 倍出やすい。
- 平日は練習 (seriousness): 平日大会では強さの差を 0.7 倍に縮め、\(N(0, 0.4^2)\) のノイズを足す。
- シードの出所 (seeding): トーナメント表を組むシードを、でたらめ / 真の強さ / 調子込みの真の強さ / 逐次 Elo / 逐次 JJPR / SPSP 本番エンジンの自己シード / 直対単体の自己シード、から選ぶ。
1.3 使った世界
basic (現象なし、でたらめなシード)、現象を 1 つだけ入れた世界、reality (全現象 + 強い人ほど大きい大会 + 平日は練習 + Elo シード)、majors (全大会 100 人以上・全大会週末開催・強い人ほど大きい大会 + Elo シード)、シードの出所を替えた世界 (真の強さ / Elo / SPSP / 直対単体 / JJPR)。乱数の種は 42 を基準に、43 と 44 でも同じ結論が出ることを確かめました。
2. 大会の作り方とトーナメントの再現
大会は週あたり平均 3 回 (ポアソン分布) で、78〜104 週。参加者数 \(n\) は 16〜512 人で、確率が \(n^{-1.8}\) に比例 (大きい大会ほど少ない)。ローカル大会は確率 0.75 で週末開催。参加者は重み付きのくじ引きで、重みは参加頻度・地域・強さ (attraction) で変わります。
トーナメントは本物のダブルエリミネーションを回します (experiments/simlab/bracket.py)。勝者側の各回戦の敗者が敗者側のどこに落ちるかは start.gg の実際の規則と同じにしました (敗者側 2 回戦は完全に反転、3 回戦は半分ごとに反転、4 回戦は半分を入れ替え、5 回戦以降はそのまま)。グランドファイナルのリセットも入れ、不戦勝は 2 のべき乗に足りない分をシード上位に配ります。最終順位はダブルエリミネーションの帯 (1, 2, 3, 4, 5, 5, 7, 7, 9, 9, 9, 9, …) で付けます。サイトのシードツールが使う bracket_core.js と、16 通りの人数で試合の組み合わせと順位が完全に一致することを確かめてあります。
初期版は敗者側の落ち方を単純にしていました。その近似は「シードを組んだ側が悪く見える」歪みを実際より大きく出しており、実際の規則に直すとその歪みはほぼ消えました。トーナメントの細部が指標の性質に効く、という例です。
3. ランキング方式の実装
各方式は大会を時間順に処理します。まずその大会の直前の状態で参加者を並べてシードを出し、それから大会の結果を学習します (予想してから学習する、現実の運用と同じ順番)。評価では序盤 25% の大会を捨てます (学習が浅い期間を除くため)。
- SPSP 本番 (ens / tj / bt): 本番の計算 (
learn_bt_lv_shared_g2_pure) をexperiments/simlab/prod_adapter.pyから呼びます。実際のデプロイと同じ 5 段階のレベル、週末だけ直対評価を更新する仕組み、順位評価 (tjpr) と直対評価 (Glicko-2) の組み合わせをそのまま使います。ens (組み合わせ)、tj (順位単体)、bt (直対単体) の 3 つの並びを得ます。 - Glicko-2 (素): 初期値 1500、不確かさ 350、変動性 0.06、\(\tau = 0.5\)。週末の仕組みなどは無し。
- Elo (素): 標準の Elo。
- Bradley-Terry 最尤 (btmle): 全履歴から強さを一括で推定する。シードを出す直前に、それまでの全試合から推定し直す。
- JJPR: 大会ごとの (参加人数, 順位) から点数を作り、直近 2 年の上位 6 大会の合計 (実データの JJPR と同じ点数の式)。
- 参照用: 実際に表を組んだシード、真の強さ、調子込みの真の強さ、でたらめ。
出力は実データの評価と同じ形式のファイル (大会ごとの順位、各方式のシード、試合、真の強さ) にまとめ、指標の計算 (experiments/metrics/seed_metrics.py) をそのまま使い回します。
4. 測り方
4.1 真の品質
方式の予想の並びと、真の強さの並びで、2 人組の上下が一致した割合 \(g\)。全大会をまとめて計算します。
4.2 指標の妥当性・検出力・歪み
定義と測り方は別記事の 3 節と同じです。要点だけ書くと: 妥当性は「指標で方式を並べた順が真の品質の順と合うか」(Spearman 相関)。検出力は「真の強さにノイズ \(\varepsilon\) を混ぜた少し悪い予想を、何大会で見分けられるか」(大会単位の対応付き \(t\))。歪みは「真の品質が同じで表と無関係な並び」を較正して作り、表を組んだ方式との差を見る (大会単位の対応付き \(t\))。
4.3 帯別
帯は 2 通りです。真の強さの順位の帯 (合成世界だけで使える) と、その大会の最終順位の帯 (実データと同じ)。帯の中の PA、帯の中の選手同士の試合の FWR と ULG (PA = ペア一致率、FWR = 上位シード勝率、ULG = アップセット幅。定義は記事「評価指標の評価と整理」の 2 節)、上位 k 人同士の試合、を対応付き \(t\) で比べます。「帯別の真値との照合」では、帯の中で「予想の並びと真の強さの並びが一致した割合」を正解にして、観測指標 (帯内 PA、帯内試合 FWR) が真の差をどれだけ写すかを見ます。
5. 結果
5.1 現象のない世界での方式の真の品質
basic 世界の \(g\): Glicko-2 0.841、Elo 0.827、最尤 0.825、直対単体 0.808、SPSP 0.800、順位単体 0.785、JJPR 0.756。試合を直接学習する方式が強く、順位に丸める方式は不利です。実データで見える「SPSP > 直対単体」「順位単体が Glicko-2 と互角」は、この世界では再現しません。
5.2 順位評価が実データで強い理由
現象を 1 つずつ入れて調べたところ、「強い人ほど大きい大会に来る」を入れたときだけ、順位単体が +0.042、JJPR が +0.071 伸び、Glicko-2 との差が半減し、「SPSP > 直対単体」が初めて再現しました。疲れ、敗者側の気持ち、待ち時間、勢い、キャラ相性の 5 つでは順位評価は復活しません。順位評価が使っている情報は「大会の規模と実力の相関」で、規模で重み付けした順位点がその構造を取り込んでいる、というのが答えです。方式を比べる世界には、この現象が必須です。
5.3 週末だけ本気の世界と、大きい大会だけの世界
「平日は練習」の世界では、平日の試合を直対評価から外す SPSP の仕組みが、外さない素の Glicko-2 より劣化を半分に抑えました。reality 世界では Glicko-2 と SPSP の差は +0.022 まで縮みます。majors 世界 (全大会 100 人以上) では全方式の \(g\) が約 0.92 まで上がって SPSP が首位になり、検出力は 3〜5 倍 (PA 4.4 倍、ULG 3.0 倍) になりました。トップ 50 だけを劣化させた検出は、大会規模がまちまちな basic では帯内 PA でも \(t \le 2.7\) ですが、強い人が大きい大会に集まる attract 世界で大きい大会だけに絞ると \(t = 3.6\) に届きました。
5.4 指標の性質
数表は別記事の 4 節にあります。要点: 妥当性は FWR、PA、ULG、MASPR (平均絶対 SPR) が 0.97〜0.99 で安定し、確信度 AUC、アップセット 1 件あたりの UF、全試合の回戦差 は世界によって崩れる。検出力は ULG が最も高い (\(\varepsilon=0.2\) で \(t=6.1\)、逐次検定で中央値 16 大会)。歪みは順位系が +5.8〜+8.9 (表を組んだ側が良く見える)、勝敗系と ULG は歪まず、生の目盛りのギャップ系は −1.7〜−3.5 (悪く見える)。
5.5 シードによる 3 つの歪み
- 水準: シードをでたらめ → Elo → 真の強さと替えるだけで、全方式の観測 PA が +0.05〜0.075 上がる。
- 不戦勝の床: でたらめなシードの PA は 0.525 (真の品質は 0.5)。2 のべき乗ちょうどの大会では 0.500、不戦勝の多い人数では 0.514。
- 表を組んだ側の有利: SPSP が自分でシードを組む世界では、真の品質は Elo > 直対単体なのに、観測の PA では直対単体 > Elo に逆転し、Glicko-2 の優位は 3 分の 1 に縮む。勝敗系では逆転しない。
5.6 表を組む方式を替えた 5 つの世界
各方式の観測 PA が「較正した無関係な並び」よりどれだけ高く出たか (ポイント):
- JJPR でシード: JJPR +4.5 > 順位単体 +3.5 > SPSP +2.9 > 直対単体 +2.3 > Glicko-2 +1.9
- SPSP でシード: SPSP +3.3 が首位
- 直対単体でシード: 直対単体 +3.0 が首位
- Elo でシード: Elo +2.5 > Glicko-2 +1.6 > 直対単体 +1.3 > SPSP +1.1 > 順位単体 +1.0 > JJPR +0.9
- 真の強さでシード: 全方式 −0.3〜−0.6 (差なし)
読み方: 一番盛られるのは表を組んだ方式で、次にそれに似た方式です。組んだ方式自身の盛られ幅は JJPR > SPSP > 直対単体 > Elo の順で、シードが大会の順位から直接作られているほど順位系の指標との結びつきが強くなります。真の強さでシードすると、Glicko-2 が毎回持っていた +1.6〜2.0 の底上げ分まで消えます。ここから、盛られる正体は「シードの見誤りを方式が共有していること」だと言えます。見誤りのないシードでは、共有するものがありません。
5.7 帯の中の信号
真の強さでシードした世界で、真の品質差 4.8 ポイントの 2 方式 (Glicko-2 と JJPR) を帯の中で見分ける \(t\) は、MASPR が 9.5 (1〜8 位) から 2.8 (129 位以下)、ULG が 6.5 から 2.7、PA が 4.3 から 2.6、FWR が 3.6 から 0.4 (途中の帯では −0.5) でした。帯で切ると帯の中の実力差が小さくなるので、どの指標も真の差を強く縮めます (basic 世界で真の差 +4.7 ポイントが観測では ±0.5 に)。帯の中の「同着」は、差がないことの証明ではなく、見分けられないと読むべきです。
5.8 帯別の真値との照合
実データの中間の帯で「順位系は SPSP が良く、勝敗系は同着」と割れる現象について、2 つの仮説を突き合わせました。仮説 1 は順位系の歪み、仮説 2 は疲れなどの現象が勝敗系を壊している、です。疲れ・敗者側の気持ち・待ち時間を 1 つだけ入れた (でたらめなシードの) 世界では、帯内の FWR の歪みは basic と同じ水準で、仮説 2 は支持されません。シードの出所がある世界 (SPSP 自己シード、reality) でだけ、帯内の PA が真の −5 ポイントを +1.3 に反転させたので、原因は仮説 1 の順位系の歪みでした。
6. 追試と、うまくいかなかった試み
- 乱数の種を替えた再現: 42 / 43 / 44 で主な結論 (指標の種類ごとの性質、attraction の効果、自己シードの逆転) がすべて再現しました。
- 逆確率重み付け (IPS) で歪みを補正する試み: 失敗でした。試合が実現する確率のモデルが合わず、ギャップ系の歪みが +3.0 から +6.5 に悪化し、ULG の検出力が 6.1 から 1.6 に落ちました。うまくいかなかった結果として記録しています。
- 自己シードの繰り返し: 自己シードで大会を組み、その結果で学習し、また自己シードで組む、を繰り返すと、歪みはシードの質が上がるにつれて増えた後、頭打ちになります (PA の \(t\) で +4.1、+5.7、+5.6 と推移)。雪だるま式には増えませんが、1.5 季分の観察です。
- トップを重視した指標: 採用する価値は低いと判断しました。トップ帯は majors 世界か大きい大会に絞って、帯の中で測るほうが良い。
- 逐次検定 (SPRT): 採用する価値があります。ULG なら中央値 16 大会で実験を判定でき、順次データが来る運用に向く。
- MASPR の階段をなくした版 (対数順位差): MASPR と相関 0.99 で、ほぼ同じ情報。検出力はわずかに上、歪みは全指標で最悪 (+8〜9)、深い帯では階段のある w2w のほうがノイズを潰す分むしろ良い。置き換える価値はありません。
7. 限界
- 真の強さは 1 つの数です。「トーナメントを走る力」やプレッシャーへの強さが選手の安定した特性なら実力の一部ですが、正解側には入っていません (現象として一部は足しましたが、正解の定義は \(\theta\) のままです)。
- 強さの散らばり、規模の分布、地域の構造は現実に寄せた近似で、絶対値を一致させることは目的にしていません。分かるのは指標や方式の性質です。
- 合成世界の大会は最大 512 人で、実データの 513 位以下の帯に直接対応するものはありません。
- 1 つの世界の帯別のセルは大会数が少なく (誤差 ±2〜4 ポイント)、多くの分け方を試すと偶然の勾配が出ます。分解で見つけた傾向は、細分・期間分割・連続版の検定・不一致だけの二項判定を通してから報告する運用にしています。
8. 再現手順
cd spsp-ranking
# 世界の生成 → 各方式の学習 → 本番エンジンの適用 → まとめ → 解析とレポート
sh experiments/simlab/run_all.sh experiments/simlab/configs/basic.json # runs/basic/REPORT.md
sh experiments/simlab/run_all.sh experiments/simlab/configs/full.json
# 自己シード世界 (毎大会それまでの結果で本番エンジンを学習してシードを組む。中断と再開ができる)
python3 experiments/simlab/gen_selfseed.py --config experiments/simlab/configs/selfens.json --max-sec 240 [--seed-by bt]
sh experiments/simlab/run_all.sh experiments/simlab/configs/selfens.json
# 歪みの測定 / 方式ごとの盛られ幅 / 帯別 / 帯別の真値との照合
python3 experiments/simlab/bias_metrics.py --run experiments/simlab/runs/selfens
python3 experiments/simlab/favoritism_by_method.py --run experiments/simlab/runs/jjprseed --methods ens bt tj jjpr glicko2
python3 experiments/simlab/band_metrics.py --run experiments/simlab/runs/majors --bands 8 16 32 64 128
python3 experiments/simlab/band_truth_check.py experiments/simlab/runs/selfens experiments/simlab/runs/reality experiments/simlab/runs/seedtrue
# 横断レポート
python3 experiments/simlab/sweep_report.py --runs experiments/simlab/runs/basic experiments/simlab/runs/attract ...
各段階は 240 秒で自動的に中断し、再実行で続きから再開します (共有ホストの CPU 時間の制限のため)。生成したレポート (横断レポート、帯別、追試、先行研究の調査、解説) は現在は非公開です。