読者です 読者をやめる 読者になる 読者になる

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)

predictpandas.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が直感的になる等のメリットがありそうです。

下の実験からのコード片ですが、traincategorical_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のデータで実験してみました。

github.com

モデル 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で分割ができる等、面白そうではあるんですが…