kaggle

kaggleのSantanderで銀メダルを取れたので解法と反省点をまとめておく

3~4月の期間kaggleでSantanderというコンペに参加し、運よく銀メダルを受賞できました。

公開されている解法と自身の取り組み内容の振り返りをしておきます。

簡単なコンペ概要

本コンペはスペイン・マドリードに本拠を置くスペイン最大の商業銀行グループSantanderが主催していたコンペで、どのユーザーが特定の取引を行うかを予測するものでした。データと評価指標は次の通りとなります。

訓練データ:20万行×200列

テストデータ:20万行×200列

特徴量:全て匿名化された数値データ

評価指標:AUC

より詳しい内容を知りたい方は公式をご確認ください。

また、簡単なモデルでAUCが0.900とある程度の精度が出ていましたが、特徴量を追加したり引いたりアレコレしても中々0.901の壁を超えることができずにしばらく団子状態が続いていました。

 

終了2週間程前から0.902を突破する人がチラホラと現れ始め、discussionではどんなマジックが使われているのかのスレッドで盛り上がっていました。

公開解法のまとめ

まず今回のコンペの肝であるマジックに関してですが、大きく分けて3つの処理を指していたようです。

・テストデータに含まれていたfake dataの取り除き

・fake data を除いた上でのcount encoding

・特徴量同士の独立性の利用

 

上位者の解法はこちらでまとめられているので、ここでは自分が抜けてた点を中心にまとめていきます。

特徴量エンジニアリング

1位のチームはcount encodingを行い、そこから5つ観点で特徴量を作成したようです。

① 訓練データの中でTarget=1で値が2回以上現れるもの

② 訓練データの中でTarget=0で値が2回以上現れるもの

③ 訓練データの中で値がTargetの値によらず2回以上現れるもの

④ 訓練データの中で値がuniqueかどうか

⑤ 訓練データと本物のテストデータの中でuniqueかどうか

さらにuniqueな値をその特徴量の平均に置き換えたものを200個追加したようで、中央値やNANに置き換えもしたが今回は平均が一番良かったようです。

他のチームの解法を見るとuniqueな値をNANに置き換えたり、逆にuniqueじゃないものをNANに置き換えると上手くいったという人もいたので、結局は全部やる必要がありそうです。笑

count encodingを行い、ある値に有意性がある場合はここで上げた処理を一通り試す

EDA(Exploratory Data Analysis)

今回のコンペはEDAでデータを理解してから処理を行うことがめちゃくちゃ大事でした。

コンペの命運を分けたcount encodingはtest dataのfakeを取り除いた後でしか効果が薄かったので、fakeを見分けるkernel(List of Fake Samples and Public/Private LB split)がなければ大多数の人が0.901の壁を越えられなかったのではないかと思います。

実はもともとDescriptionのところにtest dataにはスコアに計上しない行があると記載があったんですね…。

Descriptionをちゃんと見る

紹介したkernelではtest dataの中でuniqueな値を持たない行をfake dataとすることで20万行→10万行×2に分割することができ、

さらにfake dataはreal test dataから生成されたものと仮定した上で、fake dataの値でreal test dataに1度しか現れない値がある場合は、そのtest dataをfake dataの生成器として判断することでprivate/publicの境界を見つけています。(試しにfakeとprivateのtargetを0にして提出しましたがスコアは変わりませんでした)

yaaku

スゴイ…

train data + real test dataへcount encodingを行うことで0.901の壁を超えることができますが、スコアを伸ばすためにはuniquenessの時ターゲットの分布が異なることを気付く必要がありました。

自分はヒストグラムで一応可視化はしていたのですが、ヒートマップで可視化したkernelがあり、こちらの方が断然わかりやすいですね。

データの可視化方法も色々試す

 

Preprocessing

Data Augmentation

今回は何故か特徴量をシャッフルしてもスコアが変わらないことが確認されていたので(ホスト側で既にシャッフルしていた?)、シャッフルによるデータのかさ増しも行われていました。

今後使えるのかは微妙ですがLightGBMでのData augmentationの実装を公開してくれている人もいて、私のような初心者には非常に参考になりました。

・augmentationする時はtrain dataのみに追加し、valid dataは触らない

・追加するpositive/negativeの比率は変えて実験する

もう一つData augmentationとして上位解法で上がっていたのがPseudo labelingでした。

(Pseudo labelの直観的な理解)

劇的にスコアは伸びないようですが、上位チームはやっている人が多かった印象です。

ポイントとしては最初の分類器の出力で極端なものだけを学習データに追加したようで、今回だと1位のチームはPositive 5000個、Negative 3000個で追加していました。

NNでのpseudo labelingの実装を公開してくれていたので、NNの勉強後に取り組んでみます。(今見てもよくわからない…)

Up-pivot

上位チームの中には一つの列に全ての特徴量(200個)を積み上げて4000万行のデータとして学習しているところが何チームかありました。

yaaku

スゴイ…(2回目)

 

特徴量は値、カウント、var_number、ターゲットの4つでvar_numberは特徴量の名前でカテゴリー情報として学習を行い、単体だとAUC~0.53と低いみたいですが20万行にデータを直してやると(group by mean又はliner modelを用いたaggregationで)AUC~0.925出るようです。

また積み上げる前に正規化(Standard scaling)を行うこと、単体の特徴量で学習させた際にAUCが0.5より小さいものは値を反転させたこと(正答が低いと反転させれば正答が高くなる?)、後は単体の学習でAUCが0.5に近いものは取り除いたようです(モデルに不要な相互作用を学習させないようにするため?)

さらにあるdiscussionのコメントにありましたが、積み上げを行わず単体の特徴量を用いて予測を行い(200モデル作成)、出力を2乗ルートすることでも精度が出たという人もいたりして、1つの発見に対して色んなやり方がありますね。

Stacking

先程のものと似ていますが、独立した特徴(オリジナルの値+カウント)を用いて予測を行い、その予測値を特徴量として学習をされている方もいました。

(9th place solution (nagiss part)) 

予測値を用いて学習を行う時はnum_leavesを2に設定することで1度しか分割せず、特徴量同士の相互作用をモデルが学習しないようにしていました。

自身の参加目的と取り組み内容

続いて自身の取り組みに関してですが、今回のコンペは銅メダルを取ることを目標にして

終了2週間くらい前までは勉強がてら色々試してみて、そのあとは結果に拘り泥臭く微妙な改善を繰り返していました。

どのレベルから始めたのか?

まず簡単に自分の技術的なレベルや経験に関して書きますと・・・

・本業はITスキル無縁のエンジニア(機械工学専攻出身)

・プログラムはVBAでマクロを組んでいた程度

・英語は人並みに読める

というレベルからのスタートになります。

情報系専攻ではないですが半年で銀メダル2枚取ることができたので、これから0から始める人も運と頑張り次第で銀メダルまでは取れるのかと思います。

コンペ参加~終了2週間前

3月の初旬から始めてましたが、特徴エンジニアリングが効かなく、割と何をやっていいのかわからない状態でした。

eloコンペではLightGBMしか使用していなかったのでモデル側をちょっといじってみようということでxgboostとNNの実装を行い一応それなりの結果が出るように。

GCPで(ubuntu上で)pytorchを使うまでに色々大変だったので記録しておく今回はpytorchをubuntu(16.04)で使用するのに行ったことに関して備忘録として残しておきます。 なお細かな技術的なこ...

その後は手あたり次第勉強も兼ねて色んな手法を勉強して実装していきました。

・K-means、UMAPでクラスター分け

・adversal validationで分類(ターゲット毎にデータを分けて分類後SHAPで可視化すれば何かわかるんじゃないかと試みた←何もわからず)

・mean target encoding, smoothing target encoding

・distillation neural network

・resampling(over sampling, under sampling, Tomek links, SMOTE)

・data augmentation(pseudo labeling)

…等々

コンペ終了2週間前

何の発見もないままコンペ終了2週間前になってしまい、これはマズイということでひたすらEDAをしていました。

データをじっと見ているとtargetが0と1の時でヒストグラムの幅が違うこと、kernelで公開されていたcountとtargetの比較から、カウント数絡みの特徴が効きそうに思えたので訓練データだけでcount encodingをしてみました。

しかし、精度はあまりでず…。確か0.901くらいだったと思います。

ここで再度real test dataを含めてcount encodingすると、ついに0.901の壁を突破できました。

これで2000位→73位まで上がりましたが、スコアが微妙すぎてこれがマジックなのか?という気持ちでした。

コンペ終了1週間前

まず大阪のkagglerの方がタイムリーにまとめてくださっていたkaggleの取り組み方のスライドが非常に参考になり、その中で相関がある特徴量同士の比率を取ることが多いという記載があったので、カウント数と特徴量の値の相関が高いことから掛け算、割り算の特徴量を入れるとスコアが改善しました(感謝)

その後はaggregation、カウント数の平均など、ひたすら特徴量と作りましたが改善せず。

上位陣はスコアがジャンプしていた(0.901突破→0.910→0.920)人が多かったことから、何かしら重要な発見をしなければ上位に食い込むのは厳しい感じでした。

自分なりに思いつくことは全部やって何とか銀圏を死守。

唯一効いたのは特徴量選択(boruta 800個→550個)とxgboostで葉の数を減らすとスコアが伸びていましたが、前述したように葉を増やしすぎると相互作用を学習してしまうのが原因のようですね。

反省&所感

今回のコンペでは、count encoding後の処理(unique値の場合分け)と特徴量同士の独立性の利用に関する処理の仕方にトップとの差があったかと思います。

またモデルもNN系を取り入れてスコアを上げているチームが非常に多かったので金メダルを取るのはまだまだ厳しいなという印象です。

とはいえ、銀メダルを獲得できたので無事kaggle expertになりました。

今回のコンペもですがやってみないと効果があるか分からないということが非常に多い(自分はほとんどな)ので思いついたことは全部やる必要性を感じました。

またコード資産を増やして定石的な処理は一瞬で終わらせ、いかに創造的なアプローチを考える時間を増やすかが大事だと実感したので、今回のコンペで行った処理を次は1から作るようなことがないようにする必要があります。

次はNN系が弱いことから認知処理系のデータを扱うコンペに参加していこうと思います。

コンペ参加した皆さんお疲れさまでした!