交叉验证的结果出乎意料

时间:2018-04-06 13:08:56

标签: r cross-validation glmnet lasso

我想使用prostate data手动执行10倍交叉验证,以了解如何手动执行此操作。我使用elasticnet包代码。我通过glmnet包估计参数(当然,它也可以执行交叉验证,但我想手动完成)。在分析之后,在我看来,我需要一个不同的标准来选择调整参数而不是最小的cv.error,因为这给出了几乎为空的模型,如果不是这样的话#34;我的错误在哪里?"。 (根据Tibshirani的原始论文,最优模型有三个变量)

这是代码

library(ElemStatLearn)
library(glmnet)

x <- scale(prostate[,1:8],T,T)
y <- scale(prostate[,9],T,F)

lambda = seq(0,1,0.02)

cv.folds <- function(n, folds = 10){
  split(sample(1:n), rep(1:folds, length = n))
}

c.val <-  function(x, y, K = 10, lambda, plot.it = TRUE){
    n <- nrow(x)
    all.folds <- cv.folds(length(y), K)
    residmat <- matrix(0, length(lambda), K)
    for(i in seq(K)) {
      omit <- all.folds[[i]]
      xk <- as.matrix(x[-omit, ])
      yk <- as.vector(y[-omit])
      xg <- x[omit, ]
      yg <- y[omit]
      fit <- glmnet(xk, yk, family="gaussian", 
                    alpha=1, lambda=lambda,standardize = FALSE, intercept = FALSE)
      fit <- predict(fit,newx=xg,lambda=lambda)
      if(length(omit)==1){fit<-matrix(fit,nrow=1)}
      residmat[, i] <- apply((yg - fit)^2, 2, mean)
    }
    cv <- apply(residmat, 1, mean)
    cv.error <- sqrt(apply(residmat, 1, var)/K)
    object<-list(lambda = lambda, cv = cv, cv.error = cv.error)
    if(plot.it) {
      plot(lambda, cv, type = "b", xlab="lambda", ylim = range(cv, cv + cv.error, cv - cv.error))
    invisible(object)
    }
}

result <- c.val(x,y,K = 10,lambda = lambda)
lambda.opt <- lambda[which.min(result$cv.error)]
fit <- glmnet(x, y, family="gaussian", 
              alpha=1, lambda=lambda.opt,standardize = FALSE, intercept = FALSE)
coef(fit)

结果:

> coef(fit)
9 x 1 sparse Matrix of class "dgCMatrix"
                    s0
(Intercept) .         
lcavol      0.01926724
lweight     .         
age         .         
lbph        .         
svi         .         
lcp         .

修改 模型直接从glmnet生成。

fit.lasso <- glmnet(x, y, family="gaussian", alpha=1,
                    standardize = FALSE, intercept = FALSE)
fit.lasso.cv <- cv.glmnet(x, y, type.measure="mse", alpha=1,
                          family="gaussian",standardize = FALSE, intercept = FALSE)
coef.lambda.min <- coef(fit.lasso.cv,s=fit.lasso.cv$lambda.min)
coef.lambda.1se <- coef(fit.lasso.cv,s=fit.lasso.cv$lambda.1se)
cbind(coef.lambda.min,coef.lambda.1se)

结果:

9 x 2 sparse Matrix of class "dgCMatrix"
                      1         1
(Intercept)  .          .        
lcavol       0.59892674 0.5286355
lweight      0.23669159 0.1201279
age         -0.06979581 .        
lbph         0.09392021 .        
svi          0.24620007 0.1400748
lcp          .          .        
gleason      0.00346421 .        
pgg45        0.06631013 . 

第二列显示正确的(lambda.1se)结果。

1 个答案:

答案 0 :(得分:2)

你的错误&#34;很难发现:它来自于glmnet不会使用您自己的lambda向量的顺序来对结果向量进行排序的事实。

您使用的数据示例:

res <- glmnet(x, y, lambda=lambda)
res$lambda

因此,当您在过程结束时调用命令lambda[which.min(result$cv.error)]时,您将无法获得与交叉验证错误的最小值相对应的值。此外,它解释了为什么你的图表看起来很奇怪。

一个简单的解决方法是将脚本开头的lambda声明为递减向量:

lambda = seq(1, 0, 0.02)

最后评论:使用单个lambda时要小心。