やること
xgboostの目的関数を定義済みのものから自分で定義したものに変える。 回帰は常に 正解ラベル=予測の平均値 だったらいいのだけれど、予測を外したらまずいケースも現実問題では存在する。 なので、予測を外したらダメなケースだけは特にペナルティを大きくした目的関数を自分で定義したかった。 今回は練習でQuantile Regressionをxgboostを使って行う。
Quantile Regression
Koenker, Roger. Quantile regression. No. 38. Cambridge university press, 2005.
イメージ
の赤実線以下のサンプル(正解ラベルよりも予測値の方が大きいケース)をなくしたい。 なので非対称(asymmetric)な損失を自分で定義してモデルを作りたい。 Quantile regressionはすぐ実装できそう。
scikit-learnのGradientBoostingでの実装
scikit-learnのGradient Boostingのオプションとして選択可能。アルファの値で罰則項の値のバランスを指定する。 scikit-learnでここ以外でQuantile Regressionを目にする機会ない気がする。
class QuantileLossFunction(RegressionLossFunction): """Loss function for quantile regression. Quantile regression allows to estimate the percentiles of the conditional distribution of the target. """ def __init__(self, n_classes, alpha=0.9): super(QuantileLossFunction, self).__init__(n_classes) assert 0 < alpha < 1.0 self.alpha = alpha self.percentile = alpha * 100.0 def init_estimator(self): return QuantileEstimator(self.alpha) def __call__(self, y, pred, sample_weight=None): pred = pred.ravel() diff = y - pred alpha = self.alpha mask = y > pred if sample_weight is None: loss = (alpha * diff[mask].sum() - (1.0 - alpha) * diff[~mask].sum()) / y.shape[0] else: loss = ((alpha * np.sum(sample_weight[mask] * diff[mask]) - (1.0 - alpha) * np.sum(sample_weight[~mask] * diff[~mask])) / sample_weight.sum()) return loss
scikit-learn/gradient_boosting.py at master · scikit-learn/scikit-learn · GitHub
sample_weight
の重み分だけ正解ラベルのとの差が0以下と以上のサンプルで alpha
、 (1.0 - alpha)
だけ罰則が科される。
XGBoostの目的関数を変更する
実装のサンプルでは
def objective_function(preds, dtrain): .... return grad, hess
で dtrain
は xgboost.DMatrix
のデータ。gradとhessが以下の論文のGとHの部分に対応している、はず。
Chen, Tianqi, and Carlos Guestrin. "Xgboost: A scalable tree boosting system." Proceedings of the 22Nd ACM SIGKDD International Conference on Knowledge Discovery and Data Mining. ACM, 2016.
目的関数
def quantile(preds, labels): alpha = .8 N = preds.shape[0] O = np.zeros(N)+1 mask = labels > preds grad = ((alpha * mask) - ((1.0 - alpha) * (1-mask))) * labels hess = O return grad, hess
hess
は固定で予測値が正解を下回った時にalpha
をかけるように変更。
このあともうちょっと目的関数をクロスバリデーションしながら調節。
結果
入力データは 0 ~ 40の乱数xとxに対して多項式で従う特徴を50個くらい適当につけたもの。
赤実線以下のサンプルがほとんどなくなったことがわかる。残りの誤差が大きいデータは適当なアンサンブルを作ってカットすればいい、かな?
他の目的関数
問題によって適宜変更する必要あり。