LightGBM は Microsoft が開発した勾配ブースティング決定木 (Gradient Boosting Decision Tree) アルゴリズムを扱うためのフレームワーク。
勾配ブースティング決定木は、ブースティング (Boosting) と呼ばれる学習方法を決定木 (Decision Tree) に適用したアンサンブル学習のアルゴリズムになっている。
勾配ブースティング決定木のフレームワークとしては、他にも XGBoost や CatBoost なんかがよく使われている。
調べようとしたきっかけは、データ分析コンペサイトの Kaggle で大流行しているのを見たため。
使った環境は次の通り。
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.13.4
BuildVersion: 17E202
$ python -V
Python 3.6.5
もくじ
インストール
(2019-1-28 追記)
macOS でのインストール方法がバージョン 2.2.1 から変わった。
ビルド済みバイナリが配布されるようになったので、これまでのように gcc をインストールしてビルドする必要はない。
代わりに OpenMP のライブラリが実行時に必要となった。
$ brew install libomp
あとは PyPI からバイナリのパッケージをインストールするだけで良い。
$ pip install lightgbm
$ pip list --format=columns | grep -i lightgbm
lightgbm 2.2.2
過去のインストール方法 (バージョン 2.2.1 以前)
LightGBM は並列計算処理に OpenMP を採用しているので、まずはそれに必要なパッケージを入れておく。
$ brew install cmake gcc@7
あとは pip を使ってソースコードから LightGBM をビルドする。
$ export CXX=g++-7 CC=gcc-7
$ pip install --no-binary lightgbm lightgbm
$ pip list --format=columns | grep -i lightgbm
lightgbm 2.1.1
多値分類問題 (Iris データセット)
それでは早速 LightGBM を使ってみる。
次のサンプルコードでは Iris データセットを LightGBM で分類している。
ポイントとしては LightGBM に渡すパラメータの目的 (objective) に multiclass
(多値分類) を指定するところ。
そして、具体的なクラス数として num_class
に 3
を指定する。
scikit-learn や numpy は LightGBM の依存パッケージとして自動的にインストールされるはず。
import lightgbm as lgb
from sklearn import datasets
from sklearn.model_selection import train_test_split
import numpy as np
"""LightGBM を使った多値分類のサンプルコード"""
def main():
iris = datasets.load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y)
lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)
lgbm_params = {
'objective': 'multiclass',
'num_class': 3,
}
model = lgb.train(lgbm_params, lgb_train, valid_sets=lgb_eval)
y_pred = model.predict(X_test, num_iteration=model.best_iteration)
y_pred_max = np.argmax(y_pred, axis=1)
accuracy = sum(y_test == y_pred_max) / len(y_test)
print(accuracy)
if __name__ == '__main__':
main()
細かい精度の検証が目的ではないので、交差検証はざっくり訓練データとテストデータに分けるだけに留めた。
上記に適当な名前をつけて実行してみよう。
$ python mc.py | tail -n 1
0.9736842105263158
今回の実行では精度 (Accuracy) として 97.36% が得られた。
ちなみに、この値は訓練データとテストデータの分けられ方に依存するので毎回異なったものになる。
scikit-learn インターフェース
LightGBM には scikit-learn に準拠したインターフェースも用意されている。
ネイティブな API と好みに合わせて使い分けられるのは嬉しい。
次のサンプルコードでは、先ほどと同じコードを scikit-learn インターフェースを使って書いてみる。
import lightgbm as lgb
from sklearn import datasets
from sklearn.model_selection import train_test_split
import numpy as np
"""LightGBM を使った多値分類のサンプルコード (scikit-learn interface)"""
def main():
iris = datasets.load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y)
model = lgb.LGBMClassifier()
model.fit(X_train, y_train)
y_pred = model.predict_proba(X_test)
y_pred_max = np.argmax(y_pred, axis=1)
accuracy = sum(y_test == y_pred_max) / len(y_test)
print(accuracy)
if __name__ == '__main__':
main()
適当な名前でファイルに保存して実行してみよう。
$ python mcs.py | tail -n 1
0.9473684210526315
ちゃんと動いているようだ。
交差検証 (Cross Validation)
また、LightGBM にはブーストラウンドごとの評価関数の状況を交差検証で確認できる機能もある。
次のサンプルコードでは、先ほどと同じ Iris データセットを使った多値分類問題において、どのように学習が進むのかを可視化している。
import lightgbm as lgb
from sklearn import datasets
import numpy as np
from matplotlib import pyplot as plt
"""LightGBM を使った多値分類のサンプルコード (CV)"""
def main():
iris = datasets.load_iris()
X, y = iris.data, iris.target
lgb_train = lgb.Dataset(X, y)
lgbm_params = {
'objective': 'multiclass',
'num_class': 3,
}
cv_results = lgb.cv(lgbm_params, lgb_train, nfold=10)
cv_logloss = cv_results['multi_logloss-mean']
round_n = np.arange(len(cv_logloss))
plt.xlabel('round')
plt.ylabel('logloss')
plt.plot(round_n, cv_logloss)
plt.show()
if __name__ == '__main__':
main()
上記に適当な名前をつけたら実行してみよう。
$ python mcv.py
すると、次のようなグラフが得られる。
上記は各ブーストラウンドごとの評価関数の値を折れ線グラフでプロットしている。
ブーストラウンド数はデフォルトで 100 になっており評価関数は LogLoss 損失関数になっている。
グラフを見ると損失が最も小さいのはラウンド数が 40 付近であり、そこを過ぎるとむしろ増えていることが分かる。
つまり、汎化性能を求めるにはブーストラウンド数を 40 あたりで止めたモデルにするのが望ましいことが分かる。
次のサンプルコードでは lightgbm.train()
関数のオプションとして num_boost_round
に 40 を指定している。
これによって最適なブーストラウンド数で学習を終えている。
import lightgbm as lgb
from sklearn import datasets
"""LightGBM を使った多値分類のサンプルコード"""
def main():
iris = datasets.load_iris()
X, y = iris.data, iris.target
lgb_train = lgb.Dataset(X, y)
lgbm_params = {
'objective': 'multiclass',
'num_class': 3,
}
model = lgb.train(lgbm_params, lgb_train, num_boost_round=40)
if __name__ == '__main__':
main()
最適なブーストラウンド数を自動で決める
といっても、最適なブーストラウンド数を毎回確認して調整するのは意外と手間だったりもする。
そこで LightGBM には自動で決めるための機能として early_stopping_rounds
というものが用意されている。
これは、モデルの評価用データを渡した状態で学習させて、性能が頭打ちになったところで学習を打ち切るというもの。
次のサンプルコードでは LightGBM.train()
に early_stopping_rounds
オプションを渡して機能を有効にしている。
数値として 10
を渡しているので 10
ラウンド進めても性能に改善が見られなかったときは停止することになる。
この数値は、あまり小さいと局所最適解にはまりやすくなってしまう恐れもあるので気をつけよう。
学習ラウンド数は最大で 1000
まで回るように num_boost_round
オプションで指定している。
注意点としては、前述した通りこの機能を使う際は学習用データとは別に評価用データを valid_sets
オプションで渡す必要がある。
import lightgbm as lgb
import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
"""LightGBM を使った多値分類のサンプルコード"""
def main():
iris = datasets.load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
X_eval, X_valid, y_eval, y_valid = train_test_split(X_test, y_test, random_state=42)
lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_eval, y_eval, reference=lgb_train)
lgbm_params = {
'objective': 'multiclass',
'num_class': 3,
}
model = lgb.train(lgbm_params, lgb_train,
valid_sets=lgb_eval,
num_boost_round=1000,
early_stopping_rounds=10)
y_pred_proba = model.predict(X_valid, num_iteration=model.best_iteration)
y_pred = np.argmax(y_pred_proba, axis=1)
accuracy = accuracy_score(y_valid, y_pred)
print(accuracy)
if __name__ == '__main__':
main()
また、ホールドアウト検証するときは評価用データとはまた別に検証用データを用意する必要がある点にも注意しよう。
評価用データで得られた精度はパラメータの調整に使ってしまっているので、それで確認しても正しい検証はできない。
上記を実行すると最大 1000
ラウンドまでいくはずが性能が頭打ちになって 55
で停止していることがわかる。
$ python mes.py
[LightGBM] [Info] Total Bins 89
[LightGBM] [Info] Number of data: 112, number of used features: 4
...(snip)...
[LightGBM] [Warning] No further splits with positive gain, best gain: -inf
[65] valid_0's multi_logloss: 0.060976
Early stopping, best iteration is:
[55] valid_0's multi_logloss: 0.0541825
1.0
最終的にホールドアウト検証で精度が 100% として得られている。
ただ、これはちょっと分割を重ねすぎてデータが少なくなりすぎたせいかも。
特徴量の重要度の可視化
LightGBM では各特徴量がどれくらい予測に寄与したのか数値で確認できる。
次のサンプルコードでは lightgbm.plot_importance()
関数を使って特徴量の重要度を棒グラフでプロットしている。
import lightgbm as lgb
from sklearn import datasets
from sklearn.model_selection import train_test_split
from matplotlib import pyplot as plt
"""LightGBM を使った特徴量の重要度の可視化"""
def main():
iris = datasets.load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y)
lgb_train = lgb.Dataset(X_train, y_train, feature_name=iris.feature_names)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)
lgbm_params = {
'objective': 'multiclass',
'num_class': 3,
}
model = lgb.train(lgbm_params, lgb_train, valid_sets=lgb_eval, num_boost_round=40)
lgb.plot_importance(model, figsize=(12, 6))
plt.show()
if __name__ == '__main__':
main()
上記に適当な名前をつけて実行しよう。
$ python mci.py
すると、次のようなグラフが得られる。
上記を見ると Iris を識別するための特徴量としては Petal Width と Petal Length が重要なことが分かる。
回帰問題 (Boston データセット)
続いては Boston データセットを使った回帰問題に取り組んでみよう。
データセットには住宅価格を予測する Boston データセットを用いた。
早速だけどサンプルコードは次の通り。
多値分類問題とは、学習時に渡すパラメータしか違わない。
具体的には目的 (Objective) に regression
を渡して、評価関数に rmse
(Root Mean Squared Error) を指定している。
import lightgbm as lgb
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
import numpy as np
"""LightGBM を使った回帰のサンプルコード"""
def main():
boston = datasets.load_boston()
X, y = boston.data, boston.target
X_train, X_test, y_train, y_test = train_test_split(X, y)
lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)
lgbm_params = {
'objective': 'regression',
'metric': 'rmse',
}
model = lgb.train(lgbm_params, lgb_train, valid_sets=lgb_eval)
y_pred = model.predict(X_test, num_iteration=model.best_iteration)
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
print(rmse)
if __name__ == '__main__':
main()
上記に適当な名前をつけて実行しよう。
$ python rb.py | tail -n 1
2.686275348587732
テストデータに対して RMSE が約 2.68 として得られた。
尚、訓練データとテストデータの分けられ方によって毎回出る値が異なる点については最初の問題と同じ。
scikit-learn インターフェース
続いては回帰問題を scikit-learn インターフェースで解いてみる。
サンプルコードは次の通り。
scikit-learn インターフェースにおいて回帰問題を解くときは lightgbm.LGBMRegressor
クラスを使う。
import lightgbm as lgb
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
import numpy as np
"""LightGBM を使った回帰のサンプルコード (scikit-learn interface)"""
def main():
boston = datasets.load_boston()
X, y = boston.data, boston.target
X_train, X_test, y_train, y_test = train_test_split(X, y)
model = lgb.LGBMRegressor()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
print(rmse)
if __name__ == '__main__':
main()
実行結果の内容については先ほどと変わらないので省略する。
二値分類問題 (Breast Cancer データセット)
続いては Breast Cancer データセットを使った二値分類問題について。
サンプルコードは次の通り。
これまでの内容からも分かる通り、学習において変更すべき点は渡すパラメータ部分のみ。
今度は目的として binary
を指定する。
import lightgbm as lgb
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn import metrics
"""LightGBM を使った二値分類のサンプルコード"""
def main():
bc = datasets.load_breast_cancer()
X, y = bc.data, bc.target
X_train, X_test, y_train, y_test = train_test_split(X, y)
lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)
lgbm_params = {
'objective': 'binary',
'metric': 'auc',
}
model = lgb.train(lgbm_params, lgb_train, valid_sets=lgb_eval)
y_pred = model.predict(X_test, num_iteration=model.best_iteration)
fpr, tpr, thresholds = metrics.roc_curve(y_test, y_pred)
auc = metrics.auc(fpr, tpr)
print(auc)
if __name__ == '__main__':
main()
上記に名前をつけて実行しよう。
$ python bc.py | tail -n 1
0.9920212765957447
AUC として 99.2% という結果が得られた。
scikit-learn インターフェース
同様に scikit-learn インターフェースからも使ってみる。
とはいえ、これに関しては最初に紹介した Iris データセットを使った多値分類問題と変わらない。
lightgbm.LGBMClassifier
を使えば二値問題も多値問題も同じように扱うことができる。
import lightgbm as lgb
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn import metrics
"""LightGBM を使った二値分類のサンプルコード (scikit-learn interface)"""
def main():
bc = datasets.load_breast_cancer()
X, y = bc.data, bc.target
X_train, X_test, y_train, y_test = train_test_split(X, y)
model = lgb.LGBMClassifier()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
fpr, tpr, thresholds = metrics.roc_curve(y_test, y_pred)
auc = metrics.auc(fpr, tpr)
print(auc)
if __name__ == '__main__':
main()
実行結果の内容については先ほどと変わらない。
まとめ
今回は勾配ブースティングアルゴリズムを扱うフレームワーク LightGBM を試してみた。
使ってみて、勾配ブースティングによる性能の高さはもちろん、細部まで使いやすさに配慮されている印象を受けた。
同じアルゴリズムを分類にも回帰にも応用できる上、CV や特徴量の重要度まで確認できる。
計算量も Deep Learning ほど大きくないし、最近のコンペで Winning Solution を獲得した実績も多い。
これは Kaggle で流行る理由もうなずけるね。