競馬予測AI(機械学習)を作るとき、最初にやるべきなのはモデル選びではなく、「発走前に何が見えていて、なぜその馬を候補にしたか」を後から再現できる記録(判断台帳)を用意することです。これがないと、バックテストの良い数字が実力なのか、偶然なのか、データリークなのかを区別できません。

この記事では、シリーズ第1回として、全馬の判断を残すCSVの列設計、データの置き場所の分け方、発走前オッズの保存方法、自動購入に進まないためのルールをまとめます。予測対象は各馬が複勝圏(3着以内)に入る確率です。

競馬AIの全体フロー

この記事の結論

  • 出力は「買う馬の一覧」ではなく、判断した全馬を1頭1行で残すCSVにする。
  • decision(buy / skip)と reason(理由)を別の列にし、見送りの原因を切り分けられるようにする。
  • 取得した原本・特徴量・判断・精算を別ディレクトリに置き、払戻金などの結果情報が判定に混ざらないようにする。
  • 候補がゼロの日もCSVを出力し、「候補なし」と「処理が動かなかった」を区別する。
  • 自動購入はせず、「候補CSV → 手動確認 → 疑似運用 → 少額検証」の順で進める。

なぜ的中率・回収率だけでは不十分か

的中率や回収率は重要な指標ですが、次のような理由でも「良い数字」は簡単に出てしまいます。

  • 高配当が数回当たっただけ
  • 結果確定後の情報(確定オッズ・払戻金など)が特徴量に混ざった
  • 検証期間がたまたま相性の良い時期だった

そこで目標を「儲かること」ではなく、「同じ判断をあとから再現できること」に置きました。数字の良し悪しにかかわらず理由をたどれれば、自分の検証結果にだまされずに済みます。

補足すると、筆者は学生時代にもスクレイピングから自動購入まで含む競馬AIを作りましたが、精度が足りず買うほど損をする結果でした。今回はその反省から、記録の仕組みを先に作っています。

判断CSVの列設計(全馬1行)

候補にならなかった馬も含め、判断したすべての馬について次の列を残します。

列中身目的
race_id / horse_num競走IDと馬番結合のキー
start_time / predicted_at発走時刻と判定時刻その時点で使えた情報かを確認する
odds_timestamp / minutes_beforeオッズを取得した時刻オッズの鮮度を確認する
p_place / odds_used推定確率と使った複勝オッズ条件を後から再計算する
decision / reasonbuy / skip と、その理由候補と見送りを区別する
model_id / strategy_idモデルと戦略の版比較の条件をそろえる
input_manifest入力ファイルの一覧とハッシュ後日の再実行
decision = {
    "race_id": race_id,
    "horse_num": horse_num,
    "predicted_at": snapshot_at.isoformat(),
    "odds_timestamp": odds_at.isoformat(),
    "p_place": float(p_place),
    "strategy_id": "place-3cond-fuk-1.5-8.0",
    "buy": bool(buy),
    "reason": reason,
    "model_id": model_id,
}

decision と reason を分ける理由

「確率が低くて見送った」のと「オッズが取得できず判断できなかった」のは、意味がまったく違います。理由の列があれば、候補数が急に減ったときの原因を次のように切り分けられます。

増えた reason疑う場所
missing_prerace_oddsデータ収集(モデルの問題ではない)
below_probability_threshold確率の分布、または市場の変化

また、候補がゼロの日もCSVは必ず出力します。出力がないと「候補がなかった」のか「処理が止まっていた」のか区別できないためです。

データの置き場所を用途別に分ける

raw/odds/2023-06-10_10-02-15.csv   # 取得した原本。書き換えない
processed/features_10min.parquet   # 作成時の特徴量スキーマも保存
reports/decisions/2023-06-10.csv   # 候補・見送り・使ったオッズ
reports/settlements/2023-06-10.csv # 結果確定後の精算だけを追記

最も重要なのは、判断CSVと精算CSVを分けることです。同じ表に払戻金や確定オッズがあると、いずれ「便利な列」として学習や判定に紛れ込みます。物理的に分けておけば、この種のデータリークは起きません。

ファイル名にも日付・オッズの時点・モデル・戦略を入れます。

horse_decisions_YYYYMMDD_10min_model-v003_strategy-place.csv

後で「特定の年だけ成績が悪い」と気づいたとき、学習時期の問題か、10分前オッズの欠損か、特定開催の結合失敗かを、ファイル名とログから調べられます。

発走前オッズの保存方法(JRA-VAN DataLab.)

発走前オッズは JRA-VAN DataLab. で保存しています。DataLab. はWindows専用のため、MacではParallels Desktop上のWindowsで定時保存を動かしました。

注意点は2つありました。

  • 手動保存は抜けが出る:数週間たってから欠損に気づいたため、定時実行に切り替えた。
  • 出力ファイル名が固定で上書きされる:取得時刻が分からなくなるため、保存のたびに取得時刻入りのファイル名へ退避するようにした。

「何時の値か」が分からないオッズは、検証に使えません。

自動購入に進まないためのルール

自動化は正しい判断を速く実行しますが、壊れた結合や古いオッズでも同じように速く実行してしまいます。そのため、進め方を次の順に固定しました。

候補CSV → 手動確認 → 疑似運用 → 少額検証 →(条件がそろってから)自動化

失敗時のルールも先に決めています。

  • オッズ取得失敗・ログ出力失敗・レースID不一致のいずれかが起きたら、前回値で続けずに見送り、理由を残す。
  • 予測スコアが高くても、入力の鮮度を証明できなければその判断は無効。

次のような日は精算にも進めません。

  • 1日の候補件数が異常に多い
  • 同じレースに矛盾する判断が複数出ている
  • オッズの時刻が発走後になっている

まとめ:モデル作りに進む前のチェックリスト

  1. 任意の1日について「原本 → 特徴量 → 判断CSV → 精算CSV」をやり直せる
  2. 候補がゼロの日でもCSVが出る
  3. 全行にモデルの版と判定理由が入っている

この3つがそろってから、モデルの精度改善に進みました。この準備があったことで、後の回で出てくる「良すぎる数字」を疑うことができています。

シリーズの他の記事

注意:本記事は競馬データを用いた研究・技術解説です。利益や的中を保証するものではなく、馬券の購入を勧めるものでもありません。