本記事の目的
本記事は、下記事項を目的としています。
LightGBMを採用した理由
正直に言うと、LightGBMを採用した理由は「多くの人が回収率100%を達成できているから」です。
Googleで「競馬 機械学習 回収率」で検索すると、LightGBMの記事が多くヒットしますよね。
また、LightGBMの特徴を調査したところ、
という特徴があるようです。
LightGBMは高い回収率が期待できそうですね。
作業概要

以降では、下記の手順で競馬予想を行います。
- ①前準備
- ②モデル作成
- ③予測
- ④モデル評価
- ⑤回収率計算
前回記事を読んでいない方へ
本記事では、「【スクレイピング】「BeautifulSoup」でnetkeibaからレースデータを取得」で取得したデータを使用しています。
本記事で解説している分析を行いたい方は、まずは以下の記事を参考にしてデータを取得してください。
①前準備
""" 1. 前準備 """
# ライブラリ
import pandas as pd
# パラメータ
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 = pd.read_csv(file_directory + '/' + file_name)
# データ加工 ---------- (※2)
df = edit_data(race_result)
# 訓練データ・テストデータに分割 ---------- (※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]
# 競走馬ごとに1000m当たりの平均タイムを計算(訓練データを使用) ---------- (※4)
time_ave_list = []
horse_id_list = train['horse_ID']
for horse_id in horse_id_list:
time_sum = train[train.horse_ID == horse_id]['time'].sum()
race_sum = train[train.horse_ID == horse_id]['race_len'].sum()
time_ave_list.append(time_sum * 1000 / race_sum)
train['time_ave'] = time_ave_list
train['have_hist'] = [1] * len(train)
train = train.drop('time', axis=1)
time_df = pd.DataFrame(data={'horse_ID': train['horse_ID'],
'time_ave': train['time_ave']})
time_df_dump = time_df.drop_duplicates(subset=['horse_ID'])
# 検証データ
time_ave_list = []
have_hist_list = []
for horse_id in valid['horse_ID']:
if horse_id in time_df['horse_ID'].values:
target_time = float(time_df_dump.loc[time_df['horse_ID']==horse_id, 'time_ave'])
time_ave_list.append(target_time)
have_hist_list.append(1)
else:
time_ave_list.append(0)
have_hist_list.append(0)
valid['time_ave'] = time_ave_list
valid['have_hist'] = have_hist_list
valid = valid.drop('time', axis=1)
# テストデータ
time_ave_list = []
have_hist_list = []
for horse_id in test['horse_ID']:
if horse_id in time_df['horse_ID'].values:
target_time = float(time_df_dump.loc[time_df['horse_ID']==horse_id, 'time_ave'])
time_ave_list.append(target_time)
have_hist_list.append(1)
else:
time_ave_list.append(0)
have_hist_list.append(0)
test['time_ave'] = time_ave_list
test['have_hist'] = have_hist_list
test = test.drop('time', axis=1)
test_rank = test['rank']
# 説明変数
x_train = train.drop('rank', axis=1)
x_valid = valid.drop('rank', axis=1)
x_test = test.drop('rank', 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)
ローカルに保存しているCSVファイルを読み込みます。
使用するメソッドは、pandasの
read_csv
で、フルパスを指定することで、CSVファイルを読み込んでいます。
file_directory = 'C:/Users/kouhe/OneDrive/01_Documents/02_機械学習/データ'
file_name = 'race_result.csv'
# データ読み込み ---------- (※1)
race_result = pd.read_csv(file_directory + '/' + file_name)
また、読み込んだデータの全項目は下記のとおりです。
上記データの取得方法
このデータは「netkeiba.com」から取得しており、取得方法は「競馬AI作成:③-3」で解説しています。
データ加工(共通)(※2)
# データ加工 ---------- (※2)
df = edit_data(race_result)
読み込んだデータを、機械学習に使える形に加工します。
ここでの大まかな処理は、下記のとおりです。
メソッドについて
データ加工は、自作メソッドを使用しています。
この自作メソッドについては、「【スクレイピング】学習データ(netkeibaから取得したレースデータ)を加工」で詳細を解説しています。
訓練データ・検証データ・テストデータに分割(※3)
# パラメータ
date_split_valid = 20210401 # 訓練データと検証データの分岐点(年+月+日)
date_split_test = 20210901 # 検証データとテストデータの分岐点(年+月+日)
# 訓練データ・テストデータに分割 ---------- (※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]
読み込んだデータを3つに分割します。
それぞれの用途は、
訓練データ
→モデルを作成するために使用するデータ
検証データ
→ハイパーパラメータを調整するために使用するデータ
テストデータ
→モデルの汎用性を評価するために使用するデータ
となります。
本処理では、下記期間のデータをそれぞれ訓練データ・検証データ・テストデータとしています。
データ加工(単勝予測用)(※4)
# データ加工(単勝予測用) ---------- (※4)
# 競走馬ごとに1000m当たりの平均タイムを計算(訓練データを使用)
time_ave_list = []
horse_id_list = train['horse_ID']
for horse_id in horse_id_list:
time_sum = train[train.horse_ID == horse_id]['time'].sum()
race_sum = train[train.horse_ID == horse_id]['race_len'].sum()
time_ave_list.append(time_sum * 1000 / race_sum)
train['time_ave'] = time_ave_list
train['have_hist'] = [1] * len(train)
train = train.drop('time', axis=1)
time_df = pd.DataFrame(data={'horse_ID': train['horse_ID'],
'time_ave': train['time_ave']})
time_df_dump = time_df.drop_duplicates(subset=['horse_ID'])
# 検証データ
time_ave_list = []
have_hist_list = []
for horse_id in valid['horse_ID']:
if horse_id in time_df['horse_ID'].values:
target_time = float(time_df_dump.loc[time_df['horse_ID']==horse_id, 'time_ave'])
time_ave_list.append(target_time)
have_hist_list.append(1)
else:
time_ave_list.append(0)
have_hist_list.append(0)
valid['time_ave'] = time_ave_list
valid['have_hist'] = have_hist_list
valid = valid.drop('time', axis=1)
# テストデータ
time_ave_list = []
have_hist_list = []
for horse_id in test['horse_ID']:
if horse_id in time_df['horse_ID'].values:
target_time = float(time_df_dump.loc[time_df['horse_ID']==horse_id, 'time_ave'])
time_ave_list.append(target_time)
have_hist_list.append(1)
else:
time_ave_list.append(0)
have_hist_list.append(0)
test['time_ave'] = time_ave_list
test['have_hist'] = have_hist_list
test = test.drop('time', axis=1)
test_rank = test['rank']
# 説明変数
x_train = train.drop('rank', axis=1)
x_valid = valid.drop('rank', axis=1)
x_test = test.drop('rank', 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
続いて、単勝予測用にデータ加工を行います。
この箇所では、大きく2つの処理を行っています。
①に関しては説明を割愛しますが、②に関しては「多クラス分類」という予測方法を採用するため、このような処理を行っています。
なお、データ加工後の全項目は下記のとおりです。
②モデル作成
""" 2. モデル作成 """
# ライブラリ
from optuna.integration import lightgbm as lgb
import matplotlib.pyplot as plt
import os
import numpy as np
import pickle
import math
# LightGBM用のデータセットに変換 ----------- (※1)
lgb_train = lgb.Dataset(x_train, y_train)
lgb_valid = lgb.Dataset(x_valid, y_valid)
# ハイパーパラメータの設定 ----------- (※2)
params = {'task': 'train',
'boosting_type': 'gbdt',
'objective': 'multiclass',
'metric': 'multi_logloss',
'num_class': 3,
'learning_rate': 0.02}
# ハイパーパラメータの設定 ----------- (※3)
model = lgb.train(params,
lgb_train,
num_boost_round=1000,
valid_names=['train', 'valid'],
valid_sets=[lgb_train, lgb_valid],
early_stopping_rounds=100,
verbose_eval=False)
LightGBM用のデータセットに変換(※1)
# LightGBM用のデータセットに変換 ----------- (※1)
lgb_train = lgb.Dataset(x_train, y_train)
lgb_valid = lgb.Dataset(x_valid, y_valid)
今回作成するLightGBMのモデルは、Python APIを使用します。
そのため、予め専用のデータセットを用意する必要があります。
学習(※2)
# 学習 ----------- (※2)
params = {'task': 'train',
'boosting_type': 'gbdt',
'objective': 'multiclass',
'metric': 'multi_logloss',
'num_class': 3,
'learning_rate': 0.02}
# モデル作成
model = lgb.train(params,
lgb_train,
valid_sets=[lgb_train, lgb_valid],
early_stopping_rounds=100,
verbose_eval=False)
本処理では、「Optuna」を利用してLightGBMの学習モデルを作成します。
※約7年分のレースデータ学習させるのにかかった時間は、約40分
Optunaを利用する理由は、自動でパラメータをチューニングしてくれるからです。
ちなみに、自動チューニングしてくれるパラメータは、下記の7つです。
上記以外のパラメータは、こちらで設定する必要があるので、「params」という変数で指定しています。
以降で、今回設定したパラメータを解説していきます。
boosting_type
ブースティングのアルゴリズムを選択できます。
基本は勾配ブースティングを行うことが多いので、デフォルトの ‘gbdt’ でよいと思います。
objective
目的関数を選択できます。
今回は多クラス分類を行うため、’multiclass’ を指定しています。
metric
誤差関数の測定方法を選択できます。
多クラス分類を選択した際の選択肢としては、”multi_logloss(softmax関数)” と “mulit_error(正答率)” の2種類があります。
num_class ※多クラス分類の場合
分類したいクラスの数を指定できます。
今回は、上位・中位・下位を分類したいので、3を指定しています。
learning_rate
学習率を指定できます。
値を大きくするほど処理時間は短くなりますが、制度は落ちます。
一般的に、0.01を設定することが多いです。
③予測
""" 3. 予測 """
# テスト結果を予測 ------------- (※1)
y_pred_prob = model.predict(x_test)
# 同レースの中で上位に入る確率が高い馬を1位とする ------------- (※2)
df_y_pred = pd.DataFrame(data={'race_ID': x_test['race_ID'], 'y_pred': y_pred_prob[:, 0]})
race_id_list = x_test['race_ID'].unique()
df_pred = pd.DataFrame()
for race_id in race_id_list:
df_part = df_y_pred.loc[df_y_pred['race_ID']==race_id, ]
max_prob = df_part['y_pred'].max()
df_part.loc[df_part['y_pred']!=max_prob, 'y_pred_class'] = 0
df_part.loc[df_part['y_pred']==max_prob, 'y_pred_class'] = 1
df_pred = df_pred.append(df_part)
テスト結果を予測(※1)
# テスト結果を予測 ------------- (※1)
y_pred_prob = model.predict(x_test)
まずは、作成したモデルを用いて各カテゴリ(上位・中位・下位)に入る確率を計算します。
「モデル.predect()」を記述するだけで、簡単に予測できます。
出力結果は、下記のようになります。(一部のみを記載しています。)
競走馬 | 0 (上位に入る確率) | 1 (中位に入る確率) | 2 (下位に入る確率) |
0 | 0.635704 | 0.304167 | 0.0601289 |
1 | 0.428402 | 0.445569 | 0.126028 |
2 | 0.473814 | 0.407631 | 0.118555 |
同レースの中で上記に入る確率が最も高い競走馬を1着とする(※2)
# 同レースの中で上位に入る確率が高い馬を1位とする ------------- (※2)
df_y_pred = pd.DataFrame(data={'race_ID': x_test['race_ID'], 'y_pred': y_pred_prob[:, 0]})
race_id_list = x_test['race_ID'].unique()
df_pred = pd.DataFrame()
for race_id in race_id_list:
df_part = df_y_pred.loc[df_y_pred['race_ID']==race_id, ]
max_prob = df_part['y_pred'].max()
df_part.loc[df_part['y_pred']!=max_prob, 'y_pred_class'] = 0
df_part.loc[df_part['y_pred']==max_prob, 'y_pred_class'] = 1
df_pred = df_pred.append(df_part)
今回予測したいことは、「どの競走馬が1着になるのか?」ということなので、先ほどの確率から順位に変換する必要があります。
そこで本処理では、上位に入る確率が最も高い競争馬を1着になる、という想定で確率→順位に変換しています。
④モデル評価
""" 4. モデル評価 """
# 正答率を計算 -------------- (※1)
# トータルレース数、正答数を5%刻みで取得
df_cnt = calc_accuracy(y_test_org['rank'], df_pred)
acc_rate = df_cnt['acc_cnt'].sum() / df_cnt['total_cnt'].sum()
print('1位の正答数 : ', df_cnt['acc_cnt'].sum())
print('全レース数 : ', df_cnt['total_cnt'].sum())
print('1位の正答率 : ', str(round(acc_rate * 100)) + '%')
acc_rate_list = df_cnt['acc_cnt'] / df_cnt['total_cnt']
# 正答率を描画 -------------- (※2)
x = np.arange(0.00, 1.00, 0.05)
y1 = acc_rate_list * 100
y2 = df_cnt['total_cnt']
fig, ax1 = plt.subplots(1, 1, figsize=(10, 8))
plt.title('Accuracy Rate', fontsize=20)
ax2 = ax1.twinx()
ax1.bar(x, y1, width=0.025, color='lightblue', label='Accuracy Rate')
ax2.plot(x, y2, linestyle='solid', color='k', marker='^', label='race_cnt')
# 軸ラベルを追加
ax1.set_xlabel('y_pred_prob', fontsize=15)
ax1.set_ylabel('accuracy_rate', fontsize=15)
ax2.set_ylabel('total_race_cnt', fontsize=15)
# y軸設定
ax1.set_ylim(0, 100)
ax2.set_ylim(0, max(y2) + 50)
# 棒グラフに値ラベルを追加
for i, k in zip(x, y1):
if math.isnan(k):
continue
plt.text(i, k, str(round(k)) + '%', ha='center', va='bottom')
# 特徴量の重要度をプロット -------------- (※3)
lgb.plot_importance(model)
正答率を計算(※1)
# 正答率を計算 -------------- (※1)
# トータルレース数、正答数を5%刻みで取得
df_cnt = calc_accuracy(y_test_org['rank'], df_pred)
acc_rate = df_cnt['acc_cnt'].sum() / df_cnt['total_cnt'].sum()
print('1位の正答数 : ', df_cnt['acc_cnt'].sum())
print('全レース数 : ', df_cnt['total_cnt'].sum())
print('1位の正答率 : ', str(round(acc_rate * 100)) + '%')
acc_rate_list = df_cnt['acc_cnt'] / df_cnt['total_cnt']
本処理では、「1位の正答数 / 全レース数」といった全体の正答率だけでなく、
「1位の正答数(予測確率が0%~5%)/ 該当レース数」
「1位の正答数(予測確率が5%~10%)/ 該当レース数」
・・・
「1位の正答数(予測確率が95%~100%)/ 該当レース数」
といったように、予測確率毎の正答数も計算しています。
これによって、「予測確率が何%以上の時に購入するべきなのか?」が分かるようになります。
使用メソッドの解説
本処理では、トータルレース数・正答数を5%刻みで取得するために、”calc_accuracy” というメソッドを使用しています。
これは自作メソッドであり、ソースは下記のとおりです。
(ライン数は多いのですが、単純な処理のため解説は割愛します。)
""" メソッド : 正答率を0.05刻みで計算 """
def calc_accuracy(true_list, df_pred):
# 初期値
acc_cnt_list = [0] * 20
total_cnt_list = [0] * 20
y_class_list = df_pred['y_pred_class']
y_prob_list = df_pred['y_pred']
for cnt in range(0, len(df_pred)):
pred = y_class_list[cnt]
if pred == 0:
continue
elif pred == 1:
prob = y_prob_list[cnt]
# 0 - 0.0.5
if prob < 0.05:
total_cnt_list[0] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[0] += 1
else:
continue
# 0.05 - 0.1
elif prob <= 0.05 and prob < 0.1:
total_cnt_list[1] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[1] += 1
else:
continue
# 0.1 - 0.15
elif prob >= 0.1 and prob < 0.15:
total_cnt_list[2] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[2] += 1
else:
continue
# 0.15 - 0.2
elif prob >= 0.15 and prob < 0.2:
total_cnt_list[3] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[3] += 1
else:
continue
# 0.20 - 0.25
elif prob >= 0.20 and prob < 0.25:
total_cnt_list[4] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[4] += 1
else:
continue
# 0.25 - 0.30
elif prob >= 0.20 and prob < 0.30:
total_cnt_list[5] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[5] += 1
else:
continue
# 0.30 - 0.35
elif prob >= 0.30 and prob < 0.35:
total_cnt_list[6] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[6] += 1
else:
continue
# 0.35 - 0.40
elif prob >= 0.35 and prob < 0.40:
total_cnt_list[7] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[7] += 1
else:
continue
# 0.40 - 0.45
elif prob >= 0.40 and prob < 0.45:
total_cnt_list[8] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[8] += 1
else:
continue
# 0.45 - 0.50
elif prob >= 0.45 and prob < 0.50:
total_cnt_list[9] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[9] += 1
else:
continue
# 0.50 - 0.55
elif prob >= 0.50 and prob < 0.55:
total_cnt_list[10] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[10] += 1
else:
continue
# 0.55 - 0.60
elif prob >= 0.55 and prob < 0.60:
total_cnt_list[11] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[11] += 1
else:
continue
# 0.60 - 0.65
elif prob >= 0.60 and prob < 0.65:
total_cnt_list[12] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[12] += 1
else:
continue
# 0.65 - 0.70
elif prob >= 0.65 and prob < 0.70:
total_cnt_list[13] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[13] += 1
else:
continue
# 0.70 - 0.75
elif prob >= 0.70 and prob < 0.75:
total_cnt_list[14] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[14] += 1
else:
continue
# 0.75 - 0.80
elif prob >= 0.75 and prob < 0.80:
total_cnt_list[15] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[15] += 1
else:
continue
# 0.80 - 0.85
elif prob >= 0.80 and prob < 0.85:
total_cnt_list[16] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[16] += 1
else:
continue
# 0.85 - 0.90
elif prob >= 0.85 and prob < 0.90:
total_cnt_list[17] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[17] += 1
else:
continue
# 0.90 - 0.95
elif prob >= 0.90 and prob < 0.95:
total_cnt_list[18] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[18] += 1
else:
continue
# 0.95 - 1.00
elif prob >= 0.95 and prob < 1.00:
total_cnt_list[19] += 1
true = true_list[cnt]
if pred == true:
acc_cnt_list[19] += 1
else:
continue
# 2つのリストを結合
df_cnt = pd.DataFrame(data={'total_cnt': total_cnt_list, 'acc_cnt': acc_cnt_list})
return df_cnt
正答率を描画(※2)
# 正答率を描画 -------------- (※2)
x = np.arange(0.00, 1.00, 0.05)
y1 = acc_rate_list * 100
y2 = df_cnt['total_cnt']
fig, ax1 = plt.subplots(1, 1, figsize=(10, 8))
plt.title('Accuracy Rate', fontsize=20)
ax2 = ax1.twinx()
ax1.bar(x, y1, width=0.025, color='lightblue', label='Accuracy Rate')
ax2.plot(x, y2, linestyle='solid', color='k', marker='^', label='race_cnt')
# 軸ラベルを追加
ax1.set_xlabel('y_pred_prob', fontsize=15)
ax1.set_ylabel('accuracy_rate', fontsize=15)
ax2.set_ylabel('total_race_cnt', fontsize=15)
# y軸設定
ax1.set_ylim(0, 100)
ax2.set_ylim(0, max(y2) + 50)
# 棒グラフに値ラベルを追加
for i, k in zip(x, y1):
if math.isnan(k):
continue
plt.text(i, k, str(round(k)) + '%', ha='center', va='bottom')
先ほど計算した結果を、棒グラフと折れ線グラフの複合グラフ表します。
凡例を記載するのを忘れていたのですが、
となります。

特徴量の重要度をプロット(※3)
# 特徴量の重要度をプロット -------------- (※3)
lgb.plot_importance(model)
続いて、特徴量の重要度をプロットしますが、上記のように1行で簡単に出力できます。

出力結果を見ると、
の2つの特徴量が群を抜いて高いことが分かります。
その他の特徴量の影響度が低すぎるので、学習データには改善の余地がありそうですね。
⑤回収率を計算
""" 4. 回収率を計算 """
odds = x_test['win_odds']
step_list = np.arange(0.00, 1.00, 0.05)
return_rate_list = []
for cnt in range(0, len(step_list)):
step = step_list[cnt]
input_amount = df_cnt['total_cnt'][cnt:].sum() * 100
hit_odds_list = x_test.loc[(df_pred['y_pred_class'] == y_test_org['rank']) &
(df_pred['y_pred_class'] == 1) &
(df_pred['y_pred'] >= step),
'win_odds']
return_amount = sum(hit_odds_list) * 100
return_rate_list.append(return_amount / input_amount)
続いて回収率を計算します。
ここでも正答率を計算した時と同様に、5%刻みで回収率を計算しています。
「予測確率が0%以上のレースを購入した際の回収率」
「予測確率が5%以上のレースを購入した際の回収率」
・・・
「予測確率が100%以上のレースを購入した際の回収率」
回収率を計算した結果は、下記のとおりです。

どのポイントで購入しても、回収率は100%に届きませんでした。。。
やはり、学習データを考え直す必要がありそうですね。
コメント