趣味の競馬について、仕組み側の話をまとめてみようと思います。馬券の攻略法ではありません。JRA-VAN データラボを契約し、公式系の蓄積データを PostgreSQL に載せ、その上でランキング学習の予想パイプラインを組んだ、という構成のログです。ギャンブルを勧める意図はありません。どちらかといえば、「きれいな教師データがない趣味領域で、データ基盤とモデルをどう接続するか」のケーススタディです。
なぜスクレイピングから始めなかったか
競馬データを触ろうとすると、最初に浮かぶのは公開サイトのスクレイピングです。手軽ですが、スキーマが安定しない、利用規約と運用リスクが読みにくい、欠損や表記ゆれを自分で吸収し続ける、といったコストが後から効いてきます。趣味でも「毎週壊れるクローラを直す」は本業のオンコールに似て、続きません。
そこで選んだのが、JRA-VAN データラボ経由で整備されたデータベースを、手元の PostgreSQL にインポートする経路です。EveryDB3(以下、EveryDB)と呼ばれる構成で、レース・出走・馬・騎手といった中核テーブルが、比較的決まった物理名(n_race、n_uma_race など)で入ります。EveryDB3 自体はデータラボ会員向けの無料ソフトです。速報系は別プレフィックス、蓄積は別プレフィックス、という整理もあり、機械学習の特徴量抽出と相性がよいです。スクレイピングを完全否定する話ではなく、「履歴の正本は契約データ、不足する当日材料だけ補助」という優先順位にした、ということです。
契約の決め手は、精度というより再現性でした。同じ定義のカラムが翌週も残っている、コード表がある、過去年をまとめて取れる。実験ノートに「いつのサイトHTMLをパースしたか」を書かなくてよい、という安心感は想像以上に大きいです。料金は発生しますが、趣味の時間単価でクローラ保守を続けるより安い、と判断しました。
データの置き場所——Postgres を正とする
mac上に PostgreSQL を Docker で常駐させています。アプリ用ユーザーとは別に、読み取り専用ロール経由で MCP(モデルが SQL を投げるための接続)も用意しました。ここでの意図は単純です。エージェントやノートブックが「勝手に UPDATE する」事故を防ぐことです。趣味のDBでも、本番と同じく権限分離しておくと精神衛生がよいです。
テーブル定義やコード表は、マニュアルを人間が毎回開くのではなく、辞書用のスキーマ(dict)として同じ Postgres に載せています。競馬場コードやトラック種別を推測で書かない、先に辞書を読んでから本体へ SQL を書く——このルールをエージェントにも人間にも共通化するためです。データ基盤の話でありながら、実は「ドキュメントをクエリ可能にする」話でもあります。
結合キーはネット上のレースIDと対応づけやすいよう、年・開催場・回次・日次・レース番号の組を中心に扱います。馬は血統番号、騎手・調教師はそれぞれのコードです。このあたりを曖昧にしたまま特徴量を作ると、リークや二重計上の温床になるので、最初にキー設計を固定したのは結果的に効いています。
インポート作業そのものは華やかではありません。EveryDB 側で構築された内容を Postgres に載せる(mac上の Parallels で Windows 11 を起動して運用)、辞書を別スキーマで投入する、接続文字列と読み取り専用ロールを整える、年レンジや欠損率を一度測る——地味なチェックの連続です。ただ、この地味さが後工程を支えます。特徴量SQLを書くときに「このカラムは何か」で止まらない、開催場コードを推測でハードコードしない、学習期間と検証期間をカレンダーで切れる。基盤が先にあるから、モデルの実験サイクルが短くなります。
何を予測するか——目的関数の決め方
「AIで予想する」と一口に言っても、単勝を当てる分類、複勝確率、回収率最適化など、目的はいくつもあります。ここでは主目的を、「レース内で前に来る馬を精度よく並べる」に置きました。副目的として、後段でオッズと合成して期待値を見る、という二段構えです。重要なのは、学習ラベルにオッズを入れない方針にしたことです。人気当てモデルになると、市場の近似器になってしまい、自分が作りたい「実力寄りの順位付け」からずれます。オッズは評価と意思決定の材料に残し、学習特徴の主軸からは外す——この分離は、いま振り返っても妥当だったと思います。
評価の主指標は、予測上位と実着順の重なり(Top3 系)やランキング指標です。回収率は別レイヤで見る。精度検証と資金シミュレーションを混ぜない、というのはプロダクトのオンライン指標設計に近い感覚です。
分割は必ず時間方向です。ランダム分割は禁止にしています。競馬は時系列そのものなので、未来の走成績や同日の他レース結果が特徴に混ざると、検証精度が嘘になります。「そのレースの開催日より過去の情報だけを使う」は、設計書に一行で書いてあるだけのルールですが、実装で一番守り続けている制約でもあります。
モデルの積み上げ——表から履歴、血統と補助スコアへ
パイプラインは段階的に厚くしました。最初は出走時点の表特徴だけで動くか確認し、次に直近走の履歴を集約した特徴を足して、レース単位のランキング学習(LightGBM 系の Ranker)へ進みます。さらに血統の産駒成績や、公式側で提供されるマイニング系スコアを結合した版も用意しています。ブログではプロジェクト内のフェーズ名は使いませんが、要するに「ベースのランカー → 履歴強化 → 血統・補助スコア強化」という普通の増築です。
距離ドメインも分けています。芝2000、芝1600、芝1200など、コース性質が違う問題を一つのモデルに無理に押し込めないためです。重賞の当日推論では、該当距離向けに学習したモデルで出走表と過去走を Postgres から引き、レース内で偏差値化したスコアとして並びを出します。生のモデルスコアだけでなく、レース内偏差や補助スコアとの合算を見るのは、「その日のメンバーの中での相対位置」を意思決定に載せやすくするためです。最初は芝2000から始め、必要になった距離へ横展開する順番にしたのも、ドメインを一気に広げすぎないための選択でした。
校正(温度スケーリングなど)や、穴馬専用の二値分類など、枝葉も増えていますが、中核はずっと同じです。Postgres から特徴を切り出し、時間方向に分割して学習し、当日は同じSQL経路で推論する。学習と推論でデータ経路が分岐しないこと——ここが、スクレイピング即席ノートより強い点だと思っています。
当日の流れ——DBから並びを出すまで
重賞の当日は、だいたい次の順です。Postgres から対象レースの出走表と、各馬の過去走を引く。距離に対応する学習済みランカーでスコアを出す。レース内で偏差値化し、必要なら補助スコア(対戦・タイム系のマイニングなど)も同じレース内で偏差にして並べる。合算やしきい値で「厚い候補/薄い候補」を見る。そのあと人間側で、馬体重・馬場・オッズ推移を足して最終の並びや券面を決める。
ポイントは、モデルが最終決定者ではないことです。モデルは「メンバー内の相対順位の仮説」を出す層で、当日材料と点数制約を踏まえた意思決定は別レイヤです。プロダクトで言えば、オフラインのランキングモデルと、オンラインのビジネスルール/オペレーションが分かれている状態に近いです。分かれているからこそ、あとから「モデルが悪かったのか、ルールが悪かったのか」を切り分けられます。
運用で見えたこと——スコアと意思決定は別レイヤ
仕組みを通したあとに分かったのは、モデルが「そこそこ正しい並び」を出しても、馬券や最終判断のフィルタで成果が消えることがある、という当たり前の事実です。別記事でも書きましたが、軸と保険が結果の1・2着に重なっていても、3着候補を弱い根拠で消すと組み合わせは全滅します。これは予測誤差というより、制約下のフィルタ設計の問題です。再現率を犠牲にしたルール追加が全体KPIを壊す、というプロダクトの話と同じ構造です。
だからこの基盤の価値は、「当てる」ことだけではありません。再現可能なログを残せることです。どのテーブルから何を切ったか、どのモデル版を使ったか、当日の体重や馬場をどう解釈したか。感情で「運が悪かった/モデルがダメだ」と一括りにせず、レイヤごとに切り分けられる。趣味であっても、その習慣があるだけで次の改善入力になります。
もう一つ、実務寄りの効用があります。エージェント(コーディング支援やSQLアシスタント)にDBを読ませるとき、スキーマが安定し、辞書があり、読み取り専用である、という三点が揃っていると、幻覚混じりの「それっぽいクエリ」が減ります。趣味の競馬DBが、実はローカルLLM/エージェント運用の実験台にもなっている、という側面もあります。データが汚いとモデルが迷う、というのは人間にもエージェントにも同じでした。
まだ足りないところ
速報テーブルの埋まり方、オッズの時系列、天候急変への追従など、当日運用の穴は残っています。距離横断の汎化、少頭数と多頭数での校正の違い、海外馬の欠損の扱いも未解決です。また、契約データには利用条件があるので、記事や公開物では生データのダンプを出さない、識別子の扱いに注意する、といった制約も忘れないようにしています。趣味でも、データの出所を明示し、権利の範囲で遊ぶのが前提です。
インフラ面では、mac上の Postgres を LAN/トンネル経由でノートやエージェントから読む構成です。インターネットに不用意に晒さない、読み取り専用を既定にする——小さな趣味基盤でも、境界を決めておくと長く保てます。今後やりたいのは、学習ジョブの再現性(どのコミット・どの特徴parquet・どのモデルディレクトリか)をもっと機械的に残すことと、当日の意思決定メモをモデル出力JSONと並べて保存する形式を固定することです。当たった/外れたの感情ログではなく、差分が追える実験ノートに近づけたい、という意味です。
おわりに
まとめると、やったことは三つです。JRA-VAN データラボを契約して正本となる履歴データを得る。EveryDB 経路で PostgreSQL に載せ、辞書つきでクエリ可能にする。その上で、オッズを主学習に入れないランキング予測を距離別に積み上げ、当日は同じDBから推論する。スクレイピングで週刊の予想記事を量産する話ではなく、データ基盤とモデルと意思決定ログを一本の線でつなぐ話です。
競馬を始める必要はありません。代わりに、「ドメインデータが汚い/高い/規約がある」領域で、どこまでを契約データにし、どこからをモデルと人間の判断に分けるか——その設計の練習として読んでもらえれば十分です。次に趣味の結果報告を書くときは、またこの基盤の上で、どの信号を信じてどこでしきい値を誤ったかを切り分けることになると思います。仕組みを先に通しておくと、その切り分けが言葉になる。それが、今回いちばん残しておきたい結論です。
最後に一つだけ。この一連の作業でいちばん難しかったのはモデルの精度そのものより、「何を正とするか」を決めることでした。サイトの見た目、自分の勘、契約データのどれを正本にするか。正本をPostgresに置いた瞬間から、議論が実装可能な粒度になります。趣味でも、そこが分岐点でした。契約・インポート・学習・当日推論・振り返り、という線が一本あると、外れても次の手が言葉になる。当たっても再現できる。その両方を持てることが、この趣味基盤を組んだ実質的なリターンだと思っています。その線の上で、これからも淡々とログを残していきます。
筆者の環境
- ホスト: Mac(PostgreSQL は Docker で常駐)
- EveryDB3: データラボ会員は無料。mac上の Parallels で起動した Windows 11 上で運用し、Postgres へ投入
- データ正本: JRA-VAN データラボ(月額 2,090円・税込、執筆時点)
- アクセス: アプリ用とは別に読み取り専用ロール。ノート/エージェントは LAN またはトンネル経由(不用意に公開しない)
- モデル: 距離別のランキング学習(LightGBM 系 Ranker)。当日推論も同じ Postgres/SQL 経路