Slide 1

Slide 1 text

Pythonで 競馬予想してみた 1 2022年3月5日AkarengaLT

Slide 2

Slide 2 text

目次 2 1.自己紹介 2.競馬とは 3.機械学習とは 4.今回実装する目標 5.作成するにあたって 6.前処理について 7.モデルについて 9.結果 10.おわりに

Slide 3

Slide 3 text

自己紹介 3

Slide 4

Slide 4 text

高橋 拓也 年齢:23歳 Python歴:大体1年ぐらい 健康食品通販のWeb販売をおこなっていた。 最近はまっていること RailsDockerを使ったアプリケーションの開発 FF14、Valorant、steamゲーム、アニメ Discord:eleva0729#3352 chatwork:t_takuya0729 4

Slide 5

Slide 5 text

作ろうと思ったきっかけ • PDCAサイクルを回しやすい →レースは基本土日に行われており、結果をもとに改善しやすい。 • 自分で予想しても当たらなくなったので機械にやってもらおうと思った。 • 楽して儲けたかった。。。 5

Slide 6

Slide 6 text

競馬とは 6

Slide 7

Slide 7 text

競馬とは • 競馬とは、騎手が馬に乗り、決められたコースの距離を走り、一番早く走る スピードを競う競技、およびその着順を予想するものです。 • 賭け方の種類は1着の馬を当てる単勝、3着以内に来る馬を当てる複勝、 1着、2着、3着となる馬を着順通りに的中させる3連単などたくさんあります。 7

Slide 8

Slide 8 text

機械学習とは 8

Slide 9

Slide 9 text

機械学習とは • 機械学習とは、AIが人間のように高度な判断を実行するに必要な「法則」 をコンピューターに探させる方法(アルゴリズム)の総称 • 機械学習でできることは画像認識、音声認識、自然言語処理、予測などに 使われている。 Pythonによる機械学習入門より引用 9

Slide 10

Slide 10 text

今回実装する目標 10

Slide 11

Slide 11 text

今回実装する目標 • 回収率を100%以上を目標にする • 賭け方に関しては単勝・複勝にする • 目的変数は3着以内に来た馬を0-1変数に変換にして作成する 11

Slide 12

Slide 12 text

作成するにあたって 12

Slide 13

Slide 13 text

今回使用するライブラリー 13

Slide 14

Slide 14 text

今回使用するバージョン • Python:3.6 • Pandas:1.1.5 • Numpy:1.19.5 • scikit-learn:0.24.2 • Lightgbm:3.3.0 • Tensorflow:2.6.0 etc... 14

Slide 15

Slide 15 text

作成フロー 15 前処理 • 特徴量生成 • 主成分分析 etc... モデル • LightGBM • TensorFlow 予測 • 確率で表示

Slide 16

Slide 16 text

今回使用するデータの中身 Targetから習得(有料) https://jra-van.jp/target/index.html 本当はnetkeiba.comさんからスクレイピングをした方がいいのだが データの中身はこちらから https://kashiwapro.hatenablog.com/entry/2021/10/29/162155 16

Slide 17

Slide 17 text

前処理について 17

Slide 18

Slide 18 text

前処理 • 騎手・父馬・母父馬・父馬タイプを主成分分析・ Target Encodingで作成する。 • 前走の成績・過去5戦の成績を作成する。 • 四則演算を中心に特徴量を作成する。 GitHub: https://github.com/KHTTakuya/KeibaPrediction 18

Slide 19

Slide 19 text

前処理で利用するライブラリー 19

Slide 20

Slide 20 text

主成分分析 • 主成分分析とは、たくさんの量的 な説明変数をより少ない指標や合成 変数(複数の変数が合体したもの) に要約する手法 • 今回の場合は10個の変数を主成分 分析すると、1つの要素で約6割変数 の内容を説明できる。 20 騎手データ 0 0.599803 1 0.700485 2 0.780361 3 0.840323 4 0.889738 5 0.924274 6 0.947155 7 0.969340 8 0.985989 9 1.000000

Slide 21

Slide 21 text

主成分分析 21 df = df[df['days'] < datetime(2021, 1, 1)] df.loc[df['result'] >= 4, 'result'] = 0 df.loc[(df['result'] <= 3) & (df['result'] >= 1), 'result'] = 1 # 各ジョッキーコース別の複勝率(2021年1月1日まで集計対象) table_jockey = pd.pivot_table(df, index='jocky', columns='place', values='result', aggfunc='mean', dropna=False) table_jockey = table_jockey.fillna(0) table_jockey = pd.DataFrame(table_jockey) table_jockey = table_jockey.round(4) table_jockey = table_jockey.add_prefix('jockey_') # 主成分分析:次元削除 pca = PCA() pca.fit(table_jockey) df_score = pd.DataFrame(pca.transform(table_jockey), index=table_jockey.index) df_score = df_score.loc[:, :5] df_score = pd.DataFrame(data=df_score) df_score = df_score.rename(columns={0: 'jockey_pca1', 1: 'jockey_pca2', 2: 'jockey_pca3', 3: 'jockey_pca4', 4: 'jockey_pca5', 5: 'jockey_pca6'})

Slide 22

Slide 22 text

Target Encoding • Target Encodingとは一般的に説明 変数に含まれるカテゴリ変数と目的 変数を元にして特徴量を作り出す。 • 今回は騎手名をカテゴリ変数とし て特徴量を作成している。 22 jocky flag holdout_ts 津村明秀 0 0.137001 井上敏樹 0 0.050512 吉田隼人 0 0.173719 秋山真一 0 0.1422 藤岡康太 1 0.160703 ... ... ... 小崎綾也 1 0.104527 富田暁 0 0.082949 川又賢治 0 0.103596 浜中俊 0 0.228835 国分恭介 0 0.074155

Slide 23

Slide 23 text

Target Encoding 23

Slide 24

Slide 24 text

前走成績 24 Name_days_df = df[[“horsename”, “place”, “turf”, “distance”, “days”, “pop”, “odds”, “rank3”, “rank4”, “3ftime”, “result”, ‘speedindex’, ‘last_race_index’, ‘count’, ‘rentai’]].sort_values([‘horsename’, ‘days’]) name_list = name_days_df[‘horsename’].unique() df_shift_list = [] df_rolling_list = [] For name in name_list: name_df = name_days_df[name_days_df[‘horsename'] == name] shift_name_df = name_df[["place", "turf", "distance", "pop", "odds", "rank3", "rank4", "3ftime", "result", 'speedindex', 'last_race_index']].shift(1) rolling_name_df = name_df[["pop", "odds", "3ftime", 'speedindex', "result", 'count', 'rentai']].rolling(5, min_periods=1)¥ .agg(agg_list).shift(1) shift_name_df['horsename'] = name rolling_name_df['horsename'] = name df_shift_list.append(shift_name_df) df_rolling_list.append(rolling_name_df)

Slide 25

Slide 25 text

特徴量の生成 25 # 特徴量生成 df['flag_konkan'] = (df['distance'] % 400 == 0).astype(int) df['flag_pre_konkan'] = (df['pre_distance'] % 400 == 0).astype(int) df['odds_hi'] = (df['odds'] / df['pop']) df['re_odds_hi'] = (df['pre_odds'] / df['pre_pop']) df['odds_hi*2'] = df['odds_hi'] ** 2 df['re_odds_hi*2'] = df['re_odds_hi'] ** 2 df['re_3_to_4time'] = (df['pre_rank4'] - df['pre_rank3']) df['re_3_to_4time_hi*2'] = (df['pre_rank4'] / df['pre_rank3']) ** 2 df['re_pop_now_pop'] = (df['pre_pop'] - df['pop']) df['re_odds_now_odds'] = (df['pre_odds'] - df['odds']) df['re_result_to_pop'] = (df['pre_result'] - df['pre_pop']) df['popmax_popmin'] = df['popmax'] - df['popmin'] df['oddsmax_oddsmin'] = df['oddsmax'] - df['oddsmin'] df['rentai_ritu'] = (df["rentai5sum"] / df["count5sum"]).round(3)

Slide 26

Slide 26 text

モデルについて 26

Slide 27

Slide 27 text

モデルで利用するライブラリー 27

Slide 28

Slide 28 text

カテゴリ変数化 df = df.astype({'distance': 'string', 'pre_distance': 'string'}) cat_cols = ['place', 'turf', 'distance', 'weather', 'condition', 'sex', 'horsename', 'trainer', 'pre_place', 'pre_turf', 'pre_distance'] for c in cat_cols: le = LabelEncoder() le.fit(df[c]) df[c] = le.transform(df[c]) df['days'] = pd.to_datetime(df['days']) df = df.dropna(how='any') drop_list = ['days', 'raceid', 'result', 'racenum', 'class', 'jocky', 'horsecount', 'weight', 'father', 'mother', 'fathertype', 'legtype', 'fathermon’] LightGBMはカテゴリ変数を読み込める ので使用する。 しかしTensorflowを使う場合は(調べた 限り)カテゴリ変数を使うことができな い感じでしたのでダミー変数化や targetencodingで直していきます。 28

Slide 29

Slide 29 text

(補足)ダミー変数化 df = df.replace({'distance': [1000, 1200, 1400, 1500]}, 'sprint') df = df.replace({'distance': [1600, 1700, 1800]}, 'mile') df = df.replace({'distance': [2000, 2200, 2300, 2400]}, 'middle') df = df.replace({'distance': [2500, 2600, 3000, 3200, 3400, 3600]}, 'stayer') df = df.replace({'pre_distance': [1000, 1200, 1400, 1500]}, 'sprint') df = df.replace({'pre_distance': [1600, 1700, 1800]}, 'mile') df = df.replace({'pre_distance': [2000, 2200, 2300, 2400]}, 'middle') df = df.replace({'pre_distance': [2500, 2600, 3000, 3200, 3400, 3600]}, 'stayer') columns_list = ['place', 'class', 'turf', 'weather', 'distance', 'condition', 'sex', 'pre_place', 'pre_turf', 'pre_distance'] df = pd.get_dummies(df, columns=columns_list) df = df.drop(['father', 'mother', 'fathermon', 'fathertype', 'legtype', 'jocky', 'trainer'], axis=1) このようにダミー変数化をした際 に特徴量の数を増やし過ぎないよ うにグループを作って変数化する。 29

Slide 30

Slide 30 text

params = { 'task': 'predict', 'objective': 'binary', 'verbosity': -1, } model = lgb.train( params, lgb_train, categorical_feature=cat_cols, valid_sets=lgb_eval, num_boost_round=100, early_stopping_rounds=10, ) best_params = model.params model = lgb.train( best_params, lgb_train, categorical_feature=cat_cols, valid_sets=lgb_eval, num_boost_round=100, # 100 early_stopping_rounds=10, # 20 ) lightGBMのハイパーパラメータの作成は自動 最適化フレームワークoptunaを利用して作成 モデル(LightGBM) 30

Slide 31

Slide 31 text

model = keras.Sequential([ keras.layers.Dense( 256, activation='relu', input_shape=(train_features.shape[-1],)), keras.layers.Dense( 128, activation='relu', input_shape=(train_features.shape[-1],)), keras.layers.Dense( 128, activation='relu', input_shape=(train_features.shape[-1],)), keras.layers.Dropout(0.1), keras.layers.Dense( 256, activation='relu', input_shape=(train_features.shape[-1],)), keras.layers.Dense( 128, activation='relu', input_shape=(train_features.shape[-1],)), keras.layers.Dense( 128, activation='relu', input_shape=(train_features.shape[-1],)), keras.layers.Dropout(0.1), keras.layers.Dense( 256, activation='relu', input_shape=(train_features.shape[-1],)), keras.layers.Dense( 128, activation='relu', input_shape=(train_features.shape[-1],)), keras.layers.Dense( 128, activation='relu', input_shape=(train_features.shape[-1],)), keras.layers.Dropout(0.1), keras.layers.Dense( 256, activation='relu', input_shape=(train_features.shape[-1],)), keras.layers.Dense(1, activation='sigmoid', bias_initializer=output_bias), ]) Tensorflowのドキュメントを利用して作成しています。 右のコードのように層を作成していく。 http://marupeke296.com/IKDADV_DL_No2_Keras.html モデル(Tensorflow) 31

Slide 32

Slide 32 text

予想結果 32

Slide 33

Slide 33 text

モデルの評価 • TensorFlow binary_logloss: 0.5291 accuracy: 0.7176 • LightGBM binary_logloss: 0.4118 accuracy: 0.80951 正確度(Accuracy)が割といい感じにできているが Loglossが高くなっている。 →競馬予想であるため高くなるのは必然的な気がする。 33

Slide 34

Slide 34 text

実際の予想結果 • 今回は2月27日に行われたレースを対象とする。 35レースを対象に一番予想確率が高い馬1頭に 単勝複勝100円ずつ入れた場合とする。 ※但し新馬戦、障害レースは除外しています。 34

Slide 35

Slide 35 text

結果 • 合計は8,300円で回収率は119%と 目標である回収率100%を達成するこ とができた。 • 場所別でみると • 中山競馬場:回収率136% • 阪神競馬場:回収率110% • 小倉競馬場:回収率95% 35 単勝 複勝 合計 ¥3,960 ¥4,340 回収率 113% 124% 的中数 12 27 的中率 34% 77%

Slide 36

Slide 36 text

結論 競馬の予測はいい感じにできたのでよかった。 機械学習の勉強にもわりと向いている気がします。 競馬は知れば知るほど面白いので是非お試しにやってみてはいかがでしょうか 36

Slide 37

Slide 37 text

おわりに 37

Slide 38

Slide 38 text

今後 • 現在は競馬予想を楽しめるようにDjangoを使ってWebアプリケーション を作成しデプロイしたものの改善点は多い • 改善するために、Railsを使って開発中 38

Slide 39

Slide 39 text

さいごに <使用可能言語/ツール> 得意言語:Python Django (Django-Rest-Framework) /Ruby on Rails / Pandas / TensorFlow / React.js / AWS / GCP / Docker データ分析やWebアプリケーションの開発やりたいです。もし気になりま したらFaceBookなどメッセージをいただけると幸いです! 39

Slide 40

Slide 40 text

補足 GitHub https://github.com/KHTTakuya/KeibaPrediction Qiita https://qiita.com/KHTTakuya/items/35ea5e710f0fb3aa86e4 ホームページ https://eleva-guatemala.com 40

Slide 41

Slide 41 text

ご清聴ありがとう ございました! 41