天色グラフィティ

機械学習やプログラミングでいろいろ作って遊ぶブログ

KaggleのHome Creditコンペで銀メダルを取った話と、チームで動く際のノウハウとか

書く書くといっておきながらなかなか書かないでいたらGoogle Analyticsコンペが始まってしまいました。慌ててこの参戦記を書いています。

Home Credit Default Riskコンペに参加し、166位で銀メダルを取りました!

僕は同じ研究室の@sugawarya、東大松尾研のみなさまとチームを組んで参戦しました。

f:id:ejinote:20180917002544p:plain

僕は2枚めの銀メダルを獲得し、Kaggle Masterまであと金メダル1枚というところに来ました。就職するまでにKaggle Masterになっていたいものです。

さて、ここからコンペの振り返りをしていくのですが、ありがたいことにKaggle始めたての方も多少見てくださっているようですので、少し丁寧に書こうと思います。また、今回はじめての大きなチーム戦で試行錯誤したので、チームで進める際にこんなことをするといいんじゃないかなーと思うことも最後に書きました。

コンペ概要

Home Credit Default RiskコンペはKaggleで行われたコンペのひとつです。 Home Credit社は、信用の積み重ねが足りずに融資を受けることができない顧客にも融資を行う会社で、今回のコンペは債務不履行(デフォルト, default)になる顧客を予測する、というものです。

与えられたデータは、

  • application_train/test
    • それぞれの行が融資の申込みを表し、それが不履行になるかどうかを予測するのが今回のタスク
  • bureau
    • Home Credit社以外のクレジットビューローでの融資情報
  • bureau balance
    • bureauの負債残高の履歴
  • previous applications
    • Home Credit社における過去の融資情報
  • installment payments
    • 過去の融資の残高履歴
  • credit card balance
    • Home Credit社のカードの残高履歴
  • POS cash balance
    • Home Credit社の店頭融資の残高履歴

など、多数のファイルに分けて与えられています。

applicationの1行に対しbureauやpreviousなどのサブファイルが複数行対応付けられているため、サブファイルの情報をどうやって抽出するかがキモとなったコンペと言えるでしょう。

僕(たち)がやったこと

前処理

明らかに入力ミスと思われる外れ値が含まれていたり、ほとんど意味のない行が含まれていたりしたのでそれらを取り除きました。

欠損値の埋め方は平均値埋め、中央値埋め、0埋めなどいろいろ試しましたが、結局すべての欠損を-9999で埋めるというかなり適当な処理をしました。

特徴量作成

やったことを大まかにまとめます。

  • 各ファイルについて「債務不履行に陥りそうな人」を表す特徴量と「債務不履行に陥らなさそうな真面目な人」を表す特徴量という2つの視点から特徴量を作成
  • 各サブファイル単独でkNNとLightGBMでtargetを予測。結果を特徴量にする
  • 各サブファイルにtargetをマージし、カテゴリ変数をtarget encoding
    • 最終的にメインファイルに集約する際はtarget encodingした値の平均を利用
    • target encodingとは、カテゴリ変数を数値尺度に置き換える手法のひとつで、それぞれのカテゴリを、そのカテゴリを取った場合にtargetが1になる確率で置き換えるというものです
  • 特徴量をDenoising AutoEncodersにかける(Porto Seguroコンペの1st place solution)

@sugawaryaはkernelに上がっていたGenetic Programmingなどを試していたようです。

最初の頃、僕は債務不履行になりそうな「不真面目度」のような特徴量ばかり作成していました。しかし、それだけではすぐにネタ切れに陥ったため、途中からは「真面目度」のような特徴量も作成し始めました。

たとえば、毎月の支払いを期限ぎりぎりにする人よりは余裕を持って支払う人の方が真面目で、債務不履行になるリスクは低いと考えられます。そこで、毎月の支払いそれぞれについて何日前に払ったかを計算し、その最大値・最小値・平均値などを作成しました。他にも、契約時に想定していた分割回数と、実際の支払回数の比なども利いていました。

特徴量のネタが尽きそうなときは違う視点から着想するのが大事だと思います。

モデル

僕はLightGBMを主に使っていました。パラメータはKernelに上がっていたものとneptune-mlで公開されていたものをベースにチューニングしました。

特筆すべきはfeature_fractionで、max(0.1, sqrt(n_features)/n_features)を採用していました。feature_fractionを低めに設定しておくと、特徴量数が大きく増えても悪影響が少ないように感じます。

クロスバリデーション(以下CV)でのスコアとリーダーボード(以下LB)でのスコアが乖離していたのがかなりのストレスでした。あとからDiscussionを確認したところによると、やはり今回のデータは時系列性を持っていたため、CVがブレるのも妥当だということでした。

他にもNeural Networkを試してみましたが、スコアは全く振るいませんでした。

アンサンブル

  • @amaotone: LightGBM
  • @sugawarya: LightGBM
  • @s_kage: LightGBM、XGBoost、ExtraTrees
  • kernel best

の6つのモデルをアンサンブルして提出しました。

各モデルで予測値の分布がばらついていたため、それぞれの予測結果をrankに変換してweighted averageをしました。weightはクロスバリデーションの精度が最も良くなるように決定しました。

上位の解法まとめ

ここからは反省も交えながら書いていこうと思います。

特徴量

  • interest rate
    • 上位陣とその他大勢を分けた特徴量
    • 融資額と返済額から利率が計算でき、利率は人によって異なる
    • 利率の計算にはHome Credit社の現行のモデルが使われている
    • すなわち、現在Home Credit社が使用可能な情報の多くが含まれている(そして、中にはコンペに提供されていない情報も含まれているかも)
    • ということで、めっちゃ効く
    • 僕らは利率は分割回数などに依存すると思い込んでいて、深く検証しなかった。分割回数に依存するんだろうなーと思ったらデータにあたって確認しておけば気づけたはず
  • 主要な特徴量だけを使ってkNN(k=500)
    • 主要な特徴量だけでやるって発想がなかった
    • ゴミ特徴量が含まれた状態でkNNしてもnoisyだし、そもそもkNNは次元数が増えてくると意味がなくなるので、主要な特徴量だけ使うのは妥当だなぁ
  • 1回予測したあとにそれを特徴量に入れてもっかい学習
    • たしかに過去コンペの解法にもあった……
    • サブファイルではやってたけど、全体でもやればよかった
  • 特徴選択
    • 1変数ずつ足しながらRidgeモデルを作り、AUCが上がったら追加、上がらなかったら捨てる、を行って劇的に特徴量数をへらす
    • 特徴選択は僕たちもどうしようか悩んでいて、結局やらなかった(こればっかりだ……)
    • 本来であれば毎回LightGBMなど本チャンのモデルを作成して検証するのが良いけど、さすがに時間が足りないからRidgeで代替したらしい
    • targetに対して線形じゃない特徴量が過小評価されるけど、それでもいいのかな? という疑問はある

モデル

  • LightGBM、XGBoost、FTRL、NNなど
    • やっぱりLightGBM以外はそれほど単体性能は出ていなくて、アンサンブルのときのsupportとして採用されてたっぽい
    • @rkdにNNモデルを作っていただいていたが、依頼するのが遅かったため採用できず、申し訳ない気持ち

アンサンブル

  • 多くのチームはちゃんとstackingしていたっぽい
    • 時間が足りずできなかった
    • stackingはちゃんとやればやるだけ成果がでる印象がある
  • そういえば全然seed averageも特徴減らしてbaggingもしてない
    • 「頭が止まってもサーバーは止めるな」(by tkmさん)

その他面白かった解法

  • 同一のユーザーが複数回含まれている
    • 誕生日でソートすると見つかる←間違いです
    • 日付に関する特徴量が同じようにずれている人を探すと見つかる (ONODERAさんありがとうございます)
    • 以前にdefaultした人は90%くらいの割合でdefaultしてる
    • これを見つけたGibaさんはちょっと前に終わったSantanderでもLeakを見つけていて、さすがという感じ
    • 確かにちょっと疑ったけど、確かめなかったなぁ……
  • kernelとneptune-mlに上がっていた特徴量を使い、ランダムに特徴選択をして大量にサブモデルをつくり、bagging
    • シンプル
    • 任意のコンペで使えそう

反省まとめ

  • 仮説があったら検証。手元にデータがあるんだから
    • 正しかったら特徴量のタネになる
    • 間違っててもインサイトがあるはず
  • 上位陣と差が開き始めても、焦って小手先の精度向上をしない。なにか大事なことに気づいていないはず
  • やってみようかなと思ったことは全部やる(特徴量作成、モデリングなどすべてにおいて)
  • 頭が止まってもサーバーは止めない

チームでの動き方

今回初めて多人数でチームを組んでみました。いろいろと試行錯誤があったので、ここに書き残しておきます。

目的を共有する

まず、チームを組むなら目的を明確に設定しましょう。Kaggleに挑むモチベーションは人それぞれですし、金メダルを目指している人と、銀メダルを目指している人、データ分析を勉強しようと思っている人、興味本位の人、いろいろな人がいていいと思います。ただし、チームを組むなら最初に「このチームは〇〇を目指します」と明言しておきましょう。

ここを明言していないと、個々人の取り組み方がばらつきます。 熱心な人はフリーライダーだらけだと感じますし、少しずつ触れている人からしたら要求が厳しく感じ、楽しくありません。

理想的には熱量が同じ人同士がチームを組むべきですが、勉強会で参加しているなど、熱量にばらつきがある場合は目的を共有しておきましょう。

情報を共有する

次に、チームでslackを作りましょう。slackにはいくつかチャンネルを用意しておきます。特に重要なのは、サブミットを行った際に簡単なサマリを共有するチャンネルです。具体的には、

  • 使った特徴量
  • モデル
  • CV、LBの精度
  • メモ

あたりが簡単に書けるようになっていると良いでしょう。

その他、思いついた特徴量や興味深いDiscussion、強いKernelの出現など、こまめに話し合いましょう。 できるなら全員で顔を突き合わせてCSVとにらめっこする時間を取ると気づきがあって良いです。

コンペ終盤にはslack botを利用して、1日に利用するサブミット回数を管理するのも良いでしょう。

役割を分担する

チームでの進め方は大きく分けて2つあると思います。

  1. 基本的には個人ですすめ、最後にアンサンブルする
  2. 特徴量作成・モデル作成・アンサンブルなど、根本的に役割を分ける

メンバーの力が揃っている場合は1で進めることが多いと思います。チームで得意分野がバラけていたり、強力な旗振り役がいる場合は2で進めることもできるかと思いますが、結構難易度が高いイメージです。タレント揃いのikiri_DSチームとそれをまとめたMaxwellさんはすごい。

僕たちのチームは1で行いました。クロスバリデーションの切り方だけを共有して個別にモデルを作り、予測結果をGoogle Driveで共有しました。アンサンブルは僕が終了前日にちまちまやりました。みなさんはもっと計画的にやってください。

まとめと感想

3ヶ月がっつりコミットしたこともあり、なかなか学びが多いコンペでした。

コンペに関連するリポジトリは以下のとおりです。

次回コンペの目標は、金ピカのメダルを取ることと、ブログにドヤ顔で貼れるソースコードを書くことです。よろしくお願いします。