競馬AI予測

【競馬AI #25】8日連続ゼロ的中で点検。犯人は「バグ」ではなく「札幌」だった


前置き:基準に到達した

前回(#24)、実運用6日連続ゼロ的中(37件全ハズレ)について、自分で決めた基準(連続ゼロ7日、累計50件)にあと一歩というところまで来ていることを書いた。

そして8月29日、30日の結果が出た。

8月29日:4件推奨 的中0
8月30日:2件推奨 的中0

またゼロ。

8月8日から数えて、8日連続、累計43件、すべてハズレ。

基準2(連続ゼロ的中7日)を、ついに超えた。約束通り、ここで一度立ち止まって点検する。


まず数字で確認:今度は「偶然」で片付けられるか

いつも通り、感情ではなく数字で確認する。

41日間バックテストの「1日あたりゼロ的中率」68.3%を使うと、8日連続ゼロが起きる確率は次の通りだ。

0.683 の8乗 ≈ 4.7%

前回(6日連続)の10.2%から、さらに下がった。約20回に1回の水準まで来ている。

だが、今回はもう一つ別の角度からも計算してみた。「1日単位の連続」ではなく、「43件のベットを、それぞれ独立した試行として」見た場合だ。バックテストの全体的な的中率9.6%を前提にすると:

(1 - 0.096)^43 ≈ 1.3%

こちらの見方だと、100回に1〜2回しか起きない計算になる。

どちらの計算が「正しい」かは一概には言えない(1日の中の複数ベットは、開催場や馬場状態が共通するため完全に独立ではない)。だが、どちらの見方でも「もう単なる偶然で済ませるのは苦しい」水準に入ってきたことは、はっきりしていた。


掘り下げる:EV帯別・開催場別に分解する

前回の記事で決めた通り、基準2に到達した時点でやることは決まっていた。

1. データをEV帯別・開催場別に分解する
2. 41日間バックテストの内訳と比較する
3. 「条件を変える理由」が明確に見つかれば調整する

まずEV帯別。

EV 3.50-4.00:18件 的中0
EV 4.00-4.50:18件 的中0
EV 4.50-5.00: 7件 的中0

全帯で均等にゼロ。特定のEV帯だけが悪いわけではなかった。EVの閾値設計自体に、明確な問題は見当たらない。

次に開催場別。ここで、はっきりした偏りが見つかった。

札幌:22件 的中0
中京:11件 的中0
新潟:10件 的中0

43件中、実に22件(51%)が札幌だった。


犯人は「バグ」ではなく「検証不足」だった

ここで、41日間バックテスト(2〜7月)での開催場別の内訳を見返した。

【41日間バックテストでの開催場別サンプル数】
阪神:35件 中山:20件 京都:20件 福島:23件 東京:23件
小倉:15件 中京:14件 新潟:17件 函館:10件
札幌:わずか1件

札幌だけ、サンプルが1件しかなかった。

理由は単純で、僕がバックテストに使った2〜7月のレースカードは、たまたま札幌開催をほとんど含んでいなかった(札幌は主に夏開催)。つまり、回収率122.2%という数字は、札幌でどう機能するかをほとんど検証しないまま出した数字だった。

一方、中京・新潟は的中こそゼロだが、バックテストの的中率(それぞれ7.1%・5.9%)から期待される的中数を計算すると、11件で0.8件・10件で0.6件程度。この規模でゼロが出ること自体は、十分「よくあること」の範囲内だ。

つまり今回の8日連続ゼロは、「モデルにバグがある」わけでも「EVの閾値設計が間違っている」わけでもなく、**「検証していない条件(札幌の夏開催)に、無自覚に実戦投入してしまっていた」**という、見落としに近い話だった。


対策:札幌を一時除外する

条件(EV3.50〜5.00・既知馬限定)そのものを変える理由は見当たらなかった。だが、「札幌が未検証である」という事実には、明確に対応する理由がある。

そこで、次の対応を取ることにした。

✓ 札幌開催を、推奨対象から一時的に除外する
✓ 中京・新潟など、他の開催地は従来通り継続する
✓ 過去の札幌開催データが手に入り次第、札幌単独でバックテストを行う
✓ バックテストで妥当性が確認できた時点で、除外を解除する

推奨スクリプトに、除外リストを明示的に追加した。

EXCLUDED_VENUES = ["札幌"]

prob_filtered = df[
    (df["normalized_prob"] >= MIN_PROB)
    & (~df["is_new_horse"])
    & (~df["venue_clean"].isin(EXCLUDED_VENUES))
].copy()

テスト実行したところ、8月1日のレースカードで札幌の133頭が正しく除外され、他の開催地はそのまま推奨対象に残ることを確認した。


なぜ「EVの閾値を変える」ではなく「開催場を除外する」を選んだか

正直、43件全ハズレという結果を見て、まっさきに頭をよぎったのは「EVの下限をもっと上げるべきでは」という考えだった。だが、それは選ばなかった。

理由は、EV帯別の内訳が全帯で均等にゼロだったからだ。もしEV4.50〜5.00の帯だけが悪いなら、閾値を見直す理由になる。だが今回はそうではなく、開催場という別の軸に、はっきりした偏りがあった。

原因がはっきり特定できていないのに、原因と関係ない場所(EVの閾値)をいじるのは、これまで一番避けてきたことだ。171.5%→92.6%の顛末で学んだのは、「効きそうな理由」ではなく「データが示す理由」で判断することだった。今回もその原則を守った。


Data Scientistとしての振り返り

前回、「基準を決めることの価値は、基準に達していないときに自分を止められることにある」と書いた。

今回はその続きとして、「基準に達したときに、何を点検するかを先に決めておくことの価値」を実感した。

もし点検の手順を決めていなかったら、8日連続ゼロという結果を見た瞬間に、感覚的に「EVを厳しくしよう」と条件を変えていたかもしれない。だが、決めていた手順(EV帯別・開催場別への分解)に従ったことで、本当の原因——札幌の検証不足——にたどり着けた。

「何かがおかしい」と感じたときに、まず分解する。 これは今回、あらためて有効性を確認できた手法だった。


技術ノート

実運用累計成績(2026年8月30日時点、対策前)

実運用日数:8日
推奨件数 :43件
的中数  :0件
回収率  :0%

内訳(開催場別):
  札幌:22件 的中0
  中京:11件 的中0
  新潟:10件 的中0

内訳(EV帯別):
  3.50-4.00:18件 的中0
  4.00-4.50:18件 的中0
  4.50-5.00: 7件 的中0

統計的評価

8日連続ゼロの確率(日単位、p=68.3%想定)  :約4.7%
43件全ハズレの確率(ベット単位、p=9.6%想定):約1.3%

修正ファイル

recommend_phase3_v3_6_hybrid_v2_COMPLETE.py
  STEP 8:EXCLUDED_VENUES = ["札幌"] を追加。
          札幌開催をprob_filteredの生成段階で除外。
          (EVの閾値・既知馬限定条件は変更なし)

次回の予定

札幌を除外した状態での実運用を継続し、中京・新潟など他の開催地で的中が出るか、回収率が改善するかを記録していく。

あわせて、札幌の過去データが入手できれば、単独でバックテストを行い、除外を解除できるかどうかを検証する。


【競馬AI開発記】次回予定:

  • Day 26:札幌除外後の初回結果報告
  • Day 27:札幌データが入手できた場合の単独バックテスト結果

-競馬AI予測