学習する(1:GPU/CPU自動切替)

学習

競馬予想で当てるためのAI競馬のため、まずは過去データを学習するプログラムを作成したいと考えます。

先ずは、初期設定や、データ読み込み、そして前処理について説明します。

「GPU/CPU自動切替でどこでも動く学習環境」

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

print("CUDA available:", torch.cuda.is_available())
print("Torch version:", torch.__version__)
if torch.cuda.is_available() and torch.cuda.device_count() > 0:
    print("GPU:", torch.cuda.get_device_name(0))
else:
    print("GPU: None")

最初に torch.cuda.is_available() を使って、CUDA(GPU)が使えるかを判定。
使えるなら GPU 名を表示し、以降のテンソル/モデルは device に合わせて配置します。これで同じスクリプトをどの環境でも動かせます。

ポイント:GPU があれば学習/推論が爆速。なければ自動で CPU にフォールバック。

「時系列の肝は“相対化”と“欠損/型の堅牢化”」

data = pd.read_csv(
    "horse_db.csv",
    encoding="utf-8-sig",
    index_col=False,
    dtype={"レースID":str,"騎手ID": int, "調教師ID": int, "馬主ID": str}
)

このコードは pandas の read_csv() 関数を用いて、競馬データベース horse_db.csv を DataFrame として読み込む処理です。

  • ファイル指定: "horse_db.csv" を入力ソースとする。
  • 文字コード: encoding="utf-8-sig" により、日本語列名やデータの文字化けを防止。
  • インデックス: index_col=False により、すべての列を通常のデータ列として読み込む。
  • データ型指定: dtype 引数で主要キーを明示的に型付けする。
    • レースID → 文字列型(str
    • 騎手ID調教師ID → 整数型(int
    • 馬主ID → 文字列型(str

これにより、ID カラムが誤って数値として扱われるのを防ぎ、データベースとしての整合性を保つことができる。

「効く派生特徴ベスト10:距離カテゴリ、斤量比、クロス特徴…」

print("✅ 過去データの前処理")
epoch_start1 = time.time()
sequence_length = 3
data["日付"] = pd.to_datetime(data["日付"])

# 日数差計算
data["日数差"] = data.groupby("馬ID")["日付"].diff().dt.days.fillna(0).astype(int)
data["日数差_norm"] = data["日数差"] / max(data["日数差"].max(), 1)

# 不要列削除
data.drop(columns=["レース名", "タイプ", "着差", "賞金", "馬主ID", "枠番", "左右", "クラス"], inplace=True)

# NaNの走破タイムを削除
data = data[data["走破タイム"].notna()]

# 数値変換(まとめて)
fill_values = {"着順": 0, "斤量": 55.0, "馬体重": 470.0, "上り": 0}
data = data.assign(**{col: pd.to_numeric(data[col], errors="coerce").fillna(val) for col, val in fill_values.items()})
data = data[data["着順"] > 0]

# 新しい特徴量
data["斤量_馬体重比"] = data["斤量"] / (data["馬体重"] + 1e-3)
data["性"] = data["性齢"].str[0]
data["年齢"] = data["性齢"].str[1:].astype(int)

# 年齢層(np.selectで高速化)
data["年齢層"] = np.select(
    [data["年齢"] <= 3, data["年齢"] <= 5],
    ["若駒", "壮年"],
    default="ベテラン"
)

# 距離カテゴリ(np.selectで高速化)
data["距離カテゴリ"] = np.select(
    [data["距離"] <= 1400, data["距離"] <= 1800, data["距離"] <= 2200],
    ["スプリント", "マイル", "中距離"],
    default="長距離"
)

# クロスカテゴリ(value_countsを1回ずつ)
def create_cross_feature(df, col1, col2, min_freq=30):
    cross = df[col1].astype(str) + "_" + df[col2].astype(str)
    freq = cross.value_counts()
    return np.where(cross.isin(freq[freq >= min_freq].index), cross, "other")

cross_features = {
    "距離Cx芝ダ": ("距離カテゴリ", "芝ダ"),
    "天候x馬場": ("天候", "馬場"),
    "競馬場x芝ダ": ("競馬場", "芝ダ"),
    "年齢層x芝ダ": ("年齢層", "芝ダ"),
    "性x距離カテゴリ": ("性", "距離カテゴリ")
}
for new_col, (c1, c2) in cross_features.items():
    data[new_col] = create_cross_feature(data, c1, c2)

# 脚質(正規表現で高速化)
data["脚質"] = data["通過"].str.extract(r"^(\d+)", expand=False).astype(float)
data["脚質"] = pd.cut(data["脚質"], bins=[0, 3, 6, 10, np.inf], labels=["逃げ", "先行", "差し", "追込"]).astype(str)
data["脚質"] = data["脚質"].fillna("不明")

# 好成績
data["好成績"] = (data["着順"] <= 3).astype(int)

このコードは、競馬データの 前処理と特徴量生成 を行うものです。主な処理内容は以下の通りです。

  • 日付と休養指数: 日付を日付型に変換し、同一馬ごとの前走からの日数差を算出して正規化。
  • データ整理: 不要な列を削除し、走破タイムが欠損している行を除外。着順・斤量・馬体重・上りは数値化と欠損補完を実施。
  • 派生特徴の作成:
    • 斤量_馬体重比で負荷指標を算出
    • 性齢から性別と年齢を抽出し、年齢層を若駒・壮年・ベテランに分類
    • 距離をスプリント・マイル・中距離・長距離にカテゴリ化
  • 交差特徴量: 距離カテゴリ×芝ダ天候×馬場 などの組合せ特徴を作成し、出現頻度が少ないものは「other」に統合。
  • 脚質の抽出: 通過欄の先頭順位を基に、逃げ・先行・差し・追込の4区分に分類。欠損は「不明」とする。
  • 成績指標: 3着以内を「好成績=1」とするバイナリ列を追加。

これにより、機械学習や統計解析に利用できる クリーンで説明力のあるデータセット が得られるようになります。

コメント

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