Kaggle Eloコンペの振り返り・上位解法まとめ
KaggleのElo Merchant Category Recommendationコンペに参加しました。
僕は@kasuminkoさん、@hirokasさんとチームを組んで、ラスト2週間だけ参加しました。結果から書くと、Public 221位からのPrivate 2220位という乱高下で儚く散りました。
手元に銀メダル相当のスコアを持っていたにもかかわらず、間違ったサブミットを選んでしまっており、とても悔しいです。
コンペの概要
Eloというブラジルのカード会社が主催となり、各ユーザーの購買行動に対応したRoyalty Scoreという値を予測するコンペです。
データとしては各カードの属性を書いたtrain.csv
/test.csv
と、それぞれのカードに紐付いた購買履歴(transaction)を表すhistorical_transactions.csv
/new_merchant_transactions.csv
の4ファイルが与えられていました。
タスクの種類は値を当てる回帰で、targetの分布は0を中心とした分布に加え、-33付近に外れ値が存在するというものでした。
評価指標はRMSEで、外れ値の影響を受けやすい指標です。特に今回は-33.22付近とかなり外れた位置にサンプルが存在するため、外れ値をどう捌くかが大きな論点のひとつでした。
上位解法
1st place solution (30CrMnSiA)
Discussion: https://www.kaggle.com/c/elo-merchant-category-recommendation/discussion/82036
解法のコアはシンプルで、外れ値を予測する2値分類モデルを使って、外れ値(-33.22)と外れ値抜きで学習した予測値を加重平均するというものです。
Kernelにも分類モデルで外れ値の確率を出すという手法が投稿されていました。 しかし、そのアプローチは「外れ値の確率が低いものについては、外れ値抜きで学習させたモデルでの予測値と差し替える」というものでした。 1位解法はKernelの手法をよりシンプルにしたバージョンと言えるでしょう。
この手法の肝は、外れ値予測モデルが十分精度が高いことです。 僕が持っていた外れ値予測モデルはAUC: 0.904程度でしたが、こちらの方のモデルはAUC: 0.914とだいぶ差があります。
特徴量
分類モデルで重要だった特徴はHome Credit Default Riskの17th place solutionで書かれていたものです。
transactionのテーブルにtrainのテーブルをマージしてモデルを作り、予測値を得ることでtransactionの各行に対して予測値を得ます。これをmeta featureとしてmin、max、meanなどで集約して特徴量にします。
モデル
今回のタスクは回帰ですが、targetを[0, 1]
の範囲に正規化し、xentropy lossを使うことで分類問題のように扱うことができます。
これはAvitoコンペで行われていた手法で、評価指標がRMSEであってもxentropy lossで訓練した分類モデルの方が良い精度を出しています。
5th place solution (Evgeny Patekha)
Discussion: https://www.kaggle.com/c/elo-merchant-category-recommendation/discussion/82314
特徴量
- 数千の特徴量から特徴量選択→約100特徴量
- target encodingを利用
- targetの値そのものを使ったり、外れ値かどうかのバイナリを使ったりした
- 特徴量の組み合わせに対してtarget encodingを行った後、最近のものほど重要視されるように重みをつけて集約した
モデル
- 全データをつかった回帰モデル
- 外れ値予測のモデル
- このモデルの予測値が低いデータと高いデータで2つの回帰モデルを作った
- 予測値が高いデータに対するモデルには、外れ値予測モデルの予測値自体を特徴量に含めた
7th place solution (senkin)
Discussion: https://www.kaggle.com/c/elo-merchant-category-recommendation/discussion/82055
特徴量
hist
のみ、hist+new
、authorized_flag=1
のみ、などの組み合わせで8つのデータセットを作成new.purchase_date.max()
/hist.purchase_date.max()
が良い特徴だった- カテゴリ変数の系列をtf-idfをかけた後、TruncatedSVDで次元削減して5次元に落とした特徴
merchant_id
やmerchant_category_id
などをWord2Vecで埋め込んだ特徴- transactionのテーブルにtrainのテーブルをマージしたmeta feature
- 1st place solutionと同様
- null importanceを利用した特徴量選択
モデル
1st layerに12個のLightGBMと40個のニューラルネットワーク、2nd layerにもLightGBMとニューラルネットワークを用いてスタッキングを行った後、平均を取った。
その他
- [Isotonic Regression]でカリブレーションを行った。CVは上がったが、PublicとPrivateは両方落ちた。
- targetを
[0, 1]
に正規化し、xentropy lossで訓練した(1stと同様)
11th place solution (Zakaria EL Mesaoudi)
Discussion: https://www.kaggle.com/c/elo-merchant-category-recommendation/discussion/82127
特徴量
- 1000以上の特徴量を含んだ特徴量セットを2つ作った
- 手である程度選んだあと、追加で特徴量選択にかけた
- https://towardsdatascience.com/a-feature-selection-tool-for-machine-learning-in-python-b64dd23710f0
- 欠損値を多く含むもの、他と相関が大きいもの、importanceが低いものなどを削った
モデル
- LightGBM
- CatBoost
- XGBoost
- Random Forest(H2O)
- GBDT(H2O)
でつくった32個のモデルをBayesian Ridgeでスタッキングした。
その他
- 外れ値かを当てる分類モデルを4つ作り、各モデルで100個ずつ外れ値を予測した。4つのモデルが全て外れ値だとしたものを-33に置き換えた。
- 後処理をしたものとしていないものを最終サブミットにした
16th place solution (nlgn)
Discussion: https://www.kaggle.com/c/elo-merchant-category-recommendation/discussion/82166
特徴量
- コルモゴロフ-スミルノフ検定を利用し、trainとtestで分布の違う特徴量を削除
- 2つの母集団の確率分布が異なるかどうかを調べる検定
scipy.stats.ks_2samp
- https://www.kaggle.com/c/elo-merchant-category-recommendation/discussion/77537
- null importanceを利用し、意味のない特徴量を削除
- 7th place solutionと同様
- https://www.kaggle.com/ogrellier/feature-selection-with-null-importances
- feature importanceのランキングで、上位90%、80%、……と切って検証
コルモゴロフ-スミルノフ検定はライブラリに加えておきます。
モデル
- 全データで学習したモデルの予測値
- 外れ値抜きで学習したモデルの予測値
- 外れ値かを予測するモデルを利用して修正した予測値
の3つをBayesian Ridgeを利用してブレンドした。
18th place solution (pocket)
Discussion: https://www.kaggle.com/c/elo-merchant-category-recommendation/discussion/82107
特徴量
- Kernelと同様の集約特徴量
- transactionの最終日がmagic featureだった
authorized_flag==0
やcity_id==-1
かどうかで絞った集約- 最近のtransactionのみを集約
- magic featureを予測するモデルを作り、その誤差を特徴量にする
magic featureを予測するモデルを作り、誤差を特徴量にするのはHome Creditコンペでも有用だった手法ですね……
モデル
- スタッキングの2nd layerはシンプルなモデル(Ridgeなど)の方がLightGBMなどより良かった
- 外れ値かどうか、外れ値抜きでの予測値、全データでの予測値をRidgeにかけた
上位解法のうち、今後も使えそうなアイディアのメモ
- 回帰のときも、targetを
[0, 1]
に正規化してxentropy lossを利用して分類に落とし込む - 最近のものに大きく重みを付けたtarget encoding
- コルモゴロフ-スミルノフ検定を利用した特徴量選択
- null importanceを利用した特徴量選択
- magic featureを予測し、本来の値との誤差を特徴量にする
僕たちの解法
特徴量
時間が少なかったので、Kernelに上がっていたものに加えて、new_merchant_transations.csv
およびhistorical_transactions.csv
に含まれるカテゴリデータに対してCount Vector、NMFを行ったものを特徴量に加えました。
意味のなさそうな変数(カテゴリ同士のsumやprodなど)を手で取り除いたあと、null importanceに基づいて特徴量選択をしました。最終的に200特徴量ほどになりました。
モデル
LightGBMとXGBoostを重み4:1でアンサンブルしました。
後処理
全データで学習した回帰モデルのうち、外れ値の確率が低いものを外れ値抜きで学習したものに差し替えました。
また、外れ値の確率が十分高いものを-33.22で置き換えました。
いかにして僕たちが銀メダルを逃したか
予測値を-33.22で置き換えるのはリスクの大きい賭けであることは分かっていたので、最終サブミットは後処理を行ったものと行っていないものの2つを提出する予定でした。
最終日の朝8:00に起きた僕はKernelを確認し、ハイスコアなKernelが上がっていないことを確認して一安心しました。
次にDiscussionを確認したところ、CPMP氏がDeleted Postという投稿を書いているではありませんか! 慌ててページを開いたところ、どうやら上位ソリューションがフライングで公開されていたそうです。
念のためついていたコメントを全部読んだところ、その消された上位ソリューションの要約が貼られていました。大まかに言うと、外れ値を当てるときは2値分類でやるんじゃなくてランキング学習的にやるといいよー、みたいな感じでした。
このコンペでは外れ値の扱いが大事だと思っていた僕は慌ててLightGBMを利用したランキング学習の方法をググり、コードを書き、実験を回しはじめました。動悸がヤバかったのを覚えています。
で、もちろんその実験は終わるわけもなく、9:00を迎えました。
蓋をあけてみるとどうでしょう。最終提出に選ばれていたのは、どちらも後処理を行ったサブミットだったのです。ナイアガラの滝の如く落ちた順位。選ぶはずだったサブミットは銀メダル相当でした。
これが僕たちが銀メダルを逃した一部始終です。
みなさんは8:55くらいになったら「My Submissions」のタブを更新し、正しいサブミットが選ばれているかどうかを確認しましょう。
それでは……