Santenderコンペで、8位・金メダルという結果を残すことができました。 この記事では、Santanderコンペで金メダルをとるための手法や、コンペで金メダルをとった過程をまとめます。
santanderコンペ8位でフィニッシュしました!!
— カレーちゃん(専業Kagglerになりました) (@currypurin) 2018年8月21日
初めて真剣に取り組んだコンペで、ソロゴールドメダルという、ほぼ最高の結果をだすことができたのは、自分でも奇跡なんじゃなないかという感想です。
夢であり、目標のkaggleマスターに一歩ちかづくことができました! pic.twitter.com/rP7dhWx0US
Santanderコンペの概要
テーブルデータのコンペで
- トレーニングデータ 4459行 x 4993列
- テストデータは 49342行 x 4992列
の規模なので、データサイズとしてはお手軽な部類になると思います。
評価指標
Root Mean Squared Logarithmic Errortと言われるやつで、ログをとってtargetと比較します。
データの特徴
- 特徴が匿名化されているので、どの値が何をしめしているのか全くわかりません。
- とても0が多いデータです。だいたい以下の画像のような感じで、一部の箇所に0以外の数字が入っています。
リーク
上の画像のように、うまく並び替えると、tagetがわかることが、早々にディスカッションで共有されました。
例えば、1757行と511行は2個ずれになっており、1757行のtargetは、511行の115636.36の値から埋められるということです。
このコンペではこのリークを使い、うまく埋めることが求められるコンペでした。
Santanderコンペで金メダルをとった手法
- コンペでは、以下のQ&Aに書いたように、リークから7865行のtargetを埋めることができました。
- リークで埋められなかった行については、カーネルで公開されていた、パブリックLG1.37のカーネルのデータで埋めてサブミットしていました。
- 最後の1日で、リークで埋められない部分のスコアをあげようと思って、LightGBMのシングルモデルを作り、ローカルcv1.34のモデルまでできてサブミットしましたがパブリックLBは上がりませんでいた。
- あと10分あれば、次のツイートのモデルをサブミットできたのですが、時間切れになってしまったのが後悔です。
最後に、テストデータのリークで予想したところを、トレーニングデータに入れ込むモデルを作って、まぁまぁcv良かったんだけど、ラーニングレート0.1のやつしかサブミットできなかったのは少々残念。
— カレーちゃん(専業Kagglerになりました) (@currypurin) 2018年8月21日
Santanderコンペで金メダルをとるまでの過程を時系列で
開始直後6月から7月中旬
どうにもスコアがあがらず、PCAを試している人やランダムプロジェクションを試している人など、全員が1.3から1.4くらいのスコアで並んでいた時。
あとから考えるとこのときに、ノンリークモデルで良いモデルをつくり上げるておくと、最終的に勝てる可能性はあっがたのだと思う。
7月中旬
1人だけ異次元のスコアをだしている人が現れます。この時から、リークがあるのでは?とディスカッションで言われるようになります。
7月下旬
GIBA氏のこのディスカッションの投稿などから、行をまたがった時系列になっていることが共有され、リークがあることがわかり、「順位を上げる = リークをうまくみつける」というような状況になります。
https://www.kaggle.com/c/santander-value-prediction-challenge/discussion/61329
8月上旬
8月上旬から、私はHomeCreditコンペを一旦お休みし、Santanderコンペに注力することに。
Number of leaky rowsという、ディスカッションで、トレーニングデータとテストデータをどれくらいリークで埋められるかという情報が共有されるようになるので、ここの情報を注視していました。
Paradoxさんが、リークをみつけるコードを共有してくれたので、多くのひとがリークを見つけられるようになります。
私もこのコードを理解できるようになるまで、何度もメモを書きながら読みました。
def get_log_pred(data, feats, extra_feats, offset = 2): f1 = feats[:(offset * -1)] f2 = feats[offset:] for ef in extra_feats: f1 += ef[:(offset * -1)] f2 += ef[offset:] d1 = data[f1].apply(tuple, axis=1).to_frame().rename(columns={0: 'key'}) d2 = data[f2].apply(tuple, axis=1).to_frame().rename(columns={0: 'key'}) d2['pred'] = data[feats[offset-2]] d2 = d2[d2['pred'] != 0] # Keep? d3 = d2[~d2.duplicated(['key'], keep=False)] d4 = d1[~d1.duplicated(['key'], keep=False)] d5 = d4.merge(d3, how='inner', on='key') d = d1.merge(d5, how='left', on='key') return np.log1p(d.pred).fillna(0)
今から振り返ると、ここで最終順位が2位の方が書かれている、
To update, 3886 / 1.0 / 7.8k in test at the moment
(トレーニングデータは3886個を正解率100%で埋めることができ、テストデータは7800個を埋めることができたという意味)
との記載が、このコンペのリークの上限だったのかなぁと思います。
8月中旬
最後の週ですが、私は、どうしてもリークの正解率が100%にならずに、何度も何度も試行錯誤したりしていました。 なんとか次の改善を経て、リーダーボード金メダル圏内に入ります。努力が報われるのは本当に嬉しかったです。
- コードを間違っていてるから、正解率が100%になっていなかったこと
- テストデータに必要のない行が含まれていると、カーネルで共有されている行があったが、そのうちの一部は、必要な行であったこと
- 最終的に長さ40のextra_feats(次のQ&A参照)を108見つけることができたこと
Santanderコンペ、ついにゴールド圏内に!!最後まで頑張るぞ💪 pic.twitter.com/AgFBEQVfQ9
— カレーちゃん(専業Kagglerになりました) (@currypurin) 2018年8月18日
Q&A
いただいた、質問については、Santanderの質問まとめ(note)でまとめています。
https://www.kaggle.com/nulldata/jiazhen-to-armamut-via-gurchetan1000-0-56
上のカーネルの内容を知っていること前提の回答になってしまうのですが、リークを見つけるためには、extra_feats(上のカーネルではcolgroups)を見つけることが必要でした。
extra_featsをみつけるためのコードですが、以下のディスカッションのコードを参考にしました。 https://www.kaggle.com/c/santander-value-prediction-challenge/discussion/62189#370009
少し説明すると、
一番上の部分のコードで、行方向でひとつずれになっている部分を探します。するとつぎのようなリストを取得することができます。 [[a,b],[b,c],[c,d],[h,i],[i,j]]
次に、def chain_pairs関数を使い上記の列を結合していきます。すると、[[a,b,c,d],[a,b,c]・・・[h,i,j]]という重複を許したリストになります。
重複を許したリストに、def prune_chain関数を使うことにより、[[a,b,c,d],[h,i,j]]というような、重複がないリストになります。
この1.から3.により行方向でひとつずれになっているリストが得られるので、トレーニングデータを転置し、上記の1.から3.と同じ処理を行います。すると長さ40のextrafeatsを複数得ることができます。
なお、extrafeatsの数ですが、
- トレーニングデータのみだと、長さ40の列が98
- テストのみだと、長さ40の列が100
- トレーニング+テストを縦に連結すると、長さ40の列が108になりました。
上の写真は、トレーニングデータで検証した時のログなのですが、最終的にリークは正解率100%で3887見つかって、nonzero_meanで埋めると0.56のスコアとなります。
最終的に用いた重複を許さないextra_featsは以下のとおりです。なお短いextra_featsは使っても使わなくても結果に影響は及ぼしませんでした。
テストデータでは、次の数だけリークをみつけることができました。
感想
はじめてKaggleに真剣に取り組んでみての感想としては、
- 数カ月あっても、1人ではできることは限られるので、ディスカッションやカーネルの情報はとっても重要。
- Kaggleは英語なので敷居が高い気がするが、実はカーネルでコードが共有されているので敷居が低い。
- 初心者は、カーネルのコードをベースに、自分の改善を入れ込むことができる。
順位を上げるために重要だったこととしては、次の点でしょうか。
- ディスカッションで共有されている、基本的な点を抑える。
- 公開情報だけでは順位が上がらないので、自分だけの工夫を1つみつける
終わりに
なかなか、上手にまとめることができませんでした。
まずは次のコンペ(HomeCredit)を優先したいと思います。
HomeCreditが終わったら、また書き直したいです。
わかりづらいところがあれば、質問箱やコメントで質問ください。