Kaggle Meetup Tokyo #5
Lightning Talks
LightGBMを少し改造してみた
~カテゴリ変数の動的エンコード~
Ryuichi Kanoh
Dec / 1 / 2018 @ Indeed
自己紹介
• 名前:加納 龍一 (Ryuichi Kanoh)
Kaggle: https://www.kaggle.com/ryuichi0704
Twitter: https://twitter.com/ryuichi_74
LinkedIn: https://www.linkedin.com/in/ryuichi-kanoh-43ab4316b/
• 専門
観測天文学 → 信号処理 → データ分析屋
• Kaggleの実績
- Home Credit Default Risk: 2nd
- TalkingData AdTracking Fraud Detection Challenge: 16th
- Avito Demand Prediction Challenge: 21st
2/15
モチベーション
• テーブルデータを用いたKaggleコンペは、特徴量づくりがとても重要。
• 一方モデルに関しては、どの参加者もほぼほぼ同じものを使っている。
(特に、LightGBMが圧倒的に多い)
• 確かにLightGBMは強いが、そのまま使うだけで良いのか?
• 工夫すればモデリングの観点からでも他者より優位に立てるのでは?
→LightGBMの機能を拡張してみよう。 (今回はカテゴリ変数のエンコードに着目)
3/15
LightGBM独自のエンコード手法
LightGBMには、どのカラムがカテゴリ変数かを指定すれば、
内部でいい感じにエンコードしてくれる機能がある。
これ、何をやっているのか気になったことはありませんか?
4/15
LightGBM独自のエンコード手法
LightGBM sorts the histogram (for a categorical feature) according
to its accumulated values (sum_gradient / sum_hessian) and then
finds the best split on the sorted histogram.
https://arxiv.org/abs/1603.02754
sum_gradient / sum_hessianと書かれると分かりにくいが、
結論を言うと、LightGBMは葉に割り当てられる予測値を用いてエンコードしている。
公式ドキュメント
5/15
https://lightgbm.readthedocs.io/en/latest/Features.html#optimal-split-for-categorical-features
Gradient, Hessianとは?
6/15
各サンプルごとの、一つ前のラウンドの予測結果における
Lossの予測値での一階微分(Gradient)と二階微分(Hessian)
https://arxiv.org/abs/1603.02754
sum_gradient / sum_hessianが
葉に割り当てられる予測値となることの直感的な理解
Gradient(Lossの一階微分)は、新たな木が予測すべき誤差となる。
そのため、sum_gradientは、誤差値の総和。
Hessian(Lossの二階微分)は、誤差関数が二乗誤差ならば定数項になりる。
そのため、sum_hessianは、葉に存在するサンプル数のようなものだと考えられる。
つまり、sum_gradient/sum_hessianとは、
葉に存在するサンプルの誤差の平均値と等価。それを葉に割り当てると、誤差は小さくなる。
Lossが二乗誤差の場合を考えると理解しやすい。
7/15
カテゴリ特徴エンコードの全体像
ポイントは、使われる場所によってソートのされ方が異なるという点。(Adaptive Encoding)
8/15
Countryという特徴量があり、Japan, China, Koreaが内部には含まれているとする。
このとき、ある枝で「JapanとJapan以外」といった具合に枝をつくったら、Japanの葉にはどんな予測値が入るのかを調べる。
このように予測値をすべてのカテゴリ(Japan, China, Korea)に対して算出する。
仮に予測値がChina>Korea>Japanなのであったら、その順番にカテゴリをソートする。その上で、分割点を探索する。
具体例
https://lightgbm.readthedocs.io/en/latest/Features.html
枝ごとにgradientとhessianをチェックし、それを元に葉に入る予測値を算出する。
カテゴリごとに予測値を算出し、その順番にソートした上で分割点を探す。
木の可視化による確認
11, 28, 38などはこちらにしか無い。
6, 12, 13などはこちらにしか無い。
また、1, 5など、両者に共通しているものもある。
並び方が同じならば片方は片方の部分集合になるはずだが、そうはなっていない。 9/15
独自の動的エンコードの導入
• 動的エンコードはモデル外で特徴量エンジニアリングをしても表現できず、
ユニークな価値を持っている。
• 予測値を用いた動的エンコードは直接的でかなり強力そうだが、
経験的には色々なエンコードがなされたカテゴリ変数を入力すると
性能が伸びるため、それ以外の動的エンコードと組み合わせても良いはず。
• コンペ終盤では少しでも特性の違うモデルをアンサンブルすることで
スコアを伸ばす取り組みがなされることからも、
様々エンコードの手札を持っておくことに価値はあるはず。
(Home Credit Default Riskのikiri_DSチームでは、私だけでも26モデル×5 seed averaging)
10/15
まずは手始めに、定番のCount Encodingを動的にしてみよう
手順
• GitHubからLightGBMのコード(C++)を持ってきて、
実装変更後に自分でビルドする。
# Download
> git clone --recursive https://github.com/Microsoft/LightGBM
> [コードを書き換える…]
# Compile
> cd LightGBM
> mkdir build
> cd build
> cmake ..
> make -j4
# Python API
> cd ../python_package
> python setup.py install
11/15
手順
src/treelearner/feature_histogram.hpp :
FindBestThresholdCategorical
sum_gradient / sum_hessian と関係しているところ数カ所を書き換えればよい。
今回は簡単のため、真面目にカウントを取らずにsum_hessianでそのまま代用。
12/15
実験
• Home Credit Default Riskコンペの、application_train.csvを使用。
→ユーザーの個人情報やローンの情報セット
• 5 foldでデータを分割し、同一条件でのvalidationスコアを確認する。
• Validationのスコア(AUC)を見ながらのEarly Stoppingは有効としている。(200ラウンド)
• 条件の違いは、カテゴリ変数に対するエンコード手法のみ。
13/15
14/15
・Feature Importanceの順序が大きく変わる。モデルの多様性に貢献。
・過学習をうまく防止することで、今回はLightGBMのデフォルトよりスコアが改善。
ORGANIZATION_TYPE
2番目→72番目
NAME_EDUCATION_TYPE
22番目→19番目
OCCUPATION_TYPE
8番目→92番目
Original LightGBM Encoding Adaptive Count Encoding
Train AUC Valid AUC
Original LightGBM Encoding 0.8650 0.7729
Adaptive Count Encoding 0.8521 (-0.0139) 0.7735 (+0.0006)
結果
まとめ
• LightGBMはカテゴリ変数に対して動的なエンコードを行なっている。
• アルゴリズムを少し書き換えるだけでも、独自の動的エンコードを導入可能。
→Feature Importanceの順序を大きく変え、モデルに多様性を付与できた。
→最適なエンコード手法は問題によるので、手札が増えるだけでも価値がある。
• カテゴリのエンコードに限らず、成長戦略や高速化などでも何かできるかも。
(個人的には、Exclusive Feature Bundlingに目をつけている)
• 独自モデルの開発は、新しいKaggleの楽しさにも繋がると思う。
• 皆で知恵を出し合って、Kaggler-ja-Boostで優勝しよう!
15/15

LightGBMを少し改造してみた ~カテゴリ変数の動的エンコード~