2025/04/18

TensorFlowのモデルの訓練

先日書いたPythonでTensorFlow使ってモデルの訓練したってやつは、まあ、さらだでもやった順位予測の一番シンプルなファクター(特徴量)数の31個だったので、まあ、Epochsを50程度で本当にさらっと学習させられるものでした。

でまあ、TensorFlowの基本的な流れは出来たのでCSVデータもRaceJuniFullCKと同等なものを出力させてファクター数も230個と格段に増やしました。

# 入力データ(特徴量)
X = data[[
    "JouCD", "KaisaiTuki", "KaisaiKai", "KaisaiNichi",
    "TrackCD", "Course", "Kyori", "Tousu", "Tenko", "Baba",
    "KyosouShubetu", "KyosouJouken", "JuuryoShubetu", "StandardDeviation",
    "Uma1Barei", "Uma1Sex", "Uma1Blinkers", "Uma1Kinryo",
    "Uma1Kyakusitu1", "Uma1Kyakusitu2", "Uma1Kyakusitu3", "Uma1Kyakusitu4",
    "Uma1Kishu", "Uma1Chokyosi", "Uma1Souha", "Uma1DeviationValues",
    "Uma2Barei", "Uma2Sex", "Uma2Blinkers", "Uma2Kinryo",
    "Uma2Kyakusitu1", "Uma2Kyakusitu2", "Uma2Kyakusitu3", "Uma2Kyakusitu4",
    "Uma2Kishu", "Uma2Chokyosi", "Uma2Souha", "Uma2DeviationValues",
    "Uma3Barei", "Uma3Sex", "Uma3Blinkers", "Uma3Kinryo",
    "Uma3Kyakusitu1", "Uma3Kyakusitu2", "Uma3Kyakusitu3", "Uma3Kyakusitu4",
    "Uma3Kishu", "Uma3Chokyosi", "Uma3Souha", "Uma3DeviationValues",
    "Uma4Barei", "Uma4Sex", "Uma4Blinkers", "Uma4Kinryo",
    "Uma4Kyakusitu1", "Uma4Kyakusitu2", "Uma4Kyakusitu3", "Uma4Kyakusitu4",
    "Uma4Kishu", "Uma4Chokyosi", "Uma4Souha", "Uma4DeviationValues",
    "Uma5Barei", "Uma5Sex", "Uma5Blinkers", "Uma5Kinryo",
    "Uma5Kyakusitu1", "Uma5Kyakusitu2", "Uma5Kyakusitu3", "Uma5Kyakusitu4",
    "Uma5Kishu", "Uma5Chokyosi", "Uma5Souha", "Uma5DeviationValues",
    "Uma6Barei", "Uma6Sex", "Uma6Blinkers", "Uma6Kinryo",
    "Uma6Kyakusitu1", "Uma6Kyakusitu2", "Uma6Kyakusitu3", "Uma6Kyakusitu4",
    "Uma6Kishu", "Uma6Chokyosi", "Uma6Souha", "Uma6DeviationValues",
    "Uma7Barei", "Uma7Sex", "Uma7Blinkers", "Uma7Kinryo",
    "Uma7Kyakusitu1", "Uma7Kyakusitu2", "Uma7Kyakusitu3", "Uma7Kyakusitu4",
    "Uma7Kishu", "Uma7Chokyosi", "Uma7Souha", "Uma7DeviationValues",
    "Uma8Barei", "Uma8Sex", "Uma8Blinkers", "Uma8Kinryo",
    "Uma8Kyakusitu1", "Uma8Kyakusitu2", "Uma8Kyakusitu3", "Uma8Kyakusitu4",
    "Uma8Kishu", "Uma8Chokyosi", "Uma8Souha", "Uma8DeviationValues",
    "Uma9Barei", "Uma9Sex", "Uma9Blinkers", "Uma9Kinryo",
    "Uma9Kyakusitu1", "Uma9Kyakusitu2", "Uma9Kyakusitu3", "Uma9Kyakusitu4",
    "Uma9Kishu", "Uma9Chokyosi", "Uma9Souha", "Uma9DeviationValues",
    "Uma10Barei", "Uma10Sex", "Uma10Blinkers", "Uma10Kinryo",
    "Uma10Kyakusitu1", "Uma10Kyakusitu2", "Uma10Kyakusitu3", "Uma10Kyakusitu4",
    "Uma10Kishu", "Uma10Chokyosi", "Uma10Souha", "Uma10DeviationValues",
    "Uma11Barei", "Uma11Sex", "Uma11Blinkers", "Uma11Kinryo",
    "Uma11Kyakusitu1", "Uma11Kyakusitu2", "Uma11Kyakusitu3", "Uma11Kyakusitu4",
    "Uma11Kishu", "Uma11Chokyosi", "Uma11Souha", "Uma11DeviationValues",
    "Uma12Barei", "Uma12Sex", "Uma12Blinkers", "Uma12Kinryo",
    "Uma12Kyakusitu1", "Uma12Kyakusitu2", "Uma12Kyakusitu3", "Uma12Kyakusitu4",
    "Uma12Kishu", "Uma12Chokyosi", "Uma12Souha", "Uma12DeviationValues",
    "Uma13Barei", "Uma13Sex", "Uma13Blinkers", "Uma13Kinryo",
    "Uma13Kyakusitu1", "Uma13Kyakusitu2", "Uma13Kyakusitu3", "Uma13Kyakusitu4",
    "Uma13Kishu", "Uma13Chokyosi", "Uma13Souha", "Uma13DeviationValues",
    "Uma14Barei", "Uma14Sex", "Uma14Blinkers", "Uma14Kinryo",
    "Uma14Kyakusitu1", "Uma14Kyakusitu2", "Uma14Kyakusitu3", "Uma14Kyakusitu4",
    "Uma14Kishu", "Uma14Chokyosi", "Uma14Souha", "Uma14DeviationValues",
    "Uma15Barei", "Uma15Sex", "Uma15Blinkers", "Uma15Kinryo",
    "Uma15Kyakusitu1", "Uma15Kyakusitu2", "Uma15Kyakusitu3", "Uma15Kyakusitu4",
    "Uma15Kishu", "Uma15Chokyosi", "Uma15Souha", "Uma15DeviationValues",
    "Uma16Barei", "Uma16Sex", "Uma16Blinkers", "Uma16Kinryo",
    "Uma16Kyakusitu1", "Uma16Kyakusitu2", "Uma16Kyakusitu3", "Uma16Kyakusitu4",
    "Uma16Kishu", "Uma16Chokyosi", "Uma16Souha", "Uma16DeviationValues",
    "Uma17Barei", "Uma17Sex", "Uma17Blinkers", "Uma17Kinryo",
    "Uma17Kyakusitu1", "Uma17Kyakusitu2", "Uma17Kyakusitu3", "Uma17Kyakusitu4",
    "Uma17Kishu", "Uma17Chokyosi", "Uma17Souha", "Uma17DeviationValues",
    "Uma18Barei", "Uma18Sex", "Uma18Blinkers", "Uma18Kinryo",
    "Uma18Kyakusitu1", "Uma18Kyakusitu2", "Uma18Kyakusitu3", "Uma18Kyakusitu4",
    "Uma18Kishu", "Uma18Chokyosi", "Uma18Souha", "Uma18DeviationValues"
]]

出力データは以前のものと同じです。で、今回は当たり前ですが本格的に取り組みました。Copilotとまあ平日で夜勤なのでちょっと眠い感じではありますが、可能な限り作業を続け3日目となる昨日出勤2時間前程度にどうにか訓練してくれる所までこぎつけました。

# コースは小数点第1位までの実数ですが、整数に変換する必要があります。
X["Course"] = (X["Course"] * 10).astype(int)  # コースデータを10倍して整数化

このコース区分はAutoMLやLightGBM用のCSVデータでもそうだったんですがコース区分を数値にしてて、"A"~"E"だけど、中には"A1"とかもあるって事で"A"は1.0、"A1"は1.1的にしてました。ML.NET系は基本実数なので特に問題なく使えてましたが、TensorFlowでファクター(特徴量)をカテゴリ変数とする場合にはint64である必要があるのでこんな感じでデータを前処理加工。

# レースのカテゴリ変数
X_train_inputs = [
    X_train["JouCD"], X_train["KaisaiTuki"], X_train["TrackCD"], X_train["Course"], X_train["Tenko"], X_train["Baba"],
    X_train["KyosouShubetu"], X_train["KyosouJouken"], X_train["JuuryoShubetu"]
]
# レースの数値データを追加
X_train_inputs.extend([
    X_train["KaisaiKai"], X_train["KaisaiNichi"], X_train["Kyori"], X_train["Tousu"], X_train["StandardDeviation"]
    ])

こんな感じで指定しました。馬毎のファクターも同様にカテゴリ変数と数値データを指定。

# ウマごとのカテゴリ変数を追加
for i in range(18):
    X_train_inputs.extend([
        X_train[f"Uma{i+1}Sex"], X_train[f"Uma{i+1}Blinkers"], X_train[f"Uma{i+1}Kishu"], X_train[f"Uma{i+1}Chokyosi"]
    ])

# ウマごとの数値データを追加
X_train_inputs.extend([
    X_train[f"Uma{i+1}{feature}"]
    for i in range(18)
    for feature in ["Barei", "Kinryo", "Kyakusitu1", "Kyakusitu2", "Kyakusitu3", "Kyakusitu4", "Souha", "DeviationValues"]
])

で、TensorFlowではお決まりの層を定義していく為に

# レースのカテゴリ変数を定義
input_joucd = Input(shape=(1,), name="JouCD")  # 場コード
embedding_joucd = Embedding(input_dim=11, output_dim=4)(input_joucd)  # 場コードの埋め込み
flatten_joucd = Flatten()(embedding_joucd)
input_kaisaituki = Input(shape=(1,), name="KaisaiTuki")  # 開催月
embedding_kaisaituki = Embedding(input_dim=13, output_dim=4)(input_kaisaituki)  # 開催月の埋め込み
flatten_kaisaituki = Flatten()(embedding_kaisaituki)
input_trackcd = Input(shape=(1,), name="TrackCD")  # トラックコード
embedding_trackcd = Embedding(input_dim=60, output_dim=4)(input_trackcd)  # トラックコードの埋め込み
flatten_trackcd = Flatten()(embedding_trackcd)
input_course = Input(shape=(1,), name="Course")  # コース
embedding_course = Embedding(input_dim=100, output_dim=4)(input_course)  # コースの埋め込み
flatten_course = Flatten()(embedding_course)
input_tenko = Input(shape=(1,), name="Tenko")  # 天候
embedding_tenko = Embedding(input_dim=7, output_dim=2)(input_tenko)  # 天候の埋め込み
flatten_tenko = Flatten()(embedding_tenko)
input_baba = Input(shape=(1,), name="Baba")  # 馬場状態
embedding_baba = Embedding(input_dim=5, output_dim=2)(input_baba)  # 馬場状態の埋め込み
flatten_baba = Flatten()(embedding_baba)
input_kyosoushubetu = Input(shape=(1,), name="KyosouShubetu")  # 競走種別
embedding_kyosoushubetu = Embedding(input_dim=25, output_dim=2)(input_kyosoushubetu)  # 競走種別の埋め込み
flatten_kyosoushubetu = Flatten()(embedding_kyosoushubetu)
input_kyosoujouken = Input(shape=(1,), name="KyosouJouken")  # 競走条件
embedding_kyosoujouken = Embedding(input_dim=1000, output_dim=8)(input_kyosoujouken)  # 競走条件の埋め込み
flatten_kyosoujouken = Flatten()(embedding_kyosoujouken)
input_juuryoushubetu = Input(shape=(1,), name="JuuryoShubetu")  # 斤量種別
embedding_juuryoushubetu = Embedding(input_dim=5, output_dim=2)(input_juuryoushubetu)  # 斤量種別の埋め込み
flatten_juuryoushubetu = Flatten()(embedding_juuryoushubetu)

# レースの数値変数を定義
input_kaisai_kai = Input(shape=(1,), name="KaisaiKai")  # 開催回
input_kaisai_nichi = Input(shape=(1,), name="KaisaiNichi")  # 開催日
input_kyori = Input(shape=(1,), name="Kyori")  # 距離
input_tousu = Input(shape=(1,), name="Tousu")  # 頭数
input_standard_deviation = Input(shape=(1,), name="StandardDeviation")  # 標準偏差

てな感じでして

# レースの特徴量を結合
race_features = Concatenate()([
    flatten_joucd, flatten_kaisaituki, flatten_trackcd, flatten_course, flatten_tenko, flatten_baba,
    flatten_kyosoushubetu, flatten_kyosoujouken, flatten_juuryoushubetu,
    input_kaisai_kai, input_kaisai_nichi, input_kyori, input_tousu, input_standard_deviation
])
race_dense = Dense(64, activation='relu')(race_features)  # 隠れ層の学習
race_dense = Dense(32, activation='relu')(race_dense)     # 更に学習層を追加

この後、当然馬毎の入力層も定義し

# 馬ごとのネットワーク
merged_horses = Concatenate()(uma_networks)

# レース特徴量と馬特徴量を結合
final_features = Concatenate()([race_dense, merged_horses])

# 結合後のネットワーク全体
x = Dense(128, activation='relu')(final_features)  # 隠れ層
output = Dense(18, activation='linear', name="Output")(x)  # 出力層

これで、層は出来たので

# モデルを構築
model_inputs = [input_joucd, input_kaisaituki, input_trackcd, input_course, input_tenko, input_baba,
                input_kyosoushubetu, input_kyosoujouken, input_juuryoushubetu,
                input_kaisai_kai, input_kaisai_nichi, input_kyori, input_tousu, input_standard_deviation] + uma_inputs
model = Model(inputs=model_inputs, outputs=output)

後は入力データを↑の様に整えるのに

X_train_inputs = [
    X_train["JouCD"],  # JouCD
    X_train["KaisaiTuki"],  # KaisaiTuki
    X_train["TrackCD"],  # TrackCD
    X_train["Course"],  # Course
    X_train["Tenko"],  # Tenko
    X_train["Baba"],  # Baba
    X_train["KyosouShubetu"],  # KyosouShubetu
    X_train["KyosouJouken"],  # KyosouJouken
    X_train["JuuryoShubetu"],  # JuuryoShubetu
    X_train["KaisaiKai"],  # KaisaiKai
    X_train["KaisaiNichi"], # KaisaiNichi
    X_train["Kyori"], # Kyori
    X_train["Tousu"], # Tousu
    X_train["StandardDeviation"], # StandardDeviation
]
for i in range(18):
    X_train_inputs.extend([
        X_train[f"Uma{i+1}Sex"],   # Uma{i+1}Sex
        X_train[f"Uma{i+1}Blinkers"], # Uma{i+1}Blinkers
        X_train[f"Uma{i+1}Kishu"], # Uma{i+1}Kishu
        X_train[f"Uma{i+1}Chokyosi"], # Uma{i+1}Chokyosi
        X_train[f"Uma{i+1}Barei"], # Uma{i+1}Barei
        X_train[f"Uma{i+1}Kinryo"], # Uma{i+1}Kinryo
        X_train[f"Uma{i+1}Kyakusitu1"], # Uma{i+1}Kyakusitu1
        X_train[f"Uma{i+1}Kyakusitu2"], # Uma{i+1}Kyakusitu2
        X_train[f"Uma{i+1}Kyakusitu3"], # Uma{i+1}Kyakusitu3
        X_train[f"Uma{i+1}Kyakusitu4"], # Uma{i+1}Kyakusitu4
        X_train[f"Uma{i+1}Souha"], # Uma{i+1}Souha
        X_train[f"Uma{i+1}DeviationValues"], # Uma{i+1}DeviationValues
    ])

として、モデルの訓練は以前のものほぼ変わらずですが、

# モデルの訓練
from tensorflow.keras.callbacks import EarlyStopping
early_stopping = EarlyStopping(monitor='val_rmse', patience=5, mode='min')
history = model.fit(
    X_train_inputs,  # 入力データをリスト形式で渡す
    Y_train,         # 出力データ(ターゲット)
    epochs=2000,
    batch_size=32,
    validation_split=0.2,
    callbacks=[early_stopping]
)

流石に複雑なのでってのと、何度もやるのは辛いと想像して最初からepochsを2,000にしてます。ただ、これ、ちょっとザックリと指定してしまいましたが、昨日スタートすると明らかに各epochがこれまでと違い時間が掛かります。特にプログラム的に時間表示させたりしてないので目測的に測ると4分とかです。たった今205回目が終わってました。4×205=820÷60=13.6時間です。もし、過学習でEarlyStoppingにならないと4×2,000=8,000÷60=133.3÷24=5.5日です😰

今晩も夜勤で出勤しますが、訓練が終わらない事には予測させるコーディングも進められないので今週末には間に合わない。まっ、GW中には形に出来れば...

追記 2025.4.19 18:30
少し前に675個目のepochが終わりました。TensorFlowってテキストベースでログが出て各epochの終わりに

って感じで出るんですが、地味に進んでるのでEarlyStoppingにはならないのかな? 勝手にそろそろ終わるんじゃね?っと思ったんですが地味に続くのが良いのか悪いのか😓 MicrosoftをGoogleが超えるのをマジ期待してたりもします😉
いや、AutoMLやML.NET、LightGBMはMicrosoftで、TensorFlowはGoogleって話なら...

いやね、そろそろ、いつEarlyStoppingになってもって思ってますが地味に進んでるのがepochs=2000って指定が良かったのかが...ここまで36時間以上なのでやり直しは簡単ではないので期待してる😉

追記 2025.4.19
↑の追記は間違いが多いかな。多分675/2000って出てるEpochですが、これ次に始まった奴だったと思うので、厳密には674個目の終わりだった。更にそろそろ36時間以上ではなく48時間以上だな。いや、一昨日の出勤2時間前にコーディング終えてスタートしたんでね😉 先程750個目がスタートした。

追記 2025.4.20
何が失敗っていきなり2,000epochsで行った事ですね😭 いや、実際問題としてはその位必要な感じで進んではいます。だからこそ中断という選択は無いんです。ただ、今回ってbatch_size=32で行ったんですが、こちらのPCには64GB積んでるんでbatch_size=64でも行けたかも? これ指定してれば半分で済んだかも? その辺りも後から分かったけど今更中断出来ないのが辛いです。開始してからこれに要する時間が5.5日程度と分かった時点で一旦止める判断しなかったのが悔やまれますが、これも自分の知識不足が原因として。先程929回目がスタートしました。まだ半分に届いてない。でもloss: 6.1598と確実に進んでるのは分かります。進まなければEarlyStoppingが効くはずなのでね。まいったなぁ、終わるのはこのまま行くと3.4日後って木曜日になるのかなぁorz

0 件のコメント:

コメントを投稿