競馬予測AIの作成⑪(複数の予測モデルで順位予測)

機械学習

はじめに

複数の予測モデルで同一レースの順位予測を実施して、予測モデル同士の精度を比較したい。

今は、1ヶ月に1回、netkeibaから過去のレース情報を取得して予測モデルを作成しています。1回の予測モデル作成で、訓練データ期間が異なる10個の予測モデルを作成しています。10個の予測モデルから評価データに対しての複勝の的中率が最も高い予測モデルを1つ選定して週末のレース順位予測で使用しています。その他の9個の予測モデルは使用していませんでした。

予測モデルの選定は、各予測モデルの的中率を保存している「\keiba_ai\make_model\data\的中率.csv」を参照して決めています。
B列が評価データに対しての複勝の的中率です。最も高い的中率は90.00%になるので、予測モデルの保存パス(F列)が「\data\trainData_2021_2023_20230317_170818」を週末のレース順位予測で使用します。
※A列は単勝の的中率、C列は馬連の的中率、D列はワイドの的中率、E列は三連複の的中率

選定した予測モデルを使用して3/19のレース順位を予測した結果は下記となりました。

評価データを用いた複勝の的中率と、実際のレース情報から順位予測した複勝の的中率に大きな差が出ています。作成した予測モデルは過学習している可能性があり、その他の予測モデルでも実際のレース情報から順位予測した結果を確認してみたい。今後、訓練データ期間の変更や説明変数の変更(両親の情報を追加など)、モデル作成時の学習方法を変更(lightgbm、DeepLeaningなど)などの様々な条件で予測モデルを作成する予定です。複数の予測モデルで実際のレース順位予測を行えるようにしてみたい。

config.iniに設定した予測モデル以外に、作成した全ての予測モデルでもレース予測を行い、その結果をCSVファイルに保存できるようにプログラムを改修しました。

作成したプログラムの概要

更新・で追加したプログラムは2つです。

predict_rank.pyの更新

既存のプログラムでは、config.iniで指定した予測モデルで予測対象のレース情報の順位を予測しています。このプログラムを更新して、予測モデル郡を保存しているパス内に含まれる全ての予測モデルで順位を予測する処理と、その予測した結果をCSVファイルに出力する処理を追加しました。

該当行概要
1~17モジュールとライブラリの読み込み
23メイン処理の開始位置
28~54レース情報から1~3位の順位を予測する
58~631~3位の予測結果をツイッターに投稿する
66~75予測結果をCSVファイルに保存する
79~127保存されている予測モデルで1~3位を予測してCSVファイルに保存する
プログラムの概要

calc_hit_rate_each_mode.pyの追加

predict_rank.pyで出力した各予測モデルの予測結果を保存したCSVファイルから複勝の的中率を算出します。その算出結果をCSVファイルに保存します。

該当行概要
1~15モジュールとライブラリの読み込み
23メイン処理の開始位置
28~29各予測モデルの的中率を保存するフォルダを設定する
32~34取得したレース結果を読み込む
37~83各予測モデルで予測した1~3位を、レース結果と比較して的中率を算出する
86~87算出した的中率を保存する
プログラムの概要

更新・追加したプログラム

predict_rank.pyのコード

# 作成した予測モデルで予測対象のレース情報の順位を予測する
# 予測した結果はCSVファイルに出力する
# 呼び出し元:call_program.py
# 出力するファイル
# ./predict/data/1~3位の順位予測_(開催場所)_yyyymmdd_hhmmss.csv
#   ラウンドごとに出力する。ツイッター用メッセージの作成に使用する
#   上記のファイルは使用していない可能性が高いので削除する
# ./predict/data/predict_result_(開催場所)_yyyymmdd.csv
#   ラウンドごとに追記する。最終レース後の実際のレース結果と比較するときに使用する

# システム用のライブラリ
import time
import os
import pandas as pd

# 自作プログラムを読み込む
from my_module import fnc


# メイン処理
# 引数:開催場所、予測対象のレース情報のCSVファイル、設定ファイル、ロガー
# 戻値:無し
def main(location, csv_file, inifile, logger):
    # プログラムの開始時刻を取得する
    start_time = time.time()
    logger.info('start_time:{}'.format(start_time))

    # 予測対象のレース情報を読み込んで開催日の型をdate型に変換する
    df_race_info = pd.read_csv(csv_file, encoding='cp932')
    df_race_info['date'] = pd.to_datetime(df_race_info['date'], format='%Y年%m月%d日')

    # 作成したモデルとレース情報をエンコードするモデルのパス
    model_dir = inifile['settings']['model_path']
    model_path = model_dir + 'gbm.pkl'
    enc_model_path = model_dir + 'oe_x.pkl'

    # 予測データを追加するデータフレームを用意
    df_race_info_add_predict = df_race_info.copy()

    # レース情報をエンコードし、dmatrixに変換する
    dtest = fnc.encode_race_info(enc_model_path, df_race_info_add_predict)

    # タイムを予測し、予測結果を予測対象レース情報に追加する
    df_race_info_add_predict = fnc.predict_ranking(model_path, dtest, df_race_info_add_predict)

    # レース名と開催場所、ラウンドをラベルエンコードする
    df_race_info_add_predict = fnc.labelencode_race_location_round(df_race_info_add_predict)

    # nanが含まれている行を削除
    df_race_info_add_predict = df_race_info_add_predict.dropna()

    # ユニークなエンコード済みレース名を抽出する
    race_encoded_unique_list = df_race_info_add_predict['race_name_encoded'].unique()
    logger.info('race_encoded_unique_list:{}'.format(' '.join(map(str, race_encoded_unique_list))))

    # ツイッター用のメッセージを保存するテキストファイルのパス
    # テキストファイルが有った場合は削除する
    message_path = inifile['twitter']['message_path']
    message_location_path = fnc.set_path_for_twitter_message(message_path, location)

    # 順位予測を行い、1~3位を抽出してツイッター用のメッセージを作成して保存する
    # 同一レース名があるので、レース名と開催場所、ラウンドでレース結果を絞る
    df_predict_eval = fnc.predict_rank_1st_3rd(race_encoded_unique_list, df_race_info_add_predict, message_location_path)

    # 不要な情報を削除とカラムを並び替える
    df_predict_eval = fnc.delete_unnecessary_columns(df_predict_eval)
    df_predict_eval = fnc.sort_columns(df_predict_eval)

    # CSVの保存先を設定する
    # 予測結果を保存するフォルダがなければ作成する
    predicted_path = inifile['settings']['predicted_path']
    os.makedirs(predicted_path, exist_ok=True)

    # ラウンドごとの1~3位の予測順位をCSVファイルに追記する
    fnc.append_predicted_rank_1st_3rd_to_csv(predicted_path, df_predict_eval, location)

    # 作成した全モデルで予測順位を出す
    # 作成したモデルを保存しているパス
    model_data_path = './make_model/data/'

    # 各モデルの予測結果を保存するフォルダがなければ作成する
    predicted_path = './predict/data/predicted_each_model/'
    os.makedirs(predicted_path, exist_ok=True)

    # モデルの保存パスに設定されているディレクトリ内のディレクトリ一覧を取得する
    files = os.listdir(model_data_path)
    files_dir = [f for f in files if os.path.isdir(os.path.join(model_data_path, f))]

    # モデルごとに順位予測を行う
    for model_dir in files_dir:
        # 予測データを追加するデータフレームを用意
        df_race_info_add_predict = df_race_info.copy()

        # 作成したモデルとレース情報をエンコードするモデルのパス
        model_path = model_data_path + model_dir + '/gbm.pkl'
        enc_model_path = model_data_path + model_dir + '/oe_x.pkl'

        # 作成したモデルとレース情報をエンコードするモデルが存在していたら予測を行う
        if os.path.isfile(model_path) and os.path.isfile(enc_model_path):
            # 順位予測を行うためにレース情報をエンコードし、dmatrixに変換する
            dtest = fnc.encode_race_info(enc_model_path, df_race_info_add_predict)

            # タイムを予測し、予測結果をレース情報に追加する
            df_race_info_add_predict = fnc.predict_ranking(model_path, dtest, df_race_info_add_predict)

            # レース名と開催場所、ラウンドをラベルエンコードする
            df_race_info_add_predict = fnc.labelencode_race_location_round(df_race_info_add_predict)

            # レース名_エンコードのユニークを抽出する
            race_encoded_unique_list = df_race_info_add_predict['race_name_encoded'].unique()

            # 順位予測を行い、1~3位を抽出してツイッター用のメッセージを作成して保存する
            # 同一レース名があるので、レース名と開催場所、ラウンドでレース結果を絞る
            df_predict_eval = fnc.predict_rank_1st_3rd(race_encoded_unique_list,
                                                       df_race_info_add_predict,
                                                       'no_message_needed')

            # 不要な情報を削除とカラムを並び替える
            df_predict_eval = fnc.delete_unnecessary_columns(df_predict_eval)
            df_predict_eval = fnc.sort_columns(df_predict_eval)

            # 1~3位の順位予測の結果をCSVに保存する
            predicted_each_model = predicted_path + model_dir + '_'
            fnc.append_predicted_rank_1st_3rd_to_csv(predicted_each_model, df_predict_eval, location)

    else:
        print('モデルは存在しません')

    # プログラムの実行時間を表示する
    run_time = time.time() - start_time
    logger.info('{0} {1} 実行時間:{2:.2f}秒'.format(os.path.basename(__file__), location, run_time))


if __name__ == "__main__":
    main()

calc_hit_rate_each_model.pyのコード

# 取得したレース情報で各モデルの的中率を計算する
# 呼び出し元:main.py
# 出力するファイル
# ./predict/data/hit_rate_each_model配下に開催場所の各モデルでの的中率をCSVで出力する
# [hit_rate_開催場所.csv]に追記していく
# ファイル例
#  - hit_rate_chuukyo.csv
#  - hit_rate_hanshin.csv

# ライブラリの読み込み
import os.path
import os
import time
import pandas as pd

# 自作関数の読み込み
from my_module import fnc


# 各モデルで的中率を計算する
# 引数:開催場所、レース日
# 戻値:無し
def main(location, today_yyyymmdd):
    # プログラムの開始時刻を取得する
    start_time = time.time()

    # 的中率を保存するフォルダを設定する
    hit_rate_each_model_path = './predict/data/hit_rate_each_model/'
    os.makedirs(hit_rate_each_model_path, exist_ok=True)

    # 実際のレース結果(馬名のみ)を読み込む
    raceresults_path = './predict/data/today_raceresults/'
    real_result_csv_file = raceresults_path + 'real_result_' + location + '_' + today_yyyymmdd + '.csv'
    df_real_result = pd.read_csv(real_result_csv_file, encoding='cp932')

    # モデルが保存されているパス
    model_data_path = './make_model/data/'

    # モデルの保存パスに設定されているディレクトリ内のディレクトリ一覧を取得する
    models = os.listdir(model_data_path)
    models_dir = [d for d in models if os.path.isdir(os.path.join(model_data_path, d))]

    # 的中率を保存するデータフレームを用意する
    columns_list = ['date', 'location', 'model_dir', 'hit_rate']
    df_hit_rate = pd.DataFrame(columns=columns_list)

    for model_dir in models_dir:
        # 作成したモデルとレース情報をエンコードするモデルのパス
        model_path = model_data_path + model_dir + '/gbm.pkl'
        enc_model_path = model_data_path + model_dir + '/oe_x.pkl'

        # 作成したモデルとレース情報をエンコードするモデルが存在していたら予測を行う
        if os.path.isfile(model_path) and os.path.isfile(enc_model_path):
            # 予測したレース結果のCSVファイル
            predict_result_csv_file = './predict/data/predicted_each_model/' + model_dir + \
                                      '_predict_result_' + location + '_' + today_yyyymmdd + '.csv'

            # 予測したレース結果を読み込む
            df_predict_result = pd.read_csv(predict_result_csv_file, encoding='cp932', header=None)

            # df_predict_result = df_predict_result_tmp.copy()
            df_predict_result = df_predict_result.reset_index(drop=True)

            # 予測したレース結果に実際のレース結果(馬名のみ)を結合する
            df_predict_real_result = pd.concat([df_predict_result, df_real_result], axis=1)
            csv_file = './predict/data/predict_real_result_' + location + '_' + model_dir + '_' + today_yyyymmdd + '.csv'
            df_predict_real_result.to_csv(csv_file, encoding='cp932', index=False)

            # 的中した数、レース数を計算する
            num_wins, round_list_size = fnc.save_win_lose(location, csv_file)

            # 的中率を計算し、パーセント表記に変換する
            hit_rate = num_wins / round_list_size
            hit_rate = '{:.1%}'.format(hit_rate)

            # レース実施日,開催場所,モデル,的中率の作業用データフレームを作成する
            df_hit_rate_tmp = pd.DataFrame(data={'date': [today_yyyymmdd],
                                                 'location': [location],
                                                 'model_dir': [model_dir],
                                                 'hit_rate': [hit_rate]})

            # 的中率のデータフレームに作業用データフレームを結合する
            df_hit_rate = pd.concat([df_hit_rate, df_hit_rate_tmp])

    # 的中率をCSVファイルに保存する
    df_hit_rate_csv_file = hit_rate_each_model_path + 'hit_rate_' + location + '.csv'
    df_hit_rate.to_csv(df_hit_rate_csv_file, mode='a', encoding='cp932', index=False, header=False)

    # プログラムの実行時間を表示する
    run_time = time.time() - start_time
    print(os.path.basename(__file__), location, ' 実行時間:{:.2f}秒'.format(run_time))


if __name__ == "__main__":
    main()

実行方法

更新・追加したプログラムは単体で実行するプログラムではありません。
下記から競馬予測AIのプログラムをダウンロードしてください。ダウンロードは有料としています。
競馬予測AIのバージョン1.0.1をリリース

今後、3ヶ月あるいは6ヶ月ごとに競馬予測システムを無料で公開する予定ですので、無料で試されたい方は今しばらくお待ち下さい。

実行結果

更新・追加したプログラムを含めた競馬予測AIを実行すると、下記3つファイルが作成されます。

  • 予測モデルごとのレースの順位予測結果のCSVファイル
  • 予測モデルごとのレースの順位予測結果と実レース結果をまとめたCSVファイル
  • 予測モデルごとの複勝の的中率をまとめたCSVファイル

予測モデルごとのレースの順位予測結果のCSVファイル

  • フォルダパス:\keiba_ai\predict\data\predicted_each_model
  • ファイル名:予測モデルのフォルダ名_predict_result_開催場_開催日.csv
  • CSVファイルのカラム情報
内容
A予測順位
B予測タイム
C予測した1~3位の馬名
Dオッズ
E人気
F距離
G開催日
Hレース名
I開催場
Jラウンド

実際に作成されたCSVファイル

  • ファイル名
    trainData_2013_2023_20230202_220111_predict_result_chuukyo_20230318.csv
  • CSVファイルの中身

予測モデルごとのレースの順位予測結果と実レース結果をまとめたCSV

  • フォルダパス:\keiba_ai\predict\data
  • フォルダ名:predict_real_result_chuukyo_予測モデルのフォルダ名_開催日.csv
  • CSVファイルのカラム情報
内容
A予測順位
B予測タイム
C予測した1~3位の馬名
Dオッズ
E人気
F距離
G開催日
Hレース名
I開催場
Jラウンド
K実レースの1~3位の馬名

※「予測モデルごとのレースの順位予測結果のCSVファイル」との違いはK列のみ

実際に作成されたCSVファイル

  • ファイル名
    predict_real_result_chuukyo_trainData_2022_2023_20230304_232432_20230319.csv
  • CSVファイルの中身

予測モデルごとの複勝の的中率をまとめたCSVファイル

  • フォルダ名:\keiba_ai\predict\data\hit_rate_each_model
  • ファイル名:hit_rate_開催場.csv
  • CSVファイルのカラム情報
内容
A開催日
B開催場
C使用した予測モデル
D複勝の的中率

実際に作成されたCSVファイル

  • ファイル名
    hit_rate_chuukyo.csv
  • CSVファイルの中身

補足

ツイッターに投稿する予測結果は、config.iniで指定した予測モデルです。各予測モデルの予測結果は、予測結果をCSVに保存するだけで、ツイッターには投稿しません。

次回試したいこと

作成した予測モデルの的中率を確認したところ、2021年~2023年の競馬情報を使った予測モデルの的中率が高いことがわかりました。古い情報はむしろ無いほうが良さそうです。予測モデルは過学習しているように見えるので、評価データの期間を変更して予測モデルを作成し、評価データの期間の違いによる的中率の変化を確認してみたいと思います。

コメント

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