プログラミング

機械学習で競艇予想をやってみた

1年くらい前から機械学習に興味を持ち、
ゼロから始めるdeeplearning や courseraのmachinelearningコースを
一通り学び終えたので、アウトプットを興味のある競艇でやってみました。

機械学習をするために必要なもの

まず、機械学習を行うために、最低限必要なものは何と言ってもデータです。
競艇のデータが公開されてないか探したところ、オフィシャルサイトにデータが置いてありました。

  • データ提供ページ
    https://www.boatrace.jp/owpc/pc/extra/data/download.html

私が選んだのはこの中の番組表と競走成績です。
ただ、ダウンロードページへのリンクはなくHTMLを解析してリンクを抽出しなければいけませんでした。
下記解析して抽出したそれぞれのリンクになります。

  • 番組表
    http://www1.mbrace.or.jp/od2/B/{YYYYMM}/b{YYMMDD}.lzh
  • 競走成績
    http://www1.mbrace.or.jp/odds2/K/{YYYYMM}/k{YYMMDD}.lzh

{YYYYMM}の部分は201804(2018年4月)のように、
{YYMMDD}の部分は180401(2018年4月1日)のように
それぞれダウンロードしたい月、日を指定して下さい。

私はpythonのバッチを作成し2012年4月1日〜2018年4月30日までをまとめてダウンロードを行いました。(ダウンロードを行う際はサーバに負荷をかけないように、1リクエスト毎にインターバル数秒お開け下さい)

尚、2012年4月1日以前のデータもありますが、2012年4月から持ちペラ制度というかなりレース結果に影響する制度が廃止になりましたので、私は2012年4月1日以降のデータのみを使用しました。

データの前処理及び訓練データの作成

競艇は場(開催場所)毎に水面の特徴があり、結果の偏りも変わってきます。
例えば、競艇は1号艇がかなり有利なのですが、
芦屋競艇場は1号艇が1着になる確率が65.4%に対して、
平和島競艇場は1号艇が1着になる確率は44.5%程になります。

ですので、今回は場を1つに絞り(常滑競艇場)機械学習を進めていきました。
常滑競艇場を選んだ理由は一番レース数が多く、データ量が一番多かった為です。
実際に使用したレース数は15108レースになります。

次にオフィシャルサイトから取得したファイルはそのままでは使用できないので、レースデータ、結果の抽出、及びデータの前処理が必要でした。

下記が、今回入力データに含めた特徴量になります。

特徴量備考
レーサーの年齢 
レーサーの体重 
レーサーのクラスA1=4,A2=3,B1=2,B2=1
レーサーの直近6ヶ月の勝率 
レーサーの直近6ヶ月の2連対率 
レーサーの直近2年の開催場の勝率 
レーサーの直近2年の開催場の2連率 
レーサーの使用モーターの2連率 
レーサーの使用ボートの2連率 
レーサーの今節1日目1レース目の結果データがない場合は1-6の平均値の3.5
レーサーの今節1日目2レース目の結果データがない場合は1-6の平均値の3.5
レーサーの今節2日目1レース目の結果データがない場合は1-6の平均値の3.5
レーサーの今節2日目2レース目の結果データがない場合は1-6の平均値の3.5
レーサーの今節3日目1レース目の結果データがない場合は1-6の平均値の3.5
レーサーの今節3日目2レース目の結果データがない場合は1-6の平均値の3.5
レーサーの今節4日目1レース目の結果データがない場合は1-6の平均値の3.5
レーサーの今節4日目2レース目の結果データがない場合は1-6の平均値の3.5
レーサーの今節5日目1レース目の結果データがない場合は1-6の平均値の3.5
レーサーの今節5日目2レース目の結果データがない場合は1-6の平均値の3.5
レーサーの今節6日目1レース目の結果データがない場合は1-6の平均値の3.5
レーサーの今節6日目2レース目の結果データがない場合は1-6の平均値の3.5

また、入力データに対する前処理もやってみました。標準化か正規化かどちらを選択するか迷ったんですが、今回は外れ値が存在しないので正規化を選択しました。正規化は下記公式を元にデータのスケールの違いを解消する方法です。

$$x’=\frac{x-x_{min}}{x_{max}-x_{min}}$$

実際に作成した入力データは特徴量が出場選手分ありますので、入力データの個数は21*6=126個になります。

次に、アウトプットのデータですが、一番簡単な単勝を予想してみることにしました。
競艇は6人でレースを行いますので、1−6のどれが1位になるのかを予想することになります。ですので、出力のパターンとしては下記の6つになります。

  • 1号艇が1位になる
  • 2号艇が1位になる
  • 3号艇が1位になる
  • 4号艇が1位になる
  • 5号艇が1位になる
  • 6号艇が1位になる

上記はそれぞれonehot表現にし、最終的なアウトプットデータは下記になります。

  • [1,0,0,0,0,0] <= 1号艇が1位になる
  • [0,1,0,0,0,0] <= 2号艇が1位になる
  • [0,0,1,0,0,0] <= 3号艇が1位になる
  • [0,0,0,1,0,0] <= 4号艇が1位になる
  • [0,0,0,0,1,0] <= 5号艇が1位になる
  • [0,0,0,0,0,1] <= 6号艇が1位になる

モデルの作成

今回はtensorflowの勉強ということもあり、scikit-learnやkerasは使用せず、tensorflowのみを使用しモデルを作成しました。
また、最適なモデルの隠れ層の数、及びノード数等は分からないので、いくつか試してみることにしました。

試したモデルその1

入力  126
中間層1 64
中間層2 32
出力層   6

試したモデルその2

入力  126
中間層1 200
中間層2 100
中間層3 50
出力層   6

また、中間層の活性化関数として、sigmoid関数、出力層ではsoftmax関数を活性化関数として使用し、損失関数としてcross entropyを用いました。

さらに、オプティマイザーは勾配降下法によるオプティマイザーを使用し、学習係数は0.001としました。

検証

下記検証結果です。

モデル1(中間層2つバージョン)

Loss: 1.3836538791656494, train_acc: 0.47594502568244934, test_acc: 0.4741089344024658

モデル2(中間層3つバージョン)

Loss: 1.3555550575256348, train_acc: 0.4744509160518646, test_acc: 0.4862138628959656

考察

中間層やノードを増やしてもあまり結果に違いは見れないようです。
私が検証に選んだ場の1号艇の勝利確率は60%ですので、それ以上は最低は欲しいところなのですが、50%弱と中々厳しい結果となりました。
今回は取り急ぎ試してみたかったので、そこまで結果に期待はしてませんでしたが、今後は、選手の得意コースや選手同士の相性、また、選手の性別等のカテゴリカルデータも含めて検証してみたいと思います。