
【保存版】完成した競馬AIの使い方を徹底解説〜モデル学習・精度検証編〜【#42】
今回は、完成した競馬AIシステムの使い方に焦点を当てて、実際にソースコードを動かす方法を詳しく解説していきます。
これまでに、#38の記事でnetkeiba.comからのデータ収集、#39の記事で前処理・特徴量作成について解説してきました。
今回はこの続きとして、作成したデータを使って機械学習モデルを学習させ、精度を検証する方法を解説します。
完成したソースコードのダウンロード方法については、記事下部の「ソースコードのダウンロード」の章で案内しています。
この記事で解説するのはオフライン検証の部分、具体的には前回作成した特徴量テーブル(features.csv)を使って機械学習モデルを学習し、精度評価とシミュレーションを行う工程です。
1. オフライン検証とは?
オフライン検証とは、過去のデータを使って機械学習モデルを学習させ、「もしこのモデルを実際に使っていたら、どれくらいの精度が出ていたか」を検証する手法のことです。
競馬AIを実運用(オンライン検証)する前に、オフライン検証で十分な精度が出ることを確認しておくことが非常に重要です。
オフライン検証では、データを以下の3つに分割します。
学習データ(train):モデルの学習に使用するデータ
検証データ(validation):学習の途中でモデルの精度をモニタリングするデータ
テストデータ(test):モデルが全く見ていないデータで、最終的な精度評価に使用

【学習データ】
機械学習モデルはこのデータを使ってパターンを学習します。競馬AIの場合、過去のレース結果から「どんな特徴を持つ馬が勝ちやすいか」を学習していきます。
【検証データ】
学習の途中で精度をモニタリングし、Early stopping(アーリーストッピング)という機能に使います。
Early stoppingとは、検証データに対する精度をモニタリングしながら学習を進め、「これ以上精度が良くならない」と判断したタイミングで自動的に学習を停止する機能です。
これにより、学習データに過剰適合(オーバーフィッティング)してしまうことを防ぎます。(詳しくは#14の記事で解説しています。)
【テストデータ】
モデルが学習にも検証にも全く使用していないデータで、「本当にこのモデルが未知のデータに対して予測できるか」を最終確認するために使います。
このテストデータに対して回収率シミュレーションを実行することで、「実際にこのモデルで馬券を買っていたら、どれくらいの収益が出ていたか」を評価できます。
2. モデルの学習
ここからの内容は、動画の12:15〜でも解説しています。
オフライン検証の実行コードは、ダウンロードしたソースコード中のv3_4_3/src/offline_evaluation.ipynbに全てまとまっているので、このnotebookを上から順に実行していきます。
前提として、事前にデータを作成しておく必要があります。これについては#39の記事で解説しているので、この手順に沿ってv3_4_3/src/create_features.ipynbを実行しで、特徴量ファイル(v3_4_3/data/02_features/features.csv)を作っておいてください。
まずは必要なライブラリをインポートしていきます。基本的には上から実行するだけです。
▼v3_4_3/src/offline_evaluation.ipynb
from train import Dataset, Trainer
from evaluation import plot_calibration, Evaluator
%load_ext autoreloadこれで必要なクラスがインポートされます。
Dataset: 学習・検証・テストデータを作成するクラス
Trainer: 機械学習モデルの学習を実行するクラス
Evaluator: モデルの精度評価とシミュレーションを行うクラス
2.1. データセットの作成
冒頭で解説したように、期間を指定してデータを時系列順に学習データ、検証データ、テストデータに分割します。
▼v3_4_3/src/offline_evaluation.ipynb
# データセット作成
dataset = Dataset(
train_start_date="2019-02-17",
valid_start_date="2023-11-17",
test_start_date="2024-02-17",
test_end_date="2025-02-16",
target_strategy=2,
odds_source="fixed",
)このコードを実行することで、dataset.train_dfに学習データが、dataset.valid_dfに検証データが、dataset.test_dfにテストデータがそれぞれ格納されます。

■期間設定について
配布しているソースコードのデフォルト設定では、
学習データ:2019-02-17〜2023-11-16
検証データ(Early stopping):2023-11-17〜2024-02-16
テストデータ:2024-02-17〜2025-02-16
を指定していますが、もし自分で期間を変更したい場合は、以下の点を踏まえて変更すると良いでしょう。
1. populationの期間に含まれている必要がある
指定する期間は、create_features.ipynbで作成したpopulation(母集団)の期間に含まれていなければなりません。
例えば、populationで2019年〜2025年のデータを作成した場合、それより古い期間や新しい期間を指定しても、データが存在しないため不整合が生じます。
■NGな例
▼v3_4_3/src/create_features.ipynb
population = create_population.create(
from_="2019-01-01",
to_="2025-12-31",
output_filename="population.csv",
)▼v3_4_3/src/offline_evaluation.ipynb
# ❌ 2018年を含むのでNG
dataset = Dataset(
train_start_date="2018-01-01", # populationに含まれていない
valid_start_date="2023-11-17",
test_start_date="2024-02-17",
test_end_date="2025-02-16",
target_strategy=2,
odds_source="fixed",
)
# ❌ 2026年を含むのでNG
dataset = Dataset(
train_start_date="2019-02-17",
valid_start_date="2023-11-17",
test_start_date="2024-02-17",
test_end_date="2026-12-31", # populationに含まれていない
target_strategy=2,
odds_source="fixed",
)2. train期間 + valid期間の長さを揃える
このような時系列データに対する機械学習の場合、「どの程度過去のデータまで学習するか」という要素も精度に関わってきます。よって、例えばテスト期間を変更する際も、そのまま全ての日付をスライドさせるイメージで、学習データと検証データを合わせた期間の長さを固定するようにしましょう。
現在の設定では、train期間 + valid期間が5年間にしている(以下の記事で決定)ので、データを最新化する場合もtrain期間 + valid期間が5年間になるように揃えています。(今のデータで再実験すると、最適な期間もまた変わるかもしれませんが。)
3. validの期間は3ヶ月に設定
これはあまり根拠があるわけではなく、色々試して精度を見た結果の経験則なのですが、筆者は検証データの期間は3ヶ月に固定しています。短すぎるとEarly stoppingの判断が不安定になり、長すぎると学習データが減ってしまいまう + 最新データを学習できなくなります。
■odds_source=の設定について
馬券を買う際に実際に使用できるのは、レース開始前のオッズ(直前オッズ
)ですが、後からWeb上で取得できるデータはレース終了後に確定したオッズ(確定オッズ)になります。
本来は直前オッズを使用してシミュレーションすることが望ましいのですが、直前オッズの取得には少し手間がかかるため、odds_source=パラメータで直前オッズ or 確定オッズを切り替えられるようにコードを作成しています。
初めて使用する方は、まずはodds_source="fixed"を指定して、確定オッズでオフライン検証を試してみるのが良いでしょう。
▼v3_4_3/src/offline_evaluation.ipynb(再掲)
# データセット作成
dataset = Dataset(
train_start_date="2019-02-17",
valid_start_date="2023-11-17",
test_start_date="2024-02-17",
test_end_date="2025-02-16",
target_strategy=2,
odds_source="fixed",
)■直前オッズの指定方法
直前オッズの取得については、需要がありそうであれば、またまとめ記事を作成しようと思いますが、一番楽なのは以下の記事に書いている方法で取得する方法です。
タイトルは「MacOSのPCで〜」となっていますが、Windowユーザーの方は途中からの手順に合流すれば同様に取得できます。
この方法で直前オッズが入手できている場合、odds_source="jravan"を指定します。
▼v3_4_3/src/offline_evaluation.ipynb
# データセット作成
dataset = Dataset(
train_start_date="2019-02-17",
valid_start_date="2023-11-17",
test_start_date="2024-02-17",
test_end_date="2025-02-16",
target_strategy=2,
odds_source="jravan",
)スクレイピングによってJRAのサイトから直前オッズを入手する方法もあり、以下の記事で解説しています。
この方法で直前オッズが入手できている場合、odds_source="scraping"を指定します。
2.2. 学習の実行
次に、データセットをTrainerクラスに渡すことで、機械学習モデルの学習が自動的に実行されます。実際に学習を実行していきましょう。
▼v3_4_3/src/offline_evaluation.ipynb
# 学習実行
trainer = Trainer(config_filepath="config.yaml")
evaluation_df = trainer.run(
dataset=dataset,
importance_filename="importance",
model_filename="model.pkl",
calibration_model_filename="calibration_model.pkl",
evaluation_filename="evaluation.csv",
)# 出力例
Training until validation scores don't improve for 100 rounds
[100] valid_0's binary_logloss: 0.237875
[200] valid_0's binary_logloss: 0.227638
[300] valid_0's binary_logloss: 0.224962
[400] valid_0's binary_logloss: 0.224159
[500] valid_0's binary_logloss: 0.223916
[600] valid_0's binary_logloss: 0.22378
[700] valid_0's binary_logloss: 0.223766
Early stopping, best iteration is:
[661] valid_0's binary_logloss: 0.223736
-------------------- result --------------------
test_df binary_logloss (target_mod vs pred): 0.23385632378762675
test_df binary_logloss (target vs pred): 0.20617549689047956
test_df binary_logloss (target vs pred_calibrated): 0.2089990218948253学習にはLightGBMという機械学習アルゴリズムを使っています。
■config.yamlによる学習の設定
設定パラメータについて解説していきます。
最初に学習の設定ですが、v3_4_3/src/config.yamlに設定ファイルがあるので、以下のように指定することでこれを反映しています。
trainer = Trainer(config_filepath="config.yaml")config.yamlは大きく分けて2つのセクションで構成されており、ファイルの先頭部分には、LightGBMの学習に使用するハイパーパラメータが定義されています。
params:
"objective": "binary"
"metric": "binary_logloss"
"random_state": 100
"verbosity": -1
"feature_fraction": 0.8
"bagging_fraction": 0.8
"learning_rate": 0.01
"bagging_freq": 1それぞれのパラメータの意味は以下の通りです。
objective: "binary"(2値分類問題として学習)
metric: "binary_logloss"(評価指標として2値対数損失を使用)
random_state: 100(再現性のための乱数シード)
verbosity: -1(学習ログを抑制)
feature_fraction: 0.8(各イテレーションで80%の特徴量をランダムに使用)
bagging_fraction: 0.8(各イテレーションで80%のデータをランダムに使用)
learning_rate: 0.01(学習率、小さい値で慎重に学習)
bagging_freq: 1(毎イテレーションでバギングを実行)
LightGBMのハイパーパラメータについて、詳しくは以下の記事で解説しています。
paramsの下には、機械学習モデルに入力する特徴量のリストが記載されています。
features:
- horse_id
- jockey_id
- trainer_id
- umaban
- wakuban
# 以下省略これらの特徴量については、過去の記事で詳しく解説しています。
もう1つ、config_odds_removed.yamlというファイルもありますが、このファイルは、オッズ(tansho_odds)と人気(popularity)を特徴量から除外したモデルを作成するための設定ファイルです。
なぜこのファイルがあるかについては、以下の記事で詳しく解説しています。
■目的変数の補正
データセットにはtargetとtarget_modという2つの目的変数が含まれています。これは競馬AIの予測精度を向上させるための工夫で、学習時にはtarget_modを使用します。
target: 実際の着順ベースの目的変数(1着なら1、それ以外は0)
target_mod: target_strategyパラメータで指定した戦略に基づく補正版の目的変数
target_strategyパラメータは、学習時にどのような目的変数を使うかを指定します。
target_strategy=0: 着順そのまま(target_mod=target)
target_strategy=1: タイム同着 + 鼻差を正例に含める(1着と0.0秒差、またはハナ差で2着になった馬も1として扱う)
target_strategy=2: タイム同着を正例に含める(1着と0.0秒差で2着になった馬も1として扱う)
これによって僅差で負けた馬も正例として扱うことで、モデルの学習能力を高めています。(詳細については、以下の記事で解説しています。)
■skip_trainパラメータについて
trainer.run()には、もう1つ便利なパラメータskip_trainがあるので解説しておきます。
evaluation_df = trainer.run(
dataset=dataset,
importance_filename="importance",
model_filename="model.pkl",
calibration_model_filename="calibration_model.pkl",
evaluation_filename="evaluation.csv",
skip_train=True, # 学習をスキップして既存モデルで予測のみ実行
)skip_train=Trueにすると、学習をスキップして既存のモデルファイルを読み込み、テストデータに対する予測のみを実行します。
これは例えばテスト期間だけ変えて検証したい場合など、モデルは変えずに、テストデータだけを変更してシミュレーション結果を比較したい時に便利です。
▼使用例
dataset = Dataset(
train_start_date="2019-02-17",
valid_start_date="2023-11-17",
test_start_date="2024-02-17",
test_end_date="2025-09-30", # テスト期間を追加
target_strategy=2,
odds_source="fixed",
)
evaluation_df_latest = trainer.run(
dataset=dataset,
importance_filename="importance",
model_filename="model.pkl",
calibration_model_filename="calibration_model.pkl",
evaluation_filename="evaluation_latest.csv",
skip_train=True, # 学習をスキップして既存モデルで予測のみ実行
)このように、skip_trainを使うことで効率的に複数の条件で検証を行うことができます。
■出力の見方
学習を実行すると、以下のようなログが出力されます。
Training until validation scores don't improve for 100 rounds
[100] valid_0's binary_logloss: 0.237875
[200] valid_0's binary_logloss: 0.227638
[300] valid_0's binary_logloss: 0.224962
[400] valid_0's binary_logloss: 0.224159
[500] valid_0's binary_logloss: 0.223916
[600] valid_0's binary_logloss: 0.22378
[700] valid_0's binary_logloss: 0.223766
Early stopping, best iteration is:
[661] valid_0's binary_logloss: 0.223736
-------------------- result --------------------
test_df binary_logloss (target_mod vs pred): 0.23385632378762675
test_df binary_logloss (target vs pred): 0.20617549689047956
test_df binary_logloss (target vs pred_calibrated): 0.2089990218948253100回学習するごとに、検証データに対する損失(binary_logloss)が表示されています。
binary_loglossとは、予測の誤差を表す指標で、値が小さいほど予測精度が高いことを意味します。
ログを見ると、学習回数が増えるにつれて損失が減少していき、661回目で最小値を記録した後、100回連続で改善が見られなかったため、Early stoppingが発動して学習が終了しています。
このように、Early stoppingによって適切なタイミングで学習を停止することで、過学習を防ぎつつ最適なモデルを作成できます。
最下部の3つの指標は、学習したモデルがテストデータに対してどれだけ正確に予測できているかを表しています。
-------------------- result --------------------
test_df binary_logloss (target_mod vs pred): 0.23385632378762675
test_df binary_logloss (target vs pred): 0.20617549689047956
test_df binary_logloss (target vs pred_calibrated): 0.2089990218948253括弧内は、どの列とどの列の誤差を比較しているかを示しています。
test_df binary_logloss (target_mod vs pred): 学習に使用したtarget_modと予測値の誤差
test_df binary_logloss (target vs pred): 実際の1着かどうか(target)と予測値の誤差
test_df binary_logloss (target vs pred_calibrated): 実際の1着かどうかとキャリブレーション補正後の予測値の誤差
■出力ファイル
学習が完了すると、v3_4_3/data/03_train/配下に複数のファイルが自動的に出力されます。
▼v3_4_3/data/03_train/
03_train/
├── importance.png # 特徴量重要度の画像
├── importance.csv # 特徴量重要度のCSV
├── model.pkl # 学習済みモデル
├── calibration_model.pkl # キャリブレーション用モデル
└── evaluation.csv # テストデータの予測結果importance.png / importance.csvは、特徴量重要度を示すファイルです。学習に使用した482個の特徴量の中で、どの特徴量がどれだけ予測に貢献しているかを可視化したものです。
例えば、以下のような特徴量が重要度が高い傾向にあります。
オッズ(tansho_odds):最も予測精度に貢献する特徴量
人気(popularity):オッズと並んで重要な指標
過去賞金(per_race_typeなど):芝・ダート別の過去賞金集計
馬番(umaban):コース上の有利不利を反映
出走間隔:馬の調子を示す指標
機械学習モデルは、これらの様々な要素を総合的に加味して、最終的な勝率を予測しています。
model.pklは、学習済みモデル本体が保存されたファイルです。pickle形式で保存されているため、直接開いて内容を確認することはできませんが、予測時にはこのファイルを読み込んで勝率を予測します。
calibration_model.pklは、キャリブレーション補正用のモデルです。予測値を実際の確率に近づけるための補正モデルですが、現状では精度改善が見られないため実運用では使用していません。キャリブレーションについては、後ほど詳しく解説します。
evaluation.csvは、シミュレーションに使用するデータが保存されたファイルです。テストデータに対するモデルの予測結果が含まれており、pred列にモデルが予測した勝率が格納されています。
このファイルの内容は、notebook上でevaluation_dfとして確認することもできます。
▼v3_4_3/src/offline_evaluation.ipynb
evaluation_df
3. モデルの精度評価
学習が完了したら、次は回収率シミュレーションを実行します。
先ほど出力されたevaluation.csvをEvaluatorクラスにセットします。
▼v3_4_3/src/offline_evaluation.ipynb
evaluator = Evaluator(evaluation_filename="evaluation.csv")3.1. 回収率シミュレーション
期待値の閾値を変えながら、どの閾値で馬券を購入すれば最も回収率が高くなるかをシミュレーションします。
▼v3_4_3/src/offline_evaluation.ipynb
# 単勝馬券
result_df = evaluator.bet_by_expect_return(
min_exp=0.8, max_exp=2.0, min_pred=0.01, pred_col="pred"
)出力例:
best result:
{'expect_return': 1.7454545454545456,
'n_hits': 16.0,
'return_rate': 2.105511811023622,
'sharp_ratio': 1.6920310196591433,
'std': 0.6533637966320058,
'total_bet': 381.0,
'total_payoff': 802.2}
best result:の結果は、シャープレシオ(sharp_ratio)が最大となる期待値の閾値における、各指標を表しています。
expect_return: 期待値の閾値(この例では1.745)
n_hits: シミュレーション期間中に的中した馬券の枚数(16枚)
return_rate: 回収率(210.5%)
sharp_ratio: シャープレシオ(1.692)
std: 回収率の標準偏差(ぶれ幅の大きさ)
total_bet: 賭けた馬券の総枚数(381枚)
total_payoff: 払い戻しの総額(802.2)
シャープレシオとは、リスク(回収率のばらつき)あたりのリターン(回収率の高さ)を表す指標です。グラフ上で薄く塗られている部分は、統計的に計算した回収率の信頼区間(ぶれ幅)を示しており、回収率の値をこの「ぶれ幅」で割ったものがシャープレシオとなります。
より詳しくは、以下の記事で解説しているので、そちらをご参照ください。
bet_by_expect_return()のパラメータについても解説しておくと、まずmin_expとmax_expは、期待値の閾値についてどの範囲で実験するか、という設定になります。
result_df = evaluator.bet_by_expect_return(
min_exp=0.8, max_exp=2.0, min_pred=0.01, pred_col="pred"
)min_predは、勝率が低すぎるものを除外する設定になります。
この例では、予測勝率は0.01を超えるものを除外しています。(この戦略の背景については以下の記事で解説しています。)
pred_colは使用する予測値の列名で、デフォルトでは"pred"列ですが、キャリブレーション後のモデルを使いたい場合は"pred_calibrated"を指定します。
bet_typeは"単勝"または"複勝"を指定し、デフォルトでは"単勝"ですが、後ほど紹介する複勝馬券のシミュレーションを行う場合は"複勝"を指定します。
odds_colは使用するオッズの列名で、こちらもデフォルトは"tansho_odds"ですが、複勝馬券のシミュレーションを行う場合は"fuku_odds_low"などを指定します。
馬連馬券についても、単勝と同様にシミュレーションを実行できますが、馬連馬券のシミュレーションは、テスト期間の直前オッズを入手できている場合のみ実行可能です。
▼v3_4_3/src/offline_evaluation.ipynb
# 馬連馬券(直前オッズが取得できている場合のみ実行可)
result_df = evaluator.bet_by_expect_return_umaren(
min_exp=1.0,
max_exp=6.0,
min_pred=0.01
)Best result:
{'expect_return': 2.666666666666667,
'hits': 182.0,
'return_rate': 1.3394698795180722,
'sharp_ratio': 1.8874118940354956,
'std': 0.17985998742025938,
'total_bets': 8300.0,
'total_payoff': 11117.599999999999}
3.2. キャリブレーションプロット
キャリブレーションプロットは、モデルの予測値が実際の確率とどれくらい一致しているかを確認するものです。
▼v3_4_3/src/offline_evaluation.ipynb
# 補正前
evaluator.plot_calibration()
横軸がモデルの予測値、縦軸が実際の勝率を表しています。
つまり、図中のy=xの対角線はが「予測値と実際の確率が完全に一致している状態」を表しており、ここからどの程度ずれているかを可視化するものになります。
プロット上部に表示されるBrier Scoreは、予測確率の誤差を表す指標で小さいほど精度が良いことを表します。
先ほど解説したように、target_modという修正版の目的変数を使って学習しているため、下側にずれている(予測値を過大評価している)ことが分かります。そこで、キャリブレーション補正という処理を行い、予測値を実際の確率に近づける調整を行っています。
▼v3_4_3/src/offline_evaluation.ipynb
# 補正後
evaluator.plot_calibration(pred_col="pred_calibrated")
ただ、現在のv3.4.3では、キャリブレーション補正を行っても回収率の精度としては上がらなかったため、実運用では使用していません。
3.3. その他の精度検証
オッズと人気を特徴量から省いたモデルについても、出力ファイル名を変えることで先ほどのファイルは上書きしないようにしつつ、検証することができます。
▼v3_4_3/src/offline_evaluation.ipynb
# オッズと人気を特徴量から省いた場合
trainer = Trainer(config_filepath="config_odds_removed.yaml")
_ = trainer.run(
dataset=dataset,
importance_filename="importance_odds_removed",
model_filename="model_odds_removed.pkl",
calibration_model_filename="calibration_model_odds_removed.pkl",
evaluation_filename="evaluation_odds_removed.csv",
)
# 以下同様...config_odds_removed.yamlを指定することで、オッズ関連の特徴量を除外したモデルを学習できます。
また、rank_threshold=3を指定することで、3着以内を正例(target=1)とする複勝モデルも作成し、同様にオフライン検証をすることができます。
▼v3_4_3/src/offline_evaluation.ipynb
# データセット作成
dataset = Dataset(
train_start_date="2019-02-17",
valid_start_date="2023-11-17",
test_start_date="2024-02-17",
test_end_date="2025-02-16",
target_strategy=2,
# odds_source="fixed", # 直前オッズが必要
rank_threshold=3, # 3着以内を予測
)rank_threshold=3を指定することで、3着以内に入る馬を1、それ以外を0とする目的変数が自動的に作成されます。
▼v3_4_3/src/offline_evaluation.ipynb
# 学習実行
trainer = Trainer(config_filepath="config.yaml")
evaluation_df = trainer.run(
dataset=dataset,
importance_filename="importance_fukusho",
model_filename="model_fukusho.pkl",
calibration_model_filename="calibration_model_fukusho.pkl",
evaluation_filename="evaluation_fukusho.csv",
)学習が完了したら、複勝馬券でのシミュレーションを実行します。
evaluation_filename=には、今度は上記で指定した"evaluation_fukusho.csv"をセットします。
▼v3_4_3/src/offline_evaluation.ipynb
evaluator = Evaluator(evaluation_filename="evaluation_fukusho.csv")複勝の場合は、以下のようにパラメータを指定することで回収率のシミュレーションが実行できます。
result_df = evaluator.bet_by_expect_return(
min_exp=0.5,
max_exp=1.5,
min_pred=0.01,
pred_col="pred",
bet_type="複勝",
odds_col="fuku_odds_low",
)以上で、モデルの学習とオフライン検証の工程が完了しました。実運用前にこれらのシミュレーションを行うことによって、本番で十分な精度が出るのか、確かめることができます。
4. ソースコードのダウンロード
ソースコードは以下の記事に最新版が添付されているので、そこからダウンロードすることができます。
上記記事には、今回の記事で紹介したソースコード含め、データ収集・機械学習モデル構築・サーバー上での自動投票など、本格的な競馬AIの運用に必要な全工程のコードが含まれています。