<実践>はじめてのKaggle体験記

機械学習を勉強し始めて1年が経ちました。
そろぞろ何か一つ目に見える実績を上げたいと思いたち、
5月から8月までHomeCreditというKaggleのコンペをやっていました。

Home Credit Default Risk | Kaggle

f:id:kurupical:20180902183535p:plain

最終的に、7198人中62位で終わることができました!

Kaggleをやってみようかなと思っている人向けに、自分の体験記を残しておこうと思います。

わからない用語は、Kaggle Words For Beginnerを参考にしてください。

もくじ

  1. スペックと前準備
  2. コンペ内容の確認
  3. コンペ参加
  4. 感想

0. スペックと前準備

スペック

前準備

コンペを始める前に(一部は始めてから)、Kaggleのノウハウを探りました。
いろいろな記事を読み*1、以下が大事なのかなと思いました。

  • 一番時間を費やすべきなのは特徴量*2作成。(特徴作成8:チューニング2くらい)
  • Discussion/Kernelはvoteが最低限沢山付いているものだけでも読む。
  • ドメイン知識(データ自身に対する専門知識)を入れる。
  • 最後まであきらめない。

1. コンペ内容と基本的な内容の確認

Kaggleの公式ページ

詳しいコンペの内容は割愛しますが、データから「ユーザーが契約したローンを、遅延なく返せるかどうか」を当てるコンペでした。
すごくざっくり説明すると下の図のような感じです。赤枠を当てるコンペです。
f:id:kurupical:20180908102220p:plain:w500

Titanicのように1ファイルではなく、複数ファイル(テーブル)でデータが与えられています。
また、実際には、たくさんの特徴量があります。
1ユーザーに対して複数行の情報があるため、機械学習にかける前にデータの集計が必要でした。

EDA

Kaggleには各コンペごとにフォーラムがあり、コンペ参加者がコードを公開する場(Kernel)、議論する場(Discussion)があります。
その中でもまず見るべきなのがEDA(Exploratory Data Analysis)です。
EDAのKernelでは、投稿者が行ったデータ分析の内容を投稿してくれています。
いろいろな投稿者がEDAを投稿しており、人によってデータの分析の角度や可視化のやり方が違います。
これらの記事を見ることで、HomeCreditに留まらずデータ分析のハウツーや可視化の引き出しを増やすことができました(デキる人からすれば常識的なスキルかもしれませんが)

今回は幸運なことに、人気のEDAの日本語訳が投稿されており、この記事*3にはお世話になりました。

ただ、公開されているEDA以外にも、自分自身でEDAをやる必要があったなと反省しています。

2. コンペ参加

コンペの活動履歴を綴っていきます。
()内は、その時点のだいたいの最終順位です。

とにかく1サブミット - 3特徴量だけを使う (6600位 / 7200)

Kaggleに限らず新しいことを始めるにあたって一番しんどいのは、最初の一歩を踏むことだと思います。
完璧主義を目指して挫折するよりも、まずはどんな手法でもいいから、「1サブミット」をすることを目標にしました。

Kernelをざっと見ると、明らかに大事そうな特徴「EXT_SOURCE_1,2,3」があることに気づきました。
この3特徴量を抽出して、RandomForest(自分が最近よく使ってた手法)で分類するモデルを作りました。

1ファイル全部使う+LightGBMにチャレンジ (6100位 / 7200)

f:id:kurupical:20180908102329p:plain:w500

次は、

  • 図の赤枠部分を使って予測すること
  • LightGBMを使うこと にチャレンジしました。

LightGBMは使ったことがなく理論もわからなかったのですが、Kernelを見ながらプログラムを書いていきました。
パラメータもKernelのものを使いました。
LightGBMの場合、欠損値はそのまま処理してくれますし、カテゴリ変数も指定してあげればonehotencodeしなくてもそのまま処理してくれるので、とても便利です。

特徴量作成

特徴量作成は、思いついたものは片っ端から作って、最後に絞り込む方針でやりました。

特徴量作成① 全ファイルを機械的に集計 (1500位 / 7200)

f:id:kurupical:20180908112138p:plain

次は、使っている全ファイルを機械的に集計することにしました。
LightGBM(に限らずほとんどの機械学習アルゴリズム)を使うとき、特徴量の数は固定でないといけません。
ユーザー1は購買履歴が3行、ユーザー2は購買履歴が10行・・・のように、各ユーザーごとにデータの特徴量が違うと都合が悪いので、図のような集計を全部の数値項目に対して行いました。

この発想は独自のものではなく、Kernelの丸パクリ*4です。

特徴量作成② 項目同士の突き合わせー数値編 ( - )

f:id:kurupical:20180908110337p:plain

次は、項目同士の突き合わせです。
木のアルゴリズムはif文の集まりであるため、項目同士の関係を捉えにくいようです。
なので、元の特徴量をもとに新しく特徴量を生成しました。
例えば図にあるように、年収と子供の数の比を見れば、その家族が支払える金額の指標になりえます。

項目の意味を考えて、様々な特徴量を作成しました。
特徴量を作成するには、その業界特有の知識(=ドメイン知識)が必要とされます。

私はドメイン知識がなかったので、特徴量の重要度(LightGBMが出力してくれる)を参照し、重要な特徴量同士を四則演算して特徴量を作成しました。

これも、基本的な発想はKernelの丸パクリ*5で、そこから自分のアイデアを肉付けしていく形にしました。

特徴量作成③ 項目同士の突き合わせーカテゴリ編 ( - )

項目同士の突き合わせですが、カテゴリ変数は割り算ができません。
ので、以下2つのアプローチで行いました。

TargetEncodingとは、カテゴリ変数をTargetの平均で埋めることです。(今回であれば支払遅延有無:1を「あり」とする)
f:id:kurupical:20180908112824p:plain

また、ドメイン(もしくは常識的な)知識によりカテゴリ変数を数値に変換しました。
f:id:kurupical:20180908111911p:plain

これらの処理を行うことで、数値特徴量とカテゴリ特徴量の四則演算が可能になります。

特徴量作成④ 時系列アプローチ ( - )

f:id:kurupical:20180910200457p:plain

また、時系列的な考え方も取り入れました。
普通に考えると、直近3ヶ月の履歴はそれより前の履歴よりその人の特性を表すと思ったからです。
例として、図のような特徴量を取り入れました。

また、過去30日金額÷過去90日金額など、直近の金額が過去と比べてどうであるか、のような特徴量も作りました。

このアプローチはこのdiscussion*6から自分で考えました。

特徴量選択 (600位 / 7200)

ここまでで作成した特徴量は、およそ1800個となりました。
「LightGBMは冗長な特徴量があっても大丈夫」という意見もありましたが、特徴量が多いとメモリが足りないという都合と、やっぱり特徴量絞ったほうがいい結果が出る実感があったので*7、特徴量を絞りました。

特徴量選択は、Kernelでも様々な手法が紹介されていましたが*8、自分が試したのは以下です。

  1. 相関が高い特徴量を削除
  2. LightGBMのfeature importance(gain/split)
  3. Null Importances*9
  4. SHAP*10

結局、1で相関係数>0.95のものは片方削除→4で重要度ベスト400,500,600をそれぞれ選び、モデルに学習させました。

パラメタチューニング① hyperopt/BayesianOptimization ( - )

LightGBMのパラメータの意味がわからなくとも自動的にパラメータチューニングしてくれるすごいライブラリの使い方がKernelに公開されていたので、試しました。

  1. hyperopt *11
  2. Bayesian Optimization *12

ただ、これらを使うには調整するパラメータの範囲や種類を指定しなければなりません。

  • 範囲や種類が多いほど時間がかかる。
  • 特徴量の数によっても、最適なパラメタは違う(らしい)。

しばらく試していたのですが、上記のような理由もあり、やっぱりある程度LightGBM、GBDTの簡単な理屈を知ったうえで手でチューニングしたほうがいいかなと思い、使うのをやめました。

実際、過去の似たようなコンペの上位解法を見ると、「パラメタチューニングは勘で決めました」であったり、「我々が時間を割くべきはパラメタチューニングではない」といった記述もありました。

パラメタチューニング② LightGBMの勉強 (150位 / 7200)

パラメタの意味を知るため、簡単にLightGBMの勉強をしました。
LightGBMを知るためにはxgboostを知ってる必要があり、xgboostを知るには勾配ブースティング木(GBDT)を知る必要があり、GBDTを知るには決定木を知る必要があるということがわかりました。

参考になった資料のリンクを貼っておきます。(自分が読んだ順)

概要

NIPS2017読み会 LightGBM: A Highly Efficient Gradient Boosting Decision T…
Overview of tree algorithms from decision tree to xgboost

大事なパラメタとその意味を調査

Complete Guide to Parameter Tuning in Gradient Boosting (GBM) in Python
XGBoostやパラメータチューニングの仕方に関する調査 | かものはしの分析ブログ
Santander Product RecommendationのアプローチとXGBoostの小ネタ - Speaker Deck
XGBoostにDart boosterを追加しました - お勉強メモ
Laurae++: xgboost / LightGBM - Parameters
LightGBM/Parameters.rst at master · Microsoft/LightGBM · GitHub

論文

xgboost http://www.kdd.org/kdd2016/papers/files/rfp0697-chenAemb.pdf
lightgbm https://papers.nips.cc/paper/6907-lightgbm-a-highly-efficient-gradient-boosting-decision-tree.pdf
dart https://arxiv.org/pdf/1505.01866.pdf

論文全然わかんなかったので参照

Gradient Boosting と XGBoost | ZABURO app

過去コンペのパラメタ参照

Kaggle Past Competitions
Porto Seguroとか見ました。

結局

いろいろ見て、(結局半分も理解できませんでしたが)以下の方針を立てました。

  • モデルをロバストにする
    • colsample_bytree 0.05-0.2くらい -> 0.05がよかった
    • lamdba_l1とl2をそれぞれ0.1,1,5,10,20で組み合わせて試す -> l1=1, l2=20がよかった
    • subsample 0.9で決め打ち
  • 木の形はわからんからとりあえずいろいろ試す
    • max_depthは-1とsqrt(max_leaves) * 2、max_leavesは30〜130くらいを試した -> max_depth=-1, max_leaves=30がよかった
  • gbdt, dart, gossはわからんからそれぞれ試した -> dartメイン、gbdtもちょっと使った
    • 精度; dart>gbdt>goss
    • 計算時間: dart>gbdt>goss
  • learning_rateは0.0025〜0.05まで試した -> アンサンブル考えると(後述)、以下がよかった。
    • gbdt: 0.0025
    • dart: 0.01

※この方針はあまりよくなかった気もしています。実際、2位のikiri_DSのonoderaさんのgithub見てみると*13、colsample_bytree 0.9くらいなので・・・

アンサンブル/スタッキング① アンサンブル ( 62位 / 7200 )

最後にアンサンブルをしました。
「◯×クイズに60%正解する人が3人。3人の答えがそれぞれ独立だとすると、3人のうち多数決を取ったときのクイズの正解率は?」
答え:
3人とも正解:0.6 * 3 = 0.216、2人が正解: 3 * 0.6 ^ 2 * 0.4 = 0.432なので、0.216 + 0.432 = 0.648
正解率は一人の時より上がります!!

つまり、いろんなモデルを作って多数決をさせることで、それぞれのモデルよりよい性能が出る可能性があります。
ただし重要なのは、「モデル同士の予想の相関が低いこと」です。

モデル同士の予想の相関を低くするために、以下を行いました。

  • 異なるアルゴリズムを使う(Neural Network) -> 精度が全く出ず断念
  • 異なるパラメータを使う -> LightGBMのdart,gbdtを組み合わせることで精度が良くなった。
  • 異なるseedを使ってK-Foldする -> 単一モデルより精度が良くなった
  • 低すぎるlearning_rateを使わない(単一モデルの精度はよいが、異なるseedを使っても多様性がなく、アンサンブルの効果が薄くなった)

    相関と精度を見て、最終的に、dart(500特徴量)・dart(400特徴量)・gbdt(500特徴量)のアンサンブルを組みました。(これが最終順位62位のサブミットでした)

アンサンブル/スタッキング② スタッキング ( - )

「スタッキング」とは、「複数のモデルが出した予想値」を入力にしてもう一度学習させ、最終的な予想を行うことです。
スタッキングもやったのですが、アンサンブル以上に精度が出なかったので断念しました。

結果

LB: 498位→PB:62位
LBの順位は低かったですが、公開されているソースよりもCVが高かったので、Trust Your CV*14の精神で耐えました。
CV0.794->LB0.804のようなLBにOverfitしてると思われるものもありましたが、自分はCV0.8018 -> LB0.802でした。

3. 感想

とても勉強になりましたし、楽しかったです。
データの分析の仕方もLightGBMもわからない状態でスタートしましたが、ほとんどKernelに助けられながら、最後までできました。
実際、Kernelを全く見ないでやったら、半分より上にも行けなかったと思います。
scikit-learnあたりの使い方が少しわかってきたけど次何すればよいか・・・という人には、Kaggleを強くおすすめします!

また、コンペ参加中は、データ分析をみんなで一緒にやってる気持ちでした。
discussionを読みながら、時々参加したり。
リーダーボード(ランキング)をみて、「自分はこのスコアが限界だと思ってたけど、もっと伸びるのか」と、分析を投げずに最後までがんばれたり。
中だるみもありましたが、充実した3ヶ月でした。

自分自身が、Kaggleに参加する前とした後で変わったことは以下かなと思っています。

  • 特徴量作成が超大事だと分かった。(データを入れたら答えが帰ってくるわけじゃない)
  • 「テーブルデータの分類モデルの構築をどこから始めたらよいか」が分かった。
  • 特徴量作成の定石を少し覚えた。
  • LightGBMの分類が使えるようになった。(ある程度パラメータも理解できた)
  • GCPの使い方がわかった*15

泥臭い部分も含めたノウハウを、少しだけ、Kaggleを通じて習得できたかなと思います!

超長くなってしまいましたが、最後まで読んで頂きありがとうございましたm(_ _)m