カレーちゃんブログ

Kaggleや競技プログラミングなどのこと

Santenderコンペで金メダルとるまでの過程・コンペのまとめ

Santenderコンペで、8位・金メダルという結果を残すことができました。 この記事では、Santanderコンペで金メダルをとるための手法や、コンペで金メダルをとった過程をまとめます。

Santanderコンペの概要

テーブルデータのコンペで

  • トレーニングデータ 4459行 x 4993列
  • テストデータは 49342行 x 4992列

の規模なので、データサイズとしてはお手軽な部類になると思います。

評価指標

Root Mean Squared Logarithmic Errortと言われるやつで、ログをとってtargetと比較します。
f:id:currypurin:20180821172447p:plain

データの特徴

  • 特徴が匿名化されているので、どの値が何をしめしているのか全くわかりません。
  • とても0が多いデータです。だいたい以下の画像のような感じで、一部の箇所に0以外の数字が入っています。 f:id:currypurin:20180821173024p:plain

リーク

f:id:currypurin:20180821173716p:plain 上の画像のように、うまく並び替えると、tagetがわかることが、早々にディスカッションで共有されました。
例えば、1757行と511行は2個ずれになっており、1757行のtargetは、511行の115636.36の値から埋められるということです。

このコンペではこのリークを使い、うまく埋めることが求められるコンペでした。

Santanderコンペで金メダルをとった手法

  • コンペでは、以下のQ&Aに書いたように、リークから7865行のtargetを埋めることができました。
  • リークで埋められなかった行については、カーネルで公開されていた、パブリックLG1.37のカーネルのデータで埋めてサブミットしていました。
  • 最後の1日で、リークで埋められない部分のスコアをあげようと思って、LightGBMのシングルモデルを作り、ローカルcv1.34のモデルまでできてサブミットしましたがパブリックLBは上がりませんでいた。
  • あと10分あれば、次のツイートのモデルをサブミットできたのですが、時間切れになってしまったのが後悔です。

Santanderコンペで金メダルをとるまでの過程を時系列で

開始直後6月から7月中旬

どうにもスコアがあがらず、PCAを試している人やランダムプロジェクションを試している人など、全員が1.3から1.4くらいのスコアで並んでいた時。
あとから考えるとこのときに、ノンリークモデルで良いモデルをつくり上げるておくと、最終的に勝てる可能性はあっがたのだと思う。

7月中旬

1人だけ異次元のスコアをだしている人が現れます。この時から、リークがあるのでは?とディスカッションで言われるようになります。

f:id:currypurin:20180822005748p:plain

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見つけることができたこと

Q&A

いただいた、質問については、Santanderの質問まとめ(note)でまとめています。

f:id:currypurin:20180821152126j:plain

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

少し説明すると、

  1. 一番上の部分のコードで、行方向でひとつずれになっている部分を探します。するとつぎのようなリストを取得することができます。 [[a,b],[b,c],[c,d],[h,i],[i,j]]

  2. 次に、def chain_pairs関数を使い上記の列を結合していきます。すると、[[a,b,c,d],[a,b,c]・・・[h,i,j]]という重複を許したリストになります。

  3. 重複を許したリストに、def prune_chain関数を使うことにより、[[a,b,c,d],[h,i,j]]というような、重複がないリストになります。

  4. この1.から3.により行方向でひとつずれになっているリストが得られるので、トレーニングデータを転置し、上記の1.から3.と同じ処理を行います。すると長さ40のextrafeatsを複数得ることができます。

なお、extrafeatsの数ですが、

  • トレーニングデータのみだと、長さ40の列が98
  • テストのみだと、長さ40の列が100
  • トレーニング+テストを縦に連結すると、長さ40の列が108になりました。

f:id:currypurin:20180821153543p:plain 上の写真は、トレーニングデータで検証した時のログなのですが、最終的にリークは正解率100%で3887見つかって、nonzero_meanで埋めると0.56のスコアとなります。

最終的に用いた重複を許さないextra_featsは以下のとおりです。なお短いextra_featsは使っても使わなくても結果に影響は及ぼしませんでした。

f:id:currypurin:20180821155631p:plain

テストデータでは、次の数だけリークをみつけることができました。

f:id:currypurin:20180821161007p:plain

感想

はじめてKaggleに真剣に取り組んでみての感想としては、

  • 数カ月あっても、1人ではできることは限られるので、ディスカッションやカーネルの情報はとっても重要。
  • Kaggleは英語なので敷居が高い気がするが、実はカーネルでコードが共有されているので敷居が低い。
    • 初心者は、カーネルのコードをベースに、自分の改善を入れ込むことができる。

順位を上げるために重要だったこととしては、次の点でしょうか。

  • ディスカッションで共有されている、基本的な点を抑える。
  • 公開情報だけでは順位が上がらないので、自分だけの工夫を1つみつける

終わりに

なかなか、上手にまとめることができませんでした。
まずは次のコンペ(HomeCredit)を優先したいと思います。
HomeCreditが終わったら、また書き直したいです。

わかりづらいところがあれば、質問箱やコメントで質問ください。

peing.net