前回までに、scikit-learn のロジスティック回帰を使って競馬予測のモデルを作ってみた。
説明変数などを工夫してみても馬券回収率(単勝)は 80%超程度までで上昇は見込めず
この回からは、流行りの決定木アルゴリズムに基づいた勾配ブースティング(Gradient Boosting)の機械学習フレームワーク LightGBM を使って競馬予想してみるのだ
#データの取得
###ターゲットフロンティアのレース検索データを出力して使用
前回までと同様にターゲットフロンティアのレース検索で 2000年からのレースをすべて検索
前走読込み ボタンを押して前走のレースID等を取得して、今回はすべてのデータを CSV で出力した
全データを出力すると出力時間や pandas での読込みに時間がかかるのだが、使いたいデータがないときの出力し直しが面倒なのでとにかく全データを出力したのだ
参照:【第1回】Python で競馬予想してみる ~ 環境設定 とデータの取得 ~
#データの前処理
###pandasでcsvファイル読み込み
###使用する 特徴量 を選択
予想するレース(直近レース)で使用する特徴量は下記の通り
Index(['レースID(新)', '前走レースID(新)', '性別', '年齢', '父タイプ名', '母父タイプ名', '場所', 'クラス名',
'距離', '芝・ダ', 'コーナー', '馬場状態', '出走頭数', '枠番', '馬番', '騎手コード', '斤量', 'キャリア',
'間隔'],
dtype='object')
上記の直近レースの 前走レースID(新) に紐づけで結合するための過去走で使用する特徴量は
Index(['レースID(新)', '前走レースID(新)', '場所', 'クラス名', '距離', '芝・ダ', 'コーナー', '馬場状態',
'出走頭数', '枠番', '馬番', '騎手コード', '斤量', 'キャリア', '間隔', '確定着順', '斤量体重比',
'単勝オッズ', '人気', '着差', '上3F地点差', '脚質', 'Ave-3F', '上り3F', '上り3F順', '馬体重増減',
'補正', '補9'],
dtype='object')
###ダミー変数を作成
# ダミー変数を生成
rd_last = pd.get_dummies(rd_last, columns=['場所', 'クラス名', '芝・ダ', '馬場状態', '性別', '父タイプ名', '母父タイプ名']) # 直近レース用
rd_past = pd.get_dummies(rd_past, columns=['場所', 'クラス名', '芝・ダ', '馬場状態', '脚質']) # 過去レース用
###直近レースデータに加工走のデータをマージ
今回は 5走前まで連結
###欠損値処理はしない
LightGBM は欠損値があっても学習・予測できるらしい
欠損値処理はいつでもできるので、とりあえず残しておくのだ
###目的変数の処理
目的変数は、出走馬の着順を 3着以内なら 1、4着以下なら 0 に変更した
# 着順が3着以内かどうかのカラムを追加する
f_ranking = lambda x: 1 if x in [1, 2, 3] else 0
df['3着以内'] = df['確定着順'].map(f_ranking)
参照:【第2回】Python で競馬予想してみる ~ データの前処理(CSVデータの読み込み~各種処理) ~
#モデルの学習
###アンダーサンプリングは必要 ?
アンダーサンプリングをした方が良いのか分からないので、今回はしない
import lightgbm as lgb #LightGBM
# 説明変数をdataXに格納
dataX = df
# 目的変数をdataYに格納
dataY = df['3着以内']
# データの分割を行う(学習用データ 0.8 評価用データ 0.2) stratify:層化抽出
X_train, X_eval, y_train, y_eval = train_test_split(dataX, dataY, test_size=0.2, stratify=dataY)
# LightGBM用のデータセットに入れる
lgb_train = lgb.Dataset(X_train.values, y_train)
lgb_eval = lgb.Dataset(X_eval.values, y_eval)
# LightGBM parameters
params = {
'task': 'train',
'objective': 'binary',
'metric': 'binary_logloss'
}
# モデルの学習
model = lgb.train(params, lgb_train, valid_sets=lgb_eval,
verbose_eval=50, # 50イテレーション毎に学習結果出力
num_boost_round=1500, # 最大イテレーション回数指定
early_stopping_rounds=100)
# テストデータの予測 (クラス1の予測確率(クラス1である確率)を返す)
y_pred_proba_train = model.predict(X_train)
y_pred_proba = model.predict(X_eval)
accuracy_train = roc_auc_score(y_train, y_pred_proba_train)
accuracy_eval = roc_auc_score(y_eval,y_pred_proba)
print(" Accuracy_train = {}".format(accuracy_train))
print(" Accuracy_eval = {}".format(accuracy_eval))
結果は
予想外に時間がかかるのだ
【追記】騎手コードをカテゴリ変数化するのを忘れたので、修正して再学習
良いのか悪いのかよく分からんのだ。
roc_auc_score = 0.8105012696640244
roc_auc_score = 0.7694469787133695
混同行列
[[ 10234 33072]
[ 7232 154447]]
適合率 0.5859383945952136
再現率 0.23631829307717175
F値 0.336799842032515
feature importance を見てみると
騎手や1走前(n_〇〇〇:n走前の〇〇〇データという意味)のデータが上位に。
下位にはレースのクラス名が勢ぞろい
feature importance
6 騎手コード 0.027750
75 1_単勝オッズ 0.018916
80 1_上り3F 0.017527
9 間隔 0.015901
79 1_Ave-3F 0.015689
77 1_着差 0.015336
69 1_騎手コード 0.013781
136 2_単勝オッズ 0.013616
83 1_補正 0.013380
84 1_補9 0.013263
140 2_Ave-3F 0.013239
197 3_単勝オッズ 0.012744
130 2_騎手コード 0.012697
191 3_騎手コード 0.012532
141 2_上り3F 0.012367
258 4_単勝オッズ 0.011896
319 5_単勝オッズ 0.011755
201 3_Ave-3F 0.011708
73 1_確定着順 0.011496
145 2_補9 0.010931
323 5_Ave-3F 0.010860
313 5_騎手コード 0.010813
206 3_補9 0.010671
262 4_Ave-3F 0.010530
252 4_騎手コード 0.010506
202 3_上り3F 0.010483
263 4_上り3F 0.010342
(中略)
294 4_クラス名_JG2 0.000000
159 2_クラス名_2勝 0.000000
285 4_クラス名_OP(L) 0.000000
172 2_クラス名_JG2 0.000000
167 2_クラス名_重賞 0.000000
171 2_クラス名_JG1 0.000000
346 5_クラス名_OP(L) 0.000000
293 4_クラス名_JG1 0.000000
343 5_クラス名_3勝 0.000000
350 5_クラス名_重賞 0.000000
今回はここまで