rank(順位)を予想してみて、利益を算出してみます。
順位データのほうが、0-1のクラス分類よりも教師データに情報量は多いので、もしかしたら改善するかもしれません。
学習過程はipynbファイルで行いました。以下はその記録です。
前処理
基本的にSection02の前処理はほぼ同じです。教師データとなる列名によって、すこしコードをいじる程度の使いまわしです。
まず、使いそうなライブラリをインポートします。
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
正しく読み込めていれば、テーブルデータが表示されます。
次に学習データとテストデータの切り分け、および入力データと教師データの切り分けをします。データの正規化もします。
このとき、順位データを教師データとしますが、この値を出馬している馬の数で割ります。
これにはいくつかの利点があります。
- 出馬数の違いによって教師データの不均衡さが、出馬数で割ることで緩和される
- 順位が0-1の間の実数値になり、相対的な強さとして表現できる
$$
y = \frac{\text{rank} – 1.0}{\text{horse_num} – 1.0}
$$
1.0を分母分子から引いているのは、1位を0.0、最下位を1.0にする調整のためです。
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)
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
categoriy_columns = ['place_id', 'race_type', 'weather', 'race_condition', 'horse_count', 'waku', 'horse_number', 'sex', 'age']
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', 'rank'].reset_index(drop=True)
test_y = not_use_df.loc[not_use_df['race_date'] >= '2020-01-01', 'rank'].reset_index(drop=True)
train_y = train_y / train_X['horse_count']
test_y = test_y / test_X['horse_count']
学習
学習はクラス分類ではないので、objectiveをregressionにします。
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)}')
数時間待つと結果が出ます。
計算過程が色々表示されますが、最終的にはRMSE(Root Mean Square Error)が表示されます。
baseと書かれているものは、予測をせずにすべて定数値(予測値の平均値)を回答したときのものです。
train base rmse : 0.26274655178659634
train rmse : 0.17512189099943967
valid base rmse : 0.2740610538965701
valid rmse : 0.2259894071503427
test base rmse : 0.2755300636740716
test rmse : 0.23632728717919677
rmseの誤差はテストデータでも予想しないよりも改善していることは分かりますが、肌感としてどれほどよいかはわからないですね。
作ったモデルやスカラー変換器を保存しておきます。
import pickle
result = {
'not_use_columns':not_use_columns,
'category_columns':categoriy_columns,
'scaler':scaler,
'model':model
}
with open('/work/models/chapter2_section3_rank_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')
![](https://emoclework.jp/wp-content/uploads/2024/05/2a2c173e194f-20240310.png)
あんまりうまく行っていないですね。横方向にラインがいくつもあるように感じます。
つまり、正解値に対して、大きく予想値がブレるということです。
さらに、テストデータではほとんど四角形の分布をしていて、あまり予測がうまくいかないことを示しています
利益の解析
予測値が0.1以下のときにベットするようにして、利益を計算してみます。
train_tansyo_df = not_use_df.loc[not_use_df['race_date'] < '2020-01-01', ['tansyo_hit', 'tansyo_payout']].reset_index(drop=True)
test_tansyo_df = not_use_df.loc[not_use_df['race_date'] >= '2020-01-01', ['tansyo_hit', 'tansyo_payout']].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)
test_pred = model.predict(test_X)
def calc_win_money(pred, tansyo_df, th):
bet = (pred < 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 :387.20000000000005
test win money :-8.600000000000009
しきい値を変更した時の利益変化を見てみます。
train_win_list = []
test_win_list = []
th_list = np.linspace(0.0, 1.0, 50)
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')
![](https://emoclework.jp/wp-content/uploads/2024/05/003e137f34b4-20240310.png)
学習データでは利益が出ますが、テストデータではしきい値を変えても利益がでません。
0.0~0.2の区間を拡大してみても、テストデータでは利益がでているようには見えません。
train_win_list = []
test_win_list = []
th_list = np.linspace(0.0, 0.2, 50)
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')
![](https://emoclework.jp/wp-content/uploads/2024/05/20a3d43edf25-20240310.png)