プログラミング初心者がChatGPTを使って競馬予想AIを作ることで、生成AIとプログラミングについて学んでいく企画の第10回です。
このシリーズもついに連載二桁、感慨深いですね。
↓前回の記事
前回の記事ではついにAIモデルを構築し、実際のデータを使った競馬予想を行いました。
驚愕の精度をたたき出したため、興味のある人はぜひ前回の記事をご覧ください。
今回は入力データのテーブルを拡充し、競馬予想に重要な特徴量を増やしていきます。
特徴量テーブルの設計指針
これまでスクレイピングした情報は「レース結果」と「馬の成績」の2つです。
これらを1つのテーブルにしていきましょう。
この他にも血統や騎手、調教師などの様々な特徴量が考えられますので、徐々に追加していきましょう
LightGBMは行ごとに独立サンプルとして扱うため、race_idとhorse_idをそれぞれ含んだテーブルにしましょう。
1レースにつき、出走頭数分のデータがあるため各行にhorse_idが書かれています。
ここにそれぞれの馬の結果を列として追加します。
horse_tableには、以下の情報が書かれています。

このテーブルには天気や馬場指数といったそのレースのコンディション情報も入っています。
race_idとhorse_idをキーとして、これらが一致する列にはweather, num_of_horse, course_condition, distance_lengthの4つの特徴量を入れましょう。
(本当はrace_resultテーブルに入れておくとこんな処理をしなくてよいのですが)
race_resultテーブルに出てくる全ての馬の成績をスクレイピングしているため、最終的にこの特徴量に欠損は起きないはずです。
さらにrace_idごとにこれらの特徴量が異なることもないはず、という2つの観点からデバッグをしましょう。
過去成績の集約
horse_resultテーブルに含まれているその他の情報は過去レースのタイムやオッズなどです。
これらの情報は「過去n試合で平均して○○だった」と値を集約することが必要です。
それでは過去何試合分が必要で、値はどのように集約すればよいのでしょう。
1試合前の成績、2試合前の成績、、、とn試合前の成績を片っ端から列として追加することで余すことなくデータを使うこともできますが、これは木モデルでは悪手です。
試合数が少ない馬は欠損値だらけになりますし、膨大な列数による次元爆発を起こします。
さらに順序関係も学習できないため、木モデルでは系列データは圧縮してから使いましょう。
何走分を使うべきか
競馬の経験が全くない自分にとっては難しい問いですが、そんなときはChatGPTです。
相談した結果以下の集約の仕方が良さそうです。
| 役割 | 走数 |
|---|---|
| 直近の状態(フォーム) | 1〜3走 |
| 安定性・地力 | 5走 |
| 長期傾向 | 8〜10走(集約のみ) |
競走馬は数走で状態が大きく変わり、6走以上前は「別馬」らしいです。
よって直近は細かく、古い成績は粗くという集約の仕方がベストのようです。
データの集約方法
集約の仕方は以下の方法があります。
| 特徴量 | 意味 |
|---|---|
| mean | 平均的な実力 |
| median | 極端な凡走の影響除去 |
| min / best | 最高到達性能 |
| max | 最悪ケース |
| std | 成績のブレ* |
| IQR | 安定性指標 |
*std が小さい → 安定馬, std が大きい → ムラ馬
その他の特徴量
トレンド(成長・下降)
直近と古い成績の差分をとることで、その馬が成長中が逆に衰えているかがわかります。
例えば以下のような計算により導き出される特徴量です。
form_trend = last3_rank_mean - last6to10_rank_mean
勝ちきり能力(1着・連対率)
過去のレース数に対して1着もしくは3着以内に入賞した確率を求めることで、その馬の勝ち切る能力がわかります。
これもトレンドをとることができそう。
距離・馬場条件による能力
距離(同距離±200m)や馬場状態(芝/ダート/重馬場)といった条件に応じた成績を出すことも有効です。
有効どころかChatGPTはこの特徴量を強く推しています。
有効な特徴量がわかってきたところで、これらの値をhorse_resultテーブルから求めましょう。
今回テーブルの結合まで行いたかったのですが、長くなったので次回に。
次回はテーブルの結合と新しいテーブルを用いて予測精度がどのくらい上がるか検証したいですね。
