競馬予測システムでMLOps実践~回収率140%でぬか喜びした話~

要点

  • 競馬予測で儲けることを目的に、データ収集・モデル開発からMLOpsまで全部やってみたという記事です。
  • 過去5年間の競馬データをもとに学習し、回収率140%超えを達成しました!!!!!!!!

    • 釣りです。間違ってはいないが、最適ではない評価方法でこうなりました。時系列データのテストデータの評価って気を付けないといけないねっていう話題です。
  • 実運用を目指した機械学習システムを作っていく中で、要点が見えてきたのでまとめました。
  • 機械学習で解きたい問題を明確にする
  • 継続的データ収集
  • データバリデーション
  • モデル性能監視・可視化
  • パイプライン化して自動化

回収率140%..?(時系列データの評価方法って難しいねという話)

時系列データって時系列に沿ってトレインデータをテストデータを分ければいいと思っていたんですよね。ので、2015/01/01~2020/08/31のレース結果をトレインデータとしてモデルを作りました(内容は後述)。このモデルの評価のために、2020/09/01~2020/09/31をテストデータをして回収率を計算しました*1。結果は次のようになりました。

買った馬券数 投資額  勝ち数 配当金総額 利益 回収率
101 10100 4 14290 4190 140%

回収率140%です。ヤッター。しかし、良すぎる結果が出たら疑うことにしているので、この検証が正しいか調べてみました。すると、この検証はもの足りないことがわかりました。 こちらを参考にすると、時系列データの検証は3つあり、

  1. Train-Test Splits
  2. Multiple Train-Test Splits
  3. Walk-Forward Validation

私が行ったのは1のようです。より信頼性の高い方法として2,3があります。複数のテストデータを疑似的に作ることにより、より信頼性の高いモデルの評価をできるようになるようです。2を実際にやってみました。

トレインデータ期間 テストデータ期間 買った馬券数 投資額  勝ち数 配当金総額 利益 回収率
~2019/12/31 ~2020/01/31 8 800 1 430 -370 53%
~2019/01/31 ~2020/02/31 38 3800 0 0 -3800 0
~2019/02/31 ~2020/03/31 51 5100 1 930 -4070 18%
~2019/03/31 ~2020/04/31 65 6500 3 1830 -5670 28%
~2019/04/31 ~2020/05/31 48 4800 0 0 -4800 0%
~2019/05/31 ~2020/06/31 74 7400 3 1510 -5990 20%
~2019/06/31 ~2020/07/31 55 5500 1 410 -5090 20%
~2019/07/31 ~2020/08/31 79 7900 5 2170 -5730 20%
~2019/08/31 ~2020/09/31 101 10100 4 14290 4190 140%

もしnか月前にこのモデルを運用していたら、どうっだったか。という検証をnをずらしながらしているわけですね。一見してわかるように140%はたまたま引いたラッキーだっただけで、全然ですね。

競馬予測システムの作り方

モデルの高度化は今後の課題として、まずは運用までやってしまおうと思います。以下に概要を載せます。

f:id:wata101wata:20210101224209p:plain

netkeiba.comからレース結果をクローリングしてきます。
➁学習マートを作り、モデルを作成します。
➂モデルに予測対象レースのデータを投入し、どの馬券を買うか推論します。
④自分のメールアドレスに買い目データを送信します。
➄レース結果確定後、予測と実績を対照して、モデルの性能をモニタリングしていきます。
こんな感じです。このシステムを作っていく中で感じたことを書いていきます。

機械学習で解きたい問題を明確にする

今回の目的は馬券を買って利益を最大化することですが、これのどこに機械学習が役立つのでしょうか。また、そのためにどのようなデータが必要なのでしょうか。回帰をするのでしょうか分類をするのでしょうか。それを明らかにするために問題を明確にしてみます。 競馬は様々な馬券の買い方がありますが、今回は単勝だけ考えます。アイデアとしては、馬ごとの勝つ確率が分かれば、オッズと比較して買い目の馬券だけを買っていくことにより、利益の最大化を狙います。例えば、オッズが10倍なら、勝利確率が0.1以上なら買い目です。インスタンスの特徴量をx (馬や競馬場の情報)、単勝したかどうかのフラグをyとします。二値分類としてとらえると、勝利確率p機械学習で予測できそうです。オッズをx_oとすると、p * x_o >1 となるような馬券を買っていけば利益が最大化できる気がします。
ここまでいったん整理しておくと、

  • 二値分類を行う
  • 1レースで複数買う場合もあるし、1枚も買わない場合もある
  • 二値分類の精度が最終目的ではないので、評価は利益自体で行うほうが良い

などがわかります。この問題のほかの捉え方ではランク学習をして1位の馬券を買うアルゴリズム強化学習の問題に落とし込むことなども考えられるので、このようにまとめておくと認識を合わせることができ有用です。*2

継続的にデータを収集する

netkeiba.comよりクローリングしたHTMLを持ってきて、スクレイピングしてデータベースに突っ込んでいます。この場合、初回にデータ集めるときと、2回目以降データ集めるときでプログラムが変わってくるので注意が必要です。
例えば、レースのページならば更新されることはないので、初回のみクローリングすれば良いです。しかし、競走馬のページは、過去の出走データがあるので更新されます。全部の競走馬ページもう一回クローリングすることは無駄が多いので、更新すべき競走馬のみを抽出してクローリングするなどの工夫が必要です。
また、次のセクションにもかかわることですが、データを収集するたびにデータバリデーションを走らせました。

データバリデーションをする

このシステムを作っていて一番時間がかかったところが、「あれ、データおかしくない?」というところです。例えば、全部の馬のデータを取ったつもりが、昔の分がうまく取れていなかったり、1:1で結びつくデータのはずなのにかなり落ちてしまったりしていました。特に今回はWebページからのクローリングでデータを収集したので、失敗するトラップが多く、大変でした。 そもそも、データがどのような状態になったら収集がうまくいったと判断するのでしょうか?

  • 件数が過不足ないか
  • カラムの内容は正しいか
  • データの期間が想定内か
  • データが正しく結びつくか

などなど様々な観点が思い浮かびます。このような観点を自動テストで書いておいて、データバリデーションとして活用しました。
例を挙げると、レースのデータベースと競走馬のデータベースを正しく作成できたことを確かめるため、レースのデータベースに記録されている競走馬IDをすべて抜き出し、それらが競走馬データベースに含まれるかどうかをテストしています。
GoogleからTFDVというライブラリが出ており、単カラムの統計量やデータスキーマの検証をクイックに行うことができるので便利です。

モデルの性能監視・可視化を行う

モデルは運用を始めると劣化します。データバリデーションではデータの形式の変化やカラムごとの統計的性質の変化を検知しますが、それだけではモデルの劣化を防げません。機械学習モデルは特徴量xと 目的変数yの関数fを推定しますが、このfって時間で変わってしまいそうですね。これをコンセプトドリフトといいます。競馬で例を挙げると、

  • 体重の軽い馬に有利な走法が開発される。
  • 実はある競馬場では外側から走ったほうが有利だが、その競馬場が閉鎖される。

などなど様々な可能性が思いつきます。このような変化は、モデルの性能の劣化前に検知することは難しいこともあります。よって、予測がおかしくなり始めたことをいち早く察知して対応することが必要です。AWSとかでもこれに対応したサービスがありますね
今回は、愚直に利益をグラフにプロットすることにしました。Dashを使うと簡単にダッシュボードを作るとこができます。特に、pandasとの相性がよいと感じます。こんな感じに作りました。 f:id:wata101wata:20210102093615p:plain

これを毎週確認して、劣化していることが確認出来たらデータバリデーションやモデルの再開発などをします。

パイプライン化・自動化して運用を楽にする

今回のモデルは、出走直前にデータをとり、推論する必要があります*3。レースは一日中開催されるので、手作業でやろうとすると大変です。また、毎週レース結果をクローリングして再学習する必要があるので、それも手でやりたくないです。
要件としては最低限それぞれの.pyファイルの実行をスケジュールできれば十分と考えました。MLOpsにおいて最もメジャーなのは*4Airflow だと思いますが、今回はJenkinsでやりました*5

不労所得を目指して

僕も1億5000万円もうけたい

モデルの高度化

掛け金も最適化する

今回のモデルは掛け金の額はスコープ外でした。一旦、100円しか掛けない想定で評価をしています。買い目の程度によってこの辺は最適化できる気がしているので、そこもやってみたいです。

特徴量を追加する

パドックの様子を見て馬の調子を予測するのは一般的にするそうです。ということはパドックの映像も取り込めばもっと精度よく予測できるかも?

ドメイン勉強する

そもそも競馬場に行ったこともないし、競馬中継すら見てないです。逆にこのようなデータ分析をせずに買う馬券を決めるメカニズムを知らないです。競馬で儲けるためには、ほかのみんなの買い方をうまく捉えて活用する必要があるので、このメカニズムを知ることは役に立つはず。

運用の低コスト化

オートスケール

今はEC2をスポットで借りっぱなしにしてその中で開発から運用を行っています。ダッシュボードやjenkinsのために常時起動する必要はありますが、普段はもっと小さいインスタンスにしたて、ETLなどの重い処理をするときだけインスタンスを起動して分散処理することが考えられます。

分散処理

今は全部pandasでETLしており、メモリが余っているにも関わらずCPUが100%に張り付いてしまっています。こういう場合はメモリベースの分散処理するためにsparkを使えばいい(はず?)なので、これもありかと思っています。

別のドメインへ転用

機械学習システムの要素一通り触った感があるので、ほかのドメインへ低コストで展開できるかも。

競輪

netkeibaは競輪もやっている

感想

いままでもMLOpsやデータサイエンスの適用事例は追ってきていましたが、自分で作ってみることによって読み込める力が上がったような気がしています。システムを作るうえでの課題を身をもって感じたので、要点がつかめたことが理由でしょうか。
今回の競馬のように

  • 個人レベルで開発・運用できて
  • うまくいっているか実運用で評価することができる

機械学習システムの適用先ってなかなか思いつかないですよね。ほかにも見つけていろいろ作っていきたいです。

*1:1か月単位で再学習しようと思っていたため

*2:今回の方法だと、レースで出走するすべての馬の単勝確率を足しても1にならないので、和が1になるようスケーリングしました。

*3:モデルに確定オッズを使用しているため、なるべくオッズをそれに近づけたいから

*4:主観です。

*5:DAG書いて管理するほど複雑ではないと考えたため