1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ChatGPTと作る競馬予想AI (9)

Posted at

プログラミング初心者がChatGPTを使って競馬予想AIを作ることで、生成AIとプログラミングについて学んでいく企画の第9回です。
このシリーズも連載二桁までもう少しですね。

前回の記事はこちら

前回の記事では正規表現や前処理について学びつつ、約8000頭の馬成績を1つのテーブルにまとめました。

今回はこれまでに用意したレース結果と馬成績を結び付け、特徴量を入力できるように1つのテーブルにしていきましょう。

入力データの考え方

シリーズ第9回までやっていながら、実はまだデータを集めただけです。
この回から見始めた人は、今まで何をやっていたんだと驚愕すると思います。

しばらくBeautifulSoupやPandasと格闘していたおかげで、当初どのようなデータを入力して何を予測しようとしていたか忘れてしまいました。

したがって一度集めたデータを整理し、機械学習モデルへの入出力変数をまとめましょう。

予測モデルについて

目指している競馬予想AIはその馬が一位になるかどうかを01で判別するものです。
予測には機械学習のコンペでも使用率が高いLightGBMを使っていきます。

LightGBMへのデータの入力方法については、この企画とは別に以下の記事でまとめました。

これまで用意したデータのテーブルを元に、LightGBMの実装を行いましょう。
まずはこれまで集めたデータのおさらいです。

集めた特徴量は1つのテーブルにして、pickleファイルに保存しています。
こちらを確認してみると

with open("C:\\Users\\name\\Desktop\\mykeibaAI_ver1p0\\data\\race_result_table.pkl", "rb") as f:
    model = pickle.load(f)

print(model.columns)
print(f"Number of items: {len(model.columns)}")
Index(['date', 'race_id', 'is_win', 'rank', 'horse_id', 'weight_carried',
       'jockey_id', 'popularity', 'odds', 'last3f', 'trainer_id',
       'body_weight', 'sex', 'age', 'time_sec', 'body_diff'],
      dtype='object')
Number of items: 16

計16個の特徴量が保存されています。
これらはnetkeiba.comのレース結果が保存されているページからスクレイピングした情報です。
またそれぞれの特徴量にどんな値が保存されているか、中身を確認しておきます。

image.png

この中で特徴量に使えそうなのは 'weight_carried', 'popularity', 'odds', 'body_weight', 'sex', 'age', 'body_diff' くらいでしょうか

情報にならないID系や入れるとリークになってしまうタイムを抜くと計7個しか残らず…
いつかhorse_idやjockey_idも紐づけ、特徴量をどんどん増やしていきます。

LightGBMへの実装

入力データを見繕ったところで、機械学習モデルに入力してモデルを作っていきます。

LightGBMはDataFrame型のデータに対して説明変数(特徴量)と目的変数を指定し、データセットを学習・評価に分割、そして学習時のハイパーパラメータを設定することで使用可能です。

import lightgbm as lgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# modelにはDataFrame型のテーブルが格納されている

# 説明変数と目的変数の設定
feature = ['weight_carried', 'popularity', 'odds', 'body_weight', 'sex', 'age', 'body_diff']
X = model[feature]
y = model['is_win']

# データセットの分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)

# ハイパーパラメータの設定
params = {
    'objective': 'binary', # 2クラス分類
    'metric': 'binary_logloss', # ロス関数
    'boosting_type': 'gbdt', # 勾配ブースティング
    'learning_rate': 0.1, # 学習率(小さいほど慎重に学習、ただし学習時間は長くなる)
    'num_leaves': 31, # 木の葉の数(複雑なモデルほど大きく)
    'verbose': -1, # 全てのログの出力を非表示
    'early_stopping_round': 30, # 30回連続で改善しなければ終了
    'num_boost_round': 300 # 最大100本の木を作成
}

# モデルの学習と評価
ml_model = lgb.train(params, lgb_train, valid_sets=[lgb_train, lgb_eval], valid_names=['train', 'eval'])
y_pred_prob = ml_model.predict(X_test, num_iteration=ml_model.best_iteration)
y_pred = (y_pred_prob >= 0.5).astype(int)
accuracy = accuracy_score(y_test, y_pred)
print(f"Accuracy: {accuracy * 100:.2f}%")

results = X_test.copy()
results["actual"] = y_test
results["predicted"] = y_pred
results["predicted_prob"] = y_pred_prob

print(results.head(10))

こうしてできたモデルを見ると正答率(Accuacy)がなんと92.74%

Accuracy: 92.74%
       weight_carried  popularity   odds  body_weight  sex  age  body_diff  \
21107            57.0        12.0  110.6        446.0    0  3.0       -2.0   
17038            55.0         3.0    6.5        472.0    1  3.0       -8.0   
2281             54.0         7.0   73.5        466.0    1  3.0        8.0   
3378             58.0         1.0    3.0        496.0    2  5.0        2.0   
19085            56.0         9.0   45.2        480.0    2  6.0       -6.0   
13174            57.0        11.0   35.6        464.0    0  4.0        8.0   
4404             56.0         2.0    3.6        462.0    0  2.0       -2.0   
28181            51.0        11.0   42.0        412.0    1  3.0      -14.0   
16441            57.0         2.0    3.1        444.0    0  3.0        4.0   
14118            58.0         7.0   27.2        522.0    0  5.0       10.0   

       actual  predicted  predicted_prob  
21107       0          0        0.007538  
17038       0          0        0.125715  
2281        0          0        0.011118  
3378        0          0        0.287334  
19085       0          0        0.023021  
13174       0          0        0.015342  
4404        0          0        0.277253  
28181       0          0        0.138365  
16441       0          0        0.268297  
14118       0          0        0.031001

しかし喜ぶのも束の間、当然この少ない特徴量ではたいした予測はできていません。

評価用にimportしたscikit-learnのaccuracy_score関数では、予測した結果どれだけ01を当てることができたかを%で出力します。
このとき、0も当たれば正解としていることがネックで、こちらの1を当てたいという目的からはずれています。
したがって、よくよく評価用データの中身を確認してみると、419個ある単勝当たり馬券のうち、23個しか当てていないことがわかります。
image.png

「当たり馬券を予測できた確率」でいうと23/419=0.055
つまりたったの 5.5% です。

値としてはがっかりですが、特徴量も少ないため予測できなくて当然です。
むしろモデル評価の際にはこういった評価方法が重要だ、ということを学べたことが大きいです。

特徴量の追加やそもそものデータ拡充、期待値ベースの回収率計算など、まだまだやることはたくさんあるため、引き続き連載を続けていきます。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?