AIで競艇の予想をしようとした話

# はじめに
Courseraの機械学習コースを修了し、Kerasの基本的な使い方を勉強した今、次に何をすべきか…
タイタニックだけやって逃げていたKaggleに取り組もうと思っているんだけど、どうしても邪な考えが捨てきれず、ちょっと寄り道することにした。

お金を稼げそうなものとしては、株・FX等の投資かギャンブルがすぐに思いつく。
その中でも競艇は、出走する台数も6台で固定だし、枠順が小さい方が有利だったりと、何となく機械学習が予測するのに向いている気がします…しませんか？

ということで、物は試しだとやってみました。

※結論から言うとなんとも微妙な結果になったので、あまりお金儲けの参考にはならないと思います…

# 事前知識
これまでに学習した内容は以下の通り。
まだまだうわべしか知らないけど、予測モデルを作るだけならとりあえずできるレベル。

[courseraの機械学習コース](https://www.coursera.org/learn/machine-learning)
言わずと知れた機械学習の基礎を学べるWeb講義のコース。
とてもやさしいAndrew先生がことあるごとに褒めてくれる。
使っている言語がOctaveだったりしてちょっと特殊だけど、機械学習の基本をざらっと勉強するのにはとても良いコンテンツだと思う。

[東大のGCIデータサイエンティスト育成講座演習問題](https://weblab.t.u-tokyo.ac.jp/gci_contents/)
かの有名な松尾先生の研究室が公開している演習問題集。
元は本だけど、この演習問題をやりこむだけで一通りのことが学べると思う。
Pandasの使い方の基本はここで勉強した。

[PythonとKerasによるディープラーニング](https://www.amazon.co.jp/Python%E3%81%A8Keras%E3%81%AB%E3%82%88%E3%82%8B%E3%83%87%E3%82%A3%E3%83%BC%E3%83%97%E3%83%A9%E3%83%BC%E3%83%8B%E3%83%B3%E3%82%B0-Francois-Chollet/dp/4839964262/ref=sr_1_31?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&keywords=%E5%88%9D%E3%82%81%E3%81%A6%E3%81%AE%E3%83%87%E3%82%A3%E3%83%BC%E3%83%97%E3%83%A9%E3%83%BC%E3%83%8B%E3%83%B3%E3%82%B0&qid=1567239105&s=gateway&sr=8-31)
Kerasの作者によるKerasの使い方を詳しく解説した本。
まだ全部読み切れてないけど、とりあえずシーケンスモデルが簡単に作れるというところまでは理解した。

※ちなみに、競艇については6隻立てだということと、枠順が小さい方が有利だということ以外、全く知らない素人です。

# データ収集
競艇は公式がWeb上で結果を公開しているので、ここからデータをスクレイピングしていくことにする。
なんて機械学習向けなんだ。
[https://www.boatrace.jp/owpc/pc/extra/data/index.html](https://www.boatrace.jp/owpc/pc/extra/data/index.html)

スクレイピングの知識があまり無かったので、競艇の予測に取り組まれている先人の知恵をお借りしました。。。
[【失敗談】スクレイピングを約１時間学んだだけで作業時間が激減した件。](https://qiita.com/gorilland/items/df9b3f835079db7147ec)

2020年4月分から1年分くらい遡ってデータを集めて、2020年5月の結果予測にチャレンジします。
私は広島東洋カープファンなので会場は宮島を選択しました。
黄金期が終わって年々苦しくなってきているカープですが、この選択が吉と出るか凶と出るか。

# データの前処理
集めたばかりの生データは、カテゴリ変数が文字列のまま入っていたり、欠損値があったりと、そのままでは使えなさそうなので、前処理を行います。
データの前処理、特徴量の作成は機械学習の最重要ポイントであると同時に、明確な答えが無く、何が正解か分からないけどとりあえずやってみる。

競艇の順位には、1-6位の他に、失格になった特殊ケースがいろいろあるようだ。
例えばフライイングだったり転覆だったり。
これらの値をどう扱うべきなのかははっきりしないけど、

- 除去する
- 他の順位に置き換える

あたりがぱっと思いつく。
今回はとりあえず、失格だから最下位みたいなもんだろうということで、6位に変換してみた。

```python
df.loc[df['result']=='S2', 'result'] = '06'
df.loc[df['result']=='S0', 'result'] = '06'
df.loc[df['result']=='F ', 'result'] = '06'
df.loc[df['result']=='S1', 'result'] = '06'
df.loc[df['result']=='K0', 'result'] = '06'
df.loc[df['result']=='K1', 'result'] = '06'
df.loc[df['result']=='L1', 'result'] = '06'
df.loc[df['result']=='L0', 'result'] = '06'
```

※ちなみに、これらのデータを除去するパターンも試してみたけど、予測精度はほとんど変わりませんでした。

ランクとかのカテゴリ変数はone-hot-encodingしておく。
one-hot-encodingの場合、例えば値が4つあるとして、3つが決まれば残りの1つは決定する。
よって、1つめの列は削除している例をよく見るので倣ってみる。

```python
rank_dummy = pd.get_dummies(df['rank'])
rank_dummy =  rank_dummy.drop(['A1'], axis=1)
merge_df = pd.merge(merge_df, rank_dummy, right_index=True, left_index=True)
```

数値データは標準化しておく。

```python
from sklearn.preprocessing import StandardScaler

sc = StandardScaler()
sc.fit(x_train)

x_train_std = sc.transform(x_train)
x_val_std = sc.transform(x_val)
x_test_std = sc.transform(x_test)
```

# 予測する対象
ここで少し立ち止まって、、何を予想するべきかを考える。
競艇は競馬と同様に、単勝、複勝、2連複、2連単、3連複、3連単、等があるらしい。
複勝はあまりにもリターンが少なそうに見えたので、それ以外を予測して、どれくらいリターンがあるかを検証してみる。

当てるべきは1-3位で、分類問題の予測結果が確率で返ってくるところに注目して、以下のような考え方で検証してみることにした。
何が正解なのかはこの記事を書いている今も分からない…

各レースの6隻に対して…

| 予測対象         | 予測方法 |
|:-----------------|:------------------|
| 単勝             | それぞれが1位かどうかを予測し、確率が最も高い1隻を購入する。     |
| 2連複            | それぞれが2位以内かどうかを予測し、確率の上位2隻を購入する。     |
| 2連単            | それぞれが2位以内かどうかを予測し、確率の上位2隻を順に購入する。 |
| 3連複            | それぞれが3位以内かどうかを予測し、確率の上位3隻を購入する。     |
| 3連単            | それぞれが3位以内かどうかを予測し、確率の上位3隻を順に購入する。 |




学習はとりあえずkerasを使ってニューラルネットワークでやってみた。
層の数とかドロップアウトの値とか、何パターンか試してみてそれっぽいのを選んだけど、これまた答えが分からない…
正則化パラメータやドロップアウトの値を変動させながら、最適なもの探すしかないかな。

```python
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout
from tensorflow.keras.regularizers import l2

model = Sequential()

model.add(Dense(128, activation="relu", kernel_regularizer=l2(0.001), input_shape=(x_train_std.shape[1], )))
model.add(Dropout(0.3))
model.add(Dense(512, activation="relu", kernel_regularizer=l2(0.001)))
model.add(Dropout(0.3))
model.add(Dense(512, activation="relu", kernel_regularizer=l2(0.001)))
model.add(Dropout(0.3))
model.add(Dense(512, activation="relu", kernel_regularizer=l2(0.001)))
model.add(Dropout(0.3))
model.add(Dense(512, activation="relu", kernel_regularizer=l2(0.001)))
model.add(Dropout(0.3))
model.add(Dense(1, activation="sigmoid"))

model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

history = model.fit(x_train_std, y_train, batch_size=512,epochs=35)
```

1位かそうじゃないかの予測では、87%くらいの精度が出た。

```python
# テストデータで評価
score = model.evaluate(x_test_std, y_test)
print("Loss : ", score[0])
print("Test accuracy : ", score[1])
```

```
36/36 [==============================] - 0s 2ms/step - loss: 0.3993 - accuracy: 0.8698
Loss          :  0.3993157148361206
Test accuracy :  0.8697916865348816
```

でもよくよく考えてみると競艇は6隻立てなので、例えば全ボートを1位じゃない、と予想したとしても、5/6 = 83.3%は当たることになる。
これと比べて87%という精度が高いのかどうかちょっと分からない。

実際の評価では、この0か1かの結果を使うのではなく、6隻のうち最も確率が高いものを1位と考えて購入することを検証する。

```python
predict = model.predict(x_test_std)
print('1位の確率')
print(predict)
```

```
1位の確率
[[0.39234224]
 [0.02431795]
 [0.06290536]
 ...
 [0.2238649 ]
 [0.05873641]
 [0.03973087]]
```

同じように、2位以内かどうか、3位以内かどうかも学習して、6隻の中での確率順に並べて評価した。
予測確率も大事だけど、一番大事なのは儲かるかどうか。世の中金である。
結果やいかに。

# 結果
予測した5月のレースは192レースあった。
全レースについて、予測した結果に従って購入した場合のリターンを合計した。
購入額は、どの分類でも192レース × 100円 = 19200円という前提。

全体的に当てずっぽうで買うよりはかなり高い的中率に見えるけど、回収率でみると全然ダメ…
2連複だけかろうじて回収率100%を上回ってるけど、ほぼ誤差みたいなものなのでやり直したらマイナスになる可能性も高そう。

| 予測対象 | リターン合計 | 回収率 | 的中レース数 | 的中率 | ※ランダムに買った時の的中率|
|:---------|:-------------|:-------|:-------------|:-------|:--------------------------------|
| 単勝     | 17660円 | 92.0%  | 109レース | 56.8% | 16.7% |
| 2連複    | 18440円 | 96.0%  | 62レース  | 32.3% | 6.7%  |
| 2連単    | 19430円 | 101.2% | 47レース  | 24.5% | 3.3%  |
| 3連複    | 15530円 | 80.9%  | 39レース  | 20.3% | 5.0%  |
| 3連単    | 13080円 | 68.1%  | 17レース  | 8.9%  | 0.8%  |




# まとめ
楽してお金を稼ごうという目論見はもろくも崩れ去った。
競艇は1枠がすごく有利なので、1枠が勝つような固いレースだけ予測できているんじゃないかと推測。

でも少なからず予測はできているので、機械学習ってすごいんだなとしみじみ思った。
知識なしの自分が適当に買ってもこんなに当たるとは思えないので。

今回はとりあえずニューラルネットワークだけ使ってみたけど、XGBoostとかの他の分類方法を使って検証してみるのもいいかもしれない。
とはいえ、特徴量を増やしたりしないとあんまり結果変わらないような気もする。

次はKaggleに挑戦してしばらく修行しよう。