Pythonで競馬予測モデルを作る流れ
Pythonで競馬予測モデルを作る流れ
競馬データベースを構築したら、Pythonから直接データベースに接続して予測モデルを作れます。この記事では、PostgreSQLの競馬データを使ってLightGBMで予測モデルを構築するまでの全体の流れを紹介します。
全体の流れ
- データベースからデータを取得
- 特徴量を作る
- 学習データとテストデータに分割
- モデルを学習
- 予測して評価
1. データベースからデータを取得
psycopg2やSQLAlchemyを使ってPostgreSQLに接続し、pandasのDataFrameとしてデータを取得します。
import pandas as pd
from sqlalchemy import create_engine
engine = create_engine('postgresql://user:pass@localhost:5432/keiba')
query = """
SELECT u.race_code, u.ketto_toroku_bango, u.umaban, u.wakuban,
u.futan_juryo, u.bataiju, u.zogen_sa, u.tansho_odds,
u.kakutei_chakujun, u.soha_time, u.kohan_3f,
u.corner4_juni, u.kishu_code,
r.keibajo_code, r.kyori, r.track_code,
r.shiba_babajotai_code, r.kaisai_nen, r.kaisai_gappi
FROM umagoto_race_joho u
JOIN race_shosai r USING (race_code)
WHERE r.kaisai_nen >= '2020'
AND u.kakutei_chakujun BETWEEN '01' AND '18'
"""
df = pd.read_sql(query, engine)
2. 特徴量を作る
生データのままではモデルに入れられません。過去の成績を集約して「この馬がどれくらい強いか」を表す数値を作ります。
よく使う特徴量の例
# 着順を数値化
df['finishing_pos'] = df['kakutei_chakujun'].astype(int)
# 目的変数: 3着以内なら1
df['target'] = (df['finishing_pos'] <= 3).astype(int)
# 過去5走の平均着順(馬ごとに計算)
df = df.sort_values(['ketto_toroku_bango', 'race_code'])
df['avg_pos_5'] = df.groupby('ketto_toroku_bango')['finishing_pos'].transform(
lambda x: x.shift(1).rolling(5, min_periods=1).mean()
)
# 過去5走の上がり3Fの平均
df['avg_3f_5'] = df.groupby('ketto_toroku_bango')['kohan_3f'].transform(
lambda x: x.shift(1).rolling(5, min_periods=1).mean()
)
# 同コースでの複勝率
df['course_key'] = df['keibajo_code'] + '_' + df['kyori'].astype(str)
df['course_place_rate'] = df.groupby(['ketto_toroku_bango', 'course_key'])['target'].transform(
lambda x: x.shift(1).expanding().mean()
)
ポイントは shift(1) で「未来の情報を使わない」ようにすることです。これを忘れるとリーク(データ漏洩)が起き、実際の予想では使えないモデルになります。
特徴量の一覧例
| 特徴量 | 説明 |
|---|---|
| avg_pos_5 | 過去5走の平均着順 |
| avg_3f_5 | 過去5走の上がり3F平均 |
| best_3f | 過去の上がり3F最速値 |
| course_place_rate | 同コースでの複勝率 |
| jockey_win_rate | 騎手の勝率 |
| weight_diff | 馬体重の前走比 |
| odds | 単勝オッズ |
| draw | 枠番 |
| distance | 距離 |
| field_condition | 馬場状態 |
3. 学習データとテストデータに分割
競馬データは時系列なので、ランダム分割ではなく時間で区切るのが正しいやり方です。
# 2020〜2024年を学習、2025年をテスト
train = df[df['kaisai_nen'] <= '2024']
test = df[df['kaisai_nen'] == '2025']
feature_cols = ['avg_pos_5', 'avg_3f_5', 'course_place_rate',
'futan_juryo', 'bataiju', 'zogen_sa', 'tansho_odds',
'wakuban', 'kyori']
X_train = train[feature_cols]
y_train = train['target']
X_test = test[feature_cols]
y_test = test['target']
4. モデルを学習
LightGBMは競馬予測で最もよく使われるモデルの一つです。学習が速く、精度も高い。
import lightgbm as lgb
lgb_train = lgb.Dataset(X_train, y_train)
lgb_valid = lgb.Dataset(X_test, y_test, reference=lgb_train)
params = {
'objective': 'binary',
'metric': 'binary_logloss',
'learning_rate': 0.05,
'num_leaves': 31,
'feature_fraction': 0.8,
'bagging_fraction': 0.8,
'bagging_freq': 5,
'verbose': -1,
}
model = lgb.train(
params,
lgb_train,
num_boost_round=1000,
valid_sets=[lgb_valid],
callbacks=[lgb.early_stopping(50)],
)
5. 予測して評価
モデルが出力するのは「3着以内に入る確率」です。
test['pred_prob'] = model.predict(X_test)
# レースごとに予測確率で順位付け
test['pred_rank'] = test.groupby('race_code')['pred_prob'].rank(ascending=False)
評価指標
単純な正解率よりも、競馬予想では回収率が重要です。
# 予測1位の馬の単勝を買い続けた場合の回収率
top_picks = test[test['pred_rank'] == 1]
total_bets = len(top_picks)
wins = top_picks[top_picks['finishing_pos'] == 1]
returns = wins['tansho_odds'].sum() / 10 # オッズは10倍で格納されている
print(f'レース数: {total_bets}')
print(f'勝率: {len(wins) / total_bets * 100:.1f}%')
print(f'回収率: {returns / total_bets * 100:.1f}%')
回収率が100%を超えていればプラス収支です。
精度を上げるためのヒント
- 特徴量を増やす: 血統データ、調教データ、騎手との相性など、使えるデータは多い
- ハイパーパラメータの調整: Optunaなどの自動チューニングツールを活用
- 馬券種を工夫: 単勝だけでなく、複勝やワイドも含めて期待値を計算
- 対象レースを絞る: すべてのレースで勝つ必要はない。得意な条件だけに絞ることで回収率が上がる
まとめ
| ステップ | ポイント |
|---|---|
| データ取得 | pandasでDB直結、SQLで必要なデータだけ抽出 |
| 特徴量作成 | shift(1)でリーク防止、過去成績の集約がカギ |
| データ分割 | 時系列分割(ランダムはNG) |
| モデル学習 | LightGBMが定番、early_stoppingで過学習防止 |
| 評価 | 勝率よりも回収率で判断 |
データベースにデータさえあれば、上記のコードをそのまま動かして予測モデルを試せます。
JvLink To Importer でデータベースを構築して、予測モデル作りに挑戦してみてください。
この記事をシェア