python初学者が競馬予測をしてみたpart3(part1,2の変更点)

皆さんこんにちは、KHnodeと申します。
第三回です。
前回の記事はこちらから、まだ見ていない人は見ていただけると嬉しいです。
kashiwapro.hatenablog.com

##################################################################

前回まで1競馬予測のコードを書いてきましたが、今回はこの2週間で変更したことを書いていこうと思います。

①目的変数の変更
前の目的変数は確定順位の1,2,3着を0-1変数に直したものを目的変数としていましたが
「オッズが5倍かつ確定順位の1,2,3着を0-1変数に直したもの」に変更しました。
(オッズの倍率は変更有)

変更理由に関しては2つあります。
複勝で賭けるため少しうまみのあるオッズを買いたいから
〇説明にオッズと人気を合わせた特徴量の作成を行いたいから

1つ目の理由は、的中率よりも回収率が重要と筆者は考えているので単勝オッズ1.何倍の複勝を買ってもほぼ元返しになってしまうため、1度負けてしまうと回収不可能になってしまうからです。
2つ目の理由は、説明変数が少なくスコアが68%とかなり低いのでスコア向上のためです。

目的変数のコードはこちらです。(無駄なコードが多い気がします)

  import pandas as pd

    df = pd.read_excel(data)

    d_ranking = lambda x: 1 if x in [1, 2, 3] else 0
    df['確定フラグ'] = df['確定順位'].map(d_ranking)

    oos = lambda x: 1 if x > 5.0 else 0
    df['オッズフラグ'] = df['オッズ'].map(oos)

    df.loc[(df['確定フラグ'] == 1) & (df['オッズフラグ'] == 1), 'メインフラグ'] = "1"
    df.loc[(df['確定フラグ'] == 0) | (df['オッズフラグ'] == 0), 'メインフラグ'] = "0"

    df = df.drop(['確定フラグ', 'オッズフラグ'], axis=1)

②説明変数の変更

〇過去の戦績をのせる。(レース場、オッズ、人気、着順など)
〇特徴量の作成をする。

過去の戦績に関しては思った以上に難航しているので次週にのせれたらと思います(がんばります)
特徴量は今のところはオッズと人気で作成しようと考えています。

ではまた

python初学者が競馬予測をしてみたpart2(モデルの作成編)

皆さんこんにちは、KHnodeです。
今回も「python初学者が競馬予測をしてみた」です。

前回の記事はこちら
kashiwapro.hatenablog.com
まだ見ていない方は見ていただけると幸いです。

それでは早速やっていこうと思います。

##################################################################

モデル作成

訓練用のデータとテスト用のデータを分割します。
これは、機械学習お馴染みの手法なので覚えておくといいと思います。

from sklearn.model_selection import train_test_split

X = df.drop(['フラグ', '日付'], axis=1)
y = df['フラグ']

X_train, X_test, y_train, y_test = train_test_split(X, y,
                                                    test_size=0.31,
                                                    random_state=0)

次にモデルの作成なのですが、今回はLightGBMのoptunaを利用して自動設定にしてみました。

from optuna.integration import lightgbm as lgb

lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test)

params = {
        'objective': 'binary',
        'metric': 'auc'
     }

best_opt = lgb.train(
        params, lgb_train, valid_sets=[lgb_train, lgb_eval],
        verbose_eval=False,
        num_boost_round=10,
        early_stopping_rounds=10,
    )

レーニングしたデータのパラメータと重要度を出力します。

    y_pred = best_opt.predict(X_test)
    df.drop(columns=['フラグ', '日付'], inplace=True)

    importance = pd.DataFrame(best_opt.feature_importance(),
                  index=df.columns, columns=['importance'])
    importance = importance.sort_values(by="importance", ascending=False)

    return best_opt.params, importance

モデル組み込み

次に、予想モデルに組み込みなのですが、先ほど出力したbest_opt.paramsをbest_paramsに入れます。
もちろん下のように

best_params = best_opt.params

こんな感じにして入れるのも一つの方法ですが、これをすると乱数の影響?によって重要度がコロコロ変わってしまい正確な予想ができないのでしていません。

import lightgbm as lgb

    best_params = {
        'objective': 'binary', 'metric': 'auc', 'feature_pre_filter': False, 'lambda_l1': 0.0009216345468743798,
        'lambda_l2': 5.971595511972418e-08, 'num_leaves': 80, 'feature_fraction': 0.5, 'bagging_fraction': 1.0,
        'bagging_freq': 0, 'min_child_samples': 20
    }
    model = lgb.train(
        best_params,
        lgb_train,
        valid_sets=lgb_eval,
        num_boost_round=100,
        early_stopping_rounds=10
    )

とりあえずモデルの組み込みができたので、スコアを見ていくと、
大体68%なのですが、無茶苦茶低いです。

理由なのですが、オッズや人気といった素人が見ても重要だとわかる変数を入れていないという事が挙げられます。
入れてもいいとは思いますが、入れてしまうとかなりオッズや人気によってしまい1番人気にしか予想しなくなるからです。

とは言っても68%はかなり低いので対策を考えていく必要があると思います。

次は、実際にどういう感じで予想していくのかについて書いていこうと思います。

python初学者が競馬予測をしてみたpart1(データの前処理編)

皆さん初めまして、KHnodeと申します。

今回が初めての投稿でありますが、さっそく書いていきたいと思います。

今回のテーマは「python初学者が競馬予測をしてみた」です。
 
筆者自身は競馬が好きなのですが、中々当たらないので機械に予測させようと考え機械学習で有名なpythonの勉強を始めました。

ですので、間違っているところやめんどくさいコーディングをしているところは多々ありますが、その際はご指摘をいただけると幸いです。

 
##################################################################

 

使用するデータについて

使用するデータはJRA-VANから出ている TARGETを使用しています。
(本当はスクレイピングをしたかったのですが、勉強量が増えるとパンクしてしまいそうだったので今回は諦めました次回は挑戦したいです。)
jra-van.jp

データの中身はこんな感じ、

日付 芝ダ 距離 馬場状態 騎手 父馬名 母父馬名 馬番 年齢 性別 人気 オッズ 確定順位
2011.1.22 1000 野元昭嘉 ヴィクトリースピーチ ティッカネン 1 3 9 112.4 8


他にも前走結果や、坂路調教のタイムなど様々なデータがありますがTARGETの取り方がよくわからなかったので入れていません。

 

データの前処理

欠損値をdropnaで落とします。

import pandas as pd

 df = pd.read_excel('main.xlsx')
 df = df.dropna(how='any')

後、特徴量の生成も行いました。

df['オッズ比'] = (df['オッズ'] // df['人気']) ** 2

本当はグラフなどで関係性を見つけてから特徴量を作成していくと思うのですが、長年の感で作成しました

次に、馬場状態や騎手といった文字列が入っているところを処理していきます。
本当はダミー変数化して行きたかったのですが、それをしてしまうと何万列となってしまい、処理が大変になるだろうと思いましたので「カテゴリ変数」をつかって処理していきます。  

import category_encoders as ce

category_data = ['芝ダ', '距離', '馬場状態',
          '騎手', '父馬名', '母父馬名', '性別']
    ce_oe = ce.OrdinalEncoder(cols=category_data, handle_unknown='impute')
    df = ce_oe.fit_transform(df)

    for i in category_data:
        df[i] = df[i] - 1

    for i in category_data:
        df[i] = df[i].astype('category')

forループが2つも重なって変なコードになっていますが、合わせてしまうと何故かうまくいかなかったので分けています。
もしわかる人がいらっしゃいましたらご教授いただければ幸いです。
astypeの所はカテゴリー型にしていますが、int型でもできるらしいです。

最後に目的変数ですが、3着以内きたことがある馬にフラグを立て1-0に直したものとしました。

d_ranking = lambda x: 1 if x in [1, 2, 3] else 0
df['フラグ'] = df['確定順位'].map(d_ranking)

いろいろと調べた結果このコードが簡単にかけそうだったので書いてみました。
lambdaを初めて使ったのですが、思いのほか使いやすいのでお勧めです。

以上前処理編は終わりにします。
次はモデルを組んで予想していきたいと思います。

追記~~(2020/2/20)
特徴量を設定し、予測すると1番2番人気だけくると予想したので外しています。
詳しい内容は次のブログにて紹介します。