通过自定义目标进行 Xgboost 分位数回归

数据挖掘 机器学习 预测建模 xgboost 梯度下降 gbm
2021-09-18 07:35:07

我是 GBM 和 xgboost 的新手,目前xgboost_0.6-2在 R 中使用。模型在标准目标函数下运行良好,"objective" = "reg:linear"在阅读了这篇 NIH 论文后,我想使用自定义目标函数运行分位数回归,但它恰好迭代了 11 次并且指标不变。

我只是在GitHub xgboost 演示之后简单地切换了“pred”语句,但恐怕它比这更复杂,我找不到任何其他使用自定义目标函数的示例。我是否需要更进一步并为“grad”和“hess”部分采用衍生品?

或者它可能是 xgboost 的问题(可疑)?

qntregobj <- function(preds, dtrain) {
  qr_alpha = .5
  labels <- getinfo(dtrain, "label")
  preds <- ifelse( preds - labels >= 0
                 , (1-qr_alpha)*abs(preds - labels)
                 , qr_alpha*abs(preds - labels)
                 )
  grad <- preds - labels
  hess <- preds * (1 - preds)
  return(list(grad = grad, hess = hess))
}

step1.param <- list( "objective" = qntregobj
                   , "booster" = "gbtree"
                   , "eval.metric" = "rmse"
                   , 'nthread' = 16
                   )
set.seed(123)
step1.xgbTreeCV <- xgb.cv(param = step1.param
              , data = xgb.train
              , nrounds  = nrounds
              , nfold = 10
              , scale_pos_weight = 1
              
              , stratified = T
              , watchlist = watchlist
              
              , verbose = F
              , early_stopping_rounds = 10
              , maximize = FALSE
              
              ## set default parameters here - baseline
              , max_depth = 6
              , min_child_weight = 1
              , gamma = 0
              , subsample = 1
              , colsample_bytree = 1
              , lambda = 1
              , alpha = 0
              , eta = 0.3
  )
  print(Sys.time() - start.time)

  step1.dat <- step1.xgbTreeCV$evaluation_log
  step1.dat

产生:

iter train_rmse_mean train_rmse_std test_rmse_mean test_rmse_std nround
 1:    1        122.6362     0.04268346       122.6354     0.3849658      1
 2:    2        122.6362     0.04268346       122.6354     0.3849658      2
 3:    3        122.6362     0.04268346       122.6354     0.3849658      3
 4:    4        122.6362     0.04268346       122.6354     0.3849658      4
 5:    5        122.6362     0.04268346       122.6354     0.3849658      5
 6:    6        122.6362     0.04268346       122.6354     0.3849658      6
 7:    7        122.6362     0.04268346       122.6354     0.3849658      7
 8:    8        122.6362     0.04268346       122.6354     0.3849658      8
 9:    9        122.6362     0.04268346       122.6354     0.3849658      9
10:   10        122.6362     0.04268346       122.6354     0.3849658     10
11:   11        122.6362     0.04268346       122.6354     0.3849658     11
3个回答

也许下面的博客可以回答您的问题。

https://www.bigdatarepublic.nl/regression-prediction-intervals-with-xgboost/

如果不详细阅读代码,您的问题可能可以描述如下(来自博客):

在分位数值 q 与分区内的观测值相距较远的情况下,由于梯度和 Hessian 对于较大的差异 x_i-q 都是恒定的,因此分数保持为零并且不会发生分裂。

那么建议如下解决方案:

一个有趣的解决方案是通过向梯度添加随机化来强制拆分。当观察 x_i 和分区内的旧分位数估计 q 之间的差异很大时,这种随机化将强制对该体积进行随机分割。

是的,

grad <- preds - labels

特定于逻辑损失。请参阅此问题以获取推导。

我意识到这个问题很老了,但它可能仍然很有趣,因为 XGBoost 仍然没有提供开箱即用的分位数回归。您试图通过使用用户定义的损失函数来解决这个问题,这是显而易见的方法。要在 XGBoost 中使用用户定义的损失函数,您必须提供一阶和二阶导数(在您的代码中称为 grad 和 hess,可能用于梯度和 Hessian)。在这一点上,XGBoost 不同于您引用的 NIH 论文中讨论的梯度提升树的实现。

不幸的是,您的代码中的派生词不正确。正确的如下:

pred <- ifelse(preds-labels >= 0, 1-qr_alpha, qr_alpha)
hess <- 0

但即使这些也略有错误,因为当 preds=labels 时这两个派生都不存在。此外,二阶导数是常数的事实也是一个问题。常数二阶导数不包含 XGBoost 优化算法可以使用的任何信息。这两个问题都可以解决,但这需要的不仅仅是自定义目标函数。这可能是 XGBoost 中从未实现分位数回归的原因,尽管在撰写本文时相应的功能请求已经存在五年了。

如果您正在寻找使用梯度提升树的分位数回归的现代实现,您可能想尝试 LightGBM。它支持开箱即用的分位数回归。他们对上述问题的解决方案在这篇不错的博客文章中有更详细的解释。