このページについて
- 評価指標がRMSLEのコンペに参加したときに思ったことを色々とメモします。
- 概要としては、LightGBMとGridSearchCVとRMSLEの話です。細かい話は下記へ
- どなたの役に立つかはわかりませんが(笑)
そもそも評価関数って
- 予測の精度を評価するための関数ですね。
- 以下に回帰で定番のMAE、RMSEを並べたあと、RMSLEを眺めてみたいと思います。
平均絶対誤差(MAE,L1_loss)
- まずは、一番わかり易い絶対誤差。
- 予測と実績の絶対差の平均なのでとても理解しやすい。
\frac{1}{n} \sum_{i=1}^{n} |Pred_{i} - Act_{i}|
平均二乗誤差(MSE、RMSE、L2_loss)
- もう一つの定番、RMSE(平均二乗誤差)でしょうか。
- 機械学習ライブラリの回帰関数のデフォルトの評価関数だったりします。
- RMSEの最小化が、誤差が正規分布の最尤推定と同義である背景からでしょう。(最小二乗法)
- LightGBMの回帰タスクのデフォルトもRMSE(L2_loss)ですね。
\sqrt{\frac{1}{n} \sum_{i=1}^{n} (Pred_{i} - Act_{i})^{2}}
対数平均二乗誤差(RMSLE)
- ある値は10、ある値は10億といった、値のレンジが大きな(対数正規分布に近い)データの学習に利用します。
- 先程のRMSEと見比べると下記のような感じ。
- 対数(log)を計算してから、予測と実績を引き算。
- 予測と実績の誤差を幅ではなく比率として表現
- 対数を取る前に、予測、実績共に+1
- 予測または、実績が0の場合、log(0)となり計算できなくなるので、+1する。
\sqrt{\frac{1}{n} \sum_{i=1}^{n} (log(Pred_{i}+1) - log(Act_{i}+1))^{2}}
- RMSEとの差分は数式のこの部分。この部分以外の形は一緒。
log(Pred_{i}+1)
log(Act_{i}+1)
LightGBM + GridSearchCV(scikit-learn)
- コンペで定番の組み合わせかもしれませんが、、
- LightGBMは、sklearnのインターフェイスを実装しているので、sklearnのグリッドサーチと併用できます。
- LGBMClassifier(分類)
- LGBMRegressor(回帰)
- LightGBMで学習し、最適なパラメータはGridSearchCVで学習する。
- そんなときに、コンペの評価関数が、MAEやRMSEならいいのだが、RMSLEだとちょっと困ったことが起きる。
- GridSearchCVによるパラメータ探索は、RMSLEをターゲットとして実施可能
- 一方、LightGBMは、標準ではターゲットにRMSLEを指定できない。(独自関数で実装が必要)
ターゲットとする評価関数 | GridSearchCVでの指定方法 | LightGBMでの指定方法 |
---|---|---|
MAE | scoring=‘neg_mean_absolute_error’ | eval_metric='mae' |
RMSE | scoring=‘neg_mean_squared_error’ | eval_metric='rmse' |
RMSLE | scoring=‘neg_mean_squared_log_error’ | ※標準では無し |
ではどうするか
- 方法はいくつかある。
- 方法1
- GridSearchCVは、**‘neg_mean_squared_log_error’**を指定。
- LightGBMは、RMSLE関数を、自分で実装し指定する。(ちょっと面倒くさそう)
- 方法2
- GridSearchCV、LightGBM共に、評価関数は、RMSEで統一する。
- ただし、学習の前後で下記の変換を行う。(これだけ)
- 方法1
# 学習前にy_trainに、log(y+1)で変換
y_train = np.log(y_train + 1) # np.log1p() でもOK
# グリッドサーチし、学習する。
# ちなみに説明変数(X_train、X_test)は変換不要。
# 最後に、予測結果に対しexp(y)-1で逆変換
pred = np.exp(pred) - 1 # np.expm1() でもOK
- RMSEとRMSLEの数式上の差分を、上記変換で埋めてあげれば、GridSearchCV、LightGBM共に同じ評価関数で学習ができる。
本当にこれで良いか?
In [1]: import numpy as np
In [2]: input = np.arange(1,10,1)
In [3]: input
Out[3]: array([1, 2, 3, 4, 5, 6, 7, 8, 9])
In [4]: output = np.log(input + 1)
In [5]: output
Out[5]:
array([0.69314718, 1.09861229, 1.38629436, 1.60943791, 1.79175947,
1.94591015, 2.07944154, 2.19722458, 2.30258509])
In [6]: result = np.exp(output) - 1
In [7]: result
Out[7]: array([1., 2., 3., 4., 5., 6., 7., 8., 9.])
- np.log1pで変換したデータが、np.expm1で復元できましたね。
- 大丈夫そう!