BQML DNNで競馬予測してみる


BQMLでDNNとXGBoostとAutoML Tablesが使えるようになるというニュースがありDNNを試してみました。
なんとなくでそれなりにいい感じにできちゃいます。
https://cloud.google.com/bigquery-ml/docs/release-notes

データを準備する

説明変数と目的変数が1行に含まれているデータをBQに投入する。
私は趣味で集めていた競馬データを使ってみた。
競馬データを集めたい場合は以下の記事が大変参考になる。
データ収集からディープラーニングまで全て行って競馬の予測をしてみた

競馬の場合、以下を横持ちにしているデータを準備する。

  • 説明変数:レースの距離、会場、性齢、斤量、過去レースの結果、該当距離の成績、、等
  • 目的変数:着順(3着以内になるか否か)

明らかな連続値は数値型にして、あとは文字型にしておけばいい感じに学習してくれる。
(本当はもう少し丁寧に前処理した方が良いのだろうが、、)

モデルを作成する

CREATE OR REPLACE MODEL <モデル名>
OPTIONS(MODEL_TYPE='DNN_CLASSIFIER',
        ACTIVATION_FN = 'RELU',
        AUTO_CLASS_WEIGHTS = TRUE,
        BATCH_SIZE = 2048,
        DROPOUT = 0.1,
        EARLY_STOP = TRUE,
        HIDDEN_UNITS = [128, 128, 128],
        INPUT_LABEL_COLS = ['目的変数カラム'],
        LEARN_RATE=0.001,
        MAX_ITERATIONS = 500,
        OPTIMIZER = 'ADAM')
AS SELECT  
  <説明変数カラム>,
  <目的変数カラム>
FROM
  <table name>

大体1時間〜2時間くらいでモデルは作成できた。

各パラメータについて

上記のモデル作成の例は、公式ドキュメントに乗っているパラメータほぼそのままだが、
これでもそれなりのものができるっぽい

MODEL_TYPE

モデルのタイプを回帰(気温等の連続値の予測)か、分類(天気が晴れなのか雨なのか等のカテゴリ値)か選択する

MODEL_TYPE = { 'DNN_CLASSIFIER' | 'DNN_REGRESSOR' }

競馬の着順(3着以内になるか否か)はカテゴリ値の分類なので、DNN_CLASSIFIERにする

ACTIVATION_FN

活性化関数(ニューロンが発火するための計算式)を選択する

ACTIVATION_FN =  { 'RELU' | 'RELU6' | 'CRELU' | 'ELU' | 'SELU' | 'SIGMOID' | 'TANH' }

(どれ使えばいいんだ。。)

以下Wikipediaより

ReLU(ランプ関数)
2011年、Xavier Glorot らは隠れ層の活性化関数としてmax(0, x) を使った方がtanhやソフトプラスよりも改善するということを発表した。一般的にはこれはランプ関数と呼ばれるが、ニューラルネットワークの世界ではReLU (英: Rectified Linear Unit, Rectifier、正規化線形関数)と呼ばれる。Yann LeCunやジェフリー・ヒントンらが雑誌ネイチャーに書いた論文では、2015年5月現在これが最善であるとしている。発想としては区分線形関数を使った1次スプライン補間である。線形補間を参照。

2015年5月時点では最善だったっぽいので、とりあえずRELUにしてみる。

AUTO_CLASS_WEIGHTS

分類モデルのときだけ有効なパラメータ。
不均衡データ(例えば分類したいラベルにAとBの2種類があってAが90%みたいなとき)を分類するときにいい感じに重み付けしてくれるらしい機能。
今回の競馬分類はそれなりに不均衡データになると思うので、TRUEにする。
デフォルトはFalse。

機械学習における不均衡データの扱い方

AUTO_CLASS_WEIGHTS = { TRUE | FALSE }

BATCH_SIZE

DNNではミニバッチ勾配降下法というトレーニングデータをサブセットに分けて最適なパラメータを見つける手法が使われるようだが、そのときのサブセットのデータサイズ。

機械学習/ディープラーニングにおけるバッチサイズ、イテレーション数、エポック数の決め方

今回のデータの件数は100万件くらいなので、とりあえず2048でやってみる。

BATCH_SIZE = int64_value

DROPOUT

過学習を防止するために、学習を進める際に一定の確率でノードを無視する率。
少し調べてみると、0.5か0.1が多そう。BQML公式ドキュメントの例では0.1になってたので、とりあえず0.1でやってみる。

Dropout:ディープラーニングの火付け役、単純な方法で過学習を防ぐ

DROPOUT = float64_value

EARLY_STOP

学習の結果あまり改善がなかったときに、学習をストップさせるかどうか。
過学習防止や余計な計算リソースを使わないようにする。
デフォルトはTRUE。

EARLY_STOP = { TRUE | FALSE }

HIDDEN_UNITS

DNNの隠れ層における、層の数とユニットの数を指定。
隠れ層と隠れニューロンはいくつにするべきなのか?

(しかし適切な数がよくわからない。。)

BQML公式ドキュメントの例にならって、[128, 128, 128]にしてみる。

HIDDEN_UNITS = int_array

INPUT_LABEL_COLS

目的変数カラムを指定する

INPUT_LABEL_COLS = string_array

LEARN_RATE

学習のたびにどれだけ重み付けパラメータを更新するかという率。
高すぎるとうまく学習しなかったり、小さすぎると収束まで時間がかかりすぎる。

(どのあたりが妥当なのだろうか。。)

BQML公式ドキュメントの例にならって、0.001にしてみる。

LEARN_RATE = float64_value

MAX_ITERATIONS

最大イテレーション回数。
イテレーション回数はバッチサイズに対して、何回学習すると元のデータセット件数になるかという回数。
約100万件のデータに対してバッチサイズを2048にした場合は、500が適切っぽいのかな。

機械学習/ディープラーニングにおけるバッチサイズ、イテレーション数、エポック数の決め方

MAX_ITERATIONS = int64_value

OPTIMIZER

【2020決定版】スーパーわかりやすい最適化アルゴリズム -損失関数からAdamとニュートン法-

「損失をゼロにする」というゴールをなるべく効率よく到達してくれるものが最適化アルゴリズム です。

Adamの正体はモーメンタムとRMSPropの良いとこどり

(良いとこどりとは素晴らしい。とりあえずAdamにしよう)

OPTIMIZER =  { 'ADAGRAD' | 'ADAM' | 'FTRL' | 'RMSPROP' | 'SGD' }

モデルの評価

以下のように、説明変数カラム全部と目的変数カラムをもつWith句を作成し、
ml.evaluate関数にモデル名とWith句のテーブル名を渡す

with eval_table
as
(
select 
  <説明変数カラム>,
  <目的変数カラム>
FROM
  <table name>
)
select
 *
from
ml.evaluate(MODEL `<モデル名>`, table eval_table)

結果

以下のような結果が返ってくる

precision recall accuracy f1_score log_loss roc_auc
0.3089887640449438 0.7638888888888888 0.572736520854527 0.44 0.6899435430022666 0.6968951048951049

【入門者向け】機械学習の分類問題評価指標解説(正解率・適合率・再現率など)
機械学習でLog Lossとは何か
【ROC曲線とAUC】機械学習の評価指標についての基礎講座

どの指標を重視するのかは作るモデルごとに異なるみたいです。
(今回のモデルだと何を重視したらよいのか、、)

  • precision(正解率):正予測の正答率。今回の場合だと、3着以内と予想した馬が実際に3着以内になる確率
  • recall(再現率):正に対する正答率。今回の場合だと、実際3着以内になった馬の中で、3着以内と予想された確率
  • accuracy(正解率):全予測正答率。今回の場合だと全予測の中で、実際に3着以内予想と3着以内外の正解率
  • f1_score:適合率と再現率の調和平均(適合率と再現率のバランスを見るらしい)
  • log_loss:間違ったラベルを高い確率で出しちゃうと高くなるらしい
  • roc_auc:不均衡データを考慮した指標。高いほどいいっぽい

予測する

以下のように、説明変数カラム全部をもつWith句を作成し、ml.predict関数にモデル名とWith句のテーブル名を渡す

予測された分類結果は「predicted_目的変数カラム」の項目になる(予測項目がuriageならpredicted_uriageになる)
確率をもつ項目は「predicted_目的変数カラム_probs」になる(これはネストされた項目なので、UNNESTすることで扱いやすくなる)

with pred_table
as
(
select 
  <説明変数カラム>
FROM
  <table name>
)
select
  ~,
  predicted_目的変数カラム, /* 分類結果 */
  data.prob /*分類確率*/
from
  ml.predict(MODEL `<モデル名>`, table pred_table),
  unnest(predicted_目的変数カラム_probs) as data
where 
  data.label=1 /* 3着以内に入る確率だけ出す */

2020 年宝塚記念を予測するとこんな感じ

先週(2020/6/28)のG1宝塚記念を予測してみた。
並びは3着以内確率上位順。
1着は外しているが何となくいい線いってそうな気がする。
(ちなみにこのレース3連単 183,870円! です)

レース名 馬番 馬名 3着内確率
宝塚記念 5 サートゥルナーリア 0.82017
宝塚記念 16 クロノジェネシス 0.79715
宝塚記念 14 キセキ 0.75275
宝塚記念 12 モズベッロ 0.70728
宝塚記念 11 ラッキーライラック 0.69796
宝塚記念 10 メイショウテンゲン 0.66159
宝塚記念 1 トーセンカンビーナ 0.56264
宝塚記念 3 グローリーヴェイズ 0.55474
宝塚記念 7 ワグネリアン 0.52002
宝塚記念 18 ブラストワンピース 0.48493
宝塚記念 8 レッドジェニアル 0.48320
宝塚記念 15 スティッフェリオ 0.33708
宝塚記念 6 トーセンスーリヤ 0.33540
宝塚記念 13 ダンビュライト 0.32290
宝塚記念 17 カデナ 0.3186
宝塚記念 2 ペルシアンナイト 0.27348
宝塚記念 9 アドマイヤアルバ 0.14271
宝塚記念 4 アフリカンゴールド 0.09236

料金

BigQuery ML の料金によると、
CREATE MODELは毎月10GBの無料枠があり、それ以降は$250.00 per TBかかるようである。

しかし以下の画面のように実行見積もりは150MBだけど、実際は87GB使っちゃいましたみたいなパターンはどのような扱いになるのかイマイチよくわからない。。
(今の所BQMLの請求きてないので、見積もりのほうが優先されるのか。。)

その他参考

BigQuery MLにAutoML Tables、XGBoost、DNNが来たのでおさらい

The CREATE MODEL statement for Deep Neural Network (DNN) models