LoginSignup
7
11

More than 1 year has passed since last update.

【第5回】Python で競馬予想してみる  ~ LightGBM ~

Last updated at Posted at 2020-12-08

前回までに、scikit-learn のロジスティック回帰を使って競馬予測のモデルを作ってみた。
説明変数などを工夫してみても馬券回収率(単勝)は 80%超程度までで上昇は見込めず

この回からは、流行りの決定木アルゴリズムに基づいた勾配ブースティング(Gradient Boosting)の機械学習フレームワーク LightGBM を使って競馬予想してみるのだ

#データの取得
###ターゲットフロンティアのレース検索データを出力して使用
前回までと同様にターゲットフロンティアのレース検索で 2000年からのレースをすべて検索
前走読込み ボタンを押して前走のレースID等を取得して、今回はすべてのデータを CSV で出力した
全データを出力すると出力時間や pandas での読込みに時間がかかるのだが、使いたいデータがないときの出力し直しが面倒なのでとにかく全データを出力したのだ

参照:【第1回】Python で競馬予想してみる ~ 環境設定 とデータの取得 ~

#データの前処理
###pandasでcsvファイル読み込み
###使用する 特徴量 を選択
予想するレース(直近レース)で使用する特徴量は下記の通り

Out
Index(['レースID(新)', '前走レースID(新)', '性別', '年齢', '父タイプ名', '母父タイプ名', '場所', 'クラス名',
       '距離', '芝・ダ', 'コーナー', '馬場状態', '出走頭数', '枠番', '馬番', '騎手コード', '斤量', 'キャリア',
       '間隔'],
      dtype='object')

上記の直近レースの 前走レースID(新) に紐づけで結合するための過去走で使用する特徴量は

Out
Index(['レースID(新)', '前走レースID(新)', '場所', 'クラス名', '距離', '芝・ダ', 'コーナー', '馬場状態',
       '出走頭数', '枠番', '馬番', '騎手コード', '斤量', 'キャリア', '間隔', '確定着順', '斤量体重比',
       '単勝オッズ', '人気', '着差', '上3F地点差', '脚質', 'Ave-3F', '上り3F', '上り3F順', '馬体重増減',
       '補正', '補9'],
      dtype='object')

###ダミー変数を作成

build.ipynb
# ダミー変数を生成
rd_last = pd.get_dummies(rd_last, columns=['場所', 'クラス名', '芝・ダ', '馬場状態', '性別', '父タイプ名', '母父タイプ名']) # 直近レース用
rd_past = pd.get_dummies(rd_past, columns=['場所', 'クラス名', '芝・ダ', '馬場状態', '脚質']) # 過去レース用

###直近レースデータに加工走のデータをマージ
今回は 5走前まで連結

###欠損値処理はしない
LightGBM は欠損値があっても学習・予測できるらしい
欠損値処理はいつでもできるので、とりあえず残しておくのだ

###目的変数の処理
目的変数は、出走馬の着順を 3着以内なら 1、4着以下なら 0 に変更した

build.ipynb
# 着順が3着以内かどうかのカラムを追加する
f_ranking = lambda x: 1 if x in [1, 2, 3] else 0
df['3着以内'] = df['確定着順'].map(f_ranking)

参照:【第2回】Python で競馬予想してみる ~ データの前処理(CSVデータの読み込み~各種処理) ~

#モデルの学習
###アンダーサンプリングは必要 ?
アンダーサンプリングをした方が良いのか分からないので、今回はしない

build.ipynb
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))

結果は
予想外に時間がかかるのだ

【追記】騎手コードをカテゴリ変数化するのを忘れたので、修正して再学習
良いのか悪いのかよく分からんのだ。

Out
  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走前の〇〇〇データという意味)のデータが上位に。
下位にはレースのクラス名が勢ぞろい

Out
	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

今回はここまで:wave:

7
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
11