学習する(17:改良2 距離・斤量)

競馬予想アルゴリズム

🐎 距離・斤量の“相対化”を多視点化(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

距離・斤量まわり

  • 旧:距離_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/環境・条件の符号化が、モデルにしっかり活かされる設計に変わったことがうかがえます。

コメント

タイトルとURLをコピーしました