【问题标题】:Automatic caret parameter tuning fails in glmnetglmnet 中的自动插入符号参数调整失败
【发布时间】:2017-07-18 05:48:30
【问题描述】:

上下文和错误信息

我尝试在插入符号中使用 glmnet 来拟合二分类预测模型。使用插入符号默认调谐网格时出现错误。我不认为这是由于数据格式错误,因为在指定我自己的调整网格时,没有问题。 错误信息是:

Error in loop$lambda[loop$alpha == alph[i]] <- np[which.max(np)] : 
replacement has length zero

在检查发生错误的行时,可以看到 R 试图在 NA 的向量 np(由 caret/glmnet 选择的 lambda 值?)上找到最大值 which.na()。我未能正确调试此问题,因为在调用 train() 后我找不到单步执行每一行代码的方法。希望有经验的大侠能帮帮我。

最小的工作示例

我创建了一个最小的工作示例,方法是让我的数据集尽可能小(它从 ~200 行和 ~40 列开始)同时保留错误。请注意,manualModelFit 工作正常,但无法计算 modelFit

library(caret)
library(glmnet)
# create data frame of features
var1 <- c(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1)
var2 <- c(1,1,1,1,1,0,1,1,1,1,1,0,1,1,0,1,1)
trainData <- data.frame(v1 = var1, v2 = var2)
# create fature vector of outcomes
trainClass <- as.factor(c('event','event','event','event','event','event','event','event','event','event','nonEvent','event','event','event','event','event','nonEvent'))
# set k for k-fold CV
kInner = 5
# set randomization seed
mySeed = 1622017
# set options for caret in fitControl
fitControl <- trainControl( method = 'cv', number = kInner, classProbs = TRUE, allowParallel = FALSE, summaryFunction = twoClassSummary, verboseIter = FALSE)
# run parameter tuning with a user-specified tuning grid
set.seed(mySeed)
myTuneGrid <- expand.grid(alpha = c(0,0.5,1), lambda = c(0,0.5,1))
manualModelFit <- train(x = trainData, y = trainClass, method = 'glmnet' , trControl = fitControl, metric = 'ROC', tuneGrid = myTuneGrid)
# run default parameter tuning
set.seed(mySeed)
modelFit <- train(x = trainData, y = trainClass, method = 'glmnet' , trControl = fitControl, metric = 'ROC')

问题

失败的原因是什么? 这是插入符号/glmnet 中的错误还是由于我忽略的数据集的属性?这个错误发生在我分析的多个数据集中。

【问题讨论】:

  • 嗨,我在 glmnet 上遇到了同样的问题。就我而言,我有 1079 x 180 数据集和三个类。如果我使用 PCA 将描述 90% 方差的变量从 180 个维度减少到 16 个,那么问题就消失了。不知道会发生什么...
  • @R Kiselev 这解决了您的问题的原因可能是因为它删除了通常导致此问题的因素变量。你能检查一下吗?

标签: r r-caret glmnet


【解决方案1】:

我遇到了同样的问题,我想我会分享我的解决方案。正如@Vandenman 提到的,您需要一种制作适当网格的方法。这对我有用。基本上,如果您增加在init &lt;- glmnet(...) 步骤中尝试的 lambda 的数量,您至少会得到一些不会失败的。我刚刚选择了 52(我打赌这个数字对你有用,但你可以随时更改它,而且计算时间对我来说可以忽略不计)。然后你选择len,它们均匀分布在那些没有失败的地方。

my_glmnet <- getModelInfo("glmnet") %>% magrittr::extract2("glmnet")
my_glmnet$grid <- function (x, y, len = NULL, search = "grid") {
  if (search == "grid") {
    numLev <- if (is.character(y) | is.factor(y)) 
      length(levels(y))
    else NA
    if (!is.na(numLev)) {
      fam <- ifelse(numLev > 2, "multinomial", "binomial")
    }
    else fam <- "gaussian"
    init <- glmnet(as.matrix(x), y, family = fam, nlambda = 52, alpha = 0.5)
    lambda <- unique(init$lambda)
    lambda <- lambda[-c(1, length(lambda))]
    l_seq <- seq(1, length(lambda), length = len) %>% round %>% unique
    lambda <- lambda[l_seq]
    out <- expand.grid(alpha = seq(0.1, 1, length = len), 
                       lambda = lambda)
  }
  else {
    out <- data.frame(alpha = runif(len, min = 0, 1), lambda = 2^runif(len, 
                                                                       min = -10, 3))
  }
  out
}

然后你可以运行trainmethod = my_glmnet

【讨论】:

  • 有趣的是,此解决方案对您将 nlambda = 52 指定为 glmnet::glmnet 中的默认值要高得多:glmnet(x, y, family=c("gaussian","binomial","poisson","multinomial","cox","mgaussian"), weights, offset=NULL, alpha = 1, nlambda = 100, ...
【解决方案2】:

确实,问题出在tuneGrid。在train.default 的第 225 行有代码

tuneGrid <- models$grid(x = x, y = y, len = tuneLength, 
            search = trControl$search)

你的例子给了我

  alpha lambda
1  0.10     NA
2  0.55     NA
3  1.00     NA
Warning messages:
1: In lognet(x, is.sparse, ix, jx, y, weights, offset, alpha, nobs,  :
  one multinomial or binomial class has fewer than 8  observations; dangerous ground
2: from glmnet Fortran code (error code -2); Convergence for 2th lambda value not reached after maxit=100000 iterations; solutions for larger lambdas returned 

显然NA 的 lambda 会导致稍后循环。 models$grid 是如下函数:

findGrid <- function (x, y, len = NULL, search = "grid") {
    if (search == "grid") {
        numLev <- if (is.character(y) | is.factor(y)) 
            length(levels(y))
        else NA
        if (!is.na(numLev)) {
            fam <- ifelse(numLev > 2, "multinomial", "binomial")
        }
        else fam <- "gaussian"
        init <- glmnet(as.matrix(x), y, family = fam, nlambda = len + 
                        2, alpha = 0.5)
        lambda <- unique(init$lambda)
        lambda <- lambda[-c(1, length(lambda))]
        lambda <- lambda[1:min(length(lambda), len)]
        out <- expand.grid(alpha = seq(0.1, 1, length = len), 
                           lambda = lambda)
    }
    else {
        out <- data.frame(alpha = runif(len, min = 0, 1), lambda = 2^runif(len, 
                                                                           min = -10, 3))
    }
    out
}

我将其重命名为findGrid。如果您使用findGrid(trainData, trainClass, 3) 运行它,您应该会收到相同的警告和故障网格。在这个二进制场景中,它所做的只是:

init <- glmnet(as.matrix(x), y, family = "binomial", nlambda = len + 2, alpha = 0.5)
lambda <- unique(init$lambda) # contains one value, 
lambda <- lambda[-c(1, length(lambda))]
lambda <- lambda[1:min(length(lambda), len)]
out <- expand.grid(alpha = seq(0.1, 1, length = len), 
                   lambda = lambda)

现在,在lambda &lt;- unique(init$lambda) 之后,lambda 只包含一个值,即9.9e+35。因此,之后使用索引的任何意图都不再起作用,而是将创建NA's。增加glmnet 中的迭代次数并不能避免错误。所以让我们跳过这些行并使用获得的网格,看看是否能解决问题。

init <- glmnet(as.matrix(x), y, family = "binomial", nlambda = len + 2, alpha = 0.5)
lambda <- unique(init$lambda) # contains one value, 
out <- expand.grid(alpha = seq(0.1, 1, length = len), lambda = lambda)
modelFit <- train(x = trainData, y = trainClass, method = 'glmnet' , trControl = fitControl, metric = 'ROC', 
                  tuneGrid = out) # <-- use the tuneGrid we made

其中运行但也给了我 17 个警告,所有形式:

Warning messages:
1: In eval(expr, envir, enclos) :
  model fit failed for Fold1: alpha=0.10, lambda=9.9e+35 Error in lognet(x, is.sparse, ix, jx, y, weights, offset, alpha, nobs,  : 
  one multinomial or binomial class has 1 or 0 observations; not allowed

因此,您将不得不找到一种方法来制作合适的网格。这可以通过以某种方式修复glmnet 或通过一些猜测/反复试验来完成。但是,我对在这个答案中寻找一种调整网格的方法犹豫不决,因为它很可能是一个特定于数据的问题。一个起点是查看您的完整数据集是否在某些类别中也有很少的观察结果。

另外,要自己调试,最简单的方法是调用View(caret:::train.default) 来查看函数。 ::: 从隐藏的命名空间中导入它。接下来,您可以将所有代码复制到train2 函数中,并使用浏览器语句逐行调试代码(至少,我是这样做的)。 R 找不到的任何其他函数也必须以 caret::: 为前缀。

【讨论】:

  • 很好的回答和解释,非常感谢!我将继续调查一个类中的少量观察是否是其他数据集中反复出现的主题。
  • @Vandenman 这是一个详尽的答案,谢谢。你能想到任何通用的解决方案,它可以提供最好的猜测,你可以传递给 caret::train 函数吗?原因是我将插入符号用于基准算法,所以我试图找到使用默认参数的第一个估计值。
猜你喜欢
  • 2018-06-13
  • 2020-03-30
  • 2013-11-15
  • 2016-07-20
  • 1970-01-01
  • 2013-06-19
  • 2019-11-09
  • 2021-06-15
  • 2018-09-13
相关资源
最近更新 更多