実写版ウマ娘攻略（pythonで競馬予測モデルを作成してバックテストする方法～２章予測モデル作成編～）

今回は前回取得した競馬データを使って予測モデルの作成を行います。

前回記事は下記です。
https://qiita.com/umaining/items/be323c1a5e42e0f48987
#全体の流れ
今回の記事では「２．予測モデル作成」を説明します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1186688/da1cfa06-52dd-851b-aada-5dedf195ff68.png)

#モデル作成のイメージ
競馬データを使ってモデル作成をします。
モデル作成する際は、「学習データ」と「予測対象データ」をそれぞれ用意します。
競馬の場合では、「学習データ」に前回人気や前回オッズなどのデータを使用します。
モデルの性能は学習データの質に掛かっているので、ここでどのようなデータを入れるかが一番の肝となります。
予測対象データは着順やタイムなどを指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1186688/136fb8f1-6ae6-db71-37fd-423232c36892.png)

#事前準備
下記を実施してpythonのライブラリをインストールしてください。

```python
pip install pathlib2
pip install pandas
pip install sklearn
pip install matplotlib
pip install graphviz
pip install pydotplus
pip install IPython
pip install lightgbm
pip install optuna
```

###DBに「前回人気」「前回着順」を追加
EveryDBで構築したデータに新しく列を追加します。
下記のコードを実行して前回の成績（着順、人気）を追加します。
変数db_passにはEveryDBのディレクトリを指定してください。

```python:sql_exec.py
import sqlite3
import os
from pathlib2 import Path
import pandas

def exec_sql(db_pass):
    # カレントディレクトリに移動
    os.chdir(Path(__file__).parent)
    print("作業ディレクトリ:{0}".format(os.getcwd()))

    # DBファイル準備
    conn = sqlite3.connect(db_pass)
    cursor = conn.cursor()
    print(sql)
    cursor.execute("""
    ALTER TABLE N_UMA_RACE ADD COLUMN "pre1_KakuteiJyuni" INTEGER
    """
    )
    conn.commit()

    cursor.execute("""
    update N_UMA_RACE set pre1_KakuteiJyuni = fix_emp.number from (select N_UMA_RACE.Year,N_UMA_RACE.MonthDay,N_UMA_RACE.JyoCD,N_UMA_RACE.RaceNum,N_UMA_RACE.KettoNum,
	LAG ( N_UMA_RACE.KakuteiJyuni,1,0) OVER (PARTITION BY KettoNum ORDER BY Year,MonthDay,RaceNum) as number from N_UMA_RACE) fix_emp
	where N_UMA_RACE.KettoNum = fix_emp.KettoNum and N_UMA_RACE.Year = fix_emp.Year and N_UMA_RACE.JyoCD = fix_emp.JyoCD and N_UMA_RACE.RaceNum = fix_emp.RaceNum and N_UMA_RACE.MonthDay = fix_emp.MonthDay
    """
    )
    conn.commit()

    cursor.execute("""
    ALTER TABLE N_UMA_RACE ADD COLUMN "pre_Ninki" REAL;
    """
    )
    conn.commit()

    cursor.execute("""
    update N_UMA_RACE set pre_Ninki = fix_emp.number from (select N_UMA_RACE.Year,N_UMA_RACE.MonthDay,N_UMA_RACE.JyoCD,N_UMA_RACE.RaceNum,N_UMA_RACE.KettoNum,
    LAG ( N_UMA_RACE.Ninki,1,0) OVER (PARTITION BY KettoNum ORDER BY Year,MonthDay,RaceNum) as number from N_UMA_RACE) fix_emp
    where N_UMA_RACE.KettoNum = fix_emp.KettoNum and N_UMA_RACE.Year = fix_emp.Year and N_UMA_RACE.JyoCD = fix_emp.JyoCD and N_UMA_RACE.RaceNum = fix_emp.RaceNum and N_UMA_RACE.MonthDay = fix_emp.MonthDay;
    """
    )
    conn.commit()
    conn.close()

if __name__ == '__main__':]
    #EverDB配下のecore.dbのパスを指定してください
    db_pass = r'C:\Users\######\EveryDB2_1_0_1_0\ecore.db'
    exec_sql(db_pass)

```

下記のコマンドで上記コードをを実行すると、新しい列「pre1_KakuteiJyuni」「pre_Ninki」が追加されます。


```
python sql_exec.py
```

EveryDBのSQL実行で「select * from N_UMA_RACE」を実行するとテーブルの右端に新しい列「pre1_Kakuteijyuni」「pre_Ninki」が追加されているのがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1186688/f5f32cec-5fcf-6887-7320-9328a589f0bb.png)


#モデル作成
下記ソースコードの※１の箇所でモデル学習に使用するデータを指定できます。
今回は、先ほど作成した「前回人気」「前回着順」と「人気」を元にモデル作成をしていきます。

```python:ai_lightgbm.py
import os
from pathlib2 import Path
import sqlite3
import pandas
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
import matplotlib.pyplot as plt
from sklearn.metrics import roc_curve
from graphviz import Digraph
import pydotplus
from IPython.display import Image
from sklearn.tree import DecisionTreeClassifier, export_graphviz
from sklearn.metrics import accuracy_score
from pydotplus import graph_from_dot_data
from sklearn.metrics import confusion_matrix
import pickle
import numpy as np
from optuna.integration import lightgbm as lgb

def make_model(db_pass):

    # カレントディレクトリをスクリプト配置箇所に変更
    os.chdir(Path(__file__).parent)
    print("作業ディレクトリ:{0}".format(os.getcwd()))

    # DBファイル準備
    conn = sqlite3.connect(db_pass)
    cursor = conn.cursor()

    #2012年から2018年までのデータ取得（人気「00」、オッズ「0000」を除いたデータ取得）
    sql ="""
    select * from N_UMA_RACE WHERE ninki NOT IN ("00")  and Odds not in ("0000") and (year ="2018" or year ="2017" or year ="2016" or year ="2015")
    """

    # SELECT結果をDataFrame
    df = pandas.read_sql_query(sql=sql, con=conn)
    
    #naのデータを０に置換
    df = df.fillna(0)

    #学習データを用意----------------------------------※1----------------------------------------
    X = df.loc[:,["Ninki","pre_Ninki","pre1_KakuteiJyuni"]].astype(float)

    #０のデータをnanに置換
    df = df.replace('0', np.nan)

    #1着以内を目的変数
    y = df['1inai'].astype(int)

    print(X)
    #学習データとテストデータに分ける
    X_train,X_test,y_train,y_test = train_test_split(X,y,shuffle=False)

    #lightgbm用に（colsのデータ単位を参考にして）各レースの出走馬を集計する
    cols = ["Year","JyoCD","RaceNum","MonthDay"]
    ser = df[cols].astype(str)\
            .apply(lambda lis: [ x for x in lis], axis=1)\
            .str.join(" | ")\
            .value_counts()
    print("---------------------")
    print(ser.values)
    print("---------------------")

    #lightgbm用に学習データ、目的データを変換
    train = lgb.Dataset(X_train, y_train)
    valid = lgb.Dataset(X_test, y_test)

    # ハイパーパラメータをoptunaで自動設定
    params = {
        'objective': 'binary',
        'metric': 'auc'
    }
    best_params, history = {}, []
    model = lgb.train(params, train, valid_sets=[train,valid],
                        verbose_eval=False,
                        num_boost_round=1000,
                        early_stopping_rounds=10)
    best_params_ = model.params

    #テストデータで予測した時の値を算出
    y_pred = model.predict(X_test)

    fpr, tpr, thresholds = roc_curve(y_test, y_pred)
    print("fpr:{}".format(fpr))
    print("fpr:{}".format(tpr))
    plt.plot(fpr, tpr, marker='o')
    plt.xlabel('FPR: False positive rate')
    plt.ylabel('TPR: True positive rate')
    plt.grid()

    # モデルを保存する
    filename = 'finalized_model.sav'
    pickle.dump(model, open(filename, 'wb'))

if __name__ == '__main__':
    db_pass = r'C:\Users\XXX\EveryDB2_1_0_1_0\ecore.db'
    make_model(db_pass)



```

上記を実行すると同じディレクトリに「finalized_model.sav」というファイル名で予測モデルが保存されます。
実行結果は下記の通りに表示されます。

```
        Ninki  pre_Ninki  pre1_KakuteiJyuni
0         8.0       10.0                7.0
1         9.0        8.0               11.0
2         7.0        7.0                8.0
3         4.0        5.0                5.0
4         2.0        1.0                3.0
...       ...        ...                ...
197171    4.0       12.0                7.0
197172   15.0       10.0                8.0
197173    1.0        1.0                1.0
197174    6.0        5.0                6.0
197175   12.0        9.0                7.0

[197176 rows x 3 columns]
---------------------
[18 18 18 ...  1  1  1]
---------------------
[LightGBM] [Info] Number of positive: 10945, number of negative: 136937
[LightGBM] [Warning] Auto-choosing row-wise multi-threading, the overhead of testing was 0.000378 seconds.
You can set `force_row_wise=true` to remove the overhead.
And if memory is not enough, you can set `force_col_wise=true`.
[LightGBM] [Info] Total Bins 58
[LightGBM] [Info] Number of data points in the train set: 147882, number of used features: 3
[LightGBM] [Info] [binary:BoostFromScore]: pavg=0.074012 -> initscore=-2.526638
[LightGBM] [Info] Start training from score -2.526638
[LightGBM] [Info] Number of positive: 10945, number of negative: 136937
[LightGBM] [Warning] Auto-choosing row-wise multi-threading, the overhead of testing was 0.000472 seconds.
You can set `force_row_wise=true` to remove the overhead.

～～中略～～

And if memory is not enough, you can set `force_col_wise=true`.
[LightGBM] [Info] Total Bins 58
[LightGBM] [Info] Number of data points in the train set: 147882, number of used features: 3
[LightGBM] [Info] [binary:BoostFromScore]: pavg=0.074012 -> initscore=-2.526638
[LightGBM] [Info] Start training from score -2.526638
fpr:[0.00000000e+00 2.19591998e-05 4.39183996e-05 ... 9.99582775e-01
 9.99692571e-01 1.00000000e+00]
fpr:[0.00000000e+00 0.00000000e+00 2.66311585e-04 ... 1.00000000e+00
 1.00000000e+00 1.00000000e+00]
```

#次回
次回は今回作成した予測モデルを使用してバックテストを行う方法を説明します。
https://qiita.com/umaining/items/c52ec864c7685230312b