【競馬AI⑧】パラメーターチューニングでモデルの精度を向上させる

競馬AI

モデルの精度を上げるために今回はパラメーターチューニングを行います。

パラメーターをチューニングすることで、モデルの性能を向上させたり、オーバーフィッティングを避けたり、計算効率を良くすることができます。

ただパラメーターを見ても何が書いてあるかわからないですよね?一つ一つ値を変えて精度をチェックするのも大変だと思います。そんな問題を解決する自動でチューニングする方法を紹介します。

パラメーター例

パラメーターについて

本当はパラメーターの意味まで分かっていた方が良いので、主要なパラメーターだけ記載します。

num_iterationsブースティングの反復回数、デフォルトは100。値が大きいほどモデルは複雑になるが、過学習のリスクも増える。
learning_rate学習率で、各ブースティングステップでの更新の大きさを決める。一般的な値は0.1、0.05、0.01など。
num_leaves一つの木における葉の数。デフォルトは31。値が大きいほどモデルは複雑になり、過学習しやすくなる。
tree_learner並列学習の種類を指定。例えばserial、feature、dataなどがある。
max_depth木の最大の深さ。過学習を避けるために使われる。デフォルトは-1で、制限なしを意味する。
min_data_in_leaf一つの葉が持つデータの最小数。過学習を防ぐために重要なパラメータ。デフォルトは20。

何が何だかわからないですよね。他にも数多くのパラメーターがあるので、しっかり学びたい方は調べてみてください。
とりあえず今より精度が上がれば良いと思ってるだけの人は覚えなくても大丈夫です!

パラメーターチューニング

チューニングのコードは結構シンプルで、新しいファイルを作成して以下のコードをコピペで貼り付けて実行するだけになります。

dataやモデルのパス、ファイル名は実際の環境に合わせて修正してください。

import optuna.integration.lightgbm as lgb
import pandas as pd

def split_date(df, test_size):
    sorted_id_list = df.sort_values('日付').index.unique()
    train_id_list = sorted_id_list[:round(len(sorted_id_list) * (1-test_size))]
    test_id_list = sorted_id_list[round(len(sorted_id_list) * (1-test_size)):]
    train = df.loc[train_id_list]
    test = df.loc[test_id_list]
    return train, test

# データの読み込み
data = pd.read_csv('encoded/encoded_data.csv')
data = data.dropna()

# 着順を二値化
data['着順'] = data['着順'].map(lambda x: 1 if x<4 else 0)

# 特徴量とターゲットの分割
train, test = split_date(data, 0.3)
X_train = train.drop(['着順','オッズ','人気','上がり','走破時間','通過順'], axis=1)
y_train = train['着順']
X_test = test.drop(['着順','オッズ','人気','上がり','走破時間','通過順'], axis=1)
y_test = test['着順']

# LightGBMデータセットの作成
train_data = lgb.Dataset(X_train, label=y_train)
valid_data = lgb.Dataset(X_test, label=y_test)

params = {
    'objective': 'binary',  # 二値分類問題
    'metric': 'binary_logloss',  # 損失関数
    'verbosity': -1,
    'boosting_type': 'gbdt',
    'class_weight':'balanced',
    'random_state':100
}

# LightGBMによる学習とパラメーターチューニング
model = lgb.train(params, train_data, valid_sets=valid_data)

# チューニングしたパラメータの表示
print("Best params:", model.params)

# モデルの保存
model.save_model('model/model_optimized.txt')

チューニング結果

チューニングしたパラメーターを使って学習したモデルが保存されます。そのモデルを使って予想してみてください。

また、結果のパラメーターはターミナルに以下のように出力されますので、コピーしてestimation.pyに貼り付けて使用しても問題ありません。

このパラメーターを使って学習されると以下のようにAUCの値が向上しました。

チューニング前:0.7958876281061449
チューニング後:0.804519503521255

まとめ

このコードを使えば、機械的に多くのパターンのパラメーターを試してベストなパラメーターを探してくれます。

前回紹介した特徴量エンジニアリングと組み合わせて、より良い競馬予想AIを開発してみてください!

コメント

  1. 吉田和幸 より:

    たびたびの質問で恐縮です
    先日のエリザベス女王杯を予測した結果
    /***/***/race_table_scraping.py:371: UserWarning: Could not infer format, so each element will be parsed individually, falling back to `dateutil`….
    df_all_results[“日付差4”] = (pd.to_datetime(df_all_results[“日付4”], errors=’coerce’) – pd.to_datetime(df_all_results[“日付5”], errors=’coerce’)).dt.days
    警告?・エラー?がでました。
    ブレイディヴェーグの過去成績が4戦分しかないことが原因ですよね
    ブレイディヴェーグの予測値もかなり低い値だったのでこれが原因なのかなと勘ぐったりもしています
    どうなんでしょうか?

    それから当日の馬場状態の値をちゃんとスクレイピングするレースとしないレースがあるのですが
    どうしてでしょうか?

    ご都合の良いときにでもご回答いただければありがたいです。

    • agus agus より:

      レース数が足りないとその警告は出ますね。
      予測値に影響はあるとは思いますが、大きく変わることはないと思っています。
      ブレイディヴェーグは私の方では高く出ていたので、モデル自体の学習量や精度の問題だと思われます。

      馬場状態の方は確かに取れていないレースありました。
      気づいていなかったので、何かわかれば記事の方をアップデートします。

  2. 吉田和幸 より:

    返信ありがとうございます。
    馬場状態についてはこんな感じで自己解決したつもりになってます。
    babaSpan = soup.find(“span”, class_=”Item04″)
    baba = “”
    if babaSpan is not None:
    baba = babaSpan.text.strip()[-1]
    else:
    # “Item04″が見つからない場合、”Item03″クラスを持つ要素を検索し、そのテキストを’baba’に格納します
    item03Span = soup.find(“span”, class_=”Item03″)
    if item03Span is not None:
    baba = item03Span.text.strip()[-1]
    また、役立つ情報お待ちしてます。

  3. ren より:

    お世話になっております。競馬⑥⑦⑧にて編集したのちに実行しましたら以下のエラーが。
    Processing feature 10/10[LightGBM] [Fatal] The number of features in data (111) is not the same as it was in training data (112).
    You can set “predict_disable_shape_check=true“ to discard this error, but please be aware what you are doing.
    Traceback (most recent call last):
    line 79, in
    y_new_pred = model.predict(new_data)
    line 3538, in predict
    return predictor.predict(data, start_iteration, num_iteration,
    line 848, in predict
    preds, nrow = self.__pred_for_np2d(data, start_iteration, num_iteration, predict_type)
    line 938, in __pred_for_np2d
    return inner_predict(mat, start_iteration, num_iteration, predict_type)
    line 908, in inner_predict
    _safe_call(_LIB.LGBM_BoosterPredictForMat(
    line 125, in _safe_call
    raise LightGBMError(_LIB.LGBM_GetLastError().decode(‘utf-8’))
    lightgbm.basic.LightGBMError: The number of features in data (111) is not the same as it was in training data (112).
    You can set “predict_disable_shape_check=true“ to discard this error, but please be aware what you are doing.

    カラムが足りない旨のメッセージが出ているようです。
    普通に手作業で入力してデータを作った場合はちゃんと予測ができるのですが。。。

    • agus agus より:

      競馬AI⑥で、特徴量を一つ追加しているからだと思います。
      コメントアウトしてください。
      競馬AI②のencode.pyのコードが修正できていなかったので、近いうちに更新しておきます。
      競馬AI⑥308行目から315行目
        # CSVファイルから騎手の勝率を読み込む(※競馬AI⑦で修正したバージョン)
        # CSVに出力しておいた騎手の勝率を読み込み、特徴量に加える処理。
        # CSVのパスはご自身のコードに合わせてください。
      jockey_win_rate = pd.read_csv(‘calc_rate/jockey_win_rate.csv’)
      # 騎手ごとの勝率を取得して新たなデータフレームを作成
      jockey_stats = jockey_win_rate.groupby(‘騎手’)[‘騎手の勝率’].first().reset_index()
      # df_combinedとjockey_statsを結合して騎手の勝率を代入する
      df_all_results = df_all_results.merge(jockey_stats, on=’騎手’, how=’left’)

  4. ren より:

    お世話になっております。
    試しに
    # CSVファイルから騎手の勝率を読み込む(※競馬AI⑦で修正したバージョン)
    # CSVに出力しておいた騎手の勝率を読み込み、特徴量に加える処理。
    # CSVのパスはご自身のコードに合わせてください。
    #jockey_win_rate = pd.read_csv(‘/Users/hiragadaiki/Pictures/data/calc_rate/jockey_win_rate.csv’)
    # 騎手ごとの勝率を取得して新たなデータフレームを作成
    #jockey_stats = jockey_win_rate.groupby(‘騎手’)[‘騎手の勝率’].first().reset_index()
    #df_combinedとjockey_statsを結合して騎手の勝率を代入する
    #df_all_results = df_all_results.merge(jockey_stats, on=’騎手’, how=’left’)
    とコメントアウトしたところ、
    The number of features in data (111) is not the same as it was in training data (112).
    から
    The number of features in data (110) is not the same as it was in training data (112).
    と変更されただけでした。やはりencode.pyのコード修正が必要でしょうか。
    試しにmodel.txtの6行目
    max_feature_idx=111を112に変更して実行したところ上記msgは無くなりましたが、
    line 2639, in __init__
    _safe_call(_LIB.LGBM_BoosterCreateFromModelfile(
    line 125, in _safe_call
    raise LightGBMError(_LIB.LGBM_GetLastError().decode(‘utf-8’))
    lightgbm.basic.LightGBMError: Wrong size of feature_names
    の表示が出てしまいます、、、お忙しい中恐縮ですがご教示いただけますでしょうか。
    よろしくお願いいたします。

    • agus agus より:

      すみません。間違っていたのはスクレイピングする方のコードでした。
      ⑥と②のページ修正したので、差分を確認してみてください。
      足りない特徴量は平均斤量ではないかと思います。そちらも「encoded_data」とrace_dataのヘッダーの差分を確認してみてください。

  5. ren より:

    お世話になっております。無事予想することができました。
    本当にありがとうございました!!