学習する(16:改良1 相対化・履歴・血統)

学習

🏇 モデルが“文脈”を読むようになった話 — 相対化・履歴・血統の三本柱

これまでのモデルは「距離=1800m」「斤量=55kg」といった絶対値の並びを見ていました。
でも、予想の現場で大事なのは「この馬にとって今回は長いの?重いの?」「同条件の文脈ではどうなの?」という相対評価。
今回のアップデートは、その“当たり前”をモデルに教え込む取り組みです。

1-1. 距離・斤量を「馬の過去平均」および「クラス平均との差」からの相対差に変換

# --- 距離・斤量の相対化(馬ごと過去・クラス相当過去) ---
# 事前に作った「距離カテゴリ」「芝ダ」を使ってクラス相当キーを作る
data["クラスキー"] = data["距離カテゴリ"].astype(str) + "_" + data["芝ダ"].astype(str)

# ソート(リーク回避のため時系列順で処理)
data = data.sort_values(["馬ID", "日付"]).reset_index(drop=True)

g_horse = data.groupby("馬ID", sort=False)

# 馬ごとの過去移動平均・標準偏差(shiftで当該レースを除外)
def _rolling_mean(s, win=5):
    return s.shift(1).rolling(win, min_periods=1).mean()

def _rolling_std(s, win=5):
    return s.shift(1).rolling(win, min_periods=2).std()

for col in ["距離", "斤量"]:
    data[f"{col}_ma5_馬"] = g_horse[col].apply(_rolling_mean).reset_index(level=0, drop=True)
    data[f"{col}_sd5_馬"] = g_horse[col].apply(_rolling_std).reset_index(level=0, drop=True)
    # 差分 & Zスコア(馬の“基準”からどれだけズレているか)
    data[f"{col}_diff_vs_馬ma5"] = data[col] - data[f"{col}_ma5_馬"]
    data[f"{col}_z_vs_馬ma5"] = (
        data[f"{col}_diff_vs_馬ma5"] / data[f"{col}_sd5_馬"].replace(0, np.nan)
    )

# クラス相当(距離カテゴリ×芝ダ)ごとの“過去”平均(expanding+shift)
data = data.sort_values(["クラスキー", "日付"]).reset_index(drop=True)
g_class = data.groupby("クラスキー", sort=False)
for col in ["距離", "斤量"]:
    data[f"{col}_mean_クラス過去"] = (
        g_class[col].apply(lambda s: s.shift(1).expanding().mean())
              .reset_index(level=0, drop=True)
    )
    data[f"{col}_diff_vs_クラス"] = data[col] - data[f"{col}_mean_クラス過去"]
# クラスキーはもう不要なので削除(後段で to_numpy(float32) するために必須)
data.drop(columns=["クラスキー"], inplace=True, errors="ignore")

# 欠損の軽処理(必要に応じて方針変更可)
for c in [
    "距離_ma5_馬","距離_sd5_馬","距離_diff_vs_馬ma5","距離_z_vs_馬ma5","距離_mean_クラス過去","距離_diff_vs_クラス",
    "斤量_ma5_馬","斤量_sd5_馬","斤量_diff_vs_馬ma5","斤量_z_vs_馬ma5","斤量_mean_クラス過去","斤量_diff_vs_クラス",
]:
    if c in data.columns:
        data[c] = pd.to_numeric(data[c], errors="coerce").astype("float32")
        data[c] = data[c].fillna(0.0)

1-2. 馬体重:正規化+変化量を強調(差分/%変化/Z化/移動統計)

# --- 馬体重の正規化+変化量強調 ---
# 時系列順に整列(安全のため再度)
data = data.sort_values(["馬ID", "日付"]).reset_index(drop=True)
g_horse = data.groupby("馬ID", sort=False)

# 前走からの差分/%変化(リーク回避でshift)
prev_bw = g_horse["馬体重"].shift(1)
data["馬体重_diff_prev"] = (data["馬体重"] - prev_bw).astype("float32")
data["馬体重_pct_prev"]  = (data["馬体重_diff_prev"] / prev_bw.replace(0, np.nan)).astype("float32")

# 馬ごとの過去移動平均・標準偏差(過去のみ)
bw_ma = g_horse["馬体重"].apply(lambda s: s.shift(1).rolling(5, min_periods=1).mean()).reset_index(level=0, drop=True)
bw_sd = g_horse["馬体重"].apply(lambda s: s.shift(1).rolling(5, min_periods=2).std()).reset_index(level=0, drop=True)

data["馬体重_ma5_馬"] = bw_ma.astype("float32")
data["馬体重_sd5_馬"] = bw_sd.fillna(0).astype("float32")

# Zスコア(現在の馬体重が過去水準からどれほど外れているか)
data["馬体重_z_vs_馬ma5"] = (
    (data["馬体重"] - data["馬体重_ma5_馬"]) / data["馬体重_sd5_馬"].replace(0, np.nan)
).astype("float32").fillna(0.0)

# 直近k走の体重変化の移動平均(変化の継続性を表現)
for k in [2, 3, 5]:
    diff_ma = g_horse["馬体重_diff_prev"].apply(lambda s: s.rolling(k, min_periods=1).mean()) \
                                           .reset_index(level=0, drop=True)
    data[f"馬体重_diff_prev_ma{k}"] = diff_ma.astype("float32").fillna(0.0)

# 欠損の軽処理
for c in ["馬体重_diff_prev","馬体重_pct_prev","馬体重_ma5_馬","馬体重_sd5_馬","馬体重_z_vs_馬ma5",
          "馬体重_diff_prev_ma2","馬体重_diff_prev_ma3","馬体重_diff_prev_ma5"]:
    if c in data.columns:
        data[c] = pd.to_numeric(data[c], errors="coerce").astype("float32").fillna(0.0)

2. 血統マージにおいて、距離→距離カテゴリ、へ変更

血統マージにおいて、距離で分類していましたが、距離だと分類数が非常に多く、細分化されてしまい学習が不安定になるため、距離カテゴリに変更しました。


# -------------------------------------------------------------
# 3. 血統データマージ
# -------------------------------------------------------------
print("✅ 血統データマージ")
bloodline_file = "bloodline_id_data.csv"
final_df = pd.read_csv(bloodline_file, encoding="utf-8-sig").drop_duplicates(subset='horse_id')

# 血統データをマージ
data = (
    data.merge(
        final_df[['horse_id', 'father_id', 'mother_id']],
        left_on='馬ID',
        right_on='horse_id',
        how='left'
    )
    .drop(columns='horse_id')
)

data['血統キー'] = data['father_id'].astype(str) + '_' + data['mother_id'].astype(str)

data = data.sort_values('日付').reset_index(drop=True)

for col in ['距離カテゴリ', '脚質', '馬場', '芝ダ']:
    data[f'{col}フラグ'] = data['好成績'] * 1

for c in ['距離カテゴリ','脚質','馬場','芝ダ','好成績']:
    assert c in data.columns, f"{c} 列がありません"
for c in ['距離カテゴリフラグ','脚質フラグ','馬場フラグ','芝ダフラグ']:
    data[c] = pd.to_numeric(data[c], errors='coerce').fillna(0).astype('int32')

def cumulative_feature(df, key_col, date_col, category_col, flag_col, result_col):
    # ピボットテーブル
    pivot_df = (
        df.pivot_table(
            index=[key_col, date_col],
            columns=category_col,
            values=flag_col,
            aggfunc='sum',
            fill_value=0
        )
    )
    # 累積和(数値列のみ)
    pivot_df = pivot_df.groupby(level=0).cumsum()
    # 血統得意カテゴリの抽出
    pivot_df[result_col] = pivot_df.idxmax(axis=1)
    # indexを戻す
    pivot_df = pivot_df.reset_index()[[key_col, date_col, result_col]]
    return pivot_df


# 3種類まとめて集計
距離集計 = cumulative_feature(data, '血統キー', '日付', '距離カテゴリ', '距離カテゴリフラグ', '血統得意距離')
脚質集計 = cumulative_feature(data, '血統キー', '日付', '脚質', '脚質フラグ', '血統得意脚質')
馬場集計 = cumulative_feature(data, '血統キー', '日付', '馬場', '馬場フラグ', '血統得意馬場')
芝ダ集計 = cumulative_feature(data, '血統キー', '日付', '芝ダ', '芝ダフラグ', '血統得意芝ダ')

# まとめてマージ(1回だけ)
for df_merge in [距離集計, 脚質集計, 馬場集計, 芝ダ集計]:
    data = data.merge(df_merge, on=['血統キー', '日付'], how='left')

# NaN を "不明" で補完(列をまとめて)
fill_cols = ['血統得意距離', '血統得意脚質', '血統得意馬場', '血統得意芝ダ']
data[fill_cols] = data[fill_cols].fillna("不明")

🏇 今回の改善ポイントまとめと結果

1. 距離・斤量の「相対化」

これまでは「距離=1800m」「斤量=55kg」といった絶対値だけをモデルに入れていました。
しかし実際には「その馬にとって普段より長いか?」「いつもより斤量が重いか?」が結果に直結します。

そこで導入したのが、

  • 馬ごとの過去5走平均とのズレ(距離_ma5_馬, 斤量_ma5_馬 など)
  • 同クラス(距離カテゴリ×芝/ダ)における過去平均とのズレ(距離_diff_vs_クラス, 斤量_diff_vs_クラス

という相対指標です。

PFIの結果では特に 距離_ma5_馬距離_sd5_馬NDCG@3 を -0.0049, -0.0046 低下させる大きな寄与を見せ、
「普段からのズレ」がモデルにとって強い手掛かりになっていることがわかりました。


2. 馬体重の履歴と変化量

馬体重も「絶対値」ではなく、「変化」に注目するように改良しました。

  • 馬体重_diff_prev(前走との差分)
  • 馬体重_pct_prev(前走比の割合)
  • 馬体重_z_vs_馬ma5(過去平均からのZスコア)
  • 馬体重_diff_prev_ma3 など(直近数走の変化傾向)

これらは -0.002〜-0.0026 程度の寄与を示し、
仕上がり具合や調子の変化をモデルが学習できるようになったと考えられます。


3. 血統 × 得意条件の追加(芝/ダ)

今回のアップデートの目玉が 血統得意芝ダ の導入です。

  • 父母の組み合わせ(血統キー)ごとに、
  • その血統が芝で好成績か、ダートで好成績かを集計し、
  • 「芝得意」「ダート得意」としてカテゴリ化。

結果は ΔNDCG@3 = -0.0019
小さい数値に見えますが、これは「血統が芝に強い/ダートで安定する」といった競馬ファンの経験則を
モデルが実際に根拠として取り込めていることを意味します。

既存の「血統得意距離」「血統得意馬場」「血統得意脚質」と並び、
血統由来の得意条件を多面的に表現できるようになりました。


4. 騎手成績とレース内相対評価

騎手に関しては、累積の成績を特徴量化しました。

  • 出走数, 勝利数, 騎手勝率
  • 騎手-馬コンビ勝率

さらにレースごとに、相対的な立ち位置を示す指標を追加。

  • 相対斤量差(斤量 − レース平均斤量)
  • 相対騎手勝率差(騎手勝率 − レース平均騎手勝率)
  • 相対過去速度(過去走破速度 − レース平均過去走破速度)

これらは -0.002〜-0.0025 程度の寄与を見せ、
「同じレース内で有利か不利か」を補正する役割を果たしました。


📊 学習結果のまとめ

🚀 Training started...
Epoch 1: total=5133.7886 | top1_prob=0.692 | top1_acc=0.672 | ndcg3=0.928 | ⏱️ 421.90s
[Epoch 0] LR[0]: 0.000500
Epoch 2: total=3793.8497 | top1_prob=0.692 | top1_acc=0.700 | ndcg3=0.930 | ⏱️ 401.09s
[Epoch 1] LR[0]: 0.000500
Epoch 3: total=3939.8932 | top1_prob=0.713 | top1_acc=0.750 | ndcg3=0.949 | ⏱️ 385.94s
⛔ Early stopping: 改善停止(best=0.9447)
✅ transformer_model 保存済み
✅ final_feature_names_seq 保存済み

📊 Baseline NDCG@3: 0.9213

🚀 Numeric PFI: 45 features × 5 repeats
✅ done: 距離  ΔNDCG@3 = -0.0023 ± 0.0007  (308.4s)
✅ done: 馬番  ΔNDCG@3 = -0.0021 ± 0.0002  (303.7s)
✅ done: 斤量  ΔNDCG@3 = -0.0023 ± 0.0005  (285.4s)
✅ done: 馬体重  ΔNDCG@3 = -0.0021 ± 0.0005  (259.2s)
✅ done: 体重増減  ΔNDCG@3 = -0.0018 ± 0.0005  (297.6s)
✅ done: 日数差  ΔNDCG@3 = -0.0032 ± 0.0008  (309.8s)
✅ done: 日数差_norm  ΔNDCG@3 = -0.0028 ± 0.0001  (312.0s)
✅ done: 斤量_馬体重比  ΔNDCG@3 = -0.0031 ± 0.0001  (322.4s)
✅ done: 距離_ma5_馬  ΔNDCG@3 = -0.0049 ± 0.0020  (305.7s)
✅ done: 距離_sd5_馬  ΔNDCG@3 = -0.0046 ± 0.0009  (287.6s)
✅ done: 距離_diff_vs_馬ma5  ΔNDCG@3 = -0.0029 ± 0.0012  (287.7s)
✅ done: 距離_z_vs_馬ma5  ΔNDCG@3 = -0.0016 ± 0.0002  (283.3s)
✅ done: 斤量_ma5_馬  ΔNDCG@3 = -0.0011 ± 0.0005  (288.7s)
✅ done: 斤量_sd5_馬  ΔNDCG@3 = -0.0009 ± 0.0003  (284.6s)
✅ done: 斤量_diff_vs_馬ma5  ΔNDCG@3 = -0.0008 ± 0.0003  (293.1s)
✅ done: 斤量_z_vs_馬ma5  ΔNDCG@3 = -0.0013 ± 0.0002  (283.1s)
✅ done: 距離_mean_クラス過去  ΔNDCG@3 = -0.0009 ± 0.0007  (291.9s)
✅ done: 距離_diff_vs_クラス  ΔNDCG@3 = -0.0022 ± 0.0007  (292.9s)
✅ done: 斤量_mean_クラス過去  ΔNDCG@3 = -0.0020 ± 0.0002  (287.0s)
✅ done: 斤量_diff_vs_クラス  ΔNDCG@3 = -0.0025 ± 0.0002  (293.2s)
✅ done: 馬体重_diff_prev  ΔNDCG@3 = -0.0024 ± 0.0003  (327.0s)
✅ done: 馬体重_pct_prev  ΔNDCG@3 = -0.0021 ± 0.0000  (282.6s)
✅ done: 馬体重_ma5_馬  ΔNDCG@3 = -0.0029 ± 0.0008  (287.9s)
✅ done: 馬体重_sd5_馬  ΔNDCG@3 = -0.0028 ± 0.0003  (287.1s)
✅ done: 馬体重_z_vs_馬ma5  ΔNDCG@3 = -0.0026 ± 0.0004  (290.4s)
✅ done: 馬体重_diff_prev_ma2  ΔNDCG@3 = -0.0025 ± 0.0001  (298.5s)
✅ done: 馬体重_diff_prev_ma3  ΔNDCG@3 = -0.0022 ± 0.0003  (313.8s)
✅ done: 馬体重_diff_prev_ma5  ΔNDCG@3 = -0.0026 ± 0.0004  (305.2s)
✅ done: 好成績  ΔNDCG@3 = -0.0026 ± 0.0002  (306.7s)
✅ done: 距離カテゴリフラグ  ΔNDCG@3 = -0.0026 ± 0.0002  (314.8s)
✅ done: 脚質フラグ  ΔNDCG@3 = -0.0025 ± 0.0002  (316.3s)
✅ done: 馬場フラグ  ΔNDCG@3 = -0.0027 ± 0.0002  (322.0s)
✅ done: 芝ダフラグ  ΔNDCG@3 = -0.0022 ± 0.0003  (346.4s)
✅ done: 出走数  ΔNDCG@3 = -0.0022 ± 0.0018  (320.0s)
✅ done: 勝利数  ΔNDCG@3 = -0.0004 ± 0.0007  (308.7s)
✅ done: 騎手勝率  ΔNDCG@3 = -0.0015 ± 0.0001  (323.0s)
✅ done: 過去走破速度  ΔNDCG@3 = -0.0015 ± 0.0004  (313.3s)
✅ done: 過去人気  ΔNDCG@3 = -0.0018 ± 0.0004  (314.7s)
✅ done: 過去着順  ΔNDCG@3 = -0.0016 ± 0.0006  (294.3s)
✅ done: 過去上り  ΔNDCG@3 = -0.0018 ± 0.0003  (254.3s)
✅ done: 履歴長_norm  ΔNDCG@3 = -0.0022 ± 0.0002  (297.6s)
✅ done: 騎手-馬コンビ勝率  ΔNDCG@3 = -0.0023 ± 0.0001  (321.2s)
✅ done: 相対斤量差  ΔNDCG@3 = -0.0020 ± 0.0003  (314.2s)
✅ done: 相対騎手勝率差  ΔNDCG@3 = -0.0025 ± 0.0001  (345.0s)
✅ done: 相対過去速度  ΔNDCG@3 = -0.0024 ± 0.0004  (314.6s)

🚀 Categorical PFI: 19 features × 5 repeats
✅ done: 競馬場  ΔNDCG@3 = -0.0026 ± 0.0002  (321.6s)
✅ done: 芝ダ  ΔNDCG@3 = -0.0025 ± 0.0000  (306.1s)
✅ done: 馬場  ΔNDCG@3 = -0.0024 ± 0.0003  (310.7s)
✅ done: 性  ΔNDCG@3 = -0.0025 ± 0.0004  (318.6s)
✅ done: 年齢層  ΔNDCG@3 = -0.0026 ± 0.0002  (314.4s)
✅ done: 距離カテゴリ  ΔNDCG@3 = -0.0024 ± 0.0002  (313.8s)
✅ done: 距離Cx芝ダ  ΔNDCG@3 = -0.0026 ± 0.0002  (311.8s)
✅ done: 天候x馬場  ΔNDCG@3 = -0.0018 ± 0.0002  (313.3s)
✅ done: 競馬場x芝ダ  ΔNDCG@3 = -0.0018 ± 0.0003  (311.9s)
✅ done: 年齢層x芝ダ  ΔNDCG@3 = -0.0019 ± 0.0003  (315.7s)
✅ done: 性x距離カテゴリ  ΔNDCG@3 = -0.0025 ± 0.0004  (335.6s)
✅ done: 血統キー  ΔNDCG@3 = -0.0022 ± 0.0003  (318.3s)
✅ done: 血統得意距離  ΔNDCG@3 = -0.0020 ± 0.0001  (318.0s)
✅ done: 血統得意脚質  ΔNDCG@3 = -0.0023 ± 0.0002  (318.9s)
✅ done: 血統得意馬場  ΔNDCG@3 = -0.0021 ± 0.0002  (312.3s)
✅ done: 血統得意芝ダ  ΔNDCG@3 = -0.0019 ± 0.0000  (313.7s)
✅ done: 脚質過去  ΔNDCG@3 = -0.0019 ± 0.0002  (313.8s)
✅ done: 騎手ID  ΔNDCG@3 = -0.0015 ± 0.0006  (316.7s)
✅ done: 調教師ID  ΔNDCG@3 = -0.0014 ± 0.0005  (314.6s)

📋 数値特徴量の影響(NDCG@3 低下量: 平均±SD):
  勝利数                  drop = -0.0004 ± 0.0007 ⬇️
  斤量_diff_vs_馬ma5      drop = -0.0008 ± 0.0003 ⬇️
  距離_mean_クラス過去        drop = -0.0009 ± 0.0007 ⬇️
  斤量_sd5_馬             drop = -0.0009 ± 0.0003 ⬇️
  斤量_ma5_馬             drop = -0.0011 ± 0.0005 ⬇️
  斤量_z_vs_馬ma5         drop = -0.0013 ± 0.0002 ⬇️
  騎手勝率                 drop = -0.0015 ± 0.0001 ⬇️
  過去走破速度               drop = -0.0015 ± 0.0004 ⬇️
  距離_z_vs_馬ma5         drop = -0.0016 ± 0.0002 ⬇️
  過去着順                 drop = -0.0016 ± 0.0006 ⬇️
  過去人気                 drop = -0.0018 ± 0.0004 ⬇️
  過去上り                 drop = -0.0018 ± 0.0003 ⬇️
  体重増減                 drop = -0.0018 ± 0.0005 ⬇️
  相対斤量差                drop = -0.0020 ± 0.0003 ⬇️
  斤量_mean_クラス過去        drop = -0.0020 ± 0.0002 ⬇️
  馬体重                  drop = -0.0021 ± 0.0005 ⬇️
  馬番                   drop = -0.0021 ± 0.0002 ⬇️
  馬体重_pct_prev         drop = -0.0021 ± 0.0000 ⬇️
  馬体重_diff_prev_ma3    drop = -0.0022 ± 0.0003 ⬇️
  出走数                  drop = -0.0022 ± 0.0018 ⬇️
  芝ダフラグ                drop = -0.0022 ± 0.0003 ⬇️
  履歴長_norm             drop = -0.0022 ± 0.0002 ⬇️
  距離_diff_vs_クラス       drop = -0.0022 ± 0.0007 ⬇️
  騎手-馬コンビ勝率            drop = -0.0023 ± 0.0001 ⬇️
  斤量                   drop = -0.0023 ± 0.0005 ⬇️
  距離                   drop = -0.0023 ± 0.0007 ⬇️
  相対過去速度               drop = -0.0024 ± 0.0004 ⬇️
  馬体重_diff_prev        drop = -0.0024 ± 0.0003 ⬇️
  脚質フラグ                drop = -0.0025 ± 0.0002 ⬇️
  斤量_diff_vs_クラス       drop = -0.0025 ± 0.0002 ⬇️
  相対騎手勝率差              drop = -0.0025 ± 0.0001 ⬇️
  馬体重_diff_prev_ma2    drop = -0.0025 ± 0.0001 ⬇️
  馬体重_z_vs_馬ma5        drop = -0.0026 ± 0.0004 ⬇️
  馬体重_diff_prev_ma5    drop = -0.0026 ± 0.0004 ⬇️
  距離カテゴリフラグ            drop = -0.0026 ± 0.0002 ⬇️
  好成績                  drop = -0.0026 ± 0.0002 ⬇️
  馬場フラグ                drop = -0.0027 ± 0.0002 ⬇️
  日数差_norm             drop = -0.0028 ± 0.0001 ⬇️
  馬体重_sd5_馬            drop = -0.0028 ± 0.0003 ⬇️
  距離_diff_vs_馬ma5      drop = -0.0029 ± 0.0012 ⬇️
  馬体重_ma5_馬            drop = -0.0029 ± 0.0008 ⬇️
  斤量_馬体重比              drop = -0.0031 ± 0.0001 ⬇️
  日数差                  drop = -0.0032 ± 0.0008 ⬇️
  距離_sd5_馬             drop = -0.0046 ± 0.0009 ⬇️
  距離_ma5_馬             drop = -0.0049 ± 0.0020 ⬇️

📋 カテゴリ特徴量の影響(NDCG@3 低下量: 平均±SD):
  調教師ID                drop = -0.0014 ± 0.0005 ⬇️
  騎手ID                 drop = -0.0015 ± 0.0006 ⬇️
  競馬場x芝ダ               drop = -0.0018 ± 0.0003 ⬇️
  天候x馬場                drop = -0.0018 ± 0.0002 ⬇️
  脚質過去                 drop = -0.0019 ± 0.0002 ⬇️
  血統得意芝ダ               drop = -0.0019 ± 0.0000 ⬇️
  年齢層x芝ダ               drop = -0.0019 ± 0.0003 ⬇️
  血統得意距離               drop = -0.0020 ± 0.0001 ⬇️
  血統得意馬場               drop = -0.0021 ± 0.0002 ⬇️
  血統キー                 drop = -0.0022 ± 0.0003 ⬇️
  血統得意脚質               drop = -0.0023 ± 0.0002 ⬇️
  距離カテゴリ               drop = -0.0024 ± 0.0002 ⬇️
  馬場                   drop = -0.0024 ± 0.0003 ⬇️
  性                    drop = -0.0025 ± 0.0004 ⬇️
  芝ダ                   drop = -0.0025 ± 0.0000 ⬇️
  性x距離カテゴリ             drop = -0.0025 ± 0.0004 ⬇️
  距離Cx芝ダ               drop = -0.0026 ± 0.0002 ⬇️
  年齢層                  drop = -0.0026 ± 0.0002 ⬇️
  競馬場                  drop = -0.0026 ± 0.0002 ⬇️

✅ PFI (NDCG@3 × 5回平均, レース内S軸シャッフル, 馬ID除外) 完了
  • ベースライン NDCG@3: 0.9201
  • 改良後 NDCG@3: 0.9213

数字上の改善は小幅ですが、
特徴量の寄与度を見れば「モデルが人間の予想に近い判断をする方向へ進化した」ことがわかります。

特に効いたのは、

  • 距離_ma5_馬, 距離_sd5_馬(普段からの距離ズレ)
  • 馬体重 の変化量指標
  • 血統得意芝ダ

一方で、勝利数や単純な平均系の特徴量はほぼ効いていませんでした。


今回のモデルは・・・・・

今回の改良でモデルは、

  • 「普段より距離が長い/短い」
  • 「馬体重が増えていて調子が良さそう」
  • 「この血統は芝に強い」

といった、ファンが自然に考える文脈を理解できるようになりました。

数字の改善は小さくても、予想ロジックの納得感が増したことが最大の収穫です。

⚠️ 見えてきた課題と今後の対応

1. 改善幅が小さい

NDCG@3 は 0.9201 → 0.9213 とわずかな改善に留まりました。
特徴量の寄与度を分析すると「効いているもの」と「効いていないもの」がはっきり分かれており、
まだ無駄な特徴が多く含まれている可能性があります。

👉 対応策

  • PFI や SHAP を活用して「効かない特徴」を削る
  • 正則化を強めて過学習を防ぐ
  • シンプルな特徴に絞り込んで再学習

2. 血統特徴の拡張余地

「血統得意芝ダ」は効果を示しましたが、距離や馬場適性まではまだ十分に表現できていません。

👉 対応策

  • 「距離カテゴリ × 血統」の得意条件をさらに強化
  • 馬場状態(良/稍重/不良)ごとの血統適性を導入
  • 祖父母や兄弟馬の成績を含めた“血統クラスタリング”の検討

3. 時系列窓幅のチューニング

移動平均や直近変化量を「5走」で固定しましたが、
馬によっては 直近3走が重要なタイプや、10走以上の安定性を重視すべきタイプも存在します。

👉 対応策

  • 窓幅(3, 5, 7走など)の最適化
  • Attention で「どの過去レースが効いているか」を学習させる

4. レース当日の情報不足

今回は「過去成績ベース」の特徴量に集中しましたが、
実際の予想では 当日の馬場状態や直前オッズ が非常に重要です。

👉 対応策

  • 当日のオッズ・馬体重発表・馬場発表を追加
  • 直前の調教タイムやコメントを取り込む

5. 学習プロセスの改善

Early Stopping がすぐに効いてしまい、学習が浅い可能性も見られました。

👉 対応策

  • 学習率スケジュールの見直し(Cosine Annealing, Warmup など)
  • Dropout や Weight Decay の調整
  • データ拡張(シャッフルやブートストラップ)

📌 まとめ

今回の改良でモデルは 「人間の予想に近い文脈」 を理解する方向に進みました。
ただし、改善幅はまだ小さく、

  • 特徴量の取捨選択
  • 血統情報のさらなる拡張
  • 当日情報の導入

といった課題が浮かび上がっています。

数字の改善よりも「納得できる予想ロジック」が得られたのは大きな収穫であり、
今後はここに “精度の伸びしろ” を乗せていくのが次の一手です。 🚀

コメント

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