Variabilidad en los resultados de cv.glmnet

18

Estoy usando cv.glmnetpara encontrar predictores. La configuración que uso es la siguiente:

lassoResults<-cv.glmnet(x=countDiffs,y=responseDiffs,alpha=1,nfolds=cvfold)
bestlambda<-lassoResults$lambda.min

results<-predict(lassoResults,s=bestlambda,type="coefficients")

choicePred<-rownames(results)[which(results !=0)]

Para asegurarse de que los resultados son reproducibles set.seed(1). Los resultados son muy variables. Ejecuté exactamente el mismo código 100 para ver qué tan variables fueron los resultados. En las ejecuciones 98/100 siempre se seleccionó un predictor particular (a veces solo); se seleccionaron otros predictores (el coeficiente fue distinto de cero) generalmente 50/100 veces.

Entonces, me dice que cada vez que se ejecuta la validación cruzada, probablemente seleccionará una mejor lambda diferente, porque la aleatorización inicial de los pliegues es importante. Otros han visto este problema ( resultados de CV.glmnet ) pero no hay una solución sugerida.

Estoy pensando que tal vez el que aparece 98/100 probablemente esté muy relacionado con todos los demás. Los resultados no se estabilizan si LOOCV simplemente correr ( ), pero tengo curiosidad por qué son tan variable cuando .nfold < nfold-size=nortenfold<norte

usuario4673
fuente
1
Para ser claros, ¿te refieres a que set.seed(1)una vez corriste cv.glmnet()100 veces? Esa no es una metodología excelente para la reproducibilidad; mejor a la set.seed()derecha antes de cada ejecución, o bien mantenga los pliegues constantes a lo largo de las ejecuciones. Cada una de sus llamadas cv.glmnet()está llamando sample()N veces. Entonces, si la longitud de sus datos cambia, la reproductividad cambia.
smci

Respuestas:

14

El punto aquí es que en cv.glmnetlos pliegues K ("partes") se seleccionan al azar.

En la validación cruzada de K-folds, el conjunto de datos se divide en partes, y las partes K - 1 se usan para predecir la parte K-ésima (esto se hace K veces, usando una parte K diferente cada vez). Esto se hace para todas las lambdas, y es la que da el error de validación cruzada más pequeño.KK-1KKlambda.min

Es por eso que cuando usa los resultados no cambian: cada grupo está formado por uno, por lo que no hay muchas opciones para los grupos K.norteFolres=norteK

Del cv.glmnet()manual de referencia:

Tenga en cuenta también que los resultados de cv.glmnet son aleatorios, ya que los pliegues se seleccionan al azar. Los usuarios pueden reducir esta aleatoriedad ejecutando cv.glmnet muchas veces y promediando las curvas de error.

### cycle for doing 100 cross validations
### and take the average of the mean error curves
### initialize vector for final data.frame with Mean Standard Errors
MSEs <- NULL
for (i in 1:100){
                 cv <- cv.glmnet(y, x, alpha=alpha, nfolds=k)  
                 MSEs <- cbind(MSEs, cv$cvm)
             }
  rownames(MSEs) <- cv$lambda
  lambda.min <- as.numeric(names(which.min(rowMeans(MSEs))))

MSEs es el marco de datos que contiene todos los errores para todas las lambdas (para las 100 ejecuciones), lambda.mines su lambda con un error promedio mínimo.

Alicia
fuente
Lo que más me preocupa es que la selección de n realmente parece importar a veces. ¿Debo confiar en los resultados que pueden ser tan variables? ¿O debería marcarlo como incompleto incluso si lo ejecuto varias veces?
user4673
1
Dependiendo del tamaño de la muestra, debe elegir n para tener al menos 10 observaciones por grupo. Por lo tanto, es mejor disminuir el valor predeterminado n (= 10) si tiene un tamaño de muestra menor que 100. Dicho esto, vea la respuesta editada con el fragmento de código: con este bucle for puede repetir cv.glmnet 100 veces y promediar el curvas de error Pruébelo un par de veces y verá que lambda.min no cambiará.
Alice
2
Me gusta como lo has hecho. Tengo el mismo bucle pero con una excepción al final: miro con qué frecuencia aparecen diferentes características en comparación con el MSE más bajo de todas las iteraciones. Elijo un punto de corte arbitrario (es decir, muestran 50/100 iteraciones) y uso esas características. Curioso contraste los dos enfoques.
user4673
1
lunmetrosireunmirror,syonorteCmiCv
Como señaló user4581, esta función puede fallar debido a la variabilidad en la longitud de cv.glmnet(...)$lambda. Mi alternativa soluciona esto: stats.stackexchange.com/a/173895/19676
Max Ghenis el
9

λααλα

αλ

Luego, para cada predictor obtengo:

  • coeficiente medio
  • Desviación Estándar
  • Resumen de 5 números (mediana, cuartiles, mínimo y máximo)
  • el porcentaje de veces es diferente de cero (es decir, tiene influencia)

De esta manera obtengo una descripción bastante sólida del efecto del predictor. Una vez que tenga distribuciones para los coeficientes, podría ejecutar cualquier cosa estadística que considere valiosa para obtener CI, valores de p, etc ... pero aún no investigé esto.

Este método se puede usar con más o menos cualquier método de selección que se me ocurra.

Bakaburg
fuente
44
¿Puedes publicar tu código aquí por favor?
rbm
Sí, ¿puedes publicar tu código aquí?
smci
4

Agregaré otra solución, que maneja el error en @ Alice debido a la falta de lambdas, pero no requiere paquetes adicionales como @Max Ghenis. Gracias a todas las otras respuestas: ¡todos hacen puntos útiles!

lambdas = NULL
for (i in 1:n)
{
    fit <- cv.glmnet(xs,ys)
    errors = data.frame(fit$lambda,fit$cvm)
    lambdas <- rbind(lambdas,errors)
}
# take mean cvm for each lambda
lambdas <- aggregate(lambdas[, 2], list(lambdas$fit.lambda), mean)

# select the best one
bestindex = which(lambdas[2]==min(lambdas[2]))
bestlambda = lambdas[bestindex,1]

# and now run glmnet once more with it
fit <- glmnet(xy,ys,lambda=bestlambda)
Bob acto secundario
fuente
3

La respuesta de Alice funciona bien en la mayoría de los casos, pero a veces se equivoca debido a que a cv.glmnet$lambdaveces se obtienen resultados de diferente longitud, por ejemplo:

Error en los nombres de fila <- (tmp, valor = c (0.135739830284452, 0.12368107787663,: la longitud de 'dimnames' [1] no es igual a la extensión de la matriz.

OptimLambdaa continuación debería funcionar en el caso general, y también es más rápido al aprovechar el mclapplyprocesamiento paralelo y evitar los bucles.

Lambdas <- function(...) {
  cv <- cv.glmnet(...)
  return(data.table(cvm=cv$cvm, lambda=cv$lambda))
}

OptimLambda <- function(k, ...) {
  # Returns optimal lambda for glmnet.
  #
  # Args:
  #   k: # times to loop through cv.glmnet.
  #   ...: Other args passed to cv.glmnet.
  #
  # Returns:
  #   Lambda associated with minimum average CV error over runs.
  #
  # Example:
  #   OptimLambda(k=100, y=y, x=x, alpha=alpha, nfolds=k)
  #
  require(parallel)
  require(data.table)
  MSEs <- data.table(rbind.fill(mclapply(seq(k), function(dummy) Lambdas(...))))
  return(MSEs[, list(mean.cvm=mean(cvm)), lambda][order(mean.cvm)][1]$lambda)
}
Max Ghenis
fuente
1

Puede controlar la aleatoriedad si establece explícitamente foldid. Aquí un ejemplo de CV 5 veces

library(caret)
set.seed(284)
flds <- createFolds(responseDiffs, k = cvfold, list = TRUE, returnTrain = FALSE)
foldids = rep(1,length(responseDiffs))
foldids[flds$Fold2] = 2
foldids[flds$Fold3] = 3
foldids[flds$Fold4] = 4
foldids[flds$Fold5] = 5

Ahora ejecute cv.glmnet con estos foldids.

lassoResults<-cv.glmnet(x=countDiffs,y=responseDiffs,alpha=1,foldid = foldids)

Obtendrá los mismos resultados cada vez.

Brigitte
fuente