競馬予測プログラムの実装とDashによる馬券選択アプリの作成

今年の8月頃から機械学習での競馬予測プログラムとDashというpythonライブラリを使ってなんちゃって馬券選択アプリをせこせこ作っていました。先日その内容について社内でも発表したのでアドベントカレンダーとしてもまとめようと思います。
ちなみに去年は[機械学習を使ってコントラバスの運指を推定してみる](https://qiita.com/kyo1M/items/d36837acda78b9933ef2) という記事を書いたのですがコントラバスから競馬まで幅広く対応できるのが機械学習の面白さですね。

# はじめに
競馬は読んで字のごとくお馬さんが競争するスポーツです。ただ単にレースを見るだけ・ラジオで実況を聞くだけでも楽しいですが、公営賭博なのでお金を賭けて楽しむこともできます。一生懸命立てた予想が当たり高配当になった時の興奮は計り知れません。
競馬予測に関する記事は個人ブログから論文までググると山ほど出てきますが、着順を当てるといったところにフォーカスを当てているものが多く意外と賭け方まで検討しているものは少なく感じます。
ということで、今回は機械学習による競馬着順予測に加え、馬券を選択するためのアプリを作成したのでその紹介をしていきます。
ちなみにソースコードは全公開しませんので悪しからず。

# 競馬の基礎知識
もしかしたら競馬を全く知らないという方もいるかもしれませんので簡単に競馬の基礎知識を説明します。競馬について詳しい人はこの節は読み飛ばしていただいて構いません。
## 競馬の要素
競馬には様々な要素があります。以下に予想に用いる要素（機械学習に用いる特徴量）として代表的なものを紹介します。
### 馬自身に関するもの
| 要素名 | 概要 | 例 |
|:--|:--|:--|
|性別|馬の性別|牡馬（オス）・牝馬（雌）・騸馬（去勢したオス）|
|馬齢|馬の年齢|2歳～|
|馬体重|馬の体重|平均470kg|
|体重変化|前回の計測からの体重増減|変化なし～大体30kg増減まで|
|血統|どの馬の血統か|～産駒|
|成績|勝率・持ち時計など|前走～位・芝1600m持ち時計1分34秒3など|
|厩舎|馬の所属する場所<br>どの調教師に育てられているか|～厩舎|
|トレーニングセンター|どこでトレーニングしているか|栗東・美浦トレセン等|
### レースに関わるもの
| 要素名 | 概要 | 例 |
|:--|:--|:--|
|開催種類|中央か地方か|中央は札幌～小倉まで10会場<br>地方は全国15会場|
|クラス|新馬戦～J1|競走馬が勝利していくと上のクラスで戦う|
|馬場|芝かダートか|   |
|距離|レースの距離SPRINT・MILE・INTERMEDIATE・LONG・EXTENDEDの頭文字を取ってSMILE区分という距離区分がある|800m~3000m以上<br>|
|出走頭数|レースで同時に走る馬の数|5頭~18頭|
|出走番号|どの位置で走るか|番号が小さいと内枠・大きいと外枠|
|オッズ|人気度|オッズが小さい方が人気だが払い戻しは少ない|
|騎手|レースで馬に騎乗する人||
## 馬券の種類
競馬は馬の着順を予想しますが、馬券の買い方も着順に応じた種類があります。組み合わせを当てるもの・着順を当てるものがありますが基本的には着順を当てる方が高配当となります。枠連・枠単という種類もありますが今回は割愛します。
例えば以下の図のように8-4-1の着順でレースが決まった場合の各馬券の概要を表にまとめました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2298484/2f733494-b889-6f48-7010-11dd1779f11f.png)
| 馬券 | 概要 | 当たり例| 払い戻し例 |
|:-:|:-:|:-:|:-:|
|単勝   |1着を当てる   | 8  | 230円  |
|複勝   |3着以内を当てる   | 1<br>8<br>4  |110円<br>170円<br>160円   |
|馬連   |1着・2着の組み合わせを当てる   |4-8   |690円   |
|ワイド   |3着以内に入る2頭の組み合わせを当てる   |1-4<br>1-8<br>4-8   |310円<br>270円<br>520円   |
|馬単   |1着・2着の順番を当てる   |8-4   |960円   |
|三連複   |1着・2着・3着の組み合わせを当てる   |1-4-8   |1000円   |
|三連単   |1着・2着・3着の順番を当てる   |8-4-1   |4800円   |

馬券は1枚100円単位で購入できるので、払い戻し額は100円の馬券を購入した際の払い戻しになります。例えばオッズ6.9倍の馬券を1枚購入し的中した場合、払い戻し額は100×6.9=690円となります。

# 競馬予測プログラムの実装
いよいよ本題に入ります。全体の構成は以下のフローチャートです。実装はすべてpythonで行いました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2298484/e45f7a58-df3c-2102-021a-374eb4200b12.png)
## 1. データ収集
まずは学習モデルを作成するためにレース結果のデータを集めます。今回は[netkeiba.com](https://www.netkeiba.com/?rf=navi)からスクレイピングを用いてデータを取得します。
競馬のレースにはすべて固有のIDが振られており、netkeibaでは共通URL+レースIDでweb上に公開されているデータを参照することができます。例えば、202205050601というレースIDは2022:2022年/05:東京/05:5回/06:6日目/01:第1Rというを表しており、「https://race.netkeiba.com/race/result.html?race_id=202205050601 」にアクセスすることで以下のようにレース結果を見ることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2298484/64ba29e9-484a-2fb0-50e3-b82ffa0e6903.png)
コードの詳細は割愛しますが、pythonのrequestsモジュールを用いて該当URLのhtmlを取得し、beautifulsoupというライブラリを用いてhtmlをパースしレース結果をcsvとして保存します。また、必要に応じてseleniumというツールも使っています。
正直全体の手順の中でスクレイピングが一番面倒だったのでお金に余裕のある人は[JRA-VAN](https://jra-van.jp/)などで過去データを購入するのも手だと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2298484/15a47291-1693-a1fa-5c3e-abee46e17ccd.png)
## 2. 前処理
1. データ収集で作成したレース結果のデータに対して特徴量の追加と学習のための前処理を行います。ここでは各馬について前3走までの勝率等の特徴量の追加とラベル値（性別など数値でない値）をラベルエンコーディングするなどの処理を行っています。
## 3. 予測モデル作成
モデル作成時に考えることは以下の2つです。
1. どのモデルを選択するか
    - lightgbmやxgboostなどのGBDT系
    - NNやRNNなどのニューラルネット系
2. 何を予測対象とするか
    - 着順
    - 着タイム
    
今回は**xgboost**を使って**着順**を予測するというモデルを作成しました。モデルごとで精度比較等はしていませんが、一旦モデル作成が容易で特徴量重要度が確認できるという点からこちらを選択しました。
2016年~2021年7月までのデータを学習データとして作成したモデルの特徴量重要度は以下の通りです。
出走頭数・オッズ・芝/ダートの要素が上位3つに来ていることが分かります。出走頭数は5~18頭の範囲で単純に少ない方が確率的に当たりやすい・オッズも基本は人気順なので小さい方が当たりやすいということだと思います。芝かダートかも結構予測に効いてくるのは意外でした。本当はshapを使ってどの特徴量がどのように効いてくるのか可視化しようと思ったのですがまあそれはそのうち・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2298484/c3aa288b-8237-1509-e425-53d8602ed6fe.png)
## 4. 予測作成
さてここまできていよいよ出馬表から予測を計算することができます。ちなみに出馬表はnetkeibaではレース結果URLのresult部分をshutubaにすると見に行くことができます。
予測は下図のようにレースごとに各出走馬の順位確率を出力しています。また、少し小手先感がありますがレース単位で各順位の確率の和が1になるように正規化しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2298484/cdaabe59-69c4-f008-5f81-f0e145e5274e.png)
**3. 予測モデル作成**で作成したモデルで2021年8月～のレースに対して、単勝予測のみですが購入シミュレーションをしてみました。購入条件としては、1位確率が0.5を超える馬券を1枚(=100円)購入したとして、その購入総額と払い戻し総額の推移をプロットしています。（青線が購入額・オレンジ線が払い戻し額）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2298484/2a937f42-fa95-f100-4a84-2fe76fb8a729.png)
この条件だと途中の回収率にブレがあるものの、最終的には馬券的中率は**51.6%**、回収率は**109.9**%となりました。・・・勝ったな😎

## 5. 賭け方検討
もちろん勝っていません。シミュレーションのような買い方をするにはそもそも馬券購入のために潤沢な資金が必要です。競馬でお金を増やそうという人間が最初からお金を持っているはずがありません。**限られた資金で**回収率をプラスにするためにどのように買い目を選択するのかが非常に重要になります。
### 合成オッズ
賭け方検討の話に入る前に、買い目選択の必須知識である合成オッズについて簡単に説明します。
合成オッズとは、文字通り複数の買い目のオッズを合成したもので選択した馬券が当たった場合にどのくらい払い戻しがあるかどうかを測る指標となり、選択した買い目のオッズの逆数の和の逆数で計算することができます。
```math
合成オッズ = \frac{1}{\sum\frac{1}{odds}}
```
また、合成オッズから払い戻し額が均等になるような買い目の資金配分を計算することができます。
```math
各買い目の購入金額=総購入金額\times合成オッズ\div各買い目のオッズ
```
例えば、総購入金額(購入上限額)10000円とし、下記馬連を3点購入する場合を考えます。
```math
合成オッズ=\frac{1}{\frac{1}{2.0}+\frac{1}{5.0}+\frac{1}{8.0}}\fallingdotseq1.21
```
| 買い目 | オッズ | 購入金額 | 払い戻し額 |
|:-:|:-:|:-:|:-:|
| 馬連:3-5  | 2.0  |10000×1.21÷2.0=6050→**6100円**   |6100×2.0=**12200円**   |
| 馬連:3-7  | 5.0  |10000×1.21÷5.0=2420→**2400円**   |2400×5.0=**12000円**   |
| 馬連:3-9  | 8.0  |10000×1.21÷8.0=1512.5→**1500円**   |1500×8.0=**12000円**   |

つまり10000円用意して合成オッズが1.21倍になるように買い目を選択すると、どれか一つでも**当たれば**およそ12000円が返ってきます。逆に言えば合成オッズが1未満になるように買い目を選択してしまうと当たっても損をしてしまいます。

### Dashによる馬券選択アプリの作成
最後に、予測結果から合成オッズを考えながら馬券を選択するためのアプリをPlotly社が提供している[Dash](https://plotly.com/)というライブラリで作成しました。
このアプリでは予測作成したレースごとのレース情報・馬券ごとの予測確率・オッズ等を表示し、選択した買い目の合成オッズ・均等払い戻しになるように資金配分を自動計算することができます。
本当は動画だと分かりやすいのですがqiitaに動画を直接貼れないので下記にスクショだけ用意しました。スクショ例だと馬連の購入限度額を10000円とし、馬連の確率が高い順に選択した買い目に対して自動的に合成オッズ・購入額・払い戻し額等を自動計算して表示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2298484/08effdbd-b323-720b-91e3-0254678ef9b6.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2298484/a34224f3-c615-5a37-001c-be2cb1c81d6c.png)

# まとめと今後
今回のアドベントカレンダーでは今年の8月頃から取り組んでいた競馬予測プログラムの実装と馬券選択アプリの作成について紹介しました。
実際このアプリは自分で馬券を購入する際に使っているのですが、選択した買い目で馬券購入額を計算できるので便利です。ただしどうしても合成オッズを見て馬券購入すると当たるが損もしないという買い方になりがちでまだまだ儲けるには至っていません。
今後は買い目の手動選択ではなく予測確率・オッズ・期待値・購入額・合成オッズ等から払い戻しが最適になるような組み合わせを自動選択する方法も考えていきたいと思います。
また、今回はほとんど作成コードを共有できなかったのでそのうちコードも整理して公開するかもしれないししないかもしれません。
最後に、競馬は大人の遊びです。節度を守って負けても怒らず楽しくお馬さんと周りの人たちを応援しましょう！


## 参考
https://alphaimpact.co.jp/blog/

https://medium.com/codeworksparis/horse-racing-prediction-a-machine-learning-approach-part-2-e9f5eb9a92e9

https://team-d.club/prediction-column/about-synthetic-odds/