🐎 距離・斤量の“相対化”を多視点化(3・5・7走)
これまでは「距離=1800m」「斤量=55kg」といった絶対値をそのまま扱っていました。
しかし競馬の世界では “普段より長い距離” や “いつもより斤量が重い” ことこそが成績に直結します。そこで今回の改良では、距離・斤量を「相対化」し、さらに 3走・5走・7走という複数の窓幅 で特徴量を生成しました。
# -------------------------------------------------------------
# 距離・斤量の相対化(3/5/7 すべて生成してモデルに選ばせる)
# -------------------------------------------------------------
# 馬ID × 日付で整列
data = data.sort_values(["馬ID", "日付"]).reset_index(drop=True)
def add_rolling_features(data, group_key, col, windows=(3,5,7)):
"""
group_key: グループキー(例: "馬ID")
col : 対象列("距離" or "斤量")
windows : 生成する窓幅(3,5,7)
生成: {col}_ma{w}_馬, {col}_sd{w}_馬, {col}_diff_vs_馬ma{w}, {col}_z_vs_馬ma{w}
"""
g = data.groupby(group_key, sort=False)[col]
for w in windows:
ma = g.apply(lambda s: s.shift(1).rolling(w, min_periods=1).mean()).reset_index(level=0, drop=True)
sd = g.apply(lambda s: s.shift(1).rolling(w, min_periods=2).std()).reset_index(level=0, drop=True)
data[f"{col}_ma{w}_馬"] = ma.astype("float32")
data[f"{col}_sd{w}_馬"] = sd.fillna(0).astype("float32")
data[f"{col}_diff_vs_馬ma{w}"] = (data[col] - data[f"{col}_ma{w}_馬"]).astype("float32")
data[f"{col}_z_vs_馬ma{w}"] = (
data[f"{col}_diff_vs_馬ma{w}"] / data[f"{col}_sd{w}_馬"].replace(0, np.nan)
).astype("float32").fillna(0.0)
return data
for col in ["距離", "斤量"]:
data = add_rolling_features(data, group_key="馬ID", col=col, windows=(3,5,7))
# クラス(距離カテゴリ×芝ダ)基準の直近w走平均との差分(3/5/7)
data["クラスキー"] = data["距離カテゴリ"].astype(str) + "_" + data["芝ダ"].astype(str)
data = data.sort_values(["クラスキー", "日付"]).reset_index(drop=True)
g_class = data.groupby("クラスキー", sort=False)
for col in ["距離", "斤量"]:
for w in (3,5,7):
cls_ma = g_class[col].apply(lambda s: s.shift(1).rolling(w, min_periods=1).mean()) \
.reset_index(level=0, drop=True)
data[f"{col}_mean_クラス過去_ma{w}"] = cls_ma.astype("float32")
data[f"{col}_diff_vs_クラス_ma{w}"] = (data[col] - data[f"{col}_mean_クラス過去_ma{w}"]).astype("float32")
# クラスキーは to_numpy(float32) 前に削除(文字列残存防止)
data.drop(columns=["クラスキー"], inplace=True, errors="ignore")
👨💻 技術的アプローチ
1. 馬ごと基準(個体履歴)
- 各馬の過去レースを基準に移動平均・標準偏差を算出
- 例:
距離_ma3_馬
: 過去3走の平均距離距離_diff_vs_馬ma5
: 今回の距離が、直近5走平均からどれだけズレているか斤量_z_vs_馬ma7
: 今回の斤量が、過去7走の分布から見てどれくらい“異常値”か
これにより「この馬にとって普段より長い(短い)」「斤量が重い(軽い)」を数値化できます。
2. クラス基準(条件相場との比較)
- クラスキー = 距離カテゴリ × 芝/ダート
- その条件に属する過去出走馬の成績から平均値を算出
- 例:
距離_diff_vs_クラス_ma5
: 同じクラスの平均距離と比較してどれくらいズレているか
これで「この馬はクラス標準よりも厳しい条件を走らされている」という情報を追加できます。
3. 複数の窓幅(3/5/7走)
- 短期(3走) → 調子の変化や直近の傾向を捉える
- 中期(5走) → ある程度安定した基準を把握
- 長期(7走) → 馬の本来的な適性を反映
つまり「トレンド」「安定値」「適性」の3つを同時に入力し、モデルに自動で取捨選択させる構造にしました。
🏇 競馬予想ロジックとしての意義
- 馬ごと基準 → 「この馬は普段より長い距離を走るからリスクあり」
- クラス基準 → 「クラス標準より斤量が重い=見えないハンデを背負っている」
- 窓幅の多視点化 → 「短期的に調子を崩している馬」と「長期的に安定している馬」を同時に見抜ける
つまり、人間の予想家が自然に行っている
“普段と比べてどうか” × “条件相場と比べてどうか” を、AIにもしっかり学習させた形です。
評価
📊 Baseline NDCG@3: 0.9184
🚀 Numeric PFI: 69 features × 5 repeats
✅ done: 距離 ΔNDCG@3 = -0.0017 ± 0.0007 (223.2s)
✅ done: 馬番 ΔNDCG@3 = -0.0032 ± 0.0004 (226.5s)
✅ done: 斤量 ΔNDCG@3 = -0.0030 ± 0.0002 (223.3s)
✅ done: 馬体重 ΔNDCG@3 = -0.0029 ± 0.0003 (228.1s)
✅ done: 体重増減 ΔNDCG@3 = -0.0023 ± 0.0005 (224.3s)
✅ done: 日数差 ΔNDCG@3 = -0.0023 ± 0.0009 (227.3s)
✅ done: 日数差_norm ΔNDCG@3 = -0.0037 ± 0.0000 (226.8s)
✅ done: 斤量_馬体重比 ΔNDCG@3 = -0.0037 ± 0.0000 (224.5s)
✅ done: 距離_ma3_馬 ΔNDCG@3 = -0.0032 ± 0.0012 (226.1s)
✅ done: 距離_sd3_馬 ΔNDCG@3 = -0.0033 ± 0.0009 (223.3s)
✅ done: 距離_diff_vs_馬ma3 ΔNDCG@3 = -0.0039 ± 0.0012 (226.2s)
✅ done: 距離_z_vs_馬ma3 ΔNDCG@3 = -0.0039 ± 0.0004 (256.2s)
✅ done: 距離_ma5_馬 ΔNDCG@3 = -0.0035 ± 0.0016 (227.4s)
✅ done: 距離_sd5_馬 ΔNDCG@3 = -0.0020 ± 0.0007 (228.2s)
✅ done: 距離_diff_vs_馬ma5 ΔNDCG@3 = -0.0044 ± 0.0011 (224.6s)
✅ done: 距離_z_vs_馬ma5 ΔNDCG@3 = -0.0036 ± 0.0003 (227.0s)
✅ done: 距離_ma7_馬 ΔNDCG@3 = -0.0040 ± 0.0010 (228.3s)
✅ done: 距離_sd7_馬 ΔNDCG@3 = -0.0034 ± 0.0007 (228.8s)
✅ done: 距離_diff_vs_馬ma7 ΔNDCG@3 = -0.0025 ± 0.0006 (228.9s)
✅ done: 距離_z_vs_馬ma7 ΔNDCG@3 = -0.0023 ± 0.0003 (226.2s)
✅ done: 斤量_ma3_馬 ΔNDCG@3 = -0.0024 ± 0.0003 (232.1s)
✅ done: 斤量_sd3_馬 ΔNDCG@3 = -0.0023 ± 0.0002 (233.5s)
✅ done: 斤量_diff_vs_馬ma3 ΔNDCG@3 = -0.0026 ± 0.0002 (231.1s)
✅ done: 斤量_z_vs_馬ma3 ΔNDCG@3 = -0.0026 ± 0.0003 (236.2s)
✅ done: 斤量_ma5_馬 ΔNDCG@3 = -0.0027 ± 0.0004 (241.2s)
✅ done: 斤量_sd5_馬 ΔNDCG@3 = -0.0025 ± 0.0003 (239.3s)
✅ done: 斤量_diff_vs_馬ma5 ΔNDCG@3 = -0.0031 ± 0.0005 (240.7s)
✅ done: 斤量_z_vs_馬ma5 ΔNDCG@3 = -0.0030 ± 0.0004 (270.0s)
✅ done: 斤量_ma7_馬 ΔNDCG@3 = -0.0028 ± 0.0004 (244.2s)
✅ done: 斤量_sd7_馬 ΔNDCG@3 = -0.0021 ± 0.0002 (243.0s)
✅ done: 斤量_diff_vs_馬ma7 ΔNDCG@3 = -0.0030 ± 0.0005 (217.9s)
✅ done: 斤量_z_vs_馬ma7 ΔNDCG@3 = -0.0027 ± 0.0003 (218.8s)
✅ done: 距離_mean_クラス過去_ma3 ΔNDCG@3 = -0.0026 ± 0.0009 (220.2s)
✅ done: 距離_diff_vs_クラス_ma3 ΔNDCG@3 = -0.0020 ± 0.0010 (217.2s)
✅ done: 距離_mean_クラス過去_ma5 ΔNDCG@3 = -0.0038 ± 0.0011 (217.2s)
✅ done: 距離_diff_vs_クラス_ma5 ΔNDCG@3 = -0.0024 ± 0.0012 (216.8s)
✅ done: 距離_mean_クラス過去_ma7 ΔNDCG@3 = -0.0033 ± 0.0003 (220.4s)
✅ done: 距離_diff_vs_クラス_ma7 ΔNDCG@3 = -0.0030 ± 0.0005 (220.0s)
✅ done: 斤量_mean_クラス過去_ma3 ΔNDCG@3 = -0.0030 ± 0.0005 (223.7s)
✅ done: 斤量_diff_vs_クラス_ma3 ΔNDCG@3 = -0.0027 ± 0.0001 (221.2s)
✅ done: 斤量_mean_クラス過去_ma5 ΔNDCG@3 = -0.0025 ± 0.0004 (220.8s)
✅ done: 斤量_diff_vs_クラス_ma5 ΔNDCG@3 = -0.0022 ± 0.0003 (212.0s)
✅ done: 斤量_mean_クラス過去_ma7 ΔNDCG@3 = -0.0026 ± 0.0003 (226.2s)
✅ done: 斤量_diff_vs_クラス_ma7 ΔNDCG@3 = -0.0028 ± 0.0004 (262.2s)
✅ done: 馬体重_diff_prev ΔNDCG@3 = -0.0033 ± 0.0005 (221.7s)
✅ done: 馬体重_pct_prev ΔNDCG@3 = -0.0036 ± 0.0000 (221.4s)
✅ done: 馬体重_ma5_馬 ΔNDCG@3 = -0.0043 ± 0.0007 (220.8s)
✅ done: 馬体重_sd5_馬 ΔNDCG@3 = -0.0037 ± 0.0003 (220.3s)
✅ done: 馬体重_z_vs_馬ma5 ΔNDCG@3 = -0.0036 ± 0.0002 (222.0s)
✅ done: 馬体重_diff_prev_ma2 ΔNDCG@3 = -0.0041 ± 0.0008 (223.4s)
✅ done: 馬体重_diff_prev_ma3 ΔNDCG@3 = -0.0037 ± 0.0004 (224.0s)
✅ done: 馬体重_diff_prev_ma5 ΔNDCG@3 = -0.0036 ± 0.0005 (222.1s)
✅ done: 好成績 ΔNDCG@3 = -0.0029 ± 0.0004 (222.7s)
✅ done: 距離カテゴリフラグ ΔNDCG@3 = -0.0035 ± 0.0002 (221.0s)
✅ done: 脚質フラグ ΔNDCG@3 = -0.0036 ± 0.0001 (222.1s)
✅ done: 馬場フラグ ΔNDCG@3 = -0.0038 ± 0.0001 (221.5s)
✅ done: 芝ダフラグ ΔNDCG@3 = -0.0035 ± 0.0003 (223.1s)
✅ done: 出走数 ΔNDCG@3 = -0.0075 ± 0.0016 (228.3s)
✅ done: 勝利数 ΔNDCG@3 = -0.0074 ± 0.0006 (230.5s)
✅ done: 騎手勝率 ΔNDCG@3 = -0.0078 ± 0.0002 (250.6s)
✅ done: 過去走破速度 ΔNDCG@3 = -0.0084 ± 0.0002 (227.3s)
✅ done: 過去人気 ΔNDCG@3 = -0.0077 ± 0.0003 (231.5s)
✅ done: 過去着順 ΔNDCG@3 = -0.0072 ± 0.0004 (229.5s)
✅ done: 過去上り ΔNDCG@3 = -0.0069 ± 0.0004 (231.5s)
✅ done: 履歴長_norm ΔNDCG@3 = -0.0071 ± 0.0001 (227.9s)
✅ done: 騎手-馬コンビ勝率 ΔNDCG@3 = -0.0071 ± 0.0001 (228.5s)
✅ done: 相対斤量差 ΔNDCG@3 = -0.0074 ± 0.0004 (227.5s)
✅ done: 相対騎手勝率差 ΔNDCG@3 = -0.0071 ± 0.0001 (230.7s)
✅ done: 相対過去速度 ΔNDCG@3 = -0.0075 ± 0.0002 (228.6s)
🚀 Categorical PFI: 19 features × 5 repeats
✅ done: 競馬場 ΔNDCG@3 = -0.0074 ± 0.0002 (230.3s)
✅ done: 芝ダ ΔNDCG@3 = -0.0079 ± 0.0000 (229.4s)
✅ done: 馬場 ΔNDCG@3 = -0.0076 ± 0.0001 (228.5s)
✅ done: 性 ΔNDCG@3 = -0.0073 ± 0.0001 (230.4s)
✅ done: 年齢層 ΔNDCG@3 = -0.0074 ± 0.0003 (231.9s)
✅ done: 距離カテゴリ ΔNDCG@3 = -0.0076 ± 0.0001 (254.3s)
✅ done: 距離Cx芝ダ ΔNDCG@3 = -0.0077 ± 0.0003 (232.3s)
✅ done: 天候x馬場 ΔNDCG@3 = -0.0077 ± 0.0002 (230.2s)
✅ done: 競馬場x芝ダ ΔNDCG@3 = -0.0074 ± 0.0001 (228.1s)
✅ done: 年齢層x芝ダ ΔNDCG@3 = -0.0075 ± 0.0002 (228.6s)
✅ done: 性x距離カテゴリ ΔNDCG@3 = -0.0074 ± 0.0002 (229.8s)
✅ done: 血統キー ΔNDCG@3 = -0.0074 ± 0.0003 (228.4s)
✅ done: 血統得意距離 ΔNDCG@3 = -0.0070 ± 0.0003 (228.3s)
✅ done: 血統得意脚質 ΔNDCG@3 = -0.0074 ± 0.0001 (228.5s)
✅ done: 血統得意馬場 ΔNDCG@3 = -0.0077 ± 0.0001 (232.2s)
✅ done: 血統得意芝ダ ΔNDCG@3 = -0.0076 ± 0.0000 (230.4s)
✅ done: 脚質過去 ΔNDCG@3 = -0.0075 ± 0.0005 (232.5s)
✅ done: 騎手ID ΔNDCG@3 = -0.0075 ± 0.0002 (236.3s)
✅ done: 調教師ID ΔNDCG@3 = -0.0073 ± 0.0003 (231.8s)
一言でいうと
- NDCG@3のベースラインは 0.9213 → 0.9184 に微低下。
- しかし、PFIの“落ち幅”はほぼ全体で拡大。とくに**フォーム/実績系(過去走破速度・過去人気・過去着順・上り・出走数・勝利数・騎手勝率、相対系)**が一段と効く構図に。
- 距離系の相対重要度は低下(絶対値は小さく)し、近走・人馬実績系の比重が明確に上昇。
- 1本あたりの計算時間は約300秒 → 約220–250秒に短縮(おおむね25%前後高速化)。
Before/Afterの“顔ぶれ”の変化
トップどころの入れ替わり
- 以前(旧モデル)の最大ドロップは距離の移動統計(例:距離_ma5_馬 ≈ -0.0049)。
- 改良後はフォーム/実績系が軒並み -0.007〜-0.008台まで伸長。
- 例:過去走破速度 -0.0015 → -0.0084(約5.6倍)
過去人気 -0.0018 → -0.0077
騎手勝率 -0.0015 → -0.0078
出走数 -0.0022 → -0.0075
勝利数 -0.0004 → -0.0074
- 例:過去走破速度 -0.0015 → -0.0084(約5.6倍)
距離・斤量まわり
- 旧:距離_ma5_馬(-0.0049)、距離_sd5_馬(-0.0046)が“効く”代表格。
- 新:距離そのものは -0.0017 程度、移動統計も -0.003〜-0.004台で相対的に存在感が後退。
- 斤量系は全般に**-0.002〜-0.003台へとやや強化**はされたものの、主役はフォーム系に交代。
カテゴリ特徴の様相
- 旧:競馬場・年齢層・性・芝ダなどが -0.002前後。
- 新:ほぼ全面的に -0.007〜-0.008台へシフト。
- 競馬場 -0.0026 → -0.0074
- 芝ダ -0.0025 → -0.0079
- 騎手ID -0.0015 → -0.0075
- 調教師ID -0.0014 → -0.0073
→ ID/環境・条件の符号化が、モデルにしっかり活かされる設計に変わったことがうかがえます。
コメント