ここ数か月間、Pythonによる機械学習の勉強をしておりました。AIを使って競馬予測することに成功しましたので、その手順をご紹介したいと思います。また、実際に予測した期間での的中率や回収率についても記事の後半で公開します。
今回の機会学習では、人気やオッズなどの特徴量から、各レースの競走馬ごとに、1着になる可能性の高さをスコアとして評価します。単勝で馬券購入することを想定した予想となります。
競馬予想AIの実行手順
1.競馬データの収集
競馬予想AIの作成において、競馬データの収集が重要なポイントとなります。競馬データと言っても様々で、レース結果や指数や指標、血統データなど多数です。私は、普段の予想で使用するファクターをベースにしてデータを収集することにしました。今回は2019年1月1日~2023年8月31日までの期間で、必要なデータを揃えました。
なお、中央競馬レースの情報収集方法については話が長くなるので割愛します。参考までに、無料でデータ収集する場合は、スクレイピングなどを使用する必要がありますが、数年間のデータを収集するとなると、数時間~数日かかる場合もあります。目的や予算を考えたうえで、データ収集方法を検討すると良いと思います。
2.競馬予想AIデータの加工
必要なデータを揃えたら、AIが学習できるように各項目の値を数値などに変換します。
具体的には下記のように変更しました。
kinryo | 斤量 | そのまま使用 |
date | 日付 | そのまま使用 |
basyo | 開催地 | 札幌: 1 新潟: 2 福島: 3 中京: 4 函館: 5 東京: 6 阪神: 7 京都: 8 中山: 9 小倉’: 10 |
race_num | レース番号 | そのまま使用 |
umaban | 馬番 | そのまま使用 |
race_class | レースのグレード | 下記のように7段階に分類 未勝利: 1 新馬: 2 1勝: 3 2勝: 4 |
type | 芝、ダート | 芝’: 1 ‘ダ’: 2 |
distance | 距離 | そのまま使用 |
sei | 性別 | セ: 1 牡: 2 牝: 3 |
old | 年齢 | そのまま使用 |
pop | 人気 | そのまま使用 |
odds | オッズ | そのまま使用 |
result | レース結果 | そのまま使用 |
horse_id | 馬id | 馬名をエンコード |
3.競馬予想AI実行環境の準備
機械学習はLightGBMを採用しました。LightGBM(Light Gradient Boosting Machine)は、高速で効率的な勾配ブースティングフレームワークで、機械学習モデルのトレーニングや予測に使用されます。データセットの効率的な分割方法と、大規模データセットに対しても高速に動作することが特徴です。競馬予想AIとしても、多くの方が利用しているようです。
実行環境はColaboratoryを使用しました。ブラウザから実行できるので新規でサーバ等を立てる必要がなくて便利です。機械学習において、スペックなどで不満に思うところもありませんでした。
4.競馬予想AI実行の事前準備
競馬予想AIの実行にあたって、いくつか事前に準備したポイントを共有します。
・加工後データを3つの期間に分ける
具体的には用途別に、期間でファイルを分割しました。
訓練用:2019年1月1日~2022年1月1日
テスト用:2022年1月1日~2023年6月1日
予測用:2023年6月1日~2023年8月31日
・予測用データのrankを空欄にする
予測用データは未来のレース予想を前提としているため、過去日データでrankが入っている場合は空欄に修正します。AI側にrankを見せない状態で予測させます。
5.競馬予想AIとコード
実際に使用した競馬予想AIのコードは下記となります。各レースごとに、rankが1になる可能性の高い競走馬をスコアとして評価しています。また、各レースごとに1番評価の高い馬だけを抽出してcsvファイルに出力しています。
今回のAI用に加工した中央競馬の競馬レースデータについては、noteの記事にて有料で提供させて頂いております。本記事のデータを使って動かしてみたい方はご検討をお願いします。
それでは今回使用したコードを記載します。
import pandas as pd
import lightgbm as lgb
from sklearn.model_selection import train_test_split
# データを読み込む
data_before_20220101 = pd.read_csv('data_before_20220101.csv')
data_before_20230601 = pd.read_csv('data_before_20230601.csv')
data_after_20230601_nan = pd.read_csv('data_after_20230601_nan.csv')
# 特徴量とターゲットの準備
X_train = data_before_20220101[['kinryo', 'basyo', 'race_num', 'umaban', 'race_class', 'type', 'distance', 'sei', 'old', 'pop', 'odds', 'horse_id', 'race_id']]
y_train = data_before_20220101['result'].apply(lambda x: 1 if x == 1 else 0)
# LightGBMのデータセットに変換
train_dataset = lgb.Dataset(X_train, label=y_train)
# ハイパーパラメータを設定
params = {
'objective': 'binary',
'metric': 'binary_logloss',
'boosting_type': 'gbdt',
'num_leaves': 33,
'learning_rate': 0.05,
'feature_fraction': 0.9
}
# モデルを学習
num_boost_round = 100
model = lgb.train(params, train_dataset, num_boost_round=num_boost_round)
# 予測用データの特徴量を準備
X_pred = data_after_20230601_nan[['kinryo', 'basyo', 'race_num', 'umaban', 'race_class', 'type', 'distance', 'sei', 'old', 'pop', 'odds', 'horse_id', 'race_id']]
# テストデータの予測
y_pred_pred_prob = model.predict(X_pred)
# 予測結果をデータフレームに保存
pred_predictions_df = pd.DataFrame({'race_id': X_pred['race_id'], 'horse_id': X_pred['horse_id'], 'probability_of_1': y_pred_pred_prob})
# race_idごとに予測確率が最大のhorse_idを選択
pred_predictions_df = pred_predictions_df.loc[pred_predictions_df.groupby('race_id')['probability_of_1'].idxmax()]
# race_idごとにhorse_idの評価点を付与
hose_ratings = pred_predictions_df.groupby('race_id')['probability_of_1'].rank(ascending=False)
pred_predictions_df['horse_rating'] = hose_ratings
# 予測確率が1に近いhorse_idのリストを取得
selected_hose_ids = pred_predictions_df['horse_id'].tolist()
# 予測結果をCSVファイルに保存
pred_predictions_df.to_csv('pred_predictions.csv', index=False)
6.的中率と回収率について
予測用データの単勝的中率をレース番号別に算出してみた結果を貼ります。
期間:2023年6月1日~2023年8月31日
Race 1: 勝率 = 39%, 1着の数 = 28, 回収率 = 77% |
Race 2: 勝率 = 36%, 1着の数 = 26, 回収率 = 82% |
Race 3: 勝率 = 36%, 1着の数 = 26, 回収率 = 98% |
Race 4: 勝率 = 32%, 1着の数 = 23, 回収率 = 89% |
Race 5: 勝率 = 39%, 1着の数 = 28, 回収率 = 80% |
Race 6: 勝率 = 32%, 1着の数 = 23, 回収率 = 71% |
Race 7: 勝率 = 33%, 1着の数 = 24, 回収率 = 87% |
Race 8: 勝率 = 25%, 1着の数 = 18, 回収率 = 62% |
Race 9: 勝率 = 39%, 1着の数 = 28, 回収率 = 93% |
Race 10: 勝率 = 32%, 1着の数 = 23, 回収率 = 88% |
Race 11: 勝率 = 27%, 1着の数 = 19, 回収率 = 74% |
Race 12: 勝率 = 28%, 1着の数 = 20, 回収率 = 76% |
上記の結果より、回収率は全体平均だと85.42%という結果になりました。競馬の場合、控除率というものがありますので、回収率が85%以上という結果は悪くないかと思います。
第1レースは的中率は39%ですが、回収率でみると77%となります。人気やオッズを特徴量に含んでいるので、人気が高くてオッズが低い競走馬を選ぶ傾向があるのだと言えます。ここから血統の特徴量を追加したり、持ちタイムなどの条件で絞ったりすることで、的中率や回収率も向上を目指せるのではないかと思います。競馬予想AI入門という目的は達成されましたので、今後は高い回収率を目指したモデルも開発したいと考えております。