【LightGBM】特徴量に血統情報や過去レースの結果を追加【2値分類モデル】

競馬AI

本記事の目的

本記事は、下記事項を目的としています。

目的
  • LightGBMで着順を予測し、100%を超える回収率を得る。

前回モデルの回収率

本ページでは、過去に作成した単勝予測モデルの改善方法を解説します。

過去モデルの詳細は「【競馬予測】「Optuna」のLightGBM(多クラス分類)で単勝予測」で解説しているので、詳しく知りたい方は読んでみてください。

ちなみに回収率は80%位です。。。

前回モデルからの変更点

本記事では、「過去モデルを改善し、その結果回収率は向上するのか」という観点で解説を行っていきます。

前回モデルの問題点

前回モデルの問題点は、大きく2つありました。

①特徴量の偏りが大きい

前回モデルの特徴量の重要度を見ると、

  • win_odds(単勝オッズ)
  • time_ave(競走馬の100m当たりの平均タイム)

の2つが飛びぬけており、その他の特徴量があまり機能していませんでした。

このままだと、単純にオッズが低い(≒平均タイムが速い)競走馬を1位と予測してしまい、大きな回収率を見込めません。

②未来のデータを使用していた

これは致命的なミスなのですが、特徴量「time_ave(競走馬の1000m当たりの平均タイム)」は、訓練データ全ての平均タイムの値を採用していました。

競馬データは時系列データであるため、この方法で計算してしまうと、一部のデータが未来の結果を含んだ値となってしまいます。

本来は未来のタイムはわからないはずなので、これでは問題ですね。

今回モデルの変更点

上記の問題を受けて、今回モデルでは特徴量に対して4つの変更を加えていきます。

変更点①
  • 「win_odds」「population」を特徴量から削除
     →オッズが低い競争馬に賭ける傾向が高くなるため、的中率は下がるかもしれないですが、回収率は向上する見込みです。
  • 特徴量に「競走馬の血統情報」「過去5レースの賞金金額」「過去5レースの平均着順」「前レースからの日数」「競争馬数」を追加
     →他サイトを参考にして、有効そうな特徴量を追加します。
  • 「競走馬ID」「騎手ID」を数値型→カテゴリ変数に変更
     →これらの値は本来はカテゴリ変数として扱うべきなので、変更します。
  • 「競走馬の1000m当たりの平均タイム」を「過去5レースの1000m当たりの平均タイム」に変更
     →前回モデルでは、未来のレースも含まれた平均値を学習させていましたので、過去レースの平均値に変更します。

作業概要

以降では、下記の手順で競馬予想を行います。

  • ①前準備
  • ②モデル作成
  • ③予測
  • ④モデル評価
  • ⑤回収率計算

①前準備

""" 1. 前準備 """
# ライブラリ
import pandas as pd
import datetime
from tqdm import tqdm
from sklearn.preprocessing import LabelEncoder

# パラメータ
date_split_train = 20190401 # テストデータの開始点(年+月+日)
date_split_valid = 20210401 # 訓練データと検証データの分岐点(年+月+日)
date_split_test = 20210901  # 検証データとテストデータの分岐点(年+月+日)
file_directory = 'C:/Users/kouhe/OneDrive/01_Documents/02_機械学習/データ'
file_name = 'race_result.csv'

# データ読み込み ---------- (※1)
race_result_total = pd.read_csv(file_directory + '/race_result.csv')
horse_result = pd.read_csv(file_directory + '/horse_result.csv', index_col=0)
horse_ped = pd.read_csv(file_directory + '/horse_peds.csv', index_col=0)
return_table = pd.read_csv(file_directory + '/return_table.csv', index_col=0)

race_result = race_result_total[race_result_total['date'] >= date_split_train]

# データ加工(レース結果) ---------- (※2)
df = edit_data(race_result, horse_result, horse_ped)

# オッズを取得
odds_df = pd.DataFrame(data={'date': df['date'],
                             'win_odds': df['win_odds']})

# 不要な行を削除
drop_list = ['time', 'win_odds', 'population']
df = df.drop(drop_list, axis=1)

# 訓練データ・テストデータに分割 ---------- (※3)
train = df[df['date'] < date_split_valid]
valid = df[(df['date'] >= date_split_valid) & (df['date'] < date_split_test)]
test = df[df['date'] >= date_split_test]
odds_df_test = odds_df[odds_df['date'] >= date_split_test]

# 説明変数
drop_list = ['rank']
x_train = train.drop(drop_list, axis=1)
x_valid = valid.drop(drop_list, axis=1)
x_test = test.drop(drop_list, axis=1)

# 目的変数
y_train = pd.DataFrame(train['rank'])
y_valid = pd.DataFrame(valid['rank'])
y_test = pd.DataFrame(test['rank'])
y_test_org = pd.DataFrame(test['rank'])

# 目的変数を「0 : 上位(1~3位)」「1: 中位(4~10位)」「2 : 下位(11位-)」に変換
# 上位
y_train.loc[y_train['rank'] <= 3] = 0
y_valid.loc[y_valid['rank'] <= 3] = 0
y_test.loc[y_test['rank'] <= 3] = 0

# 中位
y_train.loc[(y_train['rank'] >= 4) & (y_train['rank'] <= 10)] = 1
y_valid.loc[(y_valid['rank'] >= 4) & (y_valid['rank'] <= 10)] = 1
y_test.loc[(y_test['rank'] >= 4) & (y_test['rank'] <= 10)] = 1

# 下位
y_train.loc[y_train['rank'] >= 11] = 2
y_valid.loc[y_valid['rank'] >= 11] = 2
y_test.loc[y_test['rank'] >= 11] = 2

以降では、上記コードについて解説していきます。

①-1 データ読み込み

ローカルフォルダに保存している

  • レース結果
  • 競走馬毎のレース結果
  • 競争馬の血統情報

を読み込みます。

使用するメソッドは、pandasの

メソッド

read_csv

で、フルパスを指定することで、CSVファイルを読み込んでいます。

# データ読み込み ---------- (※1)
race_result_total = pd.read_csv(file_directory + '/race_result.csv')
horse_result = pd.read_csv(file_directory + '/horse_result.csv')
horse_ped = pd.read_csv(file_directory + '/horse_peds.csv')

race_result = race_result_total[race_result_total['date'] >= date_split_train]

上記データの取得方法

このデータは「netkeiba.com」からスクレイピングで取得しており、取得方法は「競馬AI作成:③-3」で解説しています。

スクレイピングとは、Webサイトから任意の情報を取得する技術のことです。

①-2 データ加工

本工程は、「edit_data」という自作メソッドを使用しています。

# データ加工(レース結果) ---------- (※2)
race_result_edit = edit_data(race_result)
""" メソッド : レース結果を編集 """
# ライブラリ
import pandas as pd
from tqdm import tqdm
import numpy as np
import datetime
import sys
from sklearn.preprocessing import LabelEncoder

def edit_data(race_df, horse_df, ped_df):
    
    df = race_df
    
    # 不要な行を削除する ----------------- (※1)
    # 「タイム」が「x:xx.xx」以外の行 
    df = df.dropna(subset=['タイム'])
    horse_df = horse_df.dropna(subset=['タイム'])
    
    # 「着順」の値に"失"が含まれている行
    df['着順'] = df['着順'].astype(str)    
    df = df[~df['着順'].str.contains('失')]
    
    horse_df['着順'] = horse_df['着順'].astype(str)   
    horse_df = horse_df[~horse_df['着順'].str.contains('失')]
    
    # 「着順」に「(」が含まれている行
    df = df[~df['着順'].str.contains('\(')]
    df['着順'] = df['着順'].astype(float)
    
    horse_df = horse_df[~horse_df['着順'].str.contains('\(')]
    horse_df['着順'] = horse_df['着順'].astype(float)
    
    # 「jockey_ID」が数値以外の行
    df = df[pd.to_numeric(df['jockey_ID'], errors='coerce').notnull()]
    
    # インデックスを振りなおす
    df = df.reset_index(drop=True)
    
    # 複数の情報が含まれている項目を分割する ----------------- (※2)
    # 「性齢」と「sex」「age」に分割
    df['sex'] = [str(x)[0] for x in df['性齢']]
    df['age'] = [str(x)[1] for x in df['性齢']]
    
    # 「馬体重(増減)」を「weight」「weight_change」に分割
    df['weight'] = [int(str(x).split('(')[0]) for x in df['馬体重(増減)']]
    df['weight_change'] = [int(str(x).split('(')[1][:-1]) if '(' in x\
                           else 0 for x in df['馬体重(増減)']]
    
    # 「タイム」の値を秒数に変換 ----------------- (※3)
    change_second = lambda x: int(str(x).split(':')[0]) * 60 + float(str(x).split(':')[1])
    df['time'] = df['タイム'].map(change_second)
    
    # 「タイム」に「:」が2つ以上含まれている行を削除する
    horse_df['time_check'] = ['OK' if x.count(':') == 1 else 'NG' for x in horse_df['タイム']]
    horse_df = horse_df[horse_df['time_check'] == 'OK']  
    horse_df['time'] = horse_df['タイム'].map(change_second)   
       
    # レースIDを追加  ----------------- (※4) 
    race_id_list = df['date'].map(lambda x: str(x)[0:4])\
                  + df['area_ID'].map(lambda x: str(x).zfill(2))\
                  + df['race_month'].map(lambda x: str(x).zfill(2))\
                  + df['race_day'].map(lambda x: str(x).zfill(2))\
                  + df['race_num'].map(lambda x: str(x).zfill(2))
    df['race_ID'] = race_id_list
        
    # 出走馬数を追加  ----------------- (※5) 
    df['n_horse'] = df['race_ID'].map(df['race_ID'].value_counts())
    
    # 血統情報を追加  ----------------- (※6) 
    ped_df['horse_ID'] = ped_df.index
    df = pd.merge(df, ped_df, how='left', on='horse_ID')
    
    # 過去レースの情報を取得  ----------------- (※7) 
    horse_id_list = df['horse_ID']
    horse_df['date'] = horse_df['日付'].map(lambda x: str(x)[0:4] + str(x)[5:7] + str(x)[8:10])
    horse_df['date'] = pd.to_numeric(horse_df['date'], errors='coerce')
    horse_df = horse_df[horse_df['date'] != float('nan')].dropna(subset=['date'])
    horse_df['date'] = horse_df['date'].astype(int)
    
    horse_df['race_len'] = [int(str(x)[1:]) for x in horse_df['距離']]
    horse_df['money'] = [0 if np.isnan(x) else int(x) for x in horse_df['賞金']]
 
    x = list(horse_df['着順'])[0]
    
    # 初期値
    time_ave_list = []
    rank_ave_list = []
    money_sum_list = []
    race_interval_list = []
    
    race_date_list = df['date']
    
    for i in tqdm(range(0, len(df))):
        race_date_int = int(race_date_list[i])
        race_date = datetime.date(int(str(race_date_int)[0:4]),
                                  int(str(race_date_int)[4:6]),
                                  int(str(race_date_int)[6:8]))
        horse_id = horse_id_list[i]
        target_df = horse_df[(horse_df['date'] < race_date_int)\
                             & (horse_df.index == horse_id)].sort_values('date', ascending=False)

        if len(target_df) == 0:
            time_ave_list.append(float('nan'))
            rank_ave_list.append(float('nan'))
            money_sum_list.append(float('nan'))
            race_interval_list.append(float('nan'))
         
        elif (len(target_df) > 0) & (len(target_df) <= 5):
            time_ave_list.append(target_df['time'].sum() * 1000 / target_df['race_len'].sum())
            rank_ave_list.append(target_df['着順'].mean())
            money_sum_list.append(target_df['money'].sum())
            
            recent_date = target_df['日付'].values[0]
            recent_date = datetime.date(int(recent_date[0:4]),
                                        int(recent_date[5:7]),
                                        int(recent_date[8:10]))
            race_interval_list.append((race_date - recent_date).days)
            
        else:
            time_ave_list.append(target_df['time'].head(5).sum() * 1000 / target_df['race_len'].head(5).sum())
            rank_ave_list.append(target_df['money'].head(5).sum())
            money_sum_list.append(target_df['money'].head(5).sum())
            
            recent_date = target_df['日付'].values[0]
            recent_date = datetime.date(int(recent_date[0:4]),
                                        int(recent_date[5:7]),
                                        int(recent_date[8:10]))
            race_interval_list.append((race_date - recent_date).days)
            
    # dfに追加
    df['time_ave_5R'] = time_ave_list
    df['rank_ave_5R'] = rank_ave_list
    df['money_sum_5R'] = money_sum_list
    df['race_interval'] = race_interval_list
                            
    # 列名を英語に変換
    df = df.rename(columns={'着順':'rank', '枠':'frame',
                            '馬番':'horse_num', '斤量':'weight2',
                            '人気':'population', '単勝オッズ':'win_odds'})                            

    # 型変換 ----------------- (※8)                       
    # 文字型の値を数値化
    df = df.replace({'未勝利':0, '1勝クラス':1, '2勝クラス':2,
                     '3勝クラス':3, 'オープン':4, '新馬':5,
                     '500万下': 6, '1000万下': 7, '1600万下': 8,
                     '牝':0, '牡':1, 'セ':2})
    
    df['rank'] = df['rank'].astype(int)
    df['race_class'] = df['race_class'].astype(int)
    df['age'] = df['age'].astype(int)  
    df['jockey_ID'] = df['jockey_ID'].astype(int)
    
    # カテゴリ変数に型変換を行う
    target_list = ['ped_' + str(x) for x in range(0, 62)]
    target_list += ['horse_ID', 'jockey_ID', 'race_ID']
    for column in target_list:
        df[column] = LabelEncoder().fit_transform(df[column])
        df[column] = df[column].astype('category')    
    
    # 不要な列を削除する ----------------- (※9)
    drop_list = ['馬名', '性齢', '騎手', 'タイム', '着差', 'コーナー通過順', '厩舎', '馬体重(増減)', '後3F']
    df.drop(drop_list, axis=1, inplace=True)
        
    # 「df」のインデックスを0始まりの連番に振りなおす
    df.reset_index(drop=True, inplace=True)
    
    return df

出走馬数を追加(※5)

今回モデルでは、新たにレースごとの出走馬数を追加します。

出走馬数が多いほど1位になる確率は低くなるので、モデルの精度向上に貢献してくれるかなと思っています。

本処理では、レース結果データフレームの中で、レースIDが一致する行の合算値を計算し、新たな列(「n_horse」)に追加しています。

    # 出走馬数を追加  ----------------- (※5) 
    race_id_list = df['race_ID'].unique()
    n_horse_list = []
    for race_id in race_id_list:
        n_horse =  (df['race_ID'] == race_id).sum()
        n_horse_list += [n_horse] * n_horse
    df['n_horse'] = n_horse_list

血統情報を追加(※6)

競争馬毎に、↓のような血統情報を追加します。

netkeiba.com

予めこの表をスクレイピングで取得しておき、変数 “df” に結合します。

データフレーム同士を結合する際に、本処理ではpandasの

メソッド

merge()

を使用しています。

引数に、

  • how(結合方法):left(左結合)
  • on(結合キー):horse_ID

を指定することで、列名 “horse_ID” をキーにした外部結合を行っています。

    # 血統情報を追加  ----------------- (※6) 
    ped_df['horse_ID'] = ped_df.index
    df = pd.merge(df, ped_df, how='left', on='horse_ID')

過去レースの情報を取得(※7)

続いて、過去レースの情報を取得します。

具体的には

  • 過去5レースの平均タイム
  • 過去5レースの平均順位
  • 過去5レースの合計賞金
  • 前レースとの出走間隔

の4項目を新たに追加します。

    # 過去レースの情報を取得  ----------------- (※7) 
    horse_id_list = df['horse_ID']
    horse_df['date'] = horse_df['日付'].map(lambda x: str(x)[0:4] + str(x)[5:7] + str(x)[8:10])
    horse_df['date'] = pd.to_numeric(horse_df['date'], errors='coerce')
    horse_df = horse_df[horse_df['date'] != float('nan')].dropna(subset=['date'])
    horse_df['date'] = horse_df['date'].astype(int)
    
    horse_df['race_len'] = [int(str(x)[1:]) for x in horse_df['距離']]
    horse_df['money'] = [0 if np.isnan(x) else int(x) for x in horse_df['賞金']]
 
    x = list(horse_df['着順'])[0]
    
    # 初期値
    time_ave_list = []
    rank_ave_list = []
    money_sum_list = []
    race_interval_list = []
    
    race_date_list = df['date']
    
    for i in tqdm(range(0, len(df))):
        race_date_int = int(race_date_list[i])
        race_date = datetime.date(int(str(race_date_int)[0:4]),
                                  int(str(race_date_int)[4:6]),
                                  int(str(race_date_int)[6:8]))
        horse_id = horse_id_list[i]
        target_df = horse_df[(horse_df['date'] < race_date_int)\
                             & (horse_df.index == horse_id)].sort_values('date', ascending=False)

        if len(target_df) == 0:
            time_ave_list.append(float('nan'))
            rank_ave_list.append(float('nan'))
            money_sum_list.append(float('nan'))
            race_interval_list.append(float('nan'))
         
        elif (len(target_df) > 0) & (len(target_df) <= 5):
            time_ave_list.append(target_df['time'].sum() * 1000 / target_df['race_len'].sum())
            rank_ave_list.append(target_df['着順'].mean())
            money_sum_list.append(target_df['money'].sum())
            
            recent_date = target_df['日付'].values[0]
            recent_date = datetime.date(int(recent_date[0:4]),
                                        int(recent_date[5:7]),
                                        int(recent_date[8:10]))
            race_interval_list.append((race_date - recent_date).days)
            
        else:
            time_ave_list.append(target_df['time'].head(5).sum() * 1000 / target_df['race_len'].head(5).sum())
            rank_ave_list.append(target_df['money'].head(5).sum())
            money_sum_list.append(target_df['money'].head(5).sum())
            
            recent_date = target_df['日付'].values[0]
            recent_date = datetime.date(int(recent_date[0:4]),
                                        int(recent_date[5:7]),
                                        int(recent_date[8:10]))
            race_interval_list.append((race_date - recent_date).days)
            
    # dfに追加
    df['time_ave_5R'] = time_ave_list
    df['rank_ave_5R'] = rank_ave_list
    df['money_sum_5R'] = money_sum_list
    df['race_interval'] = race_interval_list

型変換(※8)

「競走馬ID」「騎手ID」「レースID」の3項目を、カテゴリ変数に型変換します。

「~.astype(‘category’)」と記述するだけで、列全体をカテゴリ変数に型変換できます。

    # カテゴリ変数に型変換を行う
    target_list = ['ped_' + str(x) for x in range(0, 62)]
    target_list += ['horse_ID', 'jockey_ID', 'race_ID']
    for column in target_list:
        df[column] = LabelEncoder().fit_transform(df[column])
        df[column] = df[column].astype('category')    

①-3 訓練データ・手検証データ・テストデータに分割

この部分に関しては前回モデル作成時から変更はないので、詳細な解説は割愛します。

# 訓練データ・テストデータに分割 ---------- (※3)
train = df[df['date'] < date_split_valid]
valid = df[(df['date'] >= date_split_valid) & (df['date'] < date_split_test)]
test = df[df['date'] >= date_split_test]
odds_df_test = odds_df[odds_df['date'] >= date_split_test]

# 説明変数
drop_list = ['rank']
x_train = train.drop(drop_list, axis=1)
x_valid = valid.drop(drop_list, axis=1)
x_test = test.drop(drop_list, axis=1)

# 目的変数
y_train = pd.DataFrame(train['rank'])
y_valid = pd.DataFrame(valid['rank'])
y_test = pd.DataFrame(test['rank'])
y_test_org = pd.DataFrame(test['rank'])

# 目的変数を「0 : 上位(1~3位)」「1: 中位(4~10位)」「2 : 下位(11位-)」に変換
# 上位
y_train.loc[y_train['rank'] <= 3] = 0
y_valid.loc[y_valid['rank'] <= 3] = 0
y_test.loc[y_test['rank'] <= 3] = 0

# 中位
y_train.loc[(y_train['rank'] >= 4) & (y_train['rank'] <= 10)] = 1
y_valid.loc[(y_valid['rank'] >= 4) & (y_valid['rank'] <= 10)] = 1
y_test.loc[(y_test['rank'] >= 4) & (y_test['rank'] <= 10)] = 1

# 下位
y_train.loc[y_train['rank'] >= 11] = 2
y_valid.loc[y_valid['rank'] >= 11] = 2
y_test.loc[y_test['rank'] >= 11] = 2

②AIモデル作成

訓練データ・検証データを用いて、Light GBMのモデルを作成します。

この工程に関しても、前回モデル作成時から変更はないので、解説は割愛します。

""" 2. モデル作成 """
# ライブラリ
import optuna.integration.lightgbm as lgb
import matplotlib.pyplot as plt
import os
import numpy as np
import pickle
import math
import scipy.stats
from sklearn import preprocessing
import statistics


# LightGBM用のデータセットに変換
lgb_train = lgb.Dataset(x_train, y_train)
lgb_valid = lgb.Dataset(x_valid, y_valid)

# ハイパーパラメータ
params = {'boosting_type': 'gbdt',
          'objective': 'multiclass',
          'metric': 'multi_logloss',
          'num_class': 3,
          'learning_rate': 0.01} 

# モデル作成
start = datetime.datetime.now()
model = lgb.train(params,
                  lgb_train,
                  valid_sets=[lgb_train, lgb_valid],
                  #categorical_feature=['horse_ID', 'jockey_ID'],
                  early_stopping_rounds=100,
                  verbose_eval=100)
end = datetime.datetime.now()
proc_time = end - start

③予測

作成したAIモデルに、テストデータの特徴量をインプットして、各競走馬が上位に入る確率を予測します。

""" 3. 予測 """
# テストデータを予測(レースIDの中で最も確率が高いものを1、それ以外を0にする)
y_pred_prob = pd.DataFrame(model.predict(x_test))

# 予測確率を標準化 → 正規化 ------------ (※1)
race_id_list = x_test['race_ID']
df_pred = pd.DataFrame(data={'race_ID': race_id_list,
                             'y_pred_prob': y_pred_prob[0]})
prob = df_pred.groupby('race_ID').transform(lambda x: (x - np.mean(x)) / statistics.stdev(x))['y_pred_prob']
df_pred['y_pred_prob'] = (prob - prob.min()) / (prob.max() - prob.min())

# 特徴量の重要度をプロット ------------- (※2)
lgb.plot_importance(model)
importance = np.array(model.feature_importance(importance_type='gain'))
feature = x_test.columns
df_importance = pd.DataFrame(data={'feature': feature, 'importance': importance})\
                .sort_values('importance', ascending=False)

予測確率を標準化 → 正規化(※1)

前回モデル作成時では、予測確率をそのまま使用していましたが、今回は標準化・正規化を行います。

このような処理を行うことで、他の競走馬を加味した相対的な予測確率に変換します。

# 予測確率を標準化 → 正規化 ------------- (※1)
race_id_list = x_test['race_ID']
df_pred = pd.DataFrame(data={'race_ID': race_id_list,
                             'y_pred_prob': y_pred_prob[0]})
prob = df_pred.groupby('race_ID').transform(lambda x: (x - np.mean(x)) / statistics.stdev(x))['y_pred_prob']
df_pred['y_pred_prob'] = (prob - prob.min()) / (prob.max() - prob.min())

特徴量の重要度(※2)

今回モデルの特徴量の重要度を確認してみたところ、上位15位の特徴の重要度はこのような結果になりました。

新たに追加した

  • n_horse(レースごとの競走馬数)
  • money_sum_5R(過去5レースの合計賞金)

が大きく影響していることが分かります。

# 特徴量の重要度 -------------- (※2)
importance = np.array(model.feature_importance(importance_type='gain'))
feature = x_test.columns
df_importance = pd.DataFrame(data={'feature': feature, 'importance': importance})\
                .sort_values('importance', ascending=False)

④回収率を計算

""" 5. 回収率 """
# 回収率を計算 ----------------- (※1)
odds = odds_df.loc[odds_df['date'] >= date_split_test, 'win_odds']
step_list = [x * 0.05 for x in range(0, 21)]
return_rate_list = []

for cnt in range(0, len(step_list)):
    step = step_list[cnt]
    df_pred['y_pred_class'] = [1 if x >= step else 0 for x in df_pred['y_pred_prob']] 
    input_amount = len(df_pred.loc[df_pred['y_pred_class'] == 1, 'y_pred_prob']) * 100
    hit_odds_list = list(df_pred.loc[(df_pred['y_pred_class'] == df_pred['y_test']) &\
                                     (df_pred['y_pred_class'] == 1),
                                     'win_odds'])
    return_amount = sum(hit_odds_list) * 100
    return_rate_list.append(return_amount / input_amount)

# 回収率を描画 ------------- (※2)
x = step_list
y = return_rate_list
fig, ax = plt.subplots(1, 1, figsize=(10, 8))
ax.plot(x, y, linestyle='solid', color='k', marker='^', label='race_cnt')
plt.title('Return_Rate', fontsize=20)

# 軸ラベルを追加
ax.set_xlabel('y_pred_prob', fontsize=15)
ax.set_ylabel('return_rate', fontsize=15)

# y軸設定
ax.set_ylim(0.5, 1.5)

# 値ラベルを追加
for i, k in zip(x, y):
    if math.isnan(k):
        continue
    elif k < 0.5 :
        continue
    else:
        plt.text(i, k + 0.05, str(round(k * 100)) + '%', fontsize=10)  

回収率を計算(※1)

「予測確率がxx%を超えたら、その競争馬に単勝を賭ける」というロジックで、回収率を計算します

本処理では、上記のxxの値を0%~100%の間で5%刻みで計算しています。

# 回収率を計算 -------------- (※1)
odds = odds_df.loc[odds_df['date'] >= date_split_test, 'win_odds']
step_list = [x * 0.05 for x in range(0, 21)]
return_rate_list = []

for cnt in range(0, len(step_list)):
    step = step_list[cnt]
    df_pred['y_pred_class'] = [1 if x >= step else 0 for x in df_pred['y_pred_prob']] 
    input_amount = len(df_pred.loc[df_pred['y_pred_class'] == 1, 'y_pred_prob']) * 100
    hit_odds_list = list(df_pred.loc[(df_pred['y_pred_class'] == df_pred['y_test']) &\
                                     (df_pred['y_pred_class'] == 1),
                                     'win_odds'])
    return_amount = sum(hit_odds_list) * 100
    return_rate_list.append(return_amount / input_amount)

回収率を描画(※2)

上記の方法で回収率を計算したところ、このような結果になりました。

回収率の結果は大体80%前後で、予測確率が70%以上の競走馬に賭けた際の回収率が最大で86%でした。

前回モデルから若干改善されているものの、目標の100%に達成することができませんでした。

まだまだ改善の余地がありそうですね。

コメント

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