競馬予想AIの作成⑧(AIの的中率を算出)

機械学習

はじめに

前回はハイパーパラメータを調整して、タイム予測モデルの精度向上を行いました。
タイム予測モデルを用いてタイムを予測し、予測順位と実順位を比較して三連複の的中率を算出しました。

単勝、複勝、馬連、ワイドの的中率を算出できるようにしました。
その他にも作成したタイム予測モデルの保存、的中率の保存、ハイパーパラメータの保存などを行えるようにプログラムを更新しました。

概要

  • タイム予測モデルで使用するデータは、Webスクレイピングの回で取得したデータ。
  • 今まで作成したプログラムをベースに処理を追加するのと、プログラムを3つに分割。
  • predict_race.py
    データを読み込みモデルの作成と的中率の計算を開始するプログラム。
    作成したモデルや的中率を保存するフォルダを作成します。
    複数の訓練データを用意した場合、その訓練データで順次、モデル作成を行います。
  • make_model.py
    タイム予測モデルを作成するプログラム。
    同プログラム内で的中率を計算するプログラムcalc_win_rate.pyを呼び出します。
  • calc_win_rate.py
    タイム予測モデルの的中率を計算するプログラム。
    単勝、複勝、馬連、ワイド、三連複の的中率を計算します。
    的中率をCSVに保存を行います。

タイム予測モデルとして、少なくとも計算上の的中する確率(=適当に選んだ馬で的中する確率)より高くないと、タイム予測モデルを使用する意味がありません。単勝、複勝、馬連、ワイド、三連複の馬券を1点、購入したときの計算上の的中する確率を確認しました。

<計算上の的中する確率>

出走数 単勝 複勝 馬連 ワイド 三連複
18頭 5.56% 16.67% 0.65% 1.96% 0.12%
15頭 6.67% 20.00% 0.95% 2.86% 0.22%
10頭 10.00% 30.00% 2.22% 6.67% 0.83%
5頭 20.00% 60.00% 10.00% 30.00% 10.00%

出走数が多い場合は計算上の的中する確率は低くなり、出走数が少ない場合はその逆で高くなります。タイム予測モデルでは、出走数が5頭の場合の計算上の的中する確率より高くなることを目標にしています。

以前の回で、2013年1月から2022年6月までのレース結果をWebスクレイピングで取得しました。
タイム予測モデルを作成するにあたり、訓練データを下記のように分割して用意しました。

  1.  2013年-2022年
  2.  2015年-2022年
  3.  2017年-2022年
  4.  2019年-2022年

予測するのは2022年5月21日~6月21日の間に行われたレース結果です。
予測に使用したアルゴリズムはXGBoostです。

結論としては、三連複と馬連の的中を除くと、計算上の的中する確率より高い的中率を出すことができました。

プログラムの概要

作成したプログラムは3つです。

predict_race.pyの概要

該当行 概要
1~8行 ライブラリの読み込み
16~20行 訓練データで使用するファイルを指定
22~36行 訓練データごとに、予測と評価を行う
36行の`make_model.main(df,dir_path)`で自作のタイム予測モデル作成のプログラムを呼び出している。

make_model.pyの概要

前回と差分がある箇所は「変更あり」「追加」と記載しています。

該当行 概要
1~36行 ライブラリの読み込み(変更あり
7行目で自作ライブラリ読み込み、36行目でモデル保存用ライブラリ読み込み
39行 メイン関数の開始位置
40行 モデルやハイパーパラメータを保存するフォルダを作成(追加
47行 読み込んだデータを説明変数x(距離、馬場、開催場所など)、目的変数y(タイム)に整形する
51~52行 訓練データと評価データに分割する
55行 読み込んだデータをワンホットエンコード(OneHotEncode)する
58行 訓練データと検証データに分割する
61行 読み込んだデータをdmatrixに変換する
65~67行 optunaでハイパーパラメータの調整を行い、最適なパラメータでモデルを作成する
70行 作成したモデルでタイムを予測する
73~74行 モデルを保存する(追加
77~80行 R2、MAE、RMSEで評価する(概要の⑤に該当)
83行 実タイムと予測タイムを散布図で比較する
86行 予測タイムに対する重要度分析
89行 予測タイムをCSVで保存する
92行 ハイパーパラメータの最適化の結果を表示する
97~100行 的中率の計算結果を保存する(追加
103~302行 自作の関数(変更あり

calc_win_rate.pyの概要

該当行 概要
1~8行 ライブラリ読み込み
11行 メインの開始位置
13行 予測したタイムを含む、レース結果を読み込む
16行 レース名と開催場所のラベルエンコードを行う
19行 ユニークなエンコードしたレース名を抽出する
21~62行 的中率を計算する
64~79行 的中率を表示する
81行 予測順位を加えたレース結果を保存する
84~92行 計算した的中率をCSVに保存する

プログラム

」をクリックするとプログラムが表示されます。

predict_race.py
# システム用のライブラリ
import datetime

# 自作のライブラリ
import make_model

# 数値解析用のライブラリ
import pandas as pd


def main():
    # 整形されたレース結果のパスを設定する
    # Webスクレイピングで取得した2013~2022年のレース結果を
    # 2013-2022年,2015-2022年,2017-2022年,2019-2022年の
    # 4つのデータに分割した
    df_path = 'C:\\Users\\sakur\\Google ドライブ\\Web_Scraping\\'
    df_name = ['2013_2022_df_race_result.csv',
               '2015_2022_df_race_result.csv',
               '2017_2022_df_race_result.csv',
               '2019_2022_df_race_result.csv']

    # 訓練データごとに、予測と評価を行う
    for i in df_name:
        df_file = df_path + i
        df = pd.read_csv(df_file, encoding='cp932')
        # 開催日の型をdate型に変換する
        df['開催日'] = pd.to_datetime(df['開催日'])

        # モデル、ハイパーパラメータ、予測結果、的中率を保存するフォルダのパスを設定する
        dt_now = datetime.datetime.now()
        timestamp = dt_now.strftime('%Y%m%d%H%M%S')
        i_split = i.split('_')
        dir_path = 'trainData_' + i_split[0] + '_' + i_split[1] + '_' + timestamp

        # モデルの作成
        make_model.main(df, dir_path)


if __name__ == "__main__":
    main()
make_model.py
# システム用のライブラリ
import datetime
import os
import sys
import time

# 自作のライブラリ
import calc_win_rate

# 数値解析用のライブラリ
import numpy as np
import pandas as pd

# エンコード用のライブラリ
from sklearn.preprocessing import OrdinalEncoder

# xgboostのライブラリ
import xgboost as xgb

# 訓練データとモデル評価用データに分けるライブラリ
from sklearn.model_selection import train_test_split

# ハイパーパラメータチューニング自動化ライブラリ
import optuna

# R2,MAE,RMSEの計算用ライブラリ
from sklearn.metrics import r2_score
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import mean_squared_error

# グラフ作成用のライブラリ
import matplotlib.pyplot as plt
import japanize_matplotlib

# モデル保存用のライブラリ
import joblib


def main(df, dir_path):
    # 保存フォルダを作成する
    os.makedirs(dir_path, exist_ok=True)

    # データを読み込む
    df_race_result = df

    # 説明変数x,目的変数yに整形する
    x, y = format_data(df_race_result)

    # 訓練データと評価データに分割する
    # 分割する日はdayで指定する
    day = '2022-5-21'
    x_train, x_test, y_train, y_test = split_training_evaluation_data(x, y, day)

    # データをエンコードする
    x_train_encoded, x_test_encoded = encode_data(x_train, x_test)

    # 訓練データと検証データに分割する
    x_train, x_eval, y_train, y_eval = split_training_validation_data(x_train_encoded, y_train)

    # データをdmatrixに変換する
    dtrain, deval, dtest = convert_dmatrix(x_train, x_eval, x_test_encoded, y_train, y_eval, y_test)

    # studyインスタンスを作成し、studyインスタンスを最適化する
    # timeoutで最適化を行う時間に制限を設ける
    objective = Objective(dtrain, deval, dtest, y_test)
    study = optuna.create_study()
    study.optimize(objective, timeout=30)

    # タイムを予測する
    time_predict, gbm = predict_time(study, dtrain, deval, dtest, dir_path)

    # モデルを保存する
    dump_path = dir_path + '/gbm.pkl'
    joblib.dump(gbm, dump_path, compress=3)

    # 評価 R2,MAE,RMSEの計算
    r2, mae, rmse = evaluate(y_test, time_predict, dir_path)
    print('r2:', r2)
    print('mae:', mae)
    print('rmse:', rmse)

    # 実タイムと予測タイムを散布図で比較する
    create_scatter_plot(y_test, time_predict, r2, dir_path)

    # 予測タイムに対する重要度分析
    create_importance_analysis(gbm, dir_path)

    # 予測タイムを保存する
    save_predict_results(x_test, y_test, time_predict)

    # ハイパーパラメータの最適化の結果
    print('ハイパーパラメータの最適値:', study.best_params)

    # 的中率の計算結果を保存する
    # 計算は自作のcalc_win_rate.pyで実施する
    # 計算結果は標準出力されるので、標準出力内容を的中率.txtに保存する
    win_rate = dir_path + '/的中率_整形後.txt'
    sys.stdout = open(win_rate, 'w')
    calc_win_rate.main()
    sys.stdout = sys.__stdout__


# 説明変数x、目的変数yに整形する
def format_data(df):
    x = df.drop(['タイム'], axis=1)
    y = df['タイム'].values

    # カラム名を英語に変換する
    x.columns = ['horse', 'age', 'rider_weight', 'rider', 'win', 'popular',
                 'horse_weight', 'distance', 'weather', 'ground', 'condition',
                 'date', 'race_name', 'location']

    return x, y


# 訓練データと評価データに分割する
def split_training_evaluation_data(x, y, day):
    # 訓練データと評価データを分割する日付を設定する
    mday = pd.to_datetime(day)

    # 訓練データと評価データを分割する
    train_index = x['date'] < mday test_index = x['date'] >= mday

    # 説明変数xと目的変数yを訓練データ用と評価データ用に分割する
    x_train = x[train_index]
    x_test = x[test_index]

    y_train = y[train_index]
    y_test = y[test_index]

    return x_train, x_test, y_train, y_test


# データをラベルエンコードする
def encode_data(train, test):
    # 訓練データのラベルエンコード
    oe_x_train = OrdinalEncoder()
    x_train_encoded = oe_x_train.fit_transform(train)  # Dataframeからndarrayに変わるとカラムがなくなり、
    x_train_encoded = pd.DataFrame(x_train_encoded)  # 特徴量分析で特徴量名がf0,f1,f2,,となるのでndarrayからDataframeに戻す
    x_train_encoded.columns = list(train.columns.values)  # カラムを付ける

    # 評価データのラベルエンコード
    oe_x_test = OrdinalEncoder()
    x_test_encoded = oe_x_test.fit_transform(test)  # Dataframeからndarrayに変わるとカラムがなくなり、
    x_test_encoded = pd.DataFrame(x_test_encoded)  # 特徴量分析で特徴量名がf0,f1,f2,,となるのでndarrayからDataframeに戻す
    x_test_encoded.columns = list(test.columns.values)  # カラムを付ける

    return x_train_encoded, x_test_encoded


# 訓練データと検証データに分割する
def split_training_validation_data(x, y):
    # 訓練データをさらに、8:2の割合で訓練データと検証データを分割する
    x_train, x_eval, y_train, y_eval = train_test_split(x, y, test_size=0.2, random_state=0)

    return x_train, x_eval, y_train, y_eval


# xgboostを実行するには特殊なmatrixにする必要がある
def convert_dmatrix(x_train, x_eval, x_test_encoded, y_train, y_eval, y_test):
    # dtrain:訓練データ,deval:検証データ,dtest:評価データ
    dtrain = xgb.DMatrix(x_train, label=y_train)
    deval = xgb.DMatrix(x_eval, label=y_eval)
    dtest = xgb.DMatrix(x_test_encoded, label=y_test)

    return dtrain, deval, dtest


# ハイパーパラメータ探索
class Objective:
    def __init__(self, dtrain, deval, dtest, y_test):
        # 変数X, yの初期化
        self.dtrain = dtrain
        self.deval = deval
        self.dtest = dtest
        self.y_test = y_test

    def __call__(self, trial):
        params = {'silent': 0,
                  'max_depth': trial.suggest_int('max_depth', 6, 9),
                  'min_child_weight': trial.suggest_int('min_child_weight', 1, 10),
                  'eta': trial.suggest_loguniform('eta', 0.01, 1.0),
                  'subsample': trial.suggest_uniform('subsample', 0.0, 1.0),
                  'colsample_bytree': trial.suggest_uniform('colsample_bytree', 0.0, 1.0),
                  'alpha': trial.suggest_loguniform('alpha', 0.01, 10.0),
                  'lambda': trial.suggest_loguniform('lambda', 0.01, 10.0),
                  'gamma': trial.suggest_loguniform('gamma', 0.01, 10.0),
                  'objective': 'reg:squarederror',
                  'eval_metric': 'rmse',
                  'tree_method': 'gpu_hist',    # gpuを使う設定
                  'predictor': 'gpu_predictor', # gpuを使う設定
                  'seed': 1,
                  'verbose': -1}

        gbm = xgb.train(params,
                        self.dtrain,
                        num_boost_round=10000,
                        early_stopping_rounds=10,
                        evals=[(self.deval, 'eval')] # 検証データで評価する
                        )

        # 評価データでタイムを予測し、RMSEを計算する
        predicted = gbm.predict(self.dtest)
        RMSE = np.sqrt(mean_squared_error(self.y_test, predicted))

        # 最適化処理に「枝刈り(pruning)」を含める
        pruning_callback = optuna.integration.XGBoostPruningCallback(trial, 'rmse')

        return RMSE


def predict_time(study, dtrain, deval, dtest, dir_path):
    # 最適時のハイパーパラメータでモデルを作成する
    # paramsに最適時のハイパーパラメータを設定する
    params = {'silent': 0,
              'max_depth': study.best_params['max_depth'],
              'min_child_weight': study.best_params['min_child_weight'],
              'eta': study.best_params['eta'],
              'subsample': study.best_params['subsample'],
              'colsample_bytree': study.best_params['colsample_bytree'],
              'alpha': study.best_params['alpha'],
              'lambda': study.best_params['lambda'],
              'gamma': study.best_params['gamma'],
              'objective': 'reg:squarederror',
              'eval_metric': 'rmse',
              'tree_method': 'gpu_hist',
              'predictor': 'gpu_predictor',
              'seed': 1,
              'verbose': -1}

    # 学習、評価を行う
    gbm = xgb.train(params,
                    dtrain,
                    num_boost_round=10000,
                    early_stopping_rounds=10,
                    evals=[(deval, 'eval')]
                    )

    # タイムを予測
    time_predict = gbm.predict(dtest)

    # 学習、評価で設定していた値を保存する
    params_path = dir_path + '/ハイパーパラメータ.txt'
    params_path = open(params_path, 'w')
    params_path.write(str(params))
    params_path.close()

    return time_predict, gbm


# 評価 R2,MAE,RMSEの計算
def evaluate(y_test, time_predict, dir_path):
    r2 = r2_score(y_test, time_predict)
    mae = mean_absolute_error(y_test, time_predict)
    rmse = np.sqrt(mean_squared_error(y_test, time_predict))

    # 評価の計算結果を保存する
    result_path = dir_path + '/評価結果.txt'
    result = open(result_path, 'w')
    result.write('r2:' + '\t' + str(r2) + '\n')
    result.write('mae:' + '\t' + str(mae) + '\n')
    result.write('rmse:' + '\t' + str(rmse) + '\n')
    result.close()

    return r2, mae, rmse


# 実タイムと予測タイムを散布図で比較
def create_scatter_plot(y_test, time_predict, r2, dir_path):
    plt.figure(figsize=(6, 6))
    y_max = y_test.max()
    plt.plot((0, y_max), (0, y_max), c='k')
    plt.scatter(y_test, time_predict, c='b')
    plt.xlabel('実タイム')
    plt.ylabel('予測タイム')
    plt.title(f'実タイムと予測タイムの散布図(タイム) R2={r2:.4f}')
    plt.grid()

    savefig_path = dir_path + '/実タイムと予測タイムの散布図.svg'
    plt.savefig(savefig_path)

    plt.show(block=False)


# 予測タイムに対する重要度分析
def create_importance_analysis(model, dir_path):
    fig, ax = plt.subplots(figsize=(8, 4))
    xgb.plot_importance(model, ax=ax, height=0.8, importance_type='gain',
                        show_values=False, title='予測タイムの重要度分析')

    savefig_path = dir_path + '/予測タイムの重要度分析.svg'
    plt.savefig(savefig_path)

    plt.show(block=False)


# 予測タイムを保存する
def save_predict_results(x_test, y_test, time_predict):
    df_race_result_predict = x_test.copy()
    df_race_result_predict['time'] = y_test
    df_race_result_predict['time_predict'] = time_predict

    df_race_result_predict.to_csv('./予測結果.csv', encoding='cp932', errors='ignore')


if __name__ == "__main__":
    main()
calc_win_rate.py
# CSVを処理するライブラリ
import csv

# 数値解析用のライブラリ
import pandas as pd

# ラベルエンコードのライブラリ
from sklearn.preprocessing import LabelEncoder


def main():
    # レース結果を読み込む
    df_predict_result = read_race_result()

    # レース名と開催場所のラベルエンコードを行う
    df_predict_result = labelencode_race_location(df_predict_result)

    # レース名_エンコードのユニークを抽出する
    race_encoded_list = list_unique_race(df_predict_result)

    num_races = 0       # レースの数
    tansho_hits = 0     # 単勝の当たりの数
    fukusho_hits = 0    # 複勝の当たりの数
    umaren_hits = 0     # 馬連の当たりの数
    wide_hits = 0       # ワイドの当たりの数
    sanrenpuku_hits = 0 # 三連複の当たりの数

    df_real_predict_eval = pd.DataFrame()

    # 同一レース名があるので、レース名と開催場所でレース結果を絞る
    for i in race_encoded_list:
        df_race = df_predict_result[df_predict_result['race_name_encoded'] == i]
        location_encoded_list = df_race['location_encoded'].unique()

        for j in location_encoded_list:
            df_race_location = df_race[df_race['location_encoded'] == j]

            # 実順位と予測順位を追加する
            df_race_location_assign_rank = assign_rank(df_race_location)
            df_real_predict_eval = pd.concat([df_real_predict_eval, df_race_location_assign_rank])

            # 三連複の当たり外れを判定する
            count_sanrenpuku = sanrenpuku(df_race_location_assign_rank)
            sanrenpuku_hits += count_sanrenpuku

            # 複勝の当たり外れを判定する
            count_fukusho = fukusho(df_race_location_assign_rank)
            fukusho_hits += count_fukusho

            # 単勝の当たり外れを判定する
            count_tansho = tansho(df_race_location_assign_rank)
            tansho_hits += count_tansho

            # 馬連の当たり外れを判定する
            count_umaren = umaren(df_race_location_assign_rank)
            umaren_hits += count_umaren

            # ワイドの当たり外れを判定する
            count_wide = wide(df_race_location_assign_rank)
            wide_hits += count_wide

            num_races += 1

    print('レースの数:' + '\t'*2 + str(num_races))
    print('-------')
    print('単勝の当たりの数:' + '\t'*1 + str(tansho_hits))
    print('単勝の的中率:' + '\t'*2 + '{:.2%}'.format(tansho_hits/num_races))
    print('-------')
    print('複勝の当たりの数:' + '\t'*1 + str(fukusho_hits))
    print('複勝の的中率:' + '\t'*2 + '{:.2%}'.format(fukusho_hits / num_races))
    print('-------')
    print('馬連の当たりの数:' + '\t'*1 + str(umaren_hits))
    print('馬連の的中率:' + '\t'*2 + '{:.2%}'.format(umaren_hits / num_races))
    print('-------')
    print('ワイドの当たりの数:' + '\t'*1 + str(wide_hits))
    print('ワイドの的中率:' + '\t'*1 + '{:.2%}'.format(wide_hits / num_races))
    print('-------')
    print('三連複の当たりの数:' + '\t'*1 + str(sanrenpuku_hits))
    print('三連複の的中率:' + '\t'*1 + '{:.2%}'.format(sanrenpuku_hits/num_races))

    df_real_predict_eval.to_csv('./比較結果.csv', encoding='cp932', errors='ignore')

    # それぞれの買い方の的中率をCSVに追記で保存する
    tekichu_list = ['{:.2%}'.format(tansho_hits / num_races),
                    '{:.2%}'.format(fukusho_hits / num_races),
                    '{:.2%}'.format(umaren_hits / num_races),
                    '{:.2%}'.format(wide_hits / num_races),
                    '{:.2%}'.format(sanrenpuku_hits / num_races)]

    with open('./的中率.csv', mode='a', newline='') as f:
        writer = csv.writer(f)
        writer.writerow(tekichu_list)


# 実順位を追加する
def assign_rank(df):
    df_tmp = df.copy()
    df_tmp['rank_real'] = range(1, len(df.index) + 1)

    # 予測順位を追加する
    df_predictsort = df_tmp.sort_values('time_predict')
    df_predictsort['rank_predict'] = range(1, len(df_predictsort.index) + 1)

    return df_predictsort


# 単勝
# 実順位1と予測順位1が合致するかを確認する
# 予想の当たり1/外れ0を返す
def tansho(df):
    df = df.reset_index()
    if df['rank_real'][0] == df['rank_predict'][0]:
        return 1
    else:
        return 0


# 複勝
# 8頭以上の出走レースでは、3着以内に予測順位1~3の有無を確認する
# 8頭未満5頭以上が出走しているレースでは、2着以内に予測順位1~2の有無を確認する
# 4頭以下の出走レースは、複勝馬券は発券されないので予測順位の確認処理は行わない
# 予想の当たり1/外れ0を返す
def fukusho(df):
    hores_num = len(df)
    df = df.sort_values('rank_real')
    df = df.reset_index()

    count = 0

    if hores_num >= 8:
        for i in range(0, 3):
            if df['rank_predict'][i] <= 3: count = 1 elif hores_num >= 5:
        for i in range(0, 2):
            if df['rank_predict'][i] <= 2:
                count = 1
    else:
        count = 0

    return count


# 馬連
# 2着以内に予測順位1,2の有無を確認する
# 実順位の合計が3(1位+2位)だった場合は当たり、それ以外は外れ
# 予想の当たり1/外れ0を返す
def umaren(df):
    df_sort = df.sort_values('rank_real')
    df_tmp_predict_top2 = df_sort.iloc[:2, df_sort.columns.get_loc('rank_predict')]

    if df_tmp_predict_top2.sum() == 3:
        return 1
    else:
        return 0


# ワイド
# 3着以内に予測順位1~3が2つ以上あるかを確認する
# 予想の当たり1/外れ0を返す
def wide(df):
    df = df.sort_values('rank_real')
    df = df.reset_index()

    count = 0

    for i in range(0, 3):
        if df['rank_predict'][i] <= 3: count += 1 if count >= 2:
        return 1
    else:
        return 0


# 三連複
# 実順位の合計が6(1位+2位+3位)だった場合は当たり、それ以外は外れ
# 予想の当たり1/外れ0を返す
def sanrenpuku(df):
    df_sort = df.sort_values('rank_real')
    df_tmp_predict_top3 = df_sort.iloc[:3, df_sort.columns.get_loc('rank_predict')]

    if df_tmp_predict_top3.sum() == 6:
        return 1
    else:
        return 0


# 予測結果を追加したレース結果を読み込む
def read_race_result():
    # レース結果を読み込む
    df = pd.read_csv('./予測結果.csv', encoding='cp932')
    df.drop('Unnamed: 0', axis=1, inplace=True)

    return df


# レース名と開催場所をラベルエンコーディング
def labelencode_race_location(df):
    le = LabelEncoder()
    race_name_encoded = le.fit_transform(df['race_name'].values)
    df['race_name_encoded'] = race_name_encoded

    le = LabelEncoder()
    location_encoded = le.fit_transform(df['location'].values)
    df['location_encoded'] = location_encoded

    return df


# 重複を排除したレース名の抽出
def list_unique_race(df):
    return df['race_name_encoded'].unique()


if __name__ == "__main__":
    main()

プログラムの補足

作成したモデルを、別の機会に読み込んで予測に使用できるように保存するようにしました。

# モデル保存用のライブラリ
import joblib

# モデルを保存する dump_path = dir_path + '/gbm.pkl' joblib.dump(gbm, dump_path, compress=3)

読み込むデータのファイル名の形式は(西暦)_(西暦)_xxxxx.csvです。
例:2013_2022_df_race_result.csv

保存先フォルダはファイル名の西暦の箇所を使用して作成します。
例:trainData_2013_2022_20221105184859

最後の14桁は、yyyymmddHHMMSS(西暦月日時刻)です。

プログラムの実行結果

訓練データのファイル名を「2013_2022_df_race_result.csv」でプログラムを実行すると、下記のフォルダが生成されます。

作成されるフォルダ:trainData_2013_2022_20221105184859

フォルダの中には下記ファイルが保存されます。

gbm.pkl 作成したタイム予測モデル
ハイパーパラメータ.txt タイム予測モデルの作成時に使用したハイパーパラメータの一覧
評価結果.txt R2、MAE、RMSEの値
的中率_整形後.txt 単勝、複勝、馬連、ワイド、三連複の的中率
予測タイムの重要度分析.svg 画像データ
実タイムと予測タイムの散布図.svg 画像データ

また、プログラムと同一フォルダに的中率.csvを出力します。

4つの訓練データで予測モデルを作成しました。
それぞれの的中率をまとめると下記のようになりました。

<作成したタイム予測モデルの的中率>

訓練データ 単勝 複勝 馬連 ワイド 三連複
2013-2022 18.22% 73.79% 6.46% 34.28% 3.38%
2015-2022 26.36% 82.09% 10.76% 45.20% 5.76%
2017-2022 33.74% 85.93% 14.99% 53.73% 8.22%
2019-2022 22.21% 76.71% 7.53% 36.82% 3.69%

2022年5月21日から6月21日までのレースを予測した場合、2017-2022年のデータを用いたタイム予測モデルが最も精度が高い結果になりました。また計算上の的中の確率よりも三連複以外は高い結果になりました。複勝は85.93%なので、かなり高い的中率だと思います。

次回、試したいこと

レース前日までに予測結果を公開する。
オッズや馬場の情報は、レースの前日までに公開されていることを知りました。それらの情報を使って、レース当日の予測を出せるようにしてみます。

前回、挙げた試したいことも残っているので、できそうなところから試していきます。

コメント

タイトルとURLをコピーしました