
【競馬AI開発#59】機械学習を「ボタンひとつ」で実行できるシステムを開発する
この【競馬AI開発】シリーズでは、競馬予想AIを作ることを通して、機械学習・データサイエンスの勉強になるコンテンツの発信や、筆者が行った実験の共有などを行っています。
■今回やること
前回の記事では、競馬AIの前処理〜特徴量作成の工程に dagster を導入し、処理どうしの依存関係を「一枚のグラフ」として可視化・部分実行できるようにしました。

今回はその続きで、「モデル学習 → テストデータに対する予測スコア付与」までをdagster上で可視化・実行できるようにします。

■競馬AIについて
netkeiba.comから収集したデータを機械学習モデルに学習させることで期待リターンを計算し、リターンが最大化するように自動投票を行う競馬予想AIを開発しています。
すでにシステムとしては一通り完成しており、現在はこの完成したシステムをベースに、特徴量の追加やモデルの改善など、さらなる精度向上に取り組んでいる段階です。
このシリーズでは、こうしたシステムをゼロから構築する過程を、コード付きで詳しく解説しています。ソースコードは、下に進んでダウンロード・解凍すれば使うことができるので、ぜひ実際にコードを動かしてみてください。
1. 完成イメージ
今回の記事で実現したいことは、一言で言うなら、モデル学習・評価スコア付与の工程について、「コードをいじらなくても、UI上でボタンを選択するだけで目的別に必要なものを生成できる状態」を実現することです。
まずは完成イメージから見ていきます。dagsterのUIを開くと、asset(生成されるデータ)が3つのグループに分かれて並びます。

features:前回作った前処理〜特徴量作成のグループ。
main:メインで実行されるフロー。単勝モデルとその評価。
experimental:複勝モデルやオッズ除外モデルなど、試したいときだけ使う実験用。
一つ一つが「アセット」=「処理の結果としてできる成果物」を表しており、UI上から選択して該当する処理を実行することができます。

mainを閉じてexperimentalを開くと、以下のように「複勝モデル」や「オッズ除外モデル」を作成するためのフローが現れます。

1.1. before:正しいパラメータの組み合わせを、人間が覚えていた
元々、モデルの学習は1枚のnotebookの中で行っていました。そこでは、作りたいモデルや評価のたびに、データセットを作るクラス Dataset(...)(期間・目的変数・オッズソースなど)、学習器 Trainer(...)(使う設定ファイル)、そして学習・評価を回す trainer.run(...)(出力ファイル名・skip_train など)の引数を、その都度書き換えていました。
例えば単勝モデルであれば、以下のように実行していました。
▼v3_6_0/src/02_train.ipynb
# データセット作成(odds_source はデフォルトで "jravan"、minutes_before は 6)
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,
)
# 学習実行
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",
)同じ単勝モデルをスクレイピングで集めたオッズで評価したいときは、データセットを作り直して(オッズソースを Dataset 側で差し替えて)、学習はスキップして評価データセット作成だけやり直します。
# データセット作成(オッズソースは Dataset 側で指定)
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,
)
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,
)複勝モデル(3着以内を正例とする)なら、データセット側で rank_threshold を 3 に変え(オッズソースも Dataset で指定)、ファイル名も別物に変えます。
# データセット作成(複勝は3着以内を正例。オッズソースも Dataset 側で指定)
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,
rank_threshold=3,
)
# 学習実行
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",
)このように、「正しい組み合わせ」を管理する必要があったわけです。
しかも指定する場所が Dataset・Trainer・run の3か所に散らばっているため、組み合わせの数が増えるほど、指定ミス(複勝モデルなのに単勝のファイル名で保存してしまう、オッズソースを Dataset 側で変え忘れる、など)が起きやすくなります。また、後から「evaluation_scraping.csv はどの条件で作ったんだっけ?」が分からなくなる、という問題もあります。
1.2. after:欲しいノードを選んでボタンを押すだけ
今回のポイントは、この「正しいパラメータの組み合わせ」を、それぞれ名前のついたノードに固定してしまい、dagster上で可視化することです。

evaluation_df_jravan というノードには「単勝モデル x JRA-VANの発走6分前オッズを使ったスコアリング結果」という組み合わせが、evaluation_df_scraping には「単勝モデル x スクレイピングの発走4分前オッズを使ったスコアリング結果」という組み合わせが、それぞれ最初から埋め込まれています。
このようにして、これまで引数を覚えて指定していた作業が「UIで evaluation_df_jravan を選んでボタンを押す」だけになります。引数の組み合わせは、ノードの名前そのものになったわけです。

1.3. (補足)evaluation_dfとは?
ところで、evaluation_df_jravan などのノードは、そもそもどんなデータを作るのでしょうか。ひとことで言うと、テストデータの各馬に「予測確率」と「オッズ」を付けたテーブルです。モデルが各馬の「勝ちそう度(予測確率)」を出し、そこに取得した「レース直前オッズ」を付与することで、回収率の検証などに使えるデータになります。

そして、上記の「オッズ」の付与の仕方には3種類あります。
web上で通常入手できるのは、レース発走後の「確定オッズ」ですが、この競馬AIでは以下二つの手段で「直前オッズ」を取得することで、正確な回収率シミュレーションを実現しています。
JRA-VANの公式ソフトから取得:入手方法とソースコードは#28の記事で詳しく解説しています。
スクレイピングで取得:JRA公式サイトをレース直前にスクレイピングするようにスケジュールし、直前オッズを溜める。こちらは#29の記事で詳しく解説しています。
直前オッズが手元に無いときの手段としては確定オッズを使うこともでき、これらを合わせて計3種類のオッズソース指定がある、というわけです。これをodds_source=というパラメータで管理しています。
odds_source="jravan":JRA-VANのオッズを使用
odds_source="scraping":スクレイピングしたオッズを使用
odds_source="fixed":確定オッズを使用
2. 完成した機械学習システムの使い方
まずは完成したシステムの使い方から見ていきます。最初のポイントは、依存関係を踏まえて「必要なノードだけ」を実行できることです。
前回の記事でも触れたとおり、dagsterはassetどうしの依存関係をグラフ(DAG=有向非巡回グラフ)として把握しています。今回追加したノードの依存の向きは、次のとおりです。
features → dataset_tansho → model_tansho → evaluation_df_jravan
→ evaluation_df_scraping
→ evaluation_df_fixedfeaturesから出発して、データセット作成(dataset)、学習(model)、評価(evaluation)へと一直線に進み、最後の評価のところで「JRA-VANオッズ」「スクレイピングオッズ」「確定オッズ」へと枝分かれします。

2.1. 基本:1つのノードだけを作り直す
いちばん単純なのは、asset を1つ選んで、そこだけを materialize(実行)する使い方です。

たとえば「モデルのハイパーパラメータを少しいじったので、モデルだけ学習し直したい」という場面。このとき作り直したいのは model_tansho だけで、その上流の dataset_tansho(学習データの中身)は変わっていません。なので、グラフ上で model_tansho のノードだけを選んで Materialize すればよく、重いデータセット作成をやり直す必要はありません。
materialize(マテリアライズ)は前回の記事でも登場しましたが、asset の定義(設計図)どおりに処理を実際に走らせて、データを生成・更新することです。グラフ上のノードを選んでボタンを押すだけで実行できます。
またこの時、グラフ上のノードをクリックすると、そのassetの説明・依存関係・最後にいつ実行したか、といった情報が確認できます。

2.2. 変更を下流に波及させる
開発中にいちばんよく使うのが、ある変更を下流に波及させるやり方です。ここが今回いちばん便利になったところなので、実際の画面で追っていきます。
たとえば「学習データの期間を変えたい」として、上流の dataset_tansho を新しい条件で作り直したとします。すると、dataset_tanshoに依存している下流のノードが、一斉に「Unsynced(=上流が変わったので、もう古い)」という黄色い印に変わります。

上の画面では、dataset_tanshoを1回作り直しただけで、その下流である model_tansho と、さらにその下流の evaluation_df_jravan・evaluation_df_scraping・evaluation_df_fixed の合わせて4つに「Unsynced」が付いています。「1つの変更が、どこまで影響するか」がひと目で分かるわけです。

あとは、更新したいノードをまとめて選んで、再実行するだけです。
例えばJRA-VANのオッズを使っている場合であれば、以下のように二つをcommand(windowsはctrl)を押しながらクリックして選択し、「Materialize selected」を押せば再実行されます。

「どこを作り直すべきか」をツールが教えてくれて、「その範囲だけ」を正しい順番でまとめて流せる。これが、試行錯誤を繰り返す開発ととても相性が良いポイントです。
2.3. main と experimental の使い分け
今回、assetは3つのグループに分けてあります。
features:前処理〜特徴量作成(前回#58の範囲)。
main:本線。単勝モデルと、オッズ3種類で期待値計算した評価用データセット。
experimental:実験用。複勝モデル・オッズ除外モデルと、それらの評価用データセット。
普段の運用は main を回せば十分です。複勝モデルやオッズ除外モデルを試したいときだけ experimental を開く、という運用がUI上で分かるように作っています。
experimental グループを開くと、複勝・オッズ除外の枝が入っています。

2.4. Launchpadで条件を変えて実行する
最後のポイントは、学習期間やオッズソースといった「条件」を、コードを書き換えずにUIから変えて実行できることです。
dagsterには、assetにConfigという「実行時に外から渡せる設定」を持たせる仕組みがあります。
詳しくは後ほど解説しますが、今回は4つのConfigを用意しています。
PopulationConfig:母集団の期間(from_date / to_date)。
DatasetConfig:学習・検証・テストの期間、目的変数の作り方(target_strategy)、着順の閾値(rank_threshold)。
TrainConfig:学習に使う設定ファイルのパス(config_filepath)。
EvaluationConfig:どのオッズソース(odds_source)を使うか、発走何分前のオッズを使うか(minutes_before)。
assetの関数が、これらのConfigを引数として受け取るように書いておくと、dagsterは「このassetはこういう設定を受け付けますよ」と理解してくれます。
設定を外から渡す入り口になるのが、Launchpad(ランチパッド)という画面です。
Launchpad とは、assetを実行する前に「今回はどんな設定(Config)で動かすか」をその場で編集できる、dagster UIの設定編集画面です。
assetの「Materialize」ボタンの隣のメニューから「Open launchpad」を選ぶ、または「Materialize selected」ボタンをShiftを押しながらクリックすると、

次のような画面が開きます。ここに、先ほどのConfigの項目が、初期値が入った状態で表示されます。

たとえば「学習期間を変えて学習し直したい」なら、この画面で train_start_date や valid_start_date の値を書き換えて実行することができます。

そして、各assetを実行すると、そのとき使った条件(期間・オッズソース)や結果指標(データ件数・loglossなどの精度指標)等が、実行結果のメタデータ(metadata)として自動で記録され、各アセットの「View in Asset Catelog」から詳細を確認することができます。


上の表を見れば、この evaluation_df_jravan が「単勝(tansho)のデータセットを使い、47,531行を、JRA-VANオッズ・発走6分前で評価し、loglossはいくつだったか」というところまで、把握することができます。
とくにloglossのような数値のメタデータは、「Plots」から時系列グラフとして推移を追えるので、条件を変えながら精度の変化を見るのにも向いています。

3. 実装の詳細
ここからは、こうした使い勝手を実現するための実装を、実際のソースコードとともに見ていきます。