「京都だけ壊れた」原因を95.6%の馬で測定した結果
── venue_correction.csv は「本命を当てる」より「3連複を組む」補正だった ── シリーズ第四弾
Python / アルゴリズム / データ分析 / 機械学習 / 競馬AI
対象期間:2026年5月24日〜5月31日 制作者:yuji
このシリーズについて
V1:作った
V2:検証した
V3:「ローテーション補正はノイズだった」
V4:「ダークマター係数を実装したら京都だけ壊れた」
V5:「壊れた理由を、実際のレース結果で測定した」
V4記事は投稿2.5時間で92ビューを記録した。あの記事で書けなかったことがある。なぜ京都だけ壊れたかを、数字で証明すること。 V5はその答えを出すフェーズだ。
71歳・隠居・Ubuntu・競馬AI。今回も変わらない。変わったのは補正値の作り方だ。
V3・V4との違い:補正の作り方が逆転した
| 期間 | 読者が覚えること | 補正の作り方 |
|---|---|---|
| V3 | ローテーション補正はノイズだった | 仮説 → 実装 → 検証 |
| V4 | 京都だけ壊れた | 仮説 → 実装 → 検証 |
| V5 | 補正値をデータから作るようになった | 結果 → 分析 → 係数算出 → 検証 |
順序が逆転した。これがV5の本質だ。
V3・V4では「こうなるはず」という仮説を先に立てて実装していた。V5からは「実際にどうなったか」というデータを先に集め、そこから係数を逆算している。半導体製造ラインの品質管理と同じ思想——データが出てから判断する。
V5期間の核心実装:venue_correction.csv の自動設計
venue_correction.csv とは何か
競馬場 × 騎手ランク(S/A/B/C)の組み合わせごとに補正係数を持つCSVファイルだ。「ダークマター係数(場*C)」と「騎手ランク係数」を統合した補正テーブル。
会場,ダークマター,ランクS,ランクA,ランクB,ランクC
東京,1.025,1.028,0.700,1.212,0.815
京都,0.896,1.048,1.244,0.700,0.788
V4期間はこれを手書きで編集していた。それが事故の温床だった。
No.26:hosei_mixer への venue_correction 設計モード追加
V5最大の実装はこれだ。
def auto_scan_venue(self):
"""
MIXER_INPUT_DIR_KAKUTEIの全hikaku xlsxを走査し
競馬場×騎手ランク別の乖離率を自動集計して
係数推奨値を算出する
"""
files = list(Path(MIXER_INPUT_DIR_KAKUTEI).glob("*.xlsx"))
print(f"走査対象: {len(files)}件")
venue_stats = defaultdict(lambda: defaultdict(list))
valid_count = 0
for f in files:
try:
df = self._load_hikaku_sheet(f)
if df is None:
continue
valid_count += 1
self._aggregate_venue_rank(df, venue_stats)
except Exception:
continue
print(f"有効データ: {valid_count}件")
return self._calc_recommendations(venue_stats)
487件のhikaku xlsxを自動走査、481件有効(98.8%成功率)。
手書きCSV編集という危険な作業を完全に廃止した。basho_correction_seiki/kensyou.csv(全場1.000で形骸化していた)も同時に廃止。メニュー構成・自動集計ロジック・廃止処理まで一気通貫で設計・実装した。
No.27:_get_jockey_rank_map バグの発見と修正
No.26の初動実行で即座にバグを発見した。
症状: 487件走査・481件有効を確認。ダークマター係数は正常集計されたが、ランクS/A/B/Cが一切表示されない。
原因:
# バグのあったコード(No.26)
def _get_jockey_rank_map(self, wb):
# 「予想結果」シートを探していた
if "予想結果" not in wb.sheetnames:
return {} # ← 常にここで空を返していた
実際のhikaku xlsxは「完全比較」シート1枚構成。「予想結果」シートは存在しないためrank_mapが常に空{}となり、ランク集計がゼロのまま推奨係数が表示されなかった。
修正(No.27):
# 修正後:_get_rank_map_from_hikaku()を新設
def _get_rank_map_from_hikaku(self, wb):
# 「完全比較」「比較」を含むシートを優先して探す
target_sheet = None
for name in wb.sheetnames:
if "完全比較" in name or "比較" in name:
target_sheet = wb[name]
break
if target_sheet is None:
return {}
# そのシート内の「騎手ランク」列から {馬名: ランク} を取得
rank_map = {}
headers = [cell.value for cell in target_sheet[1]]
rank_col = headers.index("騎手ランク") if "騎手ランク" in headers else None
if rank_col is None:
return {}
for row in target_sheet.iter_rows(min_row=2, values_only=True):
horse_name = row[0]
rank = row[rank_col]
if horse_name and rank:
rank_map[horse_name] = rank
return rank_map
「動いているように見えたが実は集計ゼロだった」——V4の「設計と実装のズレを深夜3時に発見した」と同じパターンだ。確認文化がなければ見落としていた。
実戦検証:46レースで測定した
検証設計
| 項目 | 内容 |
|---|---|
| 検証期間 | 2026/05/29(土)〜 05/31(月) |
| 対象レース | 東京・京都 各23R、計46R |
| 比較対象A |
seiki_v5.py(venue_correction.csv なし) |
| 比較対象B |
reisugo_v58.py(venue_correction.csv あり) |
| 検証指標 | ◎1着的中率・3着内率・上位3頭含有率・場*C変化量 |
主要KPI
| 指標 | seiki(補正なし) | reisugo(補正あり) | 差 |
|---|---|---|---|
| ◎1着的中(46R) | 13/46 28.3% | 10/46 21.7% | −6.6pt |
| ◎3着内(46R) | 23/46 50.0% | 20/46 43.5% | −6.5pt |
| ◎5着内(46R) | 29/46 63.0% | 27/46 58.7% | −4.3pt |
| 上位3頭に1着馬(46R) | 19/46 41.3% | 20/46 43.5% | +2.2pt |
| 場*C影響馬数(2日計) | — | 656/686頭(95.6%) | — |
◎1着的中率はseikiが優位。しかし「上位3頭に1着馬が含まれる率」はreisugoが上回った。
3連複の観点では、補正ありのreisugoが有利だ。これが今回の最重要示唆だ。補正は「本命を当てる精度」より「3連複を組む精度」を上げる方向に機能している。
最重要発見①:場*C中央値が2日間完全一致した
| 指標 | 5/29(土) | 5/31(月) | 評価 |
|---|---|---|---|
| 場*C平均変化(全体) | −0.167 | −0.151 | 下方偏重は再現 |
| 場*C中央値 | −0.260 | −0.260 | 2日間完全一致 → 構造的 |
| 東京 場*C平均変化 | −0.090 | −0.059 | 5/31で縮小 |
| 京都 場*C平均変化 | −0.240 | −0.267 | 5/31でさらに拡大 |
| 場*C変化馬数 | 331/348(95.1%) | 325/338(96.2%) | 全域カバーで一貫 |
中央値−0.260が2日間完全一致。これは偶発ではなく構造的な特性だ。
686頭中656頭(95.6%)に補正が影響している。venue_correction.csvは「一部の馬だけ補正する」のではなく、ほぼ全馬の指数を動かしているということだ。
最重要発見②:京都と東京は構造的に別物だった
競馬場別3着内率
| 場 | seiki 3着内 | reisugo 3着内 | 評価 |
|---|---|---|---|
| 東京(23R) | 48% | 39% | seiki優位 ⚠️ |
| 京都(23R) | 39% | 48% | reisugo逆転 ✅ |
京都では補正が機能し、東京では補正が逆効果だった。
場*C平均変化で見ると、京都は東京の2.5〜4.5倍の変動幅がある。2日連続でこのパターンが再現された。venue_correction.csvの京都補正の強さは偶発ではない。
- 京都での◎変更19件:reisugo 5勝 / seiki 2勝
- 東京での◎変更(東京12R・4R・6R):seikiがすべて1着
東京の補正係数は現時点で過剰と判断せざるを得ない。
クラス別でも違いが出た
| クラス | seiki 3着内 | reisugo 3着内 | 評価 |
|---|---|---|---|
| 未勝利(17R) | 59% | 65% | reisugo優位 |
| 1勝クラス(10R) | 50% | 30% | seiki優位 |
| 2勝クラス(4R) | 25% | 25% | 同率 |
| 特別/重賞(15R) | 27% | 33% | reisugo優位 |
未勝利と重賞でreisugoが優位、1勝クラスでseikiが優位。係数のクラス適合に偏りがある。 これは次の課題だ。
最重要発見③:ランクBへの −46.2% ペナルティが完全に逆効果だった
05/26の係数修正(再確認)
| ランク | 変更前 | 変更後 | 変化率 | 方向の根拠 | 実際の結果 |
|---|---|---|---|---|---|
| S(川田・ルメール等) | 0.762 | 1.048 | +37.5% | 乖離率0.54 > 基準0.411 | 方向正 ✅ |
| A(岩田望・横山和等) | 0.750 | 1.244 | +65.9% | 乖離率0.59 > 基準0.443 | 幅過大の疑い ⚠️ |
| B(武豊・三浦等) | 1.300 | 0.700 | −46.2% | 乖離率0.20 < 基準0.400 | 逆効果確認 ❌ |
| C(大多数) | 1.054 | 0.788 | −25.2% | 乖離率0.58 < 基準0.611 | 要継続観察 |
seikiのランクB◎:1着的中75%、3着内100%(4R)。
武豊・三浦皇成・松若風馬・鮫島駿。これだけの実績騎手の馬を−46.2%でペナルティすれば、当然指数が不当に下がる。V5期間の事前分析でも「Bランクへの+10%ブーストが過剰」と判断していたが、係数修正で逆方向に振りすぎた。
N=4のため断定はできないが、方向として逆効果は明白だ。
週明け検証:事前設定した懸念の答え合わせ
| 検証対象 | 事前懸念 | 実着順 | 判定 | 解釈 |
|---|---|---|---|---|
| 京都1R マクローリン | ランクA+65.9% → 11番人気◎ | 7着 | 過剰上昇確認 | ランクA過学習 |
| 京都12R カワキタマナレア | 同上 → 11番人気◎ | 2着 | 3着内的中 | 低人気馬の好走。一部有効性あり |
| 京都3R ワルトシュタイン | 同上 → 13番人気◎ | 3着 | 3着内的中 | 補正が実力を捉えた可能性 |
| 京都12R ブルクトーア | 同上 → 10番人気◎ | 7着 | 過剰上昇確認 | 懸念が的中 |
| 東京8R アバンデル | データ解禁 指数0→13.82 | 2着 | 圏外で好走 | seikiでは無効化。データ解禁問題は実際に影響あり |
| 東京11R ショウナンガルフ | データ解禁 指数0→10.90 | 17着 | 両者外れ | データ解禁が必ずしも有効でないことも確認 |
ランクA過剰上昇:4件中2件的中・2件外れ。「過学習」とも「本物の補正」とも言い切れない結果。
これが正直なところだ。追加サンプルが必要。N≧10になった時点で再評価する。
総合評価
| 取り組み | 判定 | 有効度 | コメント |
|---|---|---|---|
| venue_correction.csv の有意差 | ✅ 確定 | ○ | 95.6%の馬に影響。補正の存在意義は明確 |
| 京都の補正(場*C中央値一致) | ✅ 確定 | ○ | 2日完全一致で構造的再現性確認 |
| 京都補正(05/26修正後) | ✅ 達成 | ○ | 3着内率で明確に改善(39%→48%) |
| 未勝利クラスでのreisugo優位 | ✅ 確認 | ○ | ダークマター補正が未勝利馬の実力差を捉えやすい |
| 3連複軸としての上位3頭精度 | ✅ 確認 | ○ | ◎不変レースで48.1%(vs seiki 40.7%) |
| 東京補正 | △ 要見直し | △ | ◎変更が裏目多数。現状は補正なしの方が良い |
| 騎手ランクA係数(+65.9%) | △ 保留 | △ | 低人気馬の好走例あり。過剰上昇も同数で保留 |
| 騎手ランクB係数(−46.2%) | ❌ 要再検討 | ✕ | N=4で3着内100%。逆効果の疑いが強い |
| S・Aランク騎手の◎選出増加 | ✅ 達成 | ○ | 2日連続でODT目的(1番人気への集中緩和)を確認 |
V6期間への引き継ぎ:次の3手
最高優先度(緊急)
ランクB係数を 0.700 → 0.90〜0.95 に緩和。
N=4でも逆効果が示された。判断基準:N≧10でランクB 3着内率が改善傾向を確認するまで継続。
高優先度
東京S/A係数を1.0付近に抑制。
東京◎変更レースでreisugoが5割以上勝利するまで補正を絞る。段階的な緩和が現実的だ。
高優先度
ランクA係数を段階適用(0.75 → 0.95 → 1.244)またはBayesian収縮で幅を抑制。
N=25でのサンプル推定値として+65.9%は変化幅が大きすぎる。小標本問題への対処が必要だ。
このプロジェクトで守り続けているルール
V1から変わっていないものがある。
# V5期間 確定コーディングルール(V4より継続 + 1件追加)
① 修正のたびNo.XX採番・省略禁止
② 冒頭コメントはgrepで実コード照合してから更新
③ py修正後はscan_dependencies.py実行
④ バッチフォルダ更新時は _batch_dirs 同時更新(絶対ルール)
⑤ 新CSV導入前に「全馬一律乗算では順位に影響しないか」確認
⑥ 作業前に必ず確認する──「取り掛かっていいですか?」(V5追加)
⑥はこの一言から生まれた。
「会議で知ってるふりして確認もしないで、あとでドツボにハマりそうになったことあるからね。重要なのは経験済み」
半導体製造業の経験則だ。表面上動いているように見えても、深く確認することで設計と実装のズレを発見できる。No.27の「完全比較シート問題」も、⑥がなければ見落としていた。
おわりに:V5で読者に覚えてほしいこと
補正値を感覚ではなく結果から作るようになった。
V3では「ローテーション補正はノイズだった」と学んだ。
V4では「京都だけ壊れた」と学んだ。
V5では「壊れた理由をデータで測定し、次の修正を設計した」。
46レースの検証で分かったことは3つだ。
- 補正の方向性は正しい(95.6%の馬に影響・京都3着内率逆転)
- 係数の幅が大きすぎる(特に東京・ランクB)
- 3連複軸としてはreisugoが有利(上位3頭含有率+2.2pt)
次回はこの3点を修正してV6検証に臨む。
競馬データ解析プロジェクト 変革履歴レポート V5
対象期間:2026年5月24日〜5月31日 制作者:yuji
V3記事 → V4記事 → 本記事(V5)