PR

【LightGBM】血統考慮版 競馬予想AIの作り方

データサイエンス
この記事は約32分で読めます。
スポンサーリンク
スポンサーリンク

はじめに¶

私は競馬予想AIの開発をしています。動画で制作過程の解説をしています。良ければ見ていってください。

また、共有するソースの一部は有料のものを使ってます。
同じように分析したい方は、以下の記事から入手ください。

競馬予想プログラムソフト 全公開!
競馬予想プログラムソフトの開発をしている者です。今回は第一弾から第四弾記事の総集編になります。 チャンネル登録で1,000円引きで入手で…

6.セカンドモデルの作成

前回の話からセカンドモデルが決定したので、
本Notebookではその作成方法を示す

スポンサーリンク

6-0.セカンドモデルの説明¶

モデル作成の動機¶

血統情報を考慮したモデルの作成がしたい

モデルの目的¶

出走馬の血統情報と産駒たちの
勝率を追加したモデルを作成する

確認したい仮説¶

産駒の勝率と血統の馬IDから、
同じ血統を持つ競走馬の勝率を考慮した
モデルを学習できると考えた

特徴量¶

ファーストモデル+
父, 母, 母父, 母母父の馬ID+
父, 母, 母父, 母母父の産駒の勝率

目的変数¶

1着なら1, そうでないなら0のバイナリ

スポンサーリンク

6-1.下準備¶

ソースの一部は有料のものを使ってます。
同じように分析したい方は、以下の記事から入手ください。

ゼロから作る競馬予想モデル・機械学習入門

In [1]:
import pathlib
import warnings
import sys
sys.path.append(".")
sys.path.append("..")
from src.model_manager.lgbm_manager import LightGBMModelManager  # noqa
from src.core.meta.bet_name_meta import BetName  # noqa
from src.data_manager.preprocess_tools import DataPreProcessor  # noqa
from src.data_manager.data_loader import DataLoader  # noqa

warnings.filterwarnings("ignore")

root_dir = pathlib.Path(".").absolute().parent
dbpath = root_dir / "data" / "keibadata.db"
start_year = 2000  # DBが持つ最古の年を指定
split_year = 2014  # 学習対象期間の開始年を指定
target_year = 2019  # テスト対象期間の開始年を指定
end_year = 2023  # テスト対象期間の終了年を指定 (当然DBに対象年のデータがあること)

# 各種インスタンスの作成
data_loader = DataLoader(
    start_year,
    end_year,
    dbpath=dbpath  # dbpathは各種環境に合わせてパスを指定してください。絶対パス推奨
)

dataPreP = DataPreProcessor()

df = data_loader.load_racedata()
 
2024-08-26 01:08:25.453 | INFO     | src.data_manager.data_loader:load_racedata:23 - Get Year Range: 2000 -> 2023.
2024-08-26 01:08:25.454 | INFO     | src.data_manager.data_loader:load_racedata:24 - Loading Race Info ...
2024-08-26 01:08:26.385 | INFO     | src.data_manager.data_loader:load_racedata:26 - Loading Race Data ...
2024-08-26 01:08:43.502 | INFO     | src.data_manager.data_loader:load_racedata:28 - Merging Race Info and Race Data ...
スポンサーリンク

6-2.血統情報のロード¶

今回から新たに血統情報を読み込むメソッドを追加
data_loader.load_horseblood()を実行する

In [2]:
dfblood = data_loader.load_horseblood()
 
2024-08-26 01:08:45.888 | INFO     | src.data_manager.data_loader:load_horseblood:45 - Loading Horse Blood ...
スポンサーリンク

6-3.前処理の実行¶

今回より血統情報を追加する前提項目10の前処理を追加
dataPreP.exec_pipelineメソッドに第二,第三引数を指定することで前提項目10が実行される

In [3]:
df = dataPreP.exec_pipeline(df, dfblood, ["s", "b", "bs", "bbs", "f"])
 
2024-08-26 01:09:15.246 | INFO     | src.data_manager.preprocess_tools:__0_check_use_save_checkpoints:100 - Start PreProcess #0 ...
2024-08-26 01:09:15.251 | INFO     | src.data_manager.preprocess_tools:__1_exec_all_sub_prep1:103 - Start PreProcess #1 ...
2024-08-26 01:09:22.276 | INFO     | src.data_manager.preprocess_tools:__2_exec_all_sub_prep2:105 - Start PreProcess #2 ...
2024-08-26 01:09:35.626 | INFO     | src.data_manager.preprocess_tools:__3_convert_type_str_to_number:107 - Start PreProcess #3 ...
2024-08-26 01:09:39.710 | INFO     | src.data_manager.preprocess_tools:__4_drop_or_fillin_none_data:109 - Start PreProcess #4 ...
2024-08-26 01:09:43.215 | INFO     | src.data_manager.preprocess_tools:__5_exec_all_sub_prep5:111 - Start PreProcess #5 ...
2024-08-26 01:10:01.073 | INFO     | src.data_manager.preprocess_tools:__6_convert_label_to_rate_info:113 - Start PreProcess #6 ...
2024-08-26 01:10:12.081 | INFO     | src.data_manager.preprocess_tools:__7_convert_distance_to_smile:115 - Start PreProcess #7 ...
2024-08-26 01:10:12.328 | INFO     | src.data_manager.preprocess_tools:__8_category_encoding:117 - Start PreProcess #8 ...
2024-08-26 01:10:17.173 | INFO     | src.data_manager.preprocess_tools:__9_convert_raceClass_to_grade:119 - Start PreProcess #9 ...
2024-08-26 01:10:25.419 | INFO     | src.data_manager.preprocess_tools:__10_add_bloods_info:123 - Start PreProcess #10 ...
 
2024-08-26 01:10:32.554 | WARNING  | src.data_manager.preprocess_tools:__10_add_bloods_info:139 - No Info, your choice blood set. blood='f'

意図しない血統情報を指定すると警告ログを出して知らせてくれるので、適宜見直してください

【警告ログイメージ】

 

一応カラム一覧を確認

In [4]:
df.columns
Out[4]:
Index(['raceId', 'place', 'raceName', 'raceDetail', 'raceDate', 'startTime',
       'distance', 'weather', 'field', 'condition', 'direction', 'inoutside',
       'rapTime', 'rapSumTime', 'f3Ftol3F', 'remarks', 'number', 'boxNum',
       'label', 'odds', 'favorite', 'horseName', 'horseId', 'sex', 'age',
       'jweight', 'jockey', 'jockeyId', 'time', 'passLabel', 'last3F',
       'weight', 'gl', 'teacher', 'teacherId', 'boss', 'race_span', 'horseNum',
       'rough_horse', 'rough_race', 'f3F', 'l3F', 'f3F_vel', 'l3F_vel',
       'rapTime_vel', 'rapSumTime_vel', 'velocity', 'last3F_vel', 'toL3F_vel',
       'label_1C', 'label_2C', 'label_3C', 'label_4C', 'label_rate',
       'label_lastC', 'label_diff', 'dist_cat', 'place_en', 'field_en',
       'sex_en', 'condition_en', 'jockeyId_en', 'teacherId_en', 'horseId_en',
       'dist_cat_en', 'raceGrade', 'stallionId', 'stallionName', 'breedId',
       'breedName', 'bStallionId', 'bStallionName', 'b2StallionId',
       'b2StallionName'],
      dtype='object')

以下が追加した血統情報たち

'stallionId', 'stallionName', 'breedId',
'breedName', 'bStallionId', 'bStallionName',
'b2StallionId', 'b2StallionName'
スポンサーリンク

6-4.モデル作成用インスタンス作成¶

第一引数で指定したパスに学習したモデル情報を保存するためのインスタンスを作成します

In [5]:
lgbm_model_manager = LightGBMModelManager(
    # modelsディレクトリ配下に作成したいモデル名のフォルダパスを指定。
    # フォルダパスは絶対パスにすると安全です。
    root_dir / "models" / "second_model",  # セカンドモデルのモデルID
    split_year,
    target_year,
    end_year
)
 
2024-08-26 01:10:34.954 | INFO     | src.model_manager.base_manager:set_keyvalue_to_export_mapping:139 - Set Export info. key=model_type, val=lightGBM
2024-08-26 01:10:34.955 | INFO     | src.model_manager.base_manager:set_keyvalue_to_export_mapping:139 - Set Export info. key=model_id, val=second_model
2024-08-26 01:10:34.957 | INFO     | src.model_manager.base_manager:set_keyvalue_to_export_mapping:139 - Set Export info. key=model_dir, val=e:\dev_um_ai\dev-um-ai\models\second_model
2024-08-26 01:10:34.962 | INFO     | src.model_manager.base_manager:set_keyvalue_to_export_mapping:139 - Set Export info. key=model_analyze_dir, val=e:\dev_um_ai\dev-um-ai\models\second_model\analyze
2024-08-26 01:10:34.963 | INFO     | src.model_manager.base_manager:set_keyvalue_to_export_mapping:139 - Set Export info. key=model_predict_dir, val=e:\dev_um_ai\dev-um-ai\models\second_model\analyze\00_predict
2024-08-26 01:10:34.964 | INFO     | src.model_manager.base_manager:set_keyvalue_to_export_mapping:139 - Set Export info. key=confidence_column, val=pred_prob
2024-08-26 01:10:34.966 | INFO     | src.model_manager.base_manager:set_keyvalue_to_export_mapping:139 - Set Export info. key=confidence_rank_column, val=pred_rank
2024-08-26 01:10:34.967 | INFO     | src.model_manager.lgbm_manager:load_root_mode_info:322 - Load model params and dataset info columns.
2024-08-26 01:10:34.970 | INFO     | src.model_manager.lgbm_manager:load_root_mode_info:325 - ==================  model params  ========================
2024-08-26 01:10:34.970 | INFO     | src.model_manager.lgbm_manager:load_root_mode_info:328 - boosting_type             =     gbdt
2024-08-26 01:10:34.971 | INFO     | src.model_manager.lgbm_manager:load_root_mode_info:328 - objective                 =     binary
2024-08-26 01:10:34.972 | INFO     | src.model_manager.lgbm_manager:load_root_mode_info:328 - metric                    =     auc
2024-08-26 01:10:34.974 | INFO     | src.model_manager.lgbm_manager:load_root_mode_info:328 - verbose                   =     0
2024-08-26 01:10:34.975 | INFO     | src.model_manager.lgbm_manager:load_root_mode_info:328 - seed                      =     77777
2024-08-26 01:10:34.976 | INFO     | src.model_manager.lgbm_manager:load_root_mode_info:328 - learning_rate             =     0.01
2024-08-26 01:10:34.977 | INFO     | src.model_manager.lgbm_manager:load_root_mode_info:328 - n_estimators              =     10000
2024-08-26 01:10:34.978 | INFO     | src.model_manager.lgbm_manager:load_root_mode_info:329 - ==========================================================
2024-08-26 01:10:34.979 | INFO     | src.data_manager.dataset_tools:set_feature_and_objective_columns:78 - Set Feature columns. ['distance', 'number', 'boxNum', 'odds', 'favorite', 'age', 'jweight', 'weight', 'gl', 'race_span', 'raceGrade', 'place_en', 'field_en', 'sex_en', 'condition_en', 'jockeyId_en', 'teacherId_en', 'dist_cat_en', 'horseId_en', 'stallionId', 'breedId', 'bStallionId', 'b2StallionId', 'winR_stallion', 'winR_breed', 'winR_bStallion', 'winR_b2Stallion']
2024-08-26 01:10:34.980 | INFO     | src.data_manager.dataset_tools:set_feature_and_objective_columns:80 - Set Objective columns. label_in1
スポンサーリンク

6-5.モデル作成の準備¶

学習に必要な説明変数と目的変数の作成およびインスタンスへの登録

やってることは行ごとにコメントを残してます

In [6]:
# 説明変数にするカラム
feature_columns = [
    'distance',
    'number',
    'boxNum',
    'odds',
    'favorite',
    'age',
    'jweight',
    'weight',
    'gl',
    'race_span',
    "raceGrade",  # グレード情報を追加
] + dataPreP.encoding_columns

# 血統情報を追加
feature_columns += ["stallionId", "breedId", "bStallionId", "b2StallionId"]

# 勝率情報の追加(6-6節で解説してます)
feature_columns += ['winR_stallion', 'winR_breed',
                    'winR_bStallion', 'winR_b2Stallion']


# 目的変数用のカラム
objective_column = "label_in1"

# 説明変数と目的変数をモデル作成用のインスタンスへセット
lgbm_model_manager.set_feature_and_objective_columns(
    feature_columns, objective_column)

# 目的変数の作成: 1着のデータに正解フラグを立てる処理を実行
df = lgbm_model_manager.add_objective_column_to_df(df, "label", 1)
 
2024-08-26 01:10:35.398 | INFO     | src.data_manager.dataset_tools:set_feature_and_objective_columns:78 - Set Feature columns. ['distance', 'number', 'boxNum', 'odds', 'favorite', 'age', 'jweight', 'weight', 'gl', 'race_span', 'raceGrade', 'place_en', 'field_en', 'sex_en', 'condition_en', 'jockeyId_en', 'teacherId_en', 'dist_cat_en', 'horseId_en', 'stallionId', 'breedId', 'bStallionId', 'b2StallionId', 'winR_stallion', 'winR_breed', 'winR_bStallion', 'winR_b2Stallion']
2024-08-26 01:10:35.400 | INFO     | src.data_manager.dataset_tools:set_feature_and_objective_columns:80 - Set Objective columns. label_in1
2024-08-26 01:10:35.402 | INFO     | src.model_manager.lgbm_manager:add_objective_column_to_df:80 - make objective data. label_in1. topN: 1
スポンサーリンク

6-6.データセットの作成¶

今回から指定したカテゴリの勝率を計算する処理を追加
lgbm_model_manager.make_dataset_mappingメソッドを実行する際に、第二,第三引数を指定する

第二引数では計算したいカテゴリ情報リストのリスト
第三引数ではさらに細かく集計する分類を指定する

下記の例では、馬場(field)と距離カテゴリ(dist_cat)ごとに、
父(["stallionId"]), 母(["breedId"]), 母父(["bStallionId"]), 母母父(["b2StallionId"])の産駒たちの勝率を計算する

ちなみに、母×父の産駒たちの勝率を計算したい場合は、
["stallionId", "breedId"]と指定する

In [7]:
dataset_mapping = lgbm_model_manager.make_dataset_mapping(
    df,
    target_category=[["stallionId"], ["breedId"],
                     ["bStallionId"], ["b2StallionId"]],
    target_sub_category=["field", "dist_cat"]
)

# 上で作成したデータセットのマッピングをセットする
dataset_mapping = lgbm_model_manager.setup_dataset(dataset_mapping)
 
2024-08-26 01:10:36.062 | INFO     | src.data_manager.dataset_tools:make_dataset_mapping:104 - Generate dataset mapping. Year Range: 2019 -> 2023
Add blood win rate in 2023second (2024/08/26 01:13:02) ...: 100%|██████████| 10/10 [02:42<00:00, 16.21s/it]
2024-08-26 01:13:21.501 | INFO     | src.model_manager.lgbm_manager:setup_dataset:110 - Create LightGBM Dataset.

実際に追加されているか確認

In [8]:
dataset_mapping["2019first"].train.columns
Out[8]:
Index(['field', 'dist_cat', 'b2StallionId', 'bStallionId', 'breedId',
       'stallionId', 'raceId', 'place', 'raceName', 'raceDetail', 'raceDate',
       'startTime', 'distance', 'weather', 'condition', 'direction',
       'inoutside', 'rapTime', 'rapSumTime', 'f3Ftol3F', 'remarks', 'number',
       'boxNum', 'label', 'odds', 'favorite', 'horseName', 'horseId', 'sex',
       'age', 'jweight', 'jockey', 'jockeyId', 'time', 'passLabel', 'last3F',
       'weight', 'gl', 'teacher', 'teacherId', 'boss', 'race_span', 'horseNum',
       'rough_horse', 'rough_race', 'f3F', 'l3F', 'f3F_vel', 'l3F_vel',
       'rapTime_vel', 'rapSumTime_vel', 'velocity', 'last3F_vel', 'toL3F_vel',
       'label_1C', 'label_2C', 'label_3C', 'label_4C', 'label_rate',
       'label_lastC', 'label_diff', 'place_en', 'field_en', 'sex_en',
       'condition_en', 'jockeyId_en', 'teacherId_en', 'horseId_en',
       'dist_cat_en', 'raceGrade', 'stallionName', 'breedName',
       'bStallionName', 'b2StallionName', 'label_in1', 'winR_stallion',
       'winR_breed', 'winR_bStallion', 'winR_b2Stallion'],
      dtype='object')

一番最後にある

'winR_stallion', 'winR_breed', 'winR_bStallion', 'winR_b2Stallion'

の四つが勝率情報

スポンサーリンク

6-7.モデル作成実行¶

とくに追加したものはないので、いつも通りにモデル作成の実行

In [9]:
# 学習用パラメータ(ここでは適当に設定しておく)
params = {
    'boosting_type': 'gbdt',
    # 二値分類
    'objective': 'binary',
    'metric': 'auc',
    'verbose': 0,
    'seed': 77777,
    'learning_rate': 0.01,
    "n_estimators": 10000
}


lgbm_model_manager.train_all(
    params,
    dataset_mapping,
    stopping_rounds=500,  # ここで指定した値を超えるまでは、early stopさせない
    val_num=250  # ログを出力するスパン
)
for dataset_dict in dataset_mapping.values():
    lgbm_model_manager.load_model(dataset_dict.name)
    lgbm_model_manager.predict(dataset_dict)
スポンサーリンク

6-8.モデルのエクスポート¶

モデルのエクスポートをするためには、モデルの成績を先に計算しておく必要がある

モデルの成績は以下を計算する

  1. 収支の計算
  2. 基礎統計の計算
  3. オッズグラフの計算

収支の計算¶

収支計算はメンテが出来てないので少し泥臭いが、以下のコードを実行して貰えればよい

現在のソースでは単勝の収支しか計算できない

In [10]:
bet_mode = BetName.tan
bet_column = lgbm_model_manager.get_bet_column(bet_mode=bet_mode)
pl_column = lgbm_model_manager.get_profit_loss_column(bet_mode=bet_mode)
for dataset_dict in dataset_mapping.values():
    lgbm_model_manager.set_bet_column(dataset_dict, bet_mode)
_, dfbetva, dfbette = lgbm_model_manager.merge_dataframe_data(
    dataset_mapping, mode=True)

dfbetva, dfbette = lgbm_model_manager.generate_profit_loss(
    dfbetva, dfbette, bet_mode)

dfbette[["raceDate", "raceId", "label", "favorite", bet_column, pl_column]]
 
2024-08-26 01:21:56.992 | INFO     | src.model_manager.base_manager:set_keyvalue_to_export_mapping:139 - Set Export info. key=bet_columns_map, val={'tan': 'bet_tan'}
2024-08-26 01:21:56.994 | INFO     | src.model_manager.base_manager:set_keyvalue_to_export_mapping:139 - Set Export info. key=pl_column_map, val={'tan': 'pl_tan'}
2024-08-26 01:21:57.882 | INFO     | src.model_manager.base_manager:__save_profit_loss:646 - Save profit loss data. save_path: e:\dev_um_ai\dev-um-ai\models\second_model\analyze\tan\profit_loss
2024-08-26 01:21:57.884 | INFO     | src.model_manager.base_manager:set_keyvalue_to_export_mapping:139 - Set Export info. key=profit_loss_dir, val={'tan': 'e:\\dev_um_ai\\dev-um-ai\\models\\second_model\\analyze\\tan\\profit_loss'}
Out[10]:

  raceDate raceId label favorite bet_tan pl_tan
9 2019-01-05 201906010101 3 3 1 -100.0
28 2019-01-05 201906010102 4 2 1 -100.0
33 2019-01-05 201906010103 6 6 1 -100.0
47 2019-01-05 201906010104 10 12 1 -100.0
74 2019-01-05 201906010105 2 1 1 -100.0
22408 2023-12-28 202309050908 1 1 1 170.0
22431 2023-12-28 202309050909 1 1 1 250.0
22437 2023-12-28 202309050910 2 4 1 -100.0
22452 2023-12-28 202309050911 2 1 1 -100.0
22462 2023-12-28 202309050912 3 1 1 -100.0

16630 rows × 6 columns

基礎統計の計算¶

基礎統計では回収率と的中率および人気別のベット回数の集計を行う

以下のコードを実行するだけ

In [11]:
lgbm_model_manager.basic_analyze(dataset_mapping)
 
2024-08-26 01:21:57.938 | INFO     | src.model_manager.base_manager:basic_analyze:220 - Start basic analyze.
2024-08-26 01:21:58.315 | INFO     | src.model_manager.base_manager:basic_analyze:256 - Saving Return And Hit Rate Summary.
2024-08-26 01:21:58.323 | INFO     | src.model_manager.base_manager:set_keyvalue_to_export_mapping:139 - Set Export info. key=return_hit_rate_file, val={'tan': 'e:\\dev_um_ai\\dev-um-ai\\models\\second_model\\analyze\\tan\\hit_and_return_rate.csv'}
2024-08-26 01:21:58.325 | INFO     | src.model_manager.base_manager:basic_analyze:259 - Saving Favorite Bet Num Summary.
2024-08-26 01:21:58.351 | INFO     | src.model_manager.base_manager:set_keyvalue_to_export_mapping:139 - Set Export info. key=fav_bet_num_dir, val={'tan': 'e:\\dev_um_ai\\dev-um-ai\\models\\second_model\\analyze\\tan\\fav_bet_num'}

オッズグラフの計算¶

In [12]:
dftrain, dfvalid, dftest = lgbm_model_manager.merge_dataframe_data(
    dataset_mapping,
    mode=True
)
summary_dict = lgbm_model_manager.gegnerate_odds_graph(
    dftrain, dfvalid, dftest, bet_mode)
print("'test'データのオッズグラフを確認")
summary_dict["test"].fillna(0)
 
2024-08-26 01:21:59.425 | INFO     | src.model_manager.base_manager:__save_odds_graph:514 - Save Odds Graph. save_path: e:\dev_um_ai\dev-um-ai\models\second_model\analyze\tan\odds_graph
2024-08-26 01:21:59.426 | INFO     | src.model_manager.base_manager:set_keyvalue_to_export_mapping:139 - Set Export info. key=odds_graph_file, val={'tan': 'e:\\dev_um_ai\\dev-um-ai\\models\\second_model\\analyze\\tan\\odds_graph'}
 
'test'データのオッズグラフを確認
Out[12]:

  勝率 支持率 回収率100%超 weight 件数
odds_round          
1.25 66.753247 64.000000 80.000000 0.023151 385
1.75 47.619048 45.714286 57.142857 0.094708 1575
2.25 35.070028 35.555556 44.444444 0.107336 1785
2.75 29.418103 29.090909 36.363636 0.111606 1856
3.25 27.381703 24.615385 30.769231 0.095310 1585
120.00 0.000000 0.666667 0.833333 0.000842 14
130.00 0.000000 0.615385 0.769231 0.000661 11
140.00 0.000000 0.571429 0.714286 0.001022 17
150.00 0.000000 0.533333 0.666667 0.000180 3
200.00 0.000000 0.400000 0.500000 0.007697 128

90 rows × 5 columns

モデルのエクスポート

以下を実行することでモデルの成績をエクスポートできる

In [13]:
lgbm_model_manager.export_model_info()
 
2024-08-26 01:21:59.456 | INFO     | src.model_manager.base_manager:export_model_info:848 - Export Model info json. export path: e:\dev_um_ai\dev-um-ai\models\second_model\model_info.json
スポンサーリンク

6-9.性能の確認(WEBアプリ起動)¶

以下のコードを実行するとWEBアプリが起動します

In [14]:
! python ../app_keiba/manage.py makemigrations
! python ../app_keiba/manage.py migrate 
! echo server launch OK
# ! python ../app_keiba/manage.py runserver 12345
 
No changes detected
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, model_analyzer, sessions
Running migrations:
  No migrations to apply.
server launch OK

「server launch OK」の表示がでたら以下のリンクをクリックしてWEBアプリへアクセス

http://localhost:12345/index.html
スポンサーリンク

6-10.結果

  モデルID 支持率OGS 回収率OGS AonBOGS
1 second_model 1.02706 -4.55201 3.09785
2 first_model
(baseline)
0.41924 -7.64492  

結果から、ファーストモデルに比べてセカンドモデルでは、加重平均的に回収率を3.098ポイント上回っている

 

コメント

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