
【競馬AI開発#49】キャリブレーション精度を改善させる
この【競馬AI開発】シリーズでは、競馬予想AIを作ることを通して、機械学習・データサイエンスの勉強になるコンテンツの発信や、筆者が行った実験の共有などを行っています。
■今回やること
今回は、機械学習モデルの予測確率を補正する「キャリブレーション」の手法をアップデートします。
現在運用している競馬AIでは、モデルが出力した予測確率とオッズを組み合わせて「期待値」を計算し、期待値が高い馬券に賭ける戦略を取っています。この戦略を成功させるためには、予測確率が「真の勝率」に近いことが重要です。
このとき、予測確率と実際の勝率のズレを補正する処理が「キャリブレーション」と呼ばれるものになります。

しかし、後ほど説明するように、現状の手法ではこの「キャリブレーション」によって逆に精度が悪化してしまっているという課題があります。
今回はこの課題を解決する方法を探っていきます。
1. 現状の学習ロジックの復習
まずは、現状のモデル学習の流れを復習しながら、キャリブレーションの課題を確認していきましょう。
現在配布している競馬AIのソースコードでは、02_train.ipynbというnotebookを実行することで、モデルの学習ができるようになっています。
▼v3_5_0_exp1/src/02_train.ipynb
from modules.train import Dataset, TrainerDatasetクラスでは、学習に使うデータセットを作成します。引数で学習・検証・テスト期間の設定や直前オッズの取得元を指定できます。
# データセット作成
dataset = Dataset(
train_start_date="2020-01-01",
valid_start_date="2024-12-01",
test_start_date="2025-01-01",
test_end_date="2025-12-31",
target_strategy=2,
odds_source="jravan",
)上記ソースコードの入手方法については、記事の最後で案内します
■学習・検証・テスト期間の設定
上記の例では、
学習データ(train):2020年1月1日〜2024年11月30日
検証データ(valid):2024年12月1日〜2024年12月31日(Early Stoppingの判定とキャリブレーション補正に使用)
テストデータ(test):2025年1月1日〜2025年12月31日(最終的な精度評価に使用)
と設定してデータを分割しています。Early stoppingについては以下の記事で詳しく解説しているので、気になった方は見てみてください。
■odds_source=の指定
本プロジェクトでは、2種類の直前オッズを使用できます。
odds_source="jravan"(デフォルト)
・JRA-VANのソフトを使って取得
・取得できるのは単勝・複勝・枠連・馬連の4種類で、発走6分前オッズまで
・入手方法・ソースコードは#28の記事で解説
odds_source="scraping"
・JRA公式サイトからスクレイピングで取得
・利用できる馬券種は単勝・複勝・馬連・馬単・三連複・三連単で、発走直前までオッズ取得が可能
・入手方法・ソースコードは#24の記事、#25の記事で解説。
今回の実験では、両方のオッズを使って精度を検証していきます。
まずは、JRA-VANのオッズを使った検証です。以下のように、Trainerクラスを使って学習を実行します。
▼v3_5_0_exp1/src/02_train.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.23518
[200] valid_0's binary_logloss: 0.2267
[300] valid_0's binary_logloss: 0.224752
[400] valid_0's binary_logloss: 0.224349
[500] valid_0's binary_logloss: 0.224298
Early stopping, best iteration is:
[473] valid_0's binary_logloss: 0.224263
-------------------- result --------------------
test_df binary_logloss (target_mod vs pred): 0.24176613197624708
test_df binary_logloss (target vs pred): 0.21006063002191624
test_df binary_logloss (target vs pred_calibrated): 0.22162012531849284学習が完了し、最後の3行にテストデータに対する評価結果が表示されています。
-------------------- result --------------------
test_df binary_logloss (target_mod vs pred): 0.24176613197624708
test_df binary_logloss (target vs pred): 0.21006063002191624
test_df binary_logloss (target vs pred_calibrated): 0.22162012531849284Log loss(対数損失)とは、確率予測の精度を測る指標です。
予測確率と実際の結果(1着であれば1、そうでなければ0)との乖離を測定し、値が小さいほど予測精度が高いことを意味します。
target vs pred: 実際の着順(1着かどうか)とモデル予測値のlogloss(0.2101)。
target vs pred_calibrated: 実際の着順(1着かどうか)とキャリブレーション補正後の予測値のlogloss(0.2216)。
target_mod vs predについては後ほど解説します
ここで注目すべきは、target vs predよりtarget vs pred_calibratedの方がloglossの値が大きくなっているという点です。つまり、現状のキャリブレーション手法を適用すると、逆に予測精度が悪化してしまっているのです。
次に、スクレイピングで入手した直前オッズを使った場合も確認してみましょう。
▼v3_5_0_exp1/src/02_train.ipynb
# データセット作成
dataset = Dataset(
train_start_date="2020-01-01",
valid_start_date="2024-12-01",
test_start_date="2025-01-01",
test_end_date="2025-12-31",
target_strategy=2,
odds_source="scraping",
minutes_before=4,
)skip_train=Trueを指定すると、既に学習済みのモデルとキャリブレーションモデルを読み込んで、テストデータに対する予測だけを計算できます。
# 学習実行
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_scraping.csv",
skip_train=True,
)-------------------- result --------------------
test_df binary_logloss (target_mod vs pred): 0.24306377856152073
test_df binary_logloss (target vs pred): 0.21068752093202497
test_df binary_logloss (target vs pred_calibrated): 0.2246786809225069こちらでも同様に、キャリブレーション前の0.2107から、キャリブレーション後は0.2247に悪化しています。
2. キャリブレーション手法の改善
現行のキャリブレーション手法では、Log lossが逆に悪化してしまうことがわかりました。
そこで、今回はこのキャリブレーションに「ある手法」を適応します。
コードとしての変更量はたったの2行になるのですが、精度を劇的に改善させることができます。
▼改善後の実行結果(JRA-VANオッズ)
-------------------- result --------------------
test_df binary_logloss (target_mod vs pred): 0.24176613197624708
test_df binary_logloss (target vs pred): 0.21006063002191624
test_df binary_logloss (target vs pred_calibrated): 0.20744419799585248loglossが 0.2216 → 0.2074 に大幅改善
▼改善後の実行結果(スクレイピングオッズ)
-------------------- result --------------------
test_df binary_logloss (target_mod vs pred): 0.24306377856152073
test_df binary_logloss (target vs pred): 0.21068752093202497
test_df binary_logloss (target vs pred_calibrated): 0.20811430116640217loglossが 0.2246 → 0.2081 に大幅改善
ここからは具体的にどのような手法を使ったのか、詳しく解説していきます。