目次
前回までで、異常検知モデル DeepSAD をまず使うことになったのだが、学習時のハイパーパラメータの設定でも試行錯誤が続いた。そもそもハイパーパラメータって時間や計算コストをかけて走査して、最適化するほどの見返りがあるのか?
「海もみえぬに船用意」の船、DeepSAD丸に、「渡りに船」と乗っている僕である。この上さらにハイパーパラメータが多い。「船頭多くして船山に上る」である。
そして、僕は乗り物酔いが酷いのだ。
batch_size パラメータ
PyTorch 系の深層学習モデルは、学習を始める前に決めなければならない設定が多い。DeepSAD も例外ではなく、公式のリファレンス※1を開くと、コンストラクタに12個の引数が並んでいる。
device や verbose のような、どこで動かすか・どれだけログを出すかの指定を除けば、モデルの構造と学習の仕方を決めているのは epochs / batch_size / lr / rep_dim / hidden_dims / act / bias の7つだ。たった7つでも組み合わせは多いし、どれも事前に正解が分かる類のものではない。
今回取り上げるのは、そのうちの batch_size である。学習データを一度に何件ずつ見てモデルの重みを更新するか、という数字だ。既定値は 64。
当時の僕が、このパラメータが効くと思っていたのは間違いない。実際にこのパラメータを動かすとモデルの精度が変わったのだ(未来の予想が当たるかどうかは別。過去データで当たるだけ)。このパラメータひとつのために Training each batch_size という名前の並列処理コードを作成し、各ボートレース場モデルの毎回の学習で走らせていた形跡があるからだ。
学習時間というコスト
その頃のローカルマシンは MacBook Pro 2021 だった。全ボートレース24会場、24個の DeepSAD モデルの学習を毎日実行していたら本業に支障が出る。このため、以前のエピソードで話したように AWS 上の Lambda に学習コードを実装し、Step Functions で並列実行していた。
この Lambda には、さまざまな batch_size を試すコードを実装していた形跡がある。毎日異なる batch_size を使って学習し、前に batch_size=64 で学習したモデルより、今回 batch_size=96 で学習した新しいモデルの方が良いかどうか比較する。そんなことをしていたようだ。
diana-boat-ml-type3/lambda_function.py のコードとコメントの痕:
today = date.today()
days_since_year_start = (today - date(today.year, 1, 1)).days
b_size = 12 + (days_since_year_start % 15) * 10
# b_size = (int(training_date) % 60) + event_index + 15
# b_size = (event_index * 4) + 15
# b_size = int((128 * (event_index / (7 + event_index))) + 15)
# b_size = int((256 * (event_index / (5 + event_index))) + 15)
# b_size = int( (2 ** event_index) + 15 )
b_size = b_size + event_index
if b_size > 96:
b_size = 12 + (b_size % 85)
...
model = DeepSAD(device='cpu',
batch_size=b_size,
epochs=600,
#type3 hidden_dims='128,64,64,32',
#type3a hidden_dims='78,64,64,32',
hidden_dims='78,48,32,16',
rep_dim=2, # feature 91次元版は `rep_dim=64〜120`
#type3,3a act='Sigmoid')
act='ReLU')
ボートレースは365日、毎日いずれかのレース場で開催されているため、毎日新しい特徴量データが追加されていく。本来であれば同一の特徴量データを使って、ハイパーパラメータを変えて学習したモデルを比較するほうが正確だろう。が、時間もコストもかかるしそうは言っていられない(実際コストがどれぐらいかかっていたかは、別の記事で書く)。
さて、ここでも僕は失敗をやらかす。
評価指標の勘違い
僕は batch_size を日々変更して最適な値とモデルを見つけようとコーディングをしていた。
過去のコードに assessBatchSize(assess=評価)という Lambda が存在していたので間違いないだろう。コードの中では score という値が max 最大となるモデルを選んでいる。
diana-boat-assessBatchSize/lambda_function.py:
max_score_item = max(event, key=lambda x: x['body']['score'])
そして学習側のコードで算出している score の正体は以下だった。accuracy_score と roc_auc_score の両方を算出しているのだが、評価で使っていたのは accuracy_score だった。
score = accuracy_score(y_test, y_test_pred_bin) # ← これでモデルを選んでいる
auc_score = roc_auc_score(y_test, y_test_pred) # ← 計算も保存もするが選択には使っていない
僕は船に酔っていたんだろう。AIや機械学習の参考資料やそのサンプルコードをよく考えずに流用していた。僕は今、荒れるレースを予想している。そしてそれは、少なくとも 12,000円を超える払戻しがあるレースだ。
これは最も単純な考え方だ。荒れるレースに3連単120全通り、100円ずつ、12,000円賭けて元が取れればいい。もちろん効率的には 120倍を超えるオッズがついている組み合わせだけに賭ければよいのだが、この当時はまだ自動投票機能を実装していないから、現実的には投票自体が難しくなる。(120倍の組み合わせを選んでいちいち投票できないよ!それに、この段階では怖くて実際には賭けてもいない。)
それでだ。accuracy_score とは何か。
たとえば 100レースの過去データがあるとする。戸田や唐津ボートレース場の実データと照らし合わせると、100レースだとおおよそ、
- 16レース:配当が 12,000円を超えるレース → 賭けたら儲けがでるレース
- 84レース:それ以外 → 賭けたら損するレース
accuracy_score(正解率)とは、「賭ける/賭けない」の判断が、100レース中何レース合っていたかの割合なのだ。つまり、賭けないレースも正解として数えることになる。
極端に言えば、AIや機械学習を使わずに単純に、「僕は100レース全部賭けない!」という判断をしても84レースは合っているので、84%も正解している!!となる。
僕はこの評価指標でモデルを判断していた。何十時間もAWS上の学習に費やして(Lambda ではGPUは使えないので時間がかかる)。ほとんどが荒れないレースと判定するモデルを、精度が高い!と叫んでいたのだ。
これに対して、roc_auc_score (AUC) は、荒れたレースが、荒れなかったレースより高い点数がついているかを測るため、まだ大穴検知として自然な評価指標といえる。みなさんもどの評価指標を選ぶべきかは、きちんと確認されたほうが良いだろう。2値分類以外ではもっと多様な評価指標を利用する必要がでてくる。
Optuna
Optuna(オプチュナ)※2 ※3の存在に気づいた時期は正確にはわからない。コード履歴をみると、学習コードに Optuna を取り入れたのは開発を開始して4ヶ月半後のようだ。単純に Google で「機械学習のハイパーパラメータの決め方」、「ハイパーパラメータのチューニング方法」などで検索し気がついたのだろう。
気づくのが遅すぎる。Optuna は、機械学習モデルのハイパーパラメータ自動最適化を行う Python フレームワークである。コード上で無駄な試行錯誤・紆余曲折、船酔いしていた自分と、Optuna の作者は同じ人類なのだろうか。心から感謝した。
そして、2024夏へ
ここまできて、日々のボートレース特徴量データの蓄積、異常検知=大穴検知モデルの学習とその精度を上げる仕組みが動き出すことになるのだが、大穴レースの検知精度は上がらない日々が続く。(築地の焼き鳥屋では、この頃から当たらないボートレース予想の話題はご法度である)
春先から、この大穴モデルを運用しつつ、3連単予想モデル(=クラス分類モデル)の構築という二兎を追い始める。
そして、、、2024夏、僕の生活にノストラダムス的大イベントが降りかかるのである。
つづく。
関連情報
- DeepSAD(DeepOD API リファレンス) DeepOD本文で触れた DeepSAD の引数と既定値の一覧。
- Optuna(公式サイト) Preferred Networksハイパーパラメータ自動最適化フレームワーク。日本製。
- Optuna: A Next-generation Hyperparameter Optimization Framework Akiba, Sano, Yanase, Ohta, Koyama(KDD 2019)Optuna の原論文。探索空間をコードで書きながら定義する設計と、見込みのない試行を途中で打ち切る枝刈りについて。
