首页 文章

glmnet中的自动插入符号参数调整失败

提问于
浏览
7

上下文和错误消息

我尝试在插入符号中使用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')

问题

是什么导致失败?这是caret / glmnet中的错误还是由于我忽略了数据集的属性?我分析的多个数据集中会出现此错误 .

2 回答

  • 1

    的确,问题在于 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 <- unique(init$lambda) 之后, lambda 只包含一个 9.9e+35 的值 . 因此,无论事后的指数是什么意思都不再有效,而是会创建 NA . 增加 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::: 为前缀 .

  • 4

    我遇到了同样的问题,我想我会分享我的解决方案 . 正如@Vandenman所提到的,你需要一种制作合适网格的方法 . 这对我有用 . 基本上如果你增加你在 init <- glmnet(...) 步骤中尝试的lambdas数量,你至少会得到一些不会失败的 . 我刚刚选择了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
    }
    

    然后你可以用 method = my_glmnet 运行 train .

相关问题