u++の備忘録

Kaggle地震コンペ振り返り(public 5th -> private 212th)

2019年1〜6月にわたって開催されていたKaggleの「LANL Earthquake Prediction」コンペに参加し、銀メダルを獲得しました。

public LBの時点では賞金圏の5位につけていて、ドキドキしながら最終結果を待ち構えていました。

残念ながら最終結果は212位に転落しましたが、なんとか銀メダルも獲得でき、学びの多いコンペでした。

本記事では、通称「地震コンペ」とも呼ばれた本コンペを、筆者自身の視点で時系列的に振り返ります。

なお既にdiscussionにはアプローチを投稿しています(英語)。

www.kaggle.com

コンペの概要

扱うのは現実世界のデータではなく、実験室内での地震を起こした際の波形データでした。

  • 入力:波形
  • 予測対象:何秒後に地震が発生するか

より具体的な説明は、先に公開されている本コンペの振り返り記事をご参照ください。

note.mu

www.case-k.jp

コンペ参加(5月3日)

本コンペは1月から長らく開催されていましたが、参加を決めた直接のきっかけは下記のツイートでした。最初は興味本位でKernelを眺めただけでしたが、教育目的で公開されたKernelの内容が知見に富んでおり、コンペに惹かれていきました。特に、実際にモデリングが記述されているKernelではなくEDA的なKernelが素晴らしかったです。


ソロ参加期間

既にコンペ終了まで約1カ月で、既に3500チームほど参加していました。

特徴量

時間の節約も兼ねて、特徴量作成の部分は全て上記のKernelに任せ、モデリング部分で工夫していく決断をしました。(いま振り返ると、この時点で最終的な結果はある程度決まっていたと思います)

特徴量は800以上あったので、下記の2プロセスで389まで特徴量を削りました。

  • LightGBMのfeature importance (gain) 上位400件に絞り込み
  • adversarial validationのfeature importance (gain) 上位11件を削除

CV

CVは下記の設定を採用しました。「shuffle=False」も試しましたが、各Foldのスコアが大きくブレたので不採用にしました。

  • KFold
  • 8 fold
  • shuffle=True

本コンペは回帰問題でしたが、目的変数の大小をバランスよく分けるために、Stratified KFoldも検討しました。ただし、予測値の相関をKFoldと比較したところ0.998でほとんど変化がなかったので不採用としました。

  • Stratified KFold
  • 8 fold
  • shuffle=True
  • y = pd.cut(y_train['time_to_failure'], 8, labels=False)

モデル

モデルは {LightGBM, XGBoost, Catboost, MLP, RNN} を試しました。LightGBMが最もpublic LBスコアが良かったです。

本コンペでは、ハイパーパラメータを変えることで、かなりpublic LBスコアが変わりました。

  • objective: 'regression' -> 'gamma'
    • 1.433 -> 1.349
  • feature_fraction = max(0.1, sqrt(n_features)/n_features)
    • public LB score +0.004

feature_fractionは次のブログを参考にしました。

amalog.hateblo.jp

問題点

今回特徴量を作成したKernelでは、一部の重複を許してtrainデータを水増ししていました。その上でCVは単純に「shuffle=True」をKFoldを採用しているので、CVは当たり前のように過剰に良くなってしまいます。そのためLightGBMのearly stoppingが正しく機能しないと推察されたので、意図的にrounds数を決め打つ戦略を取っていました。

チーム参加期間(コンペ終了10日前ごろ)

ソロで30位以内に入っていたこともあり、当時金圏にいたmtmtさん(@mtmt14079706)とチームマージしました。最終的にはmtmtさんが既にチームマージを決めていたTelash(@TERACHIYANNU)さん含め、3人チームでコンペ終了まで戦いました。

マージ後は、主に次の2つに取り組みました。

  • お互いのアイディアを使いながらシングルモデルの精度を上げる
  • アンサンブルを試す

シングルモデルの向上

前者について、諸々の試行錯誤は割愛しますが、最終的には次の辺りを調整しました。

アンサンブル

後者はなかなか上手くいきませんでした。原因としては、1日のsubmit数が最大2と少なく、public LBに使われているデータ量も13%と少なかったことが挙げられます。特にstackingは、なかなかpublic LBスコアが向上しませんでした。

最終的に採用したアンサンブルは、勝手に命名した「rounds averaging」でした。先述した通りearly stoppingが使いづらかったので、次のようなrounds数が異なる複数モデルを作り、結果を平均しました。

  • lgbm training rounds = 10000 (public LBスコア: 1.263)
  • lgbm training rounds = 12000
  • lgbm training rounds = 14000
  • lgbm training rounds = 16000
  • lgbm training rounds = 18000
  • lgbm training rounds = 20000 (public LBスコア: 1.262)

6つを平均したモデルはpublic LBスコア: 1.259となりました。

最終submit選択

最終submitの2サブは、次の通りです。

  • ここまでここまで説明した6モデル rounds averaging
  • 上記の派生版:'mean'関連の特徴量を全て削除して学習したモデル

discussionや自らの試行錯誤を通じて平均値関連の特徴量が過学習の要因になりがちだと感じていたので、後者が比較的保守的なsubmitでした。

コンペ終了(6月4日)

終結果は冒頭でも述べた通り、212位の銀メダルでした。手持ちには金メダル相当のモデルもありましたが、チームで納得できる最終submitは選べたので、個人的には後悔はありません。

唯一の反省点を挙げるとすると、保守的なsubmitはもう少し攻めたモデルにしても良かったかもしれないと感じています。

public 28位からprivate 10位に入ったGibaさんがdiscussionで解法を非常に簡潔にまとめていました。

The equation used to post- process the top-10 solution was:
ypred = ypred * 6.51 / mean(ypred)

www.kaggle.com

本コンペではdiscussionでtestデータの可能性があるデータが考察されていて「public LBとprivate LBで分布の違うデータが使われている」と議論されていました。もちろん筆者のチームもその辺りを考慮した最終submit選択をしたつもりではあったのですが、private LBで上位に入った方々は完全にデータソースを決め打った戦略を採用していました。(自分たちのsubmitも、Gibaさんの方法で全体を約1.1倍するだけで金メダルに入るprivate LBスコアとなりました)

本質を見抜いて適切に戦う部分が大切なコンペだったので、特徴量生成をKernelに任せてしまった時点で、ある程度勝負は付いていたのかなと振り返って感じています。

おわりに

public LB賞金圏からの転落を体験したコンペでしたが、過学習の恐ろしさを身に沁みて実感でき、学ぶを深めることができました。チームメイトのmtmtさん、Telashさんに金メダルを渡せなかったのが残念なところですが、楽しいチーム戦でした。

本コンペでの銀メダル獲得をもって、Kaggle Masterになりました。今後とも精進していきます。