5月30日、日本ダービー的中
先日行われた日本ダービーにおいて、4番人気だったシャフリヤールを見事単勝で的中させることができました!
(日本ダービーで300円しか賭けないのは流石に・・・と思って直前で追加購入したのが吉と出ました笑)
ということで、ここからは5月30日東京のレースを例に、実際に競馬予想AIを使って賭ける時のソースコードを見ていきます。
(事前準備)訓練
前の章までは回収率をシミュレーションするため訓練データとテストデータに分けて、訓練データのみを機械学習モデルに入れていましたが、実際に使うときにはなるべく多くのデータ&最近のデータを使いたいので、過去レース結果データ全体を使います。
#目的変数は「3着以内に入ったかどうか」の0or1データを持った'rank'
X = r.data_c.drop(['rank', 'date', '単勝'], axis=1)
y = r.data_c['rank']
#optunaによってチューニングされたLightGBMのパラメータ
params = {
'objective': 'binary',
'random_state': 100,
'feature_pre_filter': False,
'lambda_l1': 4.693572572985162e-07,
'lambda_l2': 0.8052948126650886,
'num_leaves': 5,
'feature_fraction': 0.8,
'bagging_fraction': 0.9783720284106503,
'bagging_freq': 7,
'min_child_samples': 50
}
#LightGBMによる訓練
lgb_clf = lgb.LGBMClassifier(**params)
lgb_clf.fit(X.values, y.values)
#自作したModelEvaluatorクラスのオブジェクトを作成
me = ModelEvaluator(lgb_clf, ['return_tables.pickle'])
(事前準備)馬の過去成績と血統データを更新
まずは1R~12RまでのレースIDのリストを作り、ShutubaTableクラスのscrape関数によって、出馬表のデータを取得します。
race_id_list = ['2021050212{}'.format(str(i).zfill(2)) for i in range(1,13)]
st = ShutubaTable.scrape(race_id_list, '2021/05/30')
馬の過去成績はなるべく最新のものを使いたいので、その日に走る馬の分だけ改めてスクレイピングして、元のデータと結合したものを使います。
#スクレイピング
horse_results_20210530 = HorseResults.scrape(st.data['horse_id'].unique())
#pickleファイルに保存
horse_results_20210530.to_pickle('horse_results_20210530.pickle')
#データを結合してHorseResultsクラスのオブジェクトを作成
horse_results_list = ['horse_results_2017.pickle',
'horse_results_2018.pickle', 'horse_results_2019.pickle',
'horse_results_2020.pickle', 'horse_results_2021.pickle',
'horse_results_20210530.pickle']
hr = HorseResults.read_pickle(horse_results_list)
血統データについても、事前に追加しておくと楽だと思います。
本番の実行コード
レース直前、馬体重が発表されて出馬表の列に入ったら、改めてスクレイピングして出馬表のデータ加工を行います。
#11Rの日本ダービーの出馬表をスクレイピング
st = ShutubaTable.scrape(['202105021211'], '2021/05/30')
#データ加工
st.preprocessing() #前処理
st.merge_horse_results(hr) #馬の過去成績結合
st.merge_peds(p.peds_e) #血統データ結合
st.process_categorical(r.le_horse, r.le_jockey, r.data_h) #カテゴリ変数処理
それでは、実際に予測スコアを出してみましょう
scores = me.predict_proba(st.data_c.drop(['date'], axis=1), train=False)
pred = st.data_c[['馬番']].copy()
pred['score'] = scores
pred.loc['202105021211'].sort_values('score', ascending=False)
↓出力結果
このscoreが、レース内での馬の相対的な強さを表しています。
この時のモデルでは、「scoreが1.3あたりを超える馬が3頭以上いる場合は三連複BOX、2頭以下の場合は単勝で賭ける」 という戦略を取っていたので、この場合は馬番10を単勝で賭けることになります。
このようにして、4番人気のシャフリヤールを単勝で的中させることが出来ました!
5月30日の成績
この日は日本ダービーだけではなく、東京と中京のレースについて実際に一日運用して全てのレースを賭けてみました。日本ダービーの単勝的中の他、中京5Rでも7番人気のシャークスポットを的中させたのが大きく、全体としてプラスの収支で終えることができました!
以下、詳細成績です
レース | 馬番 | 買い目 | 的中 | 払い戻し |
---|---|---|---|---|
東京1R | 3, 5 | 単勝300円ずつ | × | 0円 |
東京2R | 3, 11 | 単勝300円ずつ | ○ | 570円 |
東京3R | 6, 11 | 単勝300円ずつ | ○ | 840円 |
東京4R | 15, 16 | 単勝300円ずつ | ○ | 570円 |
東京5R | 16, 5 | 単勝300円, 200円 | × | 0円 |
東京6R | 1-12-14 | 三連複200円 | × | 0円 |
東京7R | 3, 7 | 単勝300円ずつ | ○ | 720円 |
東京8R | 4, 8 | 単勝300円ずつ | ○ | 930円 |
東京9R | 3, 6 | 単勝300円ずつ | × | 0円 |
東京10R | 3, 5 | 単勝300円ずつ | × | 0円 |
東京11R | 10 | 単勝600円 | ○ | 7,020円 |
東京12R | 5-6-8 | 三連単BOX100円ずつ | × | 0円 |
中京1R | 6, 11 | 単勝200円ずつ | ○ | 420円 |
中京2R | 7 | 単勝200円 | ○ | 320円 |
中京3R | 2-7-8 | 三連複200円 | × | 0円 |
中京4R | 1, 5 | 単勝200円ずつ | ○ | 340円 |
中京5R | 3 | 単勝200円 | ○ | 3,460円 |
中京7R | 9 | 200円 | × | 0円 |
中京8R | 4, 8 | 単勝300円ずつ | ○ | 930円 |
中京9R | 2-4-13 | 三連単BOX100円ずつ | × | 0円 |
中京10R | 2, 13 | 単勝200円ずつ | × | 0円 |
中京12R | 5 | 単勝200円 | × | 0円 |
基本的には人気馬を単勝で当てていますが、たまに4番人気以降を単勝で当てることができ、全体的にプラスになりそうです。
外れた馬券でも、例えば東京12Rなどは8番人気を含めてBOXで買っており、あとは6番さえ3着以内に入れば巨額なリターンが期待できるところでした
今後、特徴量の追加や馬券の買い方の最適化などでさらに回収率を上げていけば、かなり期待できそうな競馬予想モデルだと思います!