插入符号和 randomForest 树数

机器算法验证 r 机器学习 随机森林 插入符号
2022-01-19 16:59:25

我很困惑为什么 R 中的caret包不允许调整随机森林(特别是randomForest包)中的树数(ntree)?我无法想象这是包作者的疏忽——所以一定有原因吗?任何人都可以阐明吗?

4个回答

从理论上讲,RF 模型的性能应该是 ntree 的单调函数,一旦你拥有“足够”的树,它就会在某个点之上保持稳定。这使得 ntree 更像是一个性能参数,而不是您想要调整的 Goldilocks 参数。Caret 倾向于专注于调整对于您希望在其中找到快乐媒介的高值和低值表现不佳的参数。

在实践中,我相信可能有研究发现,对于非常大的 ntree 值,性能确实会降低,但即使这是真的,效果也是微妙的,并且需要非常大的森林。

出于与 ntree 相同的原因,至少有 2-3 个 RF 参数没有被 Caret 调整。

Caret 确实允许您调整其后端randomForest包上的树数。例如,考虑到目前的最新版本(4.6-12),您只需传递普通ntree参数。插入符号会将其“重新传递”到randomForest,例如:

train(formula,
      data = mydata,
      method = "rf",
      ntree = 5,
      trControl = myTrControl)

如果您已经知道要使用多少棵树(Breiman 建议至少 1000 棵)并且已经randomForest::tuneRF获得了最佳mtry值(我们以 6 为例),那么:

ctrl <- trainControl(method = "none")

set.seed(2)
rforest <- train(response ~ ., data = data_set,
               method = "rf",
               ntree = 1000,
               trControl = ctrl,
               tuneGrid = data.frame(mtry = 6))

Eduardo 已在上面回答了您的问题,但我想另外演示如何调整用于分区的随机变量数量的值。在调整随机森林时,只要 ntree 足够大,这个参数就比 ntree 更重要。

虽然我同意这里发布的理论解释,在实践中,拥有过多的树会浪费计算能力,并使模型对象难以使用它们(特别是如果您使用不断保存和加载 .RDS 对象)。正因为如此,我认为如果我们希望模型足够,我们必须以某种方式找到允许稳定性能的最小必要树数(然后“让 LLN 的渐近行为完成剩下的工作”)。也许如果您是一位经验丰富的统计学家,或者您总是在处理类似的问题,您可以使用一些经验法则(比如 1000 或 10000 棵树)。但是,如果您的工作需要您适应各种建模任务,您最终将需要一些校准方法,以便您找到足够数量且成本低廉的树木。

为此,您可以从这里下载该方法的源代码,然后重写它以创建适合您需要的自定义方法。随意使用以下示例:

customRF <- list(label = "Random Forest",
 library = "randomForest",
 loop = NULL,
 type = c("Classification", "Regression"),
 parameters = data.frame(parameter = c("mtry", "ntree"), class = rep("numeric", 2), label = c("mtry", "ntree")),
 grid = function(x, y, len = NULL, search = "grid") {
   if(search == "grid") {
     out <- data.frame(mtry = caret::var_seq(p = ncol(x), 
                                             classification = is.factor(y), 
                                             len = len))
   } else {
     out <- data.frame(mtry = unique(sample(1:ncol(x), size = len, replace = TRUE)))
   }
   out
 },
 fit = function(x, y, wts, param, lev, last, classProbs, ...)
   randomForest::randomForest(x, y, mtry = param$mtry, ntree=param$ntree...),
 predict = function(modelFit, newdata, submodels = NULL)
   if(!is.null(newdata)) predict(modelFit, newdata) else predict(modelFit),
 prob = function(modelFit, newdata, submodels = NULL)
   if(!is.null(newdata)) predict(modelFit, newdata, type = "prob") else predict(modelFit, type = "prob"),
 predictors = function(x, ...) {
   ## After doing some testing, it looks like randomForest
   ## will only try to split on plain main effects (instead
   ## of interactions or terms like I(x^2).
   varIndex <- as.numeric(names(table(x$forest$bestvar)))
   varIndex <- varIndex[varIndex > 0]
   varsUsed <- names(x$forest$ncat)[varIndex]
   varsUsed
 },
 varImp = function(object, ...){
   varImp <- randomForest::importance(object, ...)
   if(object$type == "regression")
     varImp <- data.frame(Overall = varImp[,"%IncMSE"])
   else {
     retainNames <- levels(object$y)
     if(all(retainNames %in% colnames(varImp))) {
       varImp <- varImp[, retainNames]
     } else {
       varImp <- data.frame(Overall = varImp[,1])
     }
   }

   out <- as.data.frame(varImp)
   if(dim(out)[2] == 2) {
     tmp <- apply(out, 1, mean)
     out[,1] <- out[,2] <- tmp  
   }
   out
 },
 levels = function(x) x$classes,
 tags = c("Random Forest", "Ensemble Model", "Bagging", "Implicit Feature Selection"),
 sort = function(x) x[order(x[,1]),],
 oob = function(x) {
   out <- switch(x$type,
                 regression =   c(sqrt(max(x$mse[length(x$mse)], 0)), x$rsq[length(x$rsq)]),
                 classification =  c(1 - x$err.rate[x$ntree, "OOB"],
                                     e1071::classAgreement(x$confusion[,-dim(x$confusion)[2]])[["kappa"]]))
   names(out) <- if(x$type == "regression") c("RMSE", "Rsquared") else c("Accuracy", "Kappa")
   out
 })

定义此自定义方法后,您只需从 train(method=customRF) 调用它,mtry 和 ntree 都将根据您的 trainControl() 规范进行校准。