LightGBMのPythonパッケージ触ってみた
DMLCひとりアドベントカレンダー0日目の記事です。 強い競合が現れたということで、DMLCとは直接関係ないですがLightGBMについて紹介します。
LightGBMとは
勾配ブースティング木の高速な実装としてXGBoostが有名ですが、Microsoftの開発した更に高速な実装がLightGBMです。 実験によるとXGBoostの数倍高速だということです。
パフォーマンス向上の大きな要因は、決定木の分割をexactに探索するのではなく、ヒストグラムのビン単位にして計算を間引いていることにあるようです。 ビン単位にすると精度が落ちそうな気もしますが、汎化性能を考えたら特徴量が粗いくらいでちょうど良いのだろうと腹落ちしています。
同じアプローチでFastBDTが先に発表されましたが開発が滞ってしまっている一方、LightGBMでは最近Pythonパッケージがベータリリースされました。 今日、我々がPythonで勾配ブースティングをする際にはXGBoostかLightGBMの2択*1となります。
導入
XGBoostとそれほど変わりません。
使い方
Exampleより
xgboost.DMatrix
的なものを作って…
# create dataset for lightgbm
lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)
パラメータをディクショナリに格納して…
# specify your configurations as a dict params = { 'task' : 'train', 'boosting_type' : 'gbdt', 'objective' : 'regression', 'metric' : {'l2', 'auc'}, 'num_leaves' : 31, 'learning_rate' : 0.05, 'feature_fraction' : 0.9, 'bagging_fraction' : 0.8, 'bagging_freq': 5, 'verbose' : 0 }
train
するだけ。XGBoostと同じ流れですね。
# train gbm = lgb.train(params, lgb_train, num_boost_round=20, valid_sets=lgb_eval, early_stopping_rounds=5)
predict
がpandas.DataFrame
を直接受け入れてくれるのには注意が必要です。
# predict
y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration)
XGBoostのパラメータとの対応は以下のようになります。
2016/12/18追記 tksさんに指摘を頂いたので記述を修正しました。 github.com
LightGBM | XGBoost | 候補 | 備考 |
---|---|---|---|
objective | objective | regression, binary, multiclass, ... | |
boosting_type | booster | gbdt, dart | 線形モデルはなし |
max_depth | max_depth | num_leavesの制約を受けるので注意 | |
learning_rate | learning_rate | ||
feature_fraction | colsample_bytree | ||
bagging_fraction | subsample | bagging_freqを1以上にしないと有効にならない | |
min_gain_to_split | gamma | ||
lamnda_l1 | alpha | ||
lambda_l2 | lambda | ||
metric | eval_metrix | l1, l2, auc, ... |
LightGBMにもmax_depth
パラメータが用意されていますが、オフィシャルにはnum_leaves
で葉の数を制御します。
また、特徴量をまとめるビンの数をmax_bin
で設定できます。
詳細は公式のWikiを調べて下さい。
カテゴリ変数のサポート
LightGBMではカテゴリ変数がサポートされています。 One-hot encodingが不要になったので、メモリ的に有利であったり、カラム方向のサンプリングやfeature importanceが直感的になる等のメリットがありそうです。
下の実験からのコード片ですが、train
のcategorical_feature
パラメータにカテゴリ変数として扱うカラムのインデックスをリストで渡してやるだけです。
カテゴリ変数はint
のみ対応しているので、文字だったりする場合は0始まりのコードに置き換える必要があります。
obj_lgb = lgb.train( prm_lgb, dt_lgb_c, num_boost_round=num_round, valid_sets=dv_lgb_c, categorical_feature=list(range(len(x.columns.values))))
処理を完全には追っていないのですが、rpart
のようなカテゴリ併合をしてくれる訳ではないようで、数値変数であればx <= threshold
で分割されるところが、x is category
で分割されるようになるだけのようです。
また以下の実験の出力からなのですが、数値変数として扱うと以下のように小数点の閾値で分割されます。
threshold=1.5 3.5 2.5 0.5 0.5 0.5 0.5 0.5 4.5 14.5 0.5 1.5 0.5 0.5 1.5 2.5 0.5 0.5 0.5 0.5 0.5 4.5 0.5 0.5 0.5 0.5 1.5 0.5 2.5 2.5
一方、カテゴリ変数として扱うとデータにある値そのもので分割されます。
threshold=0 0 0 0 1 1 3 2 0 1 0 0 0 1 1 0 0 3 1 1 0 3 0 0 0 0 1 0 1 0
実験
手元でもパフォーマンスを確認してみようと、Ottoのデータで実験してみました。
モデル | MLogLoss | 計算時間 |
---|---|---|
XGBoost | 0.570366 | 65.20 |
LightGBM | 0.522378 | 16.02 |
LightGBM Cat | 0.671286 | 15.78 |
この設定ではLightGBMが4倍以上速い結果となりました。精度もLightGBMの方が良好です。
全変数をカテゴリ変数として扱ったLightGBM Catの有り難みがないように見えますが、One-hot encodingによってカラム数が膨らんでいる場合には計算時間の短縮が実感できるはずです。 精度が悪いのは、今回のデータが順序性を持っているためと思われます。
最後に
XGBoost4jのような使い方をすると話は別かもしれませんが、クライアントマシンでせこせこ計算する分にはLightGBMで良さそうです。 開発も活発なので、XGBoostにしかないような機能も続々と取り込まれている上に、コードもスッキリしており改造が比較的容易になっています。
CNTKなんかもパフォーマンス良いようですし、巻き返しに力の入っているMicrosoftさんのLightGBMには将来性も期待できるのではないでしょうか。
次は23日にMXNetのネタを考えています。
*1:scikit-learnのGradientBoostingもMAEで分割ができる等、面白そうではあるんですが…