Xgboost-如何使用"mae"作为目标函数?

33

我知道xgboost需要一阶梯度和二阶梯度,但是否有其他人使用过“mae”作为目标函数?

3个回答

62

先来点理论,抱歉!您要求MAE的梯度和Hessian矩阵,但是MAE不是连续二次可微的,因此试图计算一阶和二阶导数变得棘手。下面我们可以看到在x=0处的“拐点”,这阻止了MAE的连续可微性。

此外,在所有良好行为的点上,二阶导数均为零。在XGBoost中,二阶导数用作叶权重的分母,当为零时会产生严重的数学错误。

鉴于这些复杂性,我们最好的办法是尝试使用其他的、表现良好的函数来近似MAE。让我们来看看。

Some different loss functions

我们可以看到上面有几个近似绝对值的函数。很明显,对于非常小的值,平方误差(MSE)是MAE的相当不错的近似。然而,我假设这对于您的用例来说是不足够的。

Huber损失是一个有很好文献记录的损失函数。但它不连续平滑,所以我们不能保证平滑的导数。我们可以使用伪-Huber函数来近似它。它可以在python XGBoost中实现如下:

import xgboost as xgb

dtrain = xgb.DMatrix(x_train, label=y_train)
dtest = xgb.DMatrix(x_test, label=y_test)

param = {'max_depth': 5}
num_round = 10

def huber_approx_obj(preds, dtrain):
    d = preds - dtrain.get_labels() #remove .get_labels() for sklearn
    h = 1  #h is delta in the graphic
    scale = 1 + (d / h) ** 2
    scale_sqrt = np.sqrt(scale)
    grad = d / scale_sqrt
    hess = 1 / scale / scale_sqrt
    return grad, hess

bst = xgb.train(param, dtrain, num_round, obj=huber_approx_obj)  

通过替换 obj=huber_approx_obj 可以使用其他功能。

Fair Loss 并没有很好的文档记录,但似乎表现不错。 公平损失函数为:

公平损失函数

可以这样实现:

def fair_obj(preds, dtrain):
    """y = c * abs(x) - c**2 * np.log(abs(x)/c + 1)"""
    x = preds - dtrain.get_labels()
    c = 1
    den = abs(x) + c
    grad = c*x / den
    hess = c*c / den ** 2
    return grad, hess

这段代码是从Kaggle Allstate Challenge中第二名的解决方案中采用并进行了调整。

Log-Cosh 损失函数。

def log_cosh_obj(preds, dtrain):
    x = preds - dtrain.get_labels()
    grad = np.tanh(x)
    hess = 1 / np.cosh(x)**2
    return grad, hess

最后,您可以使用上述函数作为模板创建自己的定制损失函数。


警告:由于API更改,更新版本的XGBoost可能需要具有以下形式的损失函数:

def custom_objective(y_true, y_pred):
    ...
    return grad, hess

2
非常感谢Josh!关于log-cosh代码的快速评论;np.cosh对于某些输入可能会溢出,使用恒等式hess = 1- np.tanh(x)**2将避免这些问题。 - chepyle
1
我认为上面提到的公平损失函数是错误的(尽管实现是正确的)。根据 https://www.kaggle.com/c/allstate-claims-severity/discussion/24520,正确的“公平损失”函数由 y = c * abs(x) - c**2 * np.log(abs(x)/c + 1) 给出。 - kadee
1
@josh 谢谢(我是指你,不是 kadee),那很有道理。最后,你能否评论一下 @hbar137 的答案?难道应该使用 "d = preds - labels" 吗? - zkurtz
1
@josh,“grad = d / scale_sqrt”这一行与“d为负并不意味着一阶导数变为负”不兼容。梯度的符号恰好是d的符号,因为scale_sqrt始终为正。经验上,如果翻转符号,我会得到截然不同且更好的结果。 - zkurtz
4
我认为对于sklearn,你应该反转传递给目标函数的参数......第一个应该是标签,第二个应该是预测值......我想在这种情况下并没有太大的区别,但可能会造成混淆..另外get_labels -> get_label。 - gabboshow
显示剩余12条评论

3

对于上述的Huber损失函数,我认为梯度前面缺少了一个负号。应该是这样的:

grad = - d / scale_sqrt

1
请在答案的评论部分添加注释。另外,您为什么认为是这样呢?我看不出负面的来源在哪里。谢谢。 - Little Bobby Tables
如果正确,hbar137应该因指出比您的标准评论稍微重要的事情而获得点赞。 - zkurtz
1
@zkurtz 如果它是正确的话就好了,但请检查伪-胡贝尔导数 - Little Bobby Tables
@josh 在抽象意义上,你的公式是正确的。但如果你将(-x)代替x,那么在求导时需要应用链式法则,导致导数上有一个负号。hbar137在写这篇文章时是正确的,但由于你已经修正了解决方案(谢谢!),所以这个答案不再需要。 - zkurtz
谢谢你们两位的贡献和完善答案!! - Little Bobby Tables

1

我正在对大约正态分布的Y运行上述的huber/fair度量,但由于某种原因,在alpha <0的情况下(并且在公平情况下始终如此),结果预测将等于零...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接