【機械学習ガチ勢】競馬初心者だけど帯広競馬場で儲けたい!!
先日、大学の友人に誘われて帯広競馬場(ばんえい十勝)に行ってきました。正直、初めての競馬なのでどのように馬券を買えばいいかわからない…。
そこで今回は、機械学習を使ってガチ予測をしてみようという企画をやってきました。さて私のお金はどれだけ増えたのでしょうか?
ばんえい競馬は普通の競馬と違うらしい
私は普通の競馬も、プリティーなダービーも全くやったことがないので何も知らない状態からのスタートとなりました。どうやら今回の「ばんえい競馬」は普通の競馬とは異なり、馬が重いソリを引くようです。この競馬は帯広競馬場でしかみられないとのこと。
主に
- 200 mの直線コースとなっている。
- 馬が重たいソリを引くため、土壌条件で有利不利が異なる。
- 「障害」と呼ばれる山が2つあり、乗り越えなければならない。
- ソリの後端がゴールラインを過ぎたらゴールとなる。
という点が特徴のようですね。普段から競馬に勤しんでいる友人曰く「今まで累計で30万円負けた。1万勝つくらいなら3万負けた方がいい。」だそう。さすが、授業中に馬券買ってる奴は覚悟が違いますね!
数学を勉強しよう
Note: 普通に競馬初心者のワイが競馬を始めるためにググりながらまとめただけなので、普通に間違ってるかもしれない。
なんもわからないと作戦を立てられないので、一旦ここで数学を勉強しましょう。
日本の競馬は「パリミュチュエル方式」を採用しています。ざっくりいうと、JRAや主催者(帯広市)と戦うのではなく、参加者同士で賭け金の奪い合いをする方式のことです。
オッズ
色々な馬券の種類がありますが、簡単のために、一旦単勝(一着を当てる馬券)についてのみ考えることにします。
馬券全体の総売上を$W$と置きます。胴元の主催者が持っていく控除率を$\tau$とすると、プレイヤー全体に還元される割合は$1 - \tau$です。
このとき、馬$i$の単勝に賭けられた金額を$w_i$とすると、馬$i$が勝った場合のオッズ(払戻倍率)$O_i$は、以下の式で決定されます。要するに、馬券全体の売り上げから胴元の利益を引いた分を、馬券が当たった人で山わけということです。
\[O_i = \frac{W(1 - \tau)}{w_i}\]
ここで、数式を直感的にするために新しい変数$q_i$を導入します。
$q_i = {w_i}/{W}$と置くと、これは「全体の売上のうち、馬$i$に賭けられたお金の割合」を示します。つまり、「大衆が予測した、馬$i$の勝率」と解釈できます。
\[O_i = \frac{1 - \tau}{q_i}\]さて、馬$i$の「真の勝率」を$p_i$と置いたとき、払戻率の期待値$E_i$は
\[E_i = p_i \times O_i = (1 - \tau)\frac{p_i}{q_i}\]で表されます。
実は、$E_i = (1 - \tau)\frac{p_i}{q_i}$という数式が、競馬の残酷な現実をすべて説明してくれます。試しに、以下の3つの買い方について考えてみましょう。
A. 完全にランダムに馬券を買う
思考を放棄し、適当にランダムな馬の馬券を買い続けたとします。市場全体(大衆の予測)は長期的にはそこそこ優秀なので、試行回数が十分にあると仮定し、大衆の予測勝率$q_i$を真の勝率$p_i$に近似します($p_i \approx q_i$)。
これを先ほどの期待値の式に当てはめると、
\[E_i \approx (1 - \tau) \times 1 = 1 - \tau\]となることから、ランダムに買い続けると、期待値は必然的に控除率の壁にぶつかり、利益は得られなさそうです。
B. 常に一番オッズが高い馬(大穴)の単勝を買い続ける
次に、大穴を狙い続けてみましょう。夢がありますね。
しかし、行動経済学において「本命・大穴バイアス(Favorite-Longshot Bias)」と呼ばれる現象があります。人間は「一撃で100倍」のようなくじを前にすると、当たる確率を過大評価して買ってしまうという性質があります。確率的には当たるわけないと理解しながらも、みんな夢を見て買っている宝くじなどが、典型的な例です。
これが数式にどう影響するでしょうか。 大衆が大穴馬を過大評価して馬券を買うため、大衆の予測勝率$q_i$が、真の勝率$p_i$よりも不当に大きくなってしまいます。
つまり、$q_i > p_i$となり、式中の$\frac{p_i}{q_i}$の値は$1$を下回ります。
\[E_i = (1 - \tau)\frac{p_i}{q_i} < 1 - \tau\]つまり、大穴を買い続けると、期待値はランダム買いを下回ります。利益重視ならば、一番やってはいけない買い方と言えるかもしれません。
C. 常に一番オッズが低い馬の単勝を買い続ける
大穴(パターンB)とは反対に、市場は「1.2倍や1.5倍の馬券を買っても面白くない」「せっかく競馬場に来たんだから大きく勝ちたい」という心理が働き、勝率が高い大本命の馬を実力よりも過小評価して買わない傾向があります。
つまり、$q_i < p_i$となり、式中の$\frac{p_i}{q_i}$の値は$1$を上回ります。
\[E_i = (1 - \tau)\frac{p_i}{q_i} > 1 - \tau\]おや? 期待値がランダム買いを上回りました。ランダムや大穴狙いと比較すると、最も期待値が高い買い方です。しかし、$\frac{p_i}{q_i}$は控除率を打ち消す程にはならず、結局長期的には利益はでません。
A・B・Cの買い方をしたと仮定して、実際にシミュレーション(2026/02/02から08/23の期間)を回してみました。(ランダム買いは10回試行して平均をとったものです。)
| 買い方 | 回収率 | 的中率 |
|---|---|---|
| A. ランダムに1頭 | 70.8% | 10.7% |
| B. 一番オッズが高い馬(大穴) | 50.6% | 1.5% |
| C. 一番オッズが低い馬(本命) | 81.9% | 36.0% |
実際の競馬の市場には歪みがあるので、完全に理論通りとはなりませんが、それでもやはり払戻額はC>A>Bとなりました。
競馬で儲けるには?
先に示したように、競馬で勝つ ことと 競馬で儲ける ことは全く等価ではありません。ここで以下のような仮説を立てて、機械学習モデルとそれに関連したプログラムを構築することにしました。
競馬で儲けるには「払戻額の期待値が1を超える馬券」のみを買う必要がある。真の勝率$p_i$は実際にはわからないが、ここをAIで予測すれば、払戻額の期待値が1を超える馬券をリスト化できるのではないか?
具体的な作戦は、
- レース前にAIで各馬の勝率を予測する。勝率が高い順に着順の予測とする。
- 馬券購入の締め切り直前に実オッズと予測した勝率を照らし合わせる。
- 払戻額の期待値が高い馬券を選んで、いい感じに買う。
です。実際のプログラムと一緒に見ていきましょう!!
勝率予測
ここが機械学習で頑張るパートです。
何を予測する問題なのか
まず最初に思いつきで「その馬は1着になるか?」という回帰問題として解くことを考えましたが、単勝以外の馬券の期待値を評価できなくなるので途中から他の方法に切り替えました。
競馬は1レースの中で馬同士が競争しているので、ある馬が勝つかどうかは、その馬の強さだけでは決まりません。つまり1頭ずつ独立に「勝つ/勝たない」を判定させることはできません。
そこでランキング学習(LambdaRank)を採用しました。レースを1つのグループとして扱い、グループの中での並び順そのものを最適化します。「この馬は勝つか?」ではなく「このレースの8頭を、着順が正しくなるように並べ替えよ」という問題設定にしました。
データを集める
地方競馬公式の keiba.go.jp から、レース結果・払戻・出走表をスクレイピングしました。馬ごとの戦績ページも辿ることで、最終的に175,269出走レコード / 24,733レース(2008年〜2026年8月)が集まりました。
これに加えて、
- 気象庁の帯広の日別気象データ(気温・降水量・湿度・積雪深など)
- 馬の情報ページから血統(父・母・母父と3代前まで)、馬主、生産牧場、産地、生年月日
を結合しています。ばんえい競馬は屋外の砂のコースをソリで走るので、天気は馬場の状態や水分量を通じて成績に影響を与えると考えました。
特徴量を作る(243次元)
ここがマジで一番時間かかりました…。主なものを挙げると、
| 分類 | 特徴量の例 |
|---|---|
| ばんえい固有 | パワー負荷率(ソリ重量 ÷ 馬体重)・夏の暑熱ストレス |
| 実力 | 直近3走・5走の平均着順・着順の標準偏差 |
| 適性 | 乾いた馬場と重い馬場それぞれでの平均着順とその差 |
| 気象 | 気温・降水量・湿度・日照時間・積雪深 |
| 相対化 | 上記55指標について、レース内での順位と、最上位馬との差 |
⚠️ 情報のリークに注意
特徴量を作るときに一番怖いのがリーク、つまり「レース時点では知りえない未来の情報」が紛れ込むことです。これがあると検証では素晴らしい成績が出るのに、本番でまったく当たらないモデルができあがります。
実際に過去のレースをもとに学習を回すとき、そのレースの一つ手前までのレースの情報のみで推論を行わないと、すでに正解を知っている状態での推論となってしまいます。
例えば「累積獲得賞金」について、競馬サイトの情報ページに載っているのは生涯獲得額なので、そのまま使うと「この後どれだけ稼ぐか」を知っている状態になってしまいます。
モデルを学習する
LightGBMを2本使い、着順と1着/非1着の確率予測からスコアを算出しています。メインは前者の着順予測ですが、後者で「そもそも勝ちそうか」の絶対的な強さの予測を補います。異なる目的関数で学習した2本を混ぜると、片方が苦手なところをもう片方が埋めてくれると考えたためです。
ハイパーパラメータはOptunaで探索しました。結果として選ばれたのは max_depth=3 というかなり浅い木に、強い正則化をかけた構成でした。(競馬データはノイズが大きいので、複雑なモデルにすると綺麗に過学習しました。)
学習をかけてみて気づいたこと
モデルができたのでいろいろシミュレーションしてみました。ここまで学習してみた知見を並べると、
- 市場予測の単勝の的中予測35%は結構高い(集合知はやっぱりすごい)
- 逆に、市場の2位3位の的中予測は割と外れてる印象。(着順まで当てる馬券は組み合わせが増えて人気が分散するので、市場が雑になりやすい??あんまわからん)
- 三連単、たまにちゃんと当たる。試行回数無限なら利益出せそうなくらいには当たる。
ですね。ここまで理論ちゃんとガチってたのにいきなり適当かよ!とか言わないでほしいです…。
馬券を買おう
次に、どの馬券を買うべきかを提示するシステムを作ります。払戻額の期待値が高い馬券を選び続けることが、利益を最大化すると考えました。
例えば、下のような予測と実オッズがレース前に揃ったとします。

この場合、1着になりそうな馬は明らかに馬Aです。しかし、市場もそのことをわかっているため、馬Aの馬券はたくさん売れます。結果的に期待払戻率は1を下回るため買わない方がいいと判断します。
一方、馬Bの予測勝率は馬Aに劣りますが、オッズを考慮すると期待払戻率は1を越えます。そのため、買うべき馬券は馬Bと判断します。
単勝以外にも複勝や三連単など様々な馬券の種類があるため、全てを人間の手で把握するのは現実的ではありません。そのため、実オッズをスクレイピングして取得するプログラムを作りました。
実オッズは目まぐるしく変わるため、競馬の馬券購入締め切りギリギリを狙えるこのシステムは、市場においてかなり有利に働きます。実際、多くの人が単勝馬券を買いまくった結果、締め切り直前にオッズが歪むということが検証当日にもありました。

このレースでは6番の馬が一着となりましたが、単勝より複勝(3着以内に入れば当たり)の方が払戻額が多くなっています。この場合、複勝の方が当たりやすい上に払戻額も高いので、単勝を買うメリットが全くありません。
これまでの話をまとめると、競馬は当たる馬券を買うゲームではなく、自分の予想と市場の予想の乖離を見定め、控除率に勝つゲームだと言えます。
実際に競馬場に行こう!!
8月23日に、実際に友人と帯広競馬場に行ってお金を賭けてきました!!もしこの日に会場でMacBook開いて、ターミナル開きっぱなしにしてる人を見かけたらそれは私です。

これは締め切り5分前に実オッズを取得して、マークカードを急いで塗っている場面です。UIもしっかり作り込んでいきました!私のようなドパガキにぴったりの演出とデザインにしました。
マークを塗ったら発券所へダッシュし、無心でマークカードと財布のお金を突っ込みます。おじいちゃんからもらった大切なお盆のお小遣い…ごめん…。

初めて買った馬券たちです。明らかに初心者が買う馬券の枚数じゃなさすぎます。ボックスとかながしとかのテクニカルな馬券の買い方が全くわからず、全てライトカードで買ったのでこの枚数になってしまっています。

第6レースくらいまで、(友人曰く)大荒れ展開が続き馬券がどんどん紙屑になっていきます。ここまでの払戻率は約4割でかなり厳しい展開です。ばんえい競馬は特に最後の最後まで順位が読めないので本当に難しいです。ゴールライン目前で馬が力尽きて膝ついたりすると本当に絶望します。
しかし、ギャンブルで負けた分はギャンブルで取り返せば良い!!勝負はここから!!
三連単あたったぁあ!!!!!!うれしいいいいいいい!!!!!!!
ちなみにこのレースは合計1500円かけて5000円くらいになりました。ちなみにこのレースで例の友人は1の単勝と複勝をそれぞれ5000円ずつ買い、倍以上に増やしていました。賭け方が漢すぎる…。
日も沈み、競馬場の人もだいぶ減ってきます。第10レースに差し掛かった頃、私たちのすぐ後ろに優しそうなおっちゃん2人がやってきました。何やら新聞を見ながら会話をしているようです。
なんか不穏な情報が聞こえてしまったので手元の馬券を見ると見事に私は4と7をめっちゃ買っていました。まずい!!

もう応援することしかできないので必死に祈りまくります。友人も結構な額を賭けていたので、お互い決して大学では見せることがない表情で叫びまくります。
いけーーー!!!!

差せーーー!!!!!!!
結果↓

なんかまた三連単当たりました!!!
脳汁ぅぅぅうう!!!!!!
ドーパミンドッパァァァァアアアア!!!!!!!!!
後ろのおっちゃんに爆笑されるくらいの喜びの舞を見せつけてしまいましたが、100円が12350円になってしまったので、もはや感情を制御する術はありません。穴の万馬券を自分でトレーニングした機械学習モデルで当てる喜びは本当に脳の報酬系バグります。
成績発表
26,750 - 22,200 = +4,550円の利益
となりました。ETCやガソリン代とか差し引いてもプラスになったので大成功です!!回収率は120.5%となりました。
初めての競馬なので、モデル予測だけでなく運による部分もかなり大きいと思いますが、それでも利益が得られたことと、三連単を2回当てる快感を体験できたので非常に満足しています。ギャンブル依存の怖さがとてもよくわかった
今回実際にお金をかけてみたり、一日競馬場で色々情報を見聞きしたりしてかなり知見を得られたので、今回のモデルをベースにしつつ、改善してまた冬にチャレンジしてみたいと思います。(冬は雪が降ったり凍ったりするので、また展開が全然違うらしい。)
具体的には、
- 今回各レースにかける金額は決め打ちだったが、荒れやすそうなレースは引くべきだったり、逆に狙いやすいレースに重点的に賭けるべきだったりするはずなので、これの指標を出せるようにしたい
- それぞれの馬券の期待値がどれだけかをそれぞれ単独で判断しているが、この馬券の組み合わせで買った時の全体の期待値まで総合的に判断する仕組みを持っていないので、馬券提示アルゴリズムの改善をしたい
と思っています。
今回のコードはこちらに公開しているので、ぜひ俺もドパしたいよ!って方がいたらぜひ参考にしてみて下さい。ではまた!



Comments