[儲からない競馬予想AI] Section 05-05 : LightGBMによるオッズ予測

「Section 02-05: oddsを予想するRegression」の事前オッズを使う場合です。
つまり、事前オッズから確定オッズを予測することになります。

特徴量として、前回(Chapter05: 事前オッズを使った予測)求めた事前オッズを追加するだけで、あとは同じです。

学習過程はipynbファイルで行いました。以下はその記録です。

前処理

まず、使いそうなライブラリをインポートします。

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from IPython.display import display, Math

学習データの読み込み

df = pd.read_pickle('/work/formatted_source_data/analysis_data02.pkl')

次に学習データとテストデータ、バリデーションデータの切り分け、および入力データと教師データの切り分けをします。データの正規化もします。

確定オッズにノイズを付与して事前オッズを作り出します。また、オッズは対数を取って、値が飛ばないようにします。

df = df.sort_values(['race_date']).reset_index(drop=True)

not_use_columns = [
    'race_date', 'race_id', 'race_grade', 'name', 'jocky_name', 'odds', 'popular', 'rank', 'time', 'prize', 'tansyo_hit', 'tansyo_payout', 'hukusyo_hit', 'hukusyo_payout'
]

not_use_df = df[not_use_columns]
target_df = df.drop(columns=not_use_columns)

lower_noise = np.random.normal(0.0, 0.5, len(df))
upper_noise = np.random.normal(0.8, 0.3, len(df))
p_func = lambda x : -np.exp((-x+1)*0.02) + 1
p = p_func(not_use_df['odds'])
noise = lower_noise*(1-p) + upper_noise*p
not_use_df['noised_odds'] = not_use_df['odds']/np.exp(noise)

target_df['noised_odds_log'] = np.log(not_use_df['noised_odds'])

train_df = target_df.loc[not_use_df['race_date'] < '2020-01-01', :].reset_index(drop=True)
test_df = target_df.loc[not_use_df['race_date'] >= '2020-01-01', :].reset_index(drop=True)

from sklearn.preprocessing import StandardScaler
ped_columns = [col for col in target_df.columns if 'ped' in col]
categoriy_columns = ['place_id', 'race_type', 'weather', 'race_condition', 'horse_count', 'waku', 'horse_number', 'sex', 'age'] + ped_columns

scaler = StandardScaler()
train_X =  pd.DataFrame(data=scaler.fit_transform(train_df), columns=train_df.columns)
test_X =  pd.DataFrame(data=scaler.transform(test_df), columns=test_df.columns)

for col in categoriy_columns:
       train_X.loc[:, col] = train_df.loc[:, col].astype('int64')
       test_X.loc[:, col] = test_df.loc[:, col].astype('int64')

train_y = not_use_df.loc[not_use_df['race_date'] < '2020-01-01', 'odds'].reset_index(drop=True)
test_y = not_use_df.loc[not_use_df['race_date'] >= '2020-01-01', 'odds'].reset_index(drop=True)
train_y = np.log(train_y)
test_y = np.log(test_y)

学習

学習にはoptunaのlightgbmラッパーを使います。

import optuna.integration.lightgbm as lgb
#import lightgbm as lgb

params = {
    'objective': 'regression',
    'metric': 'rmse',
    'learning_rate': 0.01,    # default = 0.1
}
num_round = 10000

from sklearn.model_selection import train_test_split

lgb_train_X, lgb_valid_X, lgb_train_y, lgb_valid_y = train_test_split(train_X, train_y, test_size=0.3)
lgb_train_X = lgb_train_X.reset_index(drop=True)
lgb_valid_X = lgb_valid_X.reset_index(drop=True)
lgb_train_y = lgb_train_y.reset_index(drop=True)
lgb_valid_y = lgb_valid_y.reset_index(drop=True)


lgb_train = lgb.Dataset(lgb_train_X, lgb_train_y)
lgb_eval = lgb.Dataset(lgb_valid_X, lgb_valid_y)

# training
model = lgb.train(params, lgb_train, 
                  num_boost_round=num_round, 
                  valid_sets=[lgb_train, lgb_eval],
                  valid_names=['train', 'valid'], 
                  callbacks=[lgb.early_stopping(stopping_rounds=10, verbose=1)]
                 )
rmse = lambda a, b : np.mean(np.sqrt((a-b))**2)

lgb_train_pred = model.predict(lgb_train_X)
lgb_valid_pred = model.predict(lgb_valid_X)
test_pred = model.predict(test_X)

print(f'train base rmse : {rmse(np.mean(lgb_train_y), lgb_train_y)}')
print(f'train rmse      : {rmse(lgb_train_pred, lgb_train_y)}')
print(f'valid base rmse : {rmse(np.mean(lgb_valid_y), lgb_valid_y)}')
print(f'valid rmse      : {rmse(lgb_valid_pred, lgb_valid_y)}')
print(f'test base rmse  : {rmse(np.mean(test_y), test_y)}')
print(f'test  rmse      : {rmse(test_pred, test_y)}')

計算過程が色々表示されますが、最終的にはaccuracyが表示されます。
baseと書かれているものは、予測をせずにすべて定数値(予測値の平均値)を回答したときのものです。

train base rmse : 1.1665303134669378
train rmse      : 0.2640023638990434
valid base rmse : 1.170796280381049
valid rmse      : 0.3059182769455585
test base rmse  : 1.1602586017381806
test  rmse      : 0.30487671172178177

事前オッズを利用しない場合のテストデータのrmseは0.7176だったので精度が倍以上になっています。

作ったモデルやスカラー変換器を保存しておきます。

import pickle
result = {
    'not_use_columns':not_use_columns,
    'category_columns':categoriy_columns,
    'scaler':scaler,
    'model':model
}
with open('/work/models/chapter5_section5_odds_pred_model_result', 'wb') as f:
    pickle.dump(result, f)

学習結果の解析

予測値と教師データを散布図としてプロットしてみて、予測がどれほど正確なのかを体感してみます。

plt.figure(tight_layout=True, figsize=(12, 6))
plt.subplot(131)
plt.scatter(lgb_train_pred, lgb_train_y)
plt.title('train')
plt.xlabel('pred')
plt.ylabel('y')

plt.subplot(132)
plt.scatter(lgb_valid_pred, lgb_valid_y)
plt.title('valid')
plt.xlabel('pred')
plt.ylabel('y')

plt.subplot(133)
plt.scatter(test_pred, test_y)
plt.title('test')
plt.xlabel('pred')
plt.ylabel('y')


散布図の見た目が事前オッズを利用しない場合と比べて、かなり分散が小さくなっているのがわかります。

利益の解析

予測値が実際のoddsよりも0.1より大きいときにベットするようにして、利益を計算してみます。

train_tansyo_df = not_use_df.loc[not_use_df['race_date'] < '2020-01-01', ['tansyo_hit', 'tansyo_payout', 'odds']].reset_index(drop=True)
test_tansyo_df = not_use_df.loc[not_use_df['race_date'] >= '2020-01-01', ['tansyo_hit', 'tansyo_payout', 'odds']].reset_index(drop=True)

train_tansyo_df = train_tansyo_df.fillna(0)
test_tansyo_df = test_tansyo_df.fillna(0)

train_pred = model.predict(train_X)
train_pred = np.exp(train_pred)

test_pred = model.predict(test_X)
test_pred = np.exp(test_pred)

def calc_win_money(pred, tansyo_df, th):
    bet = (pred > (tansyo_df['odds']+th)).astype(int)
    hit = tansyo_df['tansyo_hit'].to_numpy()
    payout = tansyo_df['tansyo_payout'].to_numpy()

    return_money = np.sum(bet * hit * payout)
    bet_money = np.sum(bet)

    win_money = return_money - bet_money
    return win_money

print(f'train win money :{calc_win_money(train_pred, train_tansyo_df, 0.1)}')
print(f'test  win money :{calc_win_money(test_pred, test_tansyo_df, 0.1)}')

利益はでませんでした。学習データでも利益が出ないので、オッズの予測から利益を出すことは難しいかもしれません。

train win money :-65655.29999999996
test  win money :-14653.299999999988

しきい値を変えてもても、利益が上がる気配はありませんでした。

train_win_list = []
test_win_list = []
th_list = np.linspace(0, 1.0, 100)
for th in th_list:
    train_win_list.append(calc_win_money(train_pred, train_tansyo_df, th))
    test_win_list.append(calc_win_money(test_pred, test_tansyo_df, th))


plt.plot(th_list, train_win_list, label='train')
plt.plot(th_list, test_win_list, label='test')
plt.legend()
plt.xlabel('th')
plt.ylabel('win money')
タイトルとURLをコピーしました