今回は LightGBM で、組み込みで用意されていない独自の評価指標 (カスタムメトリック) を扱う方法について。 ユースケースとしては、学習自体は別の評価指標を使って進めつつ、本来の目標としている評価指標を同時に確認するといったもの。 例えば、精度 (Accuracy) やマシューズ相関係数 (Matthews Correlation Coefficient) は、学習にそのまま用いることは難しい。 しかしながら、最終的な目標としている評価指標がそれらになっていることはよくある。
使った環境は次の通り。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.14.4 BuildVersion: 18E226 $ python -V Python 3.7.3 $ pip list | grep -i lightgbm lightgbm 2.2.3
下準備
$ brew install libomp $ pip install lightgbm scikit-learn matplotlib
独自の評価指標を用いる
以下のサンプルコードでは、学習には LogLoss を使いつつ、同時に Accuracy を計算している。
独自の評価指標を計算するときは train()
関数や cv()
関数で feval
というオプションを用いる。
指定するのは評価指標を計算する関数で、引数はモデルが予測した値と学習に使ったデータの二つ。
データからは get_label()
という関数で真のラベルを取得できる。
注意点として、モデルが予測した値は多値分類問題であっても一次元の配列になっているため reshape する必要がある。
評価指標を計算する関数では、返り値として評価指標の名前、スコア、そしてスコアが大きい方が優れているのか否かを表す真偽値を返す。
#!/usr/bin/env python # -*- coding: utf-8 -*- import lightgbm as lgb from sklearn import datasets from matplotlib import pyplot as plt import numpy as np from sklearn.model_selection import train_test_split def accuracy(preds, data): """精度 (Accuracy) を計算する関数""" # 正解ラベル y_true = data.get_label() # 推論の結果が 1 次元の配列になっているので直す N_LABELS = 3 # ラベルの数 reshaped_preds = preds.reshape(N_LABELS, len(preds) // N_LABELS) # 最尤と判断したクラスを選ぶ y_pred = np.argmax(reshaped_preds, axis=0) # メトリックを計算する acc = np.mean(y_true == y_pred) # name, result, is_higher_better return 'accuracy', acc, True def main(): # Iris データセットを読み込む iris = datasets.load_iris() X, y = iris.data, iris.target # 学習用データと検証用データに分割する X_train, X_test, y_train, y_test = train_test_split(X, y, shuffle=True, random_state=42) # LightGBM のデータセットの表現に直す 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, } evals_result = {} lgb.train(lgbm_params, lgb_train, # メトリックを追跡する対象のデータセット valid_sets=[lgb_eval, lgb_train], # 上記の名前 valid_names=['eval', 'train'], num_boost_round=1000, # メトリックの履歴を残すオブジェクト evals_result=evals_result, # 独自メトリックを計算する関数 feval=accuracy, ) # 組み込みのメトリック eval_metric_logloss = evals_result['eval']['multi_logloss'] train_metric_logloss = evals_result['train']['multi_logloss'] # カスタムメトリック eval_metric_acc = evals_result['eval']['accuracy'] train_metric_acc = evals_result['train']['accuracy'] # グラフにプロットする _, ax1 = plt.subplots(figsize=(8, 4)) ax1.plot(eval_metric_logloss, label='eval logloss', c='r') ax1.plot(train_metric_logloss, label='train logloss', c='b') ax1.set_ylabel('logloss') ax1.set_xlabel('rounds') ax1.legend() ax2 = ax1.twinx() ax2.plot(eval_metric_acc, label='eval accuracy', c='g') ax2.plot(train_metric_acc, label='train accuracy', c='y') ax2.set_ylabel('accuracy') ax2.legend() plt.grid() plt.show() if __name__ == '__main__': main()
サンプルコードでは、評価指標の推移を折れ線グラフとしてプロットしている。
上記を実行してみよう。 ログを確認すると、ちゃんと Accuracy に関する情報も出力される。
$ python lgbcm.py ... [1000] train's multi_logloss: 8.23321e-05 train's accuracy: 1 eval's multi_logloss: 0.396726 eval's accuracy: 0.947368
上記を実行すると、以下のようなグラフが得られる。
上記のグラフを見ると、学習に使った LogLoss に加えて Accuracy の推移も確認できる。 どうやら、イテレーション数が 100 を越えないあたりから検証データに対する LogLoss が増加に転じているようだ。 検証データに対する LogLoss は増加することなく現象し続けており、過学習を起こしていることがわかる。 検証データの LogLoss が増加するタイミングでは、学習データに対する Accuracy が増加すると共に検証データの Accuracy も低下している。
複数のカスタムメトリックを計測したい場合
先ほどの例ではカスタムメトリックが一つだったけど、複数確認したい場合もあるはず。 そのような場合には複数のメトリックをリストとして返す関数を作ることで対応できる。
以下のサンプルコードでは Breast Cancer データセットを用いて Accuracy, Precision, Recall をカスタムメトリックとして計測している。
#!/usr/bin/env python # -*- coding: utf-8 -*- import numpy as np from sklearn import datasets from sklearn.metrics import precision_score from sklearn.metrics import recall_score from sklearn.model_selection import train_test_split from matplotlib import pyplot as plt import lightgbm as lgb def accuracy(preds, data): """精度 (Accuracy) を計算する関数""" y_true = data.get_label() y_pred = np.where(preds > 0.5, 1, 0) metric = np.mean(y_true == y_pred) return 'accuracy', metric, True def precision(preds, data): """適合率 (Precision) を計算する関数""" y_true = data.get_label() y_pred = np.where(preds > 0.5, 1, 0) metric = precision_score(y_true, y_pred) return 'precision', metric, True def recall(preds, data): """再現率 (Recall) を計算する関数""" y_true = data.get_label() y_pred = np.where(preds > 0.5, 1, 0) metric = recall_score(y_true, y_pred) return 'recall', metric, True def metrics(preds, data): """複数の評価指標を計算するための関数""" # リストでまとめて返せば良い return [ accuracy(preds, data), precision(preds, data), recall(preds, data), ] def main(): # Breast Cancer データセットを読み込む dataset = datasets.load_breast_cancer() X, y = dataset.data, dataset.target # 学習用データと検証用データに分割する X_train, X_test, y_train, y_test = train_test_split(X, y, shuffle=True, random_state=42) # LightGBM のデータセットの表現に直す lgb_train = lgb.Dataset(X_train, y_train) lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train) lgbm_params = { 'objective': 'binary', 'metric': 'binary_logloss', } evals_result = {} lgb.train(lgbm_params, lgb_train, num_boost_round=1000, early_stopping_rounds=100, # メトリックを追跡する対象のデータセット valid_sets=[lgb_eval, lgb_train], # 上記の名前 valid_names=['eval', 'train'], # メトリックの履歴を残すオブジェクト evals_result=evals_result, # 独自メトリックを計算する関数 feval=metrics, ) # 組み込みのメトリック eval_metric_logloss = evals_result['eval']['binary_logloss'] train_metric_logloss = evals_result['train']['binary_logloss'] # カスタムメトリック (Accuracy) eval_metric_acc = evals_result['eval']['accuracy'] train_metric_acc = evals_result['train']['accuracy'] # カスタムメトリック (Precision) eval_metric_prec = evals_result['eval']['precision'] train_metric_prec = evals_result['train']['precision'] # カスタムメトリック (Precision) eval_metric_recall = evals_result['eval']['recall'] train_metric_recall = evals_result['train']['recall'] # グラフにプロットする _, ax1 = plt.subplots(figsize=(8, 4)) ax1.plot(eval_metric_logloss, label='eval logloss') ax1.plot(train_metric_logloss, label='train logloss') ax1.set_ylabel('logloss') ax1.set_xlabel('rounds') ax1.legend() ax2 = ax1.twinx() ax2.plot(eval_metric_acc, label='eval accuracy') ax2.plot(train_metric_acc, label='train accuracy') ax2.plot(eval_metric_prec, label='eval precision') ax2.plot(train_metric_prec, label='train precision') ax2.plot(eval_metric_recall, label='eval recall') ax2.plot(train_metric_recall, label='train recall') ax2.set_ylabel('ratio') ax2.legend() plt.grid() plt.show() if __name__ == '__main__': main()
上記を実行してみよう。
$ python lgbmcm.py ... [1] train's binary_logloss: 0.58296 train's accuracy: 0.629108 train's precision: 0.629108 train's recall: 1 eval's binary_logloss: 0.585825 eval's accuracy: 0.622378 eval's precision: 0.622378 eval's recall: 1
以下のようなグラフが得られる。 たしかに、複数のメトリックが計測できていることが分かる。
注意点
今回のように複数の評価指標を LightGBM に計算させるときは early_stopping_rounds
との併用において注意が必要になる。
というのも early_stopping_rounds
で early stopping の対象になるのが「いずれかの評価指標で条件を満たした場合」になっているため。
つまり、本来は LogLoss の値が増加に転じた場合に止めたいのに、まだ現象を続けている段階で別の評価指標が条件を満たすと学習が止まってしまう恐れがある。
スマートPythonプログラミング: Pythonのより良い書き方を学ぶ
- 作者: もみじあめ
- 発売日: 2016/03/12
- メディア: Kindle版
- この商品を含むブログ (1件) を見る