黒魔術への招待:Neural Network Stacking の探求
この記事は Kaggle Advent Calendar 2020 の16日目の記事です。去年ネタ記事*1を書いたので今年は真面目なやつにしました。
はじめに
Kaggler はコンペにおいてあらゆる手段を用いて評価指標の改善を目指します。特徴量エンジニアリング、モデルや学習手法の試行錯誤、特殊な前処理・後処理の考案、はたまた Leakage の利用に至るまで、ルールを破らない範囲であれば何でもする*2のが Kaggler です。今挙げた例はそのコンペ固有の性質(データの生成過程・分布、評価指標、... etc.)に着目することで大きな効果をもたらす場合が多いのですが、一方でいずれのコンペにおいても一定の効果が得られる手法があります。それは複数のモデルの予測結果を統合して予測を行う Ensemble です。Ensemble は統合するモデルに多様性があるほど性能が向上すると一般に知られており、(別々のやり方で)競い合っていたライバル達とチームマージして順位を一気に伸ばす参加者もしばしば見受けられます。このため、コンペ終盤にメダルの border が一気に上がって参加者が苦しむのも Kaggle でよく見られる光景です。
記事のタイトルにも入っている Stacking は、大雑把に言うと Ensemble の中でも各モデルの予測値を特徴量として新たなモデルを学習させる手法のことを指します。過去コンペの solution*3*4 で行われた Stacking の複雑さから†黒魔術†なんて言われたりもしますね。注意深く扱わないと local data に overfit して大惨事(Big Shake Down)になりかねませんが、時に強力な決め手ともなり得る手法だと考えています。 (Stacking の詳細や注意点については Kaggle Ensembling Guide*5 や Kaggle 本*6 の "7章 アンサンブル" をご覧ください。)
本記事では Stacking の中でも比較的新しい部類である(と僕が勝手に思っている) Neural Network を用いた Stacking をいくつか紹介し、特に Stacking するのが面倒な Multi-Label Classification のタスクで比較検討して行きます。
この記事が参考になりそうな方:
- NN の扱いをある程度心得ており、学習・推論が一通りできる Kaggler
- 仕組みとか気にしないのでとにかく score を上げたい Kaggler
- NN を用いた Stacking に興味がある Kaggler
この記事を読まない方が良いと思われる方:
- Kaggle 初心者、もしくはこれから Kaggle 始めたいって方 (闇に魅入られてはなりません)
- Stacking の理論的な部分に興味がある方(僕は気分でやってるだけなので何もわかりません...)
- Ensemble は甘えだと思っている玄人 Kaggler (精進するので許して...)
目次
Stacking を Neural Network でやると何が嬉しいの?
理論も何もなくただのお気持ちですが、大きく二つの利点があると思っています。
一つは、Multi-Class Classification、Multi-Label Classification、Multi-Value Regression といった、複数の値を予測するタスクで Stacking を行うのが楽だということです。これは別に Stacking に限った話ではないのですが、GBDT などでこれらのタスクを行う場合は one-vs-all の Model を Class ごとに作る羽目になって管理等々がめんどくさいです*7。NN はここら辺非常に楽で、しかも タスク間で weight を共有するので Multi-Task Learning の効果が見込めます。
もう一つは NN を使うと予測値の関係性を明示的に構造に入れ込めることで、今回 CNN や GCN を使っている最大の理由です。GBDT 等に学習させる場合は Stacking 元の Model の出力を一列に並べて入力することになりますが、予測値の関係性(「この値は この model の予測値である」、「この値とこの値は別々のモデルの同じ class に対する予測値である」)が失われてしまいます。GBDTなら勝手に関係性を取ってくれるはずだという期待はありますが、明示的に与えられるなら与えてしまって良いと思います。
もちろん別に NN 以外でも予測値の関係性を入れ込むことは不可能では無いと思いますが、一個目の理由から特に Multi-Label Classification になってくると NN で Stacking するのが一番楽なのは間違いありません。
使用する Competition Data
最近まで (2020-10-01 ~ 2020-12-01) 開催されていた Mechanisms of Action (MoA) Prediction のデータを使用します。僕もこのコンペにチームで参加していました。
Task が Multi-Label Classification であったこと、Ensemble が非常に有効であったこと*8、そして何より Stacking 手法の工夫だけでどこまで score を上げられるのか検証したかったからこのデータを選びました。
コンペの詳細についてはこの記事では触れませんが、以下の振り返り記事(Kaggle Advent Calendar 2020 の 2日目の記事です) で簡潔にまとめて下さっているので是非ご覧ください。
Stacking に使用する Model
ここからは今回 Stacking に使う Model 達*9を図と共に紹介します。MoA の Task は 206 Class の Multi-Label Classification で、ここでは 5 つの Model の出力を Stacking Model に入力する体で図を作成しました。つまり、各 Stacking Model の入力は 5 x (206, ) としています。
MLP
ただの Multi Layer Perceptron です。各 model の出力値を 1 列に並べて入力します。 一列に並べてしまうので「この値とこの値は同じ class に対する予測値である」といった情報は失われてしまいます。
ここでは全結合層が3層で Dropout を 0.2 としていますが、あくまでハイパラなのでコンペによっては違う値を使った方が良い場合もあると思います(これより後に紹介するモデルも同様です)。個人的には2層で Dropout 0.5 が好きなんですけど結局タスク次第になっちゃいますね。
1D-CNN
僕の所属チームの solution で使いました。本当は次に述べる 2D-CNN も使いたかったんですが時間が無かったので省エネでやった感じ。予測値 (5 model x 206 class ) を (Channel, Sequence) = (Class, Model) = (206, 5) の形に変形して 1D-CNN の入力とします。 カーネルサイズ 3 の畳み込みを使用し、モデル方向の解像度を減らしていく方式です。
土壇場で試したのですが思ったより上手く行きました。少し注意して頂きたいのは、大きさ 3 の畳み込みフィルタを 長さ 5 の 系列(モデル方向) に適用するため、モデルの出力を並べる順番によって性能が変わる可能性があるということです。本当に有効だったかは不明なのですが、このコンペでは複数の seed で averaging を取るのが有効だったため seed ごとにモデルの順番をシャッフルする作戦を執りました。
2D-CNN
4th solution でも使われていた手法です。上の 1D-CNN との対比のために少しだけ実装を変えている*10のでそこはご留意ください。1D-CNN では Class を Channel と見なしていたのに対して 2D-CNN では Channel として新しい軸を作ります。具体的には、予測値 ( 5 model x 206 class ) を (Channel, Height, Width) = (1, Class, Model) = (1, 206, 5) に変形して 2D-CNN の入力とします。カーネルサイズは (1, 3) で、上で述べた 1D-CNN と同様にモデル方向の解像度を減らしていく方式です。
4th solution でも触れられていますが、僕もこの手法は iMet2019(通称:壺コンペ)*11 で知りました。モデルの並び順が性能に影響する可能性がある以外は非常に使い勝手が良いやり方だと思っています。
GCN
前述の 2D-CNN は iMet2019 で Multi-Label Classification の Stacking のために用いられていましたが、別の方法を試す Kaggler も居ました。僕です。結果的には自業自得でひどい目にあったのですが そのときの solution では Gated-GNN を用いた Stacking を行っていました。今回の実装は TReNDSコンペ*12 に参加したときの物
を流用していますが、Chainer から PyTorch に移植するのが大変だった & 時間が足りなかったので簡易的な実装に落ち着きました。
「なんで GNN 使ったの?」って何度か聞かれたことがあるのですが、node に class を割り当てると node から node への伝播が class 間の関係性として捉えられて良いんじゃないかな~と思ったのが発端だった気がします*13。未だにちゃんと分析したこと無いので、何かしらのデータでやってみたいと言いつつ 1年半が過ぎました😇
結果
Stacking 元の Model の Score
先に今回使用した 5つの Model の score だけ紹介しておきます。Single Model の Private Best が0.01624 (644th ~ 695th に相当) ですが、Stacking によってどれだけ伸びるのか乞うご期待。
name | Public | Private |
---|---|---|
TabNet | 0.01840 | 0.01627 |
ResNet | 0.01862 | 0.01646 |
ThrNN | 0.01836 | 0.01624 |
NN(drugCV) | 0.01833 | 0.01627 |
ThrNN(drugCV) | 0.01841 | 0.01626 |
これらは僕が MoA 参加中に最後の 2 sub で Stacking に使用したものと同様なので、各モデルの概要については solution をご覧ください。自分で一から用意するのは大変なので僕が作った ThrNN 以外はチームメイトからお借りしました。Team90s の皆さん、改めてありがとうございます!
因みに、今回一番精力的に動いて下さっていた sinchir0 さんと Team90s のリーダーを務めて下さっていた takapy さんも Kaggle Advent Calendar 2020 に参加しているのでどうぞお楽しみに。
各 Stacking Model の Score
それでは本題です。紹介してきた Stacking Model の結果は以下のようになりました。一行目と二行目は比較用の単純平均(AVG)と Weight Optimization(WO) です。学習については drug id を用いた Split*14 による 7 fold CV での学習を 5つの seed で行って averaging しています*15。
name | Local CV | Public | Private | Rank(Private) |
---|---|---|---|---|
AVG | - | 0.01824 | 0.01614 | 235th ~ 437th |
WO | - | 0.01831 | 0.01618 | 522nd ~ 541st |
MLP | 0.016232 | 0.01829 | 0.01617 | 500th ~ 521th |
1D-CNN | 0.016123 | 0.01829 | 0.01610 | 88th ~ 119th |
2D-CNN | 0.015817 | 0.01827 | 0.01609 | 38th ~ 64th |
GCN | 0.016089 | 0.01829 | 0.01607 | 20th ~ 24th |
このスコアを見て「このモデルが一番 Multi-Label Stacking で一番使えるぜ!」と言うのはなかなか難しいところ。そもそもこのコンペは Train, Public Test, Private Test の分布がそんな均質でなかったくさいですし、一般的なデータかというと大分特殊なデータだったと思います。 でも、予測値を一列に並べてしまって入力の関係性を考慮しない MLP よりも、入力の関係性をモデルの構造に入れ込んでいる CNN, GCN の方がMulti-Label Stacking で性能が出そうというのは言っても良いのかな~って思います(願望込み)。
少し裏話をするとこの記事のための実験の過程でそれなりにハイパラ探索をしたのですが*16、 Private の最良の値は↑の表の通りではありません(CV, Public, Private のいずれもが良い感じだったものを載せています)。それを踏まえた私見としては基本的に MLP < CNN == GCN という関係性は変わらないのかなという感じ。GCN が Private で謎の伸びを見せていますが原因はよくわかっていなくて、何より簡易実装で終わってしまったのでもうちょい改善の余地があると考えています。個人的には GCN 使うの好きなのですが実装が面倒なところあったり tuning の勘所も掴めていないので難しいです。
どれが一番いいかという話はこのコンペの結果だけで結論付けられないですが、この中でどれか一つをお薦めするなら 2D-CNN です。実装も簡単ですし、主観ですが実験やってる中で一番学習とか score が安定していました。モデルの入力順の問題はありますが逆に言えば入力順を変えることで同じ構造でありながら多様性が出せるので、shuffle することでお手軽にロバスト性が上げられるかもしれません。もっと差分が出てたら GCN 推したかったんですが微妙なところですね。
Take Me Higher
ここで終わりと言いたいところですがまだやれることがあります。僕の所属チームは最終日に MLP, 1D-CNN, WO を Averaging することで 34th まで駆け上がりました。
データを選んだ理由にも書きましたがこのコンペは Ensemble が恐ろしく有効です。 では、今回作成したモデルたちを混ぜれば更なる高みに行けるのでは...?
というわけでシンプルに上記の Stacking Model 達 (+ Weight Optimization) を平均して submit してみます。果たして結果は ...!?
・
・
・
・
...これだけ言わせてください。
Team90s の皆さん、本当にすみませんでした!!!!
終わりに
はい、というわけで†闇の力†を用いることで Single Model でのベストである 644th ~ 695th 相当から 1st ~ 2nd 相当まで score を伸ばすことが出来ました。最後の averaging でここまで伸びるのは正直予想外だったので本当に驚いています。
これを読んだあなた、黒魔術を使いたくなってきたのではありませんか?順位を圧倒的に伸ばす力が、金メダルが、欲しいんでしょう...?
冗談はさておき、冒頭でも述べたように Stacking は諸刃の剣です。今回のコンペのように凄まじく効果を発揮する場合もありますが、コンペによっては Local CV・Public LB はめちゃくちゃ良かったのに Private LB でどん底に叩き落されたなんて話はいくらでもあります。闇に呑まれた者が身を滅ぼすのは世の常ですね。
またこれは初心者の方がこの記事を読まない方がいいと書いた理由でもありますが、Stacking だけに注力するようなことは絶対に止めた方が良いです。Ensemble を行う以前にデータをちゃんと見て Single Model を作ることの方が遥かに重要だと思います。今回の記事で MoA コンペの元データの性質にほぼ触れていなかった(split CV で drug id の話をしたくらい)ことからわかるように、Stacking だけやっていてもそのコンペのデータだからこそ得られる固有の学びは少ないので、まずはちゃんと良い Score を出す Single Model を作ることから始めるべきです。「少し Score の悪い Model でも捨てないでいた方が Ensemble の伸び代が増す」というのはよく聞く話ですし今回の例でも少し Score が悪い奴が貢献していますが、それは良いモデルと混ぜるから効果があるのであって、Score が悪い Model だけ混ぜても良い結果は得られないと思います。
打つ手が無くなってどうしても順位を伸ばしたいとき、魔が差してしまったとき、黒魔術に手を伸ばしてしまう瞬間がきっと訪れるでしょう。そのときに破滅を迎えるかどうかはあなた次第です。用法・用量を守って正しく使い、Kaggle を楽しんで行きましょう!
付録
各 Model の 学習・推論の Notebook 等は全て公開してあります。お目汚しですがご参考まで。
Discussion:
Averaging の Notebook:
各 Stacking Model の Notebook:
name | Local CV | Public | Private | Notebook | Weight |
---|---|---|---|---|---|
MLP | 0.016232 | 0.01829 | 0.01617 | Link | Link |
1D-CNN | 0.016123 | 0.01829 | 0.01610 | Link | Link |
2D-CNN | 0.015817 | 0.01827 | 0.01609 | Link | Link |
GCN | 0.016089 | 0.01829 | 0.01607 | Link | Link |
*1:https://tawara.hatenablog.com/entry/2019/12/02/225525 (spa 行きたい)
*2:個人の信念にもよる
*3:KDD Cup 2015 1st Solution: https://speakerdeck.com/smly/techniques-tricks-for-data-mining-competitions?slide=47
*4:Otto Group Product Classification Challenge 1st Solution: https://www.kaggle.com/c/otto-group-product-classification-challenge/discussion/14335
*5:https://mlwave.com/kaggle-ensembling-guide/
*6:Kaggleで勝つデータ分析の技術: https://gihyo.jp/book/2019/978-4-297-10843-4
*7:GBDT は Multi-Class を 1 Model でやっているように見えますが、内部的には Class ごとに Model を作っていたはず
*8:僕の所属チームは Stacking によって順位を 200 位くらい上げました
*9:本当はあと 2 model くらい行きたかったのですが GPU quota が枯渇したので諦めました😇
*10:モデル方向とクラス方向を入れ替えている
*11:iMet Collection 2019 - FGVC6: https://www.kaggle.com/c/imet-2019-fgvc6
*12:TReNDS Neuroimaging: https://www.kaggle.com/c/trends-assessment-prediction
*13:実のところ今回の実装では実現できていません
*14:https://www.kaggle.com/c/lish-moa/discussion/195195
*15:普段はこんなに fold も seed も増やさないのですが、今回のコンペではかなり有効でした
*16:60 sub ぐらいしました