見出し画像

[競馬予想AI] ランク学習完結編~チューニングからアンサンブル学習~

前々回からランク学習による競馬着順予想モデルについて紹介しております。前回はランク学習モデルを3つ用意してアンサンブル学習を行ったことについて紹介しました。

この記事で使用したモデルはまだハイパーパラメータのチューニングを行っていませんでした。なので今回はチューニングを行った上で改めてアンサンブル学習を行った結果を紹介し、ランク学習についてはここで一旦区切りをつけることとします。

結果としては、3モデル中2モデルの性能向上に成功し、アンサンブル学習による効果も多少上昇しました。

hyperoptとは?

最適なハイパーパラメータを探索するにあたって使用する方法としてグリッドサーチがあります。グリッドサーチはいわゆる総当たりで全パラメータの組合せを調べる方法です。パラメータの種類と数が増えると探索パターンは指数的に上昇して計算コストが増えることが容易に想像できると思います。

そこで、Pythonのhyperoptを使用します。ベイズ最適化による方法で最適なパラメータの組合せを探索します。すべての組合せを調べることなく最適値の探索を行うのでパラメータの種類と数が増えても高速に処理することが可能です。

hyperoptはある関数f(x)が最小となる変数xを探索するイメージです。ここでピンと来た人もいるかもしれませんが、コレ、まさに機械学習が行おうとしてることなんですよね。機械学習はある損失関数を最小とするような各パラメータを探索してモデルを構築するのでやってることはほぼ同じです。
つまり、簡単な最適化問題であればhyperoptだけでも実は解けるということです。もし興味を持った方はhyperoptで色んなものを最適化してみてください。

hyperoptでハイパーパラメータを探索する

hyperoptの使い方についての詳細は省略します。こちらの記事が参考になるかと思います。

今回最適化する項目はたったの2つです。1つはランク学習特有のパラメータと、もう1つは学習率です。2つの場合、グリッドサーチでもいい気がします。ちなみにxgboostを使ったモデル作成の時にもhyperoptを使用しています。この時は5つのパラメータを最適化しています。

今回最小化する関数はNDCG@3とします。
NDCG@3はランク学習における性能評価指数であり、この値が大きいほど性能が高いことを指します。「あれ?最小化したらダメなのでは?」となりますが、探索時はこの値をマイナスにして返してやればNDCG@3を最大化することと同じになります。

以下のコードは最小化したい関数です。関数の戻り値は学習によって求められたNDCG@3の符号を反転したものです。
trainのオプションでevals_resultを指定しておくと各種指標にアクセスできます。

def score(params):
   print("Training start:")

   N_boost_round = []
   Score = []

   lgb_results={}  #履歴格納用
   lgtrain = lgb.Dataset(train_data, train_target, group=train_query)
   lgvalid = lgb.Dataset(val_data, val_target, reference=lgtrain, group=val_query)
   lgb_clf = lgb.train(
       params,
       lgtrain,
       num_boost_round=1000,
       valid_sets=lgvalid,
       valid_names=['train','valid'],
       early_stopping_rounds=20,
       verbose_eval=5,
       evals_result=lgb_results
   )

   return {'loss': -1.0 * lgb_results['valid']['ndcg@3'][lgb_clf.best_iteration], 'status': STATUS_OK}
  

以下のコードはパラメータ探索スペースを指定したものです。

def optimize(trials):
   #探索スペース
   space = {
       'objective': 'lambdarank',
       'metric': 'ndcg',
       'lambdarank_truncation_level': hp.uniform('lambdarank_truncation_level', 10, 20),
       'ndcg_eval_at': [1,2,3],
       'learning_rate': hp.uniform('learning_rate', 0.01, 0.1),
       'boosting_type': 'gbdt',
       'random_state': 777,
   }

   max_evals = 50      #探索回数(25くらいで十分)
   best = fmin(score, space, algo=tpe.suggest, trials=trials, max_evals=max_evals)

   print("best parameters:", best)

lambdarank_truncation_levelのパラメータは10~20の一様分布として定義、学習率も0.01~0.1の一様分布として定義しています。

パラメータには「大体これくらいの値におちつくよ」というものが存在します。例えばlambdarank_truncation_levelというパラメータはデフォルトで20が指定されており多くの場合で特に変更する必要はないとされています。なので今回は10~20で見当をつけています。機械学習における学習率は0.001~0.1以内をとることが多いと思います。今回の場合は0.001では小さすぎたので0.01~0.1で見当をつけています。このように探索スペースの指定は経験則的なものありますがあまり広すぎない範囲を指定するのがミソです。
ちなみにですが、学習率のような10の-n乗~10のn乗の範囲を取るようなパラメータはuniformではなくloguniformで指定するべきです。

探索回数ですがこれもだいたい20回くらいで十分と言われています。多くても50回くらいではないでしょうか。

探索はものの数分で完了します。正直、学習前や学習後のデータ整形の方が数倍も時間がかかります。hyperopt感謝します。探索中はゲームでもして待ちます。

アンサンブル学習の結果

ハイパーパラメータの最適化が完了したら各モデルの性能評価と再びアンサンブル学習をしましょう。今回も重み付き平均多数決の2通りのアンサンブル学習をさせました。アンサンブル学習の詳細は前回の記事をご覧ください。
結果は以下の通りです。前回の結果も再掲しておきます。

▼今回の結果

アンサンブル結果(モデル最適化後)

▼前回の結果

アンサンブル結果2(最適化前)

赤文字は各馬券の中で最高のモデルの値を示します。
オレンジ背景は今回と前回の結果で良かった方を示します。

前回の結果ではモデルB一強のような状況でしたが、今回モデルのパラメータチューニングを行ったことによりバランスの良い結果になりました。また、モデルAとモデルCにおいては一部項目では的中率が下がっているものもありますが全体としてはわずかに性能が向上したように思います。

アンサンブル学習の結果について見てみると、複数馬が関わる馬券において各モデル以上の性能を発揮しているように見えます。
ワイド、馬単、3連複においてアンサンブル学習の結果が優れているのは各モデルの強みをうまく活用できた結果ではないでしょうか。

まとめ

今回はモデルのハイパーパラメータの最適化とアンサンブル学習を行いました。

ハイパーパラメータの最適化によりわずかではありますが確実に性能の向上が見られました。アンサンブル学習においては性能が向上したと単純に評価できませんが、複数馬の関わる馬券においてモデル単体より高い性能が出ているのは興味深かったです。

実際に着順を予想する場合は、それぞれのモデルの強みを考慮して選択したほうがよさそうです。

今後の予定

今回でランク学習については一旦区切りとします。なので、近いうちにこれまでの成果として少しずつ実際の着順予想を行えたらと思っています。現在は場当たり的にプログラムを組んでいるところもありますので、速やかに予想ができるようにプログラムを改良していきます。

次回からはいよいよ深層学習を使っていきます。どのような結果が出るか楽しみにお待ちください。

よろしければ今後の研究・開発のためにサポートをよろしくお願い致します。

よろしければサポートをよろしくお願い致します。いただいたサポートは今後の技術向上のために書籍費用等に当てられ、このnoteで還元できればと思います。