競馬予測：Google Cloud Functionsで実現する自動化戦略

# はじめに

12月24日は有馬記念です！競馬に興味がない人も知っているぐらい有名なレースですよね！  
私が今までに作った予測モデルや最適化結果も試したくなっちゃいますね！  
結構オッズとか変わるから、できるだけレースの直前まで何に賭けるか考えたいな～  

いや～、予測だけで一日潰れちゃいますね～
<br><br><br><br><br><br><br><br>
ん・・・？
<br><br><br><br><br><br><br><br>
クリスマス・・・
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/927654/9e027877-c583-217a-ef1e-4e291ead297e.png" width="500">
[^1]

[^1]: ChatGPT(DALL-E3)に作ってもらった「クリスマスだと気付いて驚く機械学習エンジニアの男性」の画像です。


# 動機
昨年開発した馬券最適化アルゴリズムは、レース開始直前のオッズを参照することが重要です。理由は、最新のオッズに基づいて計算する方がより精度の高い予測が可能となるためです。しかし、このアプローチには一つの課題があります。

私が注目している中央競馬は主に週末に開催されます。このため、レース直前にパソコンの前で最適化処理を行う必要がありますが、週末は他の予定が入りがちです。

たとえば、土日に外出することがある場合、その時間に馬券最適化を実行できないという問題が発生します。これは、競馬予測の精度に直接的な影響を与えるため、解決が必要です。

この問題を解決するために、私はレース直前に自動で馬券最適化を実行するアプリケーションの開発に着手しました。このアプリケーションによって、いかなる週末の予定があっても、最新のオッズに基づいた最適な馬券選択が可能になります。

以前に執筆した競馬に関する記事は以下の通りです。
機械学習モデル: [TensorFlowでRankNetを実装して、競馬の着順予測モデルを作りました。](https://qiita.com/_tamiya/items/3926684b6ba023ebf667)
最適化: [競馬予測モデルの結果を使った馬券最適化手法を検討しました。](https://qiita.com/_tamiya/items/746db7506b7ef4d69fd7)

# 全体像
## 現状
1. モデルの学習、推論で使用するデータをローカルPCからGoogle Driveにアップロード(JRA-VAN Data-labより)
1. Google ColaboratoryにてDriveのデータを読み込み、モデル推論。レース出走馬ごとの勝利確率を予測。
1. 単勝オッズを[netkeiba](https://www.netkeiba.com/)からスクレイピングで取得
1. 勝利確率と単勝オッズから馬券最適化を実施
1. 最適化結果を見て馬券購入

## 本記事の改良
1~2は同様
1. モデルの学習、推論で使用するデータをローカルPCからGoogle Driveにアップロード(JRA-VAN Data-labより)
1. Google ColaboratoryにてDriveのデータを読み込み、モデル推論。レース出走馬ごとの勝利確率を予測。
1. レースごとの発送日時と予測結果をJson形式でGoogle Driveに保存。
1. Google Schedularから5分毎にPub/Subのメッセージ出します。それにキックされてGoogle Cloud FunctionsがGoogle Driveの予測結果を確認。
1. 発走時間の5~10分前であれば、オッズのスクレイピングと馬券最適化を実行。
1. 最適化結果をLINE Notifyを使ってLINEに通知。

# 定期処理の実装

## 事前準備
参考サイト: [GCPプロジェクトの作成](https://cloud.google.com/appengine/docs/standard/python3/building-app/creating-gcp-project?hl=ja)

事前にGCPでプロジェクトを作成しておきます。

## Google Drive APIの設定
参考サイト: [Google Drive API設定](https://zenn.dev/spacegeek/articles/45270323b1d2bc)

認証情報を持つToken.jsonを作成します。  

## Cloud Schedulerの設定
参考サイト: [Cloud Scheduler](https://cloud.google.com/scheduler/docs/schedule-run-cron-job?hl=ja)

Cloud Schedulerで5分毎にPub/Subでメッセージを送るcronジョブを設定します。
注意点として、タイムゾーンは日本標準時を設定します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/927654/d66d2d2f-70fa-6657-2968-9d02005c9e55.png" width="400">

## LINE Notifyの設定
参考サイト: [LINE Notify](https://notify-bot.line.me/ja/)

LINE NotifyはWebサービスからLINEへの通知が可能になるAPIサービスです。
レース開始前の最適化結果をLINEに通知として送るために使用します。

参考サイトにてLINEアカウントの登録を行い、認証情報を取得します。

## Cloud Functionsの設定

参考サイト: 
- [Source Repositoryの設定](https://cloud.google.com/source-repositories/docs/create-code-repository?hl=ja)
- [Cloud Functionsのデプロイ](https://cloud.google.com/functions/docs/deploy?hl=ja#console)

Cloud Functionsにデプロイする方法はいくつかありますが、今回はCloud Source Repositoryを使います。

今回使用するコードなどはgithubで管理しているので、Source Repositoryでgithubの該当リポジトリを参照させます。

ここからCloud Functionsのコンソール上で設定します。
- 環境は**第1世代**を選択。第2世代にするとSource Repositoryからのデプロイができませんでした。
- トリガーは**Cloud Pub/Sub**を選択し、トピックは先ほど設定したCloud Schedularのジョブを設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/927654/e372062c-8776-6542-15c3-7829801f3532.png)

---
次にランタイムの設定も行います。
ランタイム環境変数に以下を追加します。
- TZ: Tokyo/Asiaに設定。現在時刻を取得する際に日本時間になります。
- GOOGLE_DRIVE_TOKEN: Google Drive APIにアクセスするための認証情報。(Token.jsonの中身)
- LINE_NOTIFY_TOKEN: LINE Notifyサービスで発行した認証情報
![cloud_functions_2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/927654/35599af2-1ed3-dd97-694b-ab05fbb7b57d.png)

---
デプロイするコードの設定を行います。
- ランタイムを**Python**に設定。
- ソースコードを**Cloud Source Repositories**に設定。
- エントリポイントをmain.py内で実行する関数名に設定。
- プロジェクトIDはSource Repositoryを作成したプロジェクトのIDを設定
- リポジトリ名はSource Repository内におけるリポジトリ名を設定。
- ブランチ名も設定。
![cloud_functions_3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/927654/61877e86-7e25-f575-e690-653a9640efa8.png)

# 実験
12月24日に実験します。
14:05に14:10に始まる中山9Rの馬券結果が返ってきました。(ハズレでしたが)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/927654/1c5327c8-37b0-634d-36c8-b97a007f49e3.png" width="300">
  
ここまでくるとseleniumを使った馬券購入の自動化まで行った方がよさそう？


# ChatGPTからの疑問
ChatGPTに今までの記事を入力として与えて、疑問点をまとめてもらいました。

## Q1: データの取得と利用
**「なぜGoogle Driveを経由してデータを取得するのですか？また、データのプライバシーとセキュリティはどのように保たれていますか？」**

A1:
Google Driveを使用する主な理由は、利用が容易なところです。
現状の課題の1つとして、JRA-VAN Data-labのデータ取得はWindowsのみで可能です。そのため、一旦使用PCでJRA公式から提供されているアプリからDBデータを取得し、それをGoogle Driveにあげてから、Google Colaboratoryで処理しています。
両者ともGoogleのサービスであり、親和性も高いです。

## Q2: オッズスクレイピングの合法性と倫理
「netkeibaからのスクレイピングは合法ですか？倫理的な側面はどうですか？」

A2:
netkeibaサイトにてrobot.txtはありませんでしたが、スクレイピングの頻度については十分に注意して行っております。今回作成した定期実行は5分毎に実行され、各競馬場にて同時刻のレースがあった場合にも3回程度であり、大量のリクエスト送るものではないと考えています。
[参考: 通信制限がかかった（スクレイビングについて）](https://support.keiba.netkeiba.com/hc/ja/articles/18841959592857-%E9%80%9A%E4%BF%A1%E5%88%B6%E9%99%90%E3%81%8C%E3%81%8B%E3%81%8B%E3%81%A3%E3%81%9F-%E3%82%B9%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%93%E3%83%B3%E3%82%B0%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6-)

## Q3: 自動化の具体的なメリット
「自動化を導入することの具体的なメリットは何ですか？手動プロセスと比べてどのような違いがありますか？」

A3:
自動化の最大のメリットは、時間と場所からの解放です。  
手動プロセスでは、レース直前にPCからColaboratoryで馬券最適化を実行する必要があり、外出時には実行することができませんでした。
スクレイピング、馬券最適化を自動化することにより、どの場所からでも最適化結果を確認することができます。

## Q4: 技術選定の理由
「なぜGoogle ColaboratoryやGoogle Cloud Functionsなど特定の技術を選んだのですか？」

A4:
Google Colaboratoryは無料でアクセスが容易なため、モデル開発において適しています。Google Cloud Functionsはサーバレスな定期実行においてコストが高くなく、実装も容易であるため選択しました。

## Q5: 馬券購入の自動化に関する検討
「seleniumを使った馬券購入の自動化を検討していますが、その利点と潜在的なリスクは何ですか？」

A5:
馬券購入の自動化を行うことで、さらに手動コストを下げることが可能です。一方で自動購入ではアルゴリズムのエラーなど意図しない購入などが想定されます。

# まとめ
ChatGPTくん、イイ感じにまとめてくれ

この記事では、競馬予測における新しいアプローチについて詳しく探求しました。主に、Google Cloud Functionsを用いた部分自動化戦略に焦点を当て、競馬の予馬券最適化プロセスの効率化を実現しました。

- **部分自動化の利点**：スクレイピングと馬券最適化の部分自動化により、予測モデルの活用が格段に向上しました。これにより、レース直前のデータを活用してより精度の高い最適化が可能になり、外出時でも最適化結果を活用できるようになりました。
- **技術的選択**：Google Cloud Functionsはそのスケーラビリティとコスト効率の良さから選ばれました。これにより、リソースを効率的に利用しつつ、技術的な柔軟性を保持できます。
- **今後の展望**：自動化の範囲を拡大し、将来的には馬券購入プロセスも自動化することを検討しています。ただし、これにはさらなる技術的な洞察と慎重な実装が必要です。

このプロジェクトは、テクノロジーが伝統的な競馬予測にどのように貢献できるかを示す一例です。Cloud Functionsなどのクラウド技術を活用することで、データ分析と予測の新たな可能性が広がっています。今回の取り組みが、同様のプロジェクトを検討している方々にとって有用な洞察を提供できれば幸いです。

# 最後に
有馬記念はかれこれ1時間以上予想しましたが、残念ながらハズしちゃいました。。。
ほぼリアルタイムで見てましたが、いいレースだったと思います。。。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/927654/6393dd8b-0e1b-8b19-c4ed-0f372afe253e.png" width="500">