Variabilität der cv.glmnet-Ergebnisse

18

Ich benutze cv.glmnet, um Prädiktoren zu finden. Das Setup, das ich verwende, ist wie folgt:

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)]

Um sicherzustellen, dass die Ergebnisse reproduzierbar sind, habe ich set.seed(1). Die Ergebnisse sind sehr unterschiedlich. Ich habe genau den gleichen Code 100 ausgeführt, um zu sehen, wie variabel die Ergebnisse waren. In den 98/100 Läufen wurde immer ein bestimmter Prädiktor ausgewählt (manchmal nur für sich); Andere Prädiktoren wurden in der Regel 50/100 Mal ausgewählt (der Koeffizient war ungleich Null).

Es heißt also für mich, dass jedes Mal, wenn die Kreuzvalidierung ausgeführt wird, wahrscheinlich ein anderes bestes Lambda ausgewählt wird, da die anfängliche Randomisierung der Falten eine Rolle spielt. Andere haben dieses Problem gesehen ( CV.glmnet-Ergebnisse ), aber es gibt keine vorgeschlagene Lösung.

Ich denke, dass vielleicht diejenige, die 98/100 zeigt, ziemlich stark mit allen anderen korreliert ist? Die Ergebnisse haben stabilisieren , wenn ich nur laufen LOOCV ( ), aber ich bin neugierig , warum sie so variabel sind , wenn .Fold-Size=nnfold<n

user4673
quelle
1
Um es klar zu sagen, meinst du, dass du set.seed(1)einmal und dann cv.glmnet()100 Mal rennst? Das ist keine großartige Methode für die Reproduzierbarkeit. Besser set.seed()vor jedem Lauf nach rechts oder die Faltlinien über die Läufe hinweg konstant halten. Jeder Ihrer Anrufe an cv.glmnet()ruft sample()N-mal an. Wenn sich also die Länge Ihrer Daten ändert, ändert sich die Reproduzierbarkeit.
smci

Antworten:

14

Der Punkt hier ist, dass in cv.glmnetder K-Falte ("Teile") nach dem Zufallsprinzip ausgewählt werden.

Bei der Kreuzvalidierung mit K-Faltungen wird der Datensatz in Teile unterteilt, und Teile werden verwendet, um den K-ten Teil vorherzusagen (dies wird mal durchgeführt, wobei jedes Mal ein anderer Teil verwendet wird). Dies wird für alle Lambdas durchgeführt und ist derjenige, der den kleinsten Kreuzvalidierungsfehler ergibt.KK-1KKlambda.min

Aus diesem Grund die Ergebnisse bei Verwendung von nicht: Jede Gruppe besteht aus einer, sodass für die Gruppen keine große Auswahl besteht .nfÖlds=nK

Aus dem cv.glmnet()Referenzhandbuch:

Beachten Sie auch, dass die Ergebnisse von cv.glmnet zufällig sind, da die Falten zufällig ausgewählt werden. Benutzer können diese Zufälligkeit reduzieren, indem sie cv.glmnet viele Male ausführen und die Fehlerkurven mitteln.

### 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 ist der Datenrahmen, der alle Fehler für alle Lambdas enthält (für die 100 Läufe), lambda.minist Ihr Lambda mit minimalem Durchschnittsfehler.

Alice
quelle
Das, worüber ich am meisten besorgt bin, ist, dass die Auswahl von n manchmal wirklich wichtig zu sein scheint. Sollte ich Ergebnissen vertrauen, die so unterschiedlich sein können? Oder sollte ich es als lückenhaft kennzeichnen, auch wenn ich es mehrmals ausführe?
user4673
1
Abhängig von der Stichprobengröße sollten Sie n wählen, damit Sie mindestens 10 Beobachtungen pro Gruppe haben. Daher ist es besser, den Standardwert n (= 10) zu verringern, wenn Sie eine Stichprobengröße von weniger als 100 haben. Beachten Sie hierzu die bearbeitete Antwort mit dem Code: Mit dieser for-Schleife können Sie cv.glmnet 100-mal wiederholen und den Mittelwert der Fehlerkurven. Versuchen Sie es ein paar Mal und Sie werden sehen, dass sich lambda.min nicht ändern wird.
Alice
2
Ich mag, wie du es gemacht hast. Ich habe die gleiche Schleife, aber mit einer Ausnahme am Ende: Ich schaue, wie oft verschiedene Funktionen im Gegensatz zur niedrigsten MSE aller Iterationen angezeigt werden. Ich wähle einen beliebigen Schnittpunkt (dh zeige 50/100 Iterationen) und benutze diese Funktionen. Merkwürdig kontrastieren die beiden Ansätze.
user4673
1
leinmbdeinerrÖr,sichncecv
Wie user4581 bemerkte, kann diese Funktion aufgrund der Variabilität in der Länge von fehlschlagen cv.glmnet(...)$lambda. Meine Alternative behebt dies: stats.stackexchange.com/a/173895/19676
Max Ghenis
9

λααλα

αλ

Dann bekomme ich für jeden Prädiktor:

  • mittlerer Koeffizient
  • Standardabweichung
  • 5 Zahlenübersicht (Median, Quartile, Min und Max)
  • Prozentsatz der Zeit ist von Null verschieden (dh hat einen Einfluss)

Auf diese Weise erhalte ich eine ziemlich solide Beschreibung der Wirkung des Prädiktors. Sobald Sie Verteilungen für die Koeffizienten haben, können Sie alle statistischen Dinge ausführen, die Sie für wert halten, um CI-, p-Werte usw. zu erhalten, aber ich habe dies noch nicht untersucht.

Diese Methode kann mit mehr oder weniger jeder Auswahlmethode verwendet werden, die ich mir vorstellen kann.

Bakaburg
quelle
4
Können Sie bitte Ihren Code hier posten?
RBM
Ja, kannst du bitte deinen Code hier posten?
smci
4

Ich werde eine weitere Lösung hinzufügen, die den Fehler in @ Alice aufgrund fehlender Lambdas behebt, aber keine zusätzlichen Pakete wie @ Max Ghenis benötigt. Vielen Dank an alle anderen Antworten - jeder macht nützliche Punkte!

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)
Nebenschau Bob
quelle
3

Die Antwort von Alice funktioniert in den meisten Fällen gut, aber manchmal treten Fehler auf, weil cv.glmnet$lambdamanchmal Ergebnisse unterschiedlicher Länge zurückgegeben werden, z.

Fehler in rownames <- (tmp, value = c (0.135739830284452, 0.12368107787663,: Länge von 'dimnames' [1] ungleich Array-Ausdehnung.

OptimLambdaDas folgende sollte im Allgemeinen funktionieren und ist auch schneller, wenn die mclapplyparallele Verarbeitung und die Vermeidung von Schleifen genutzt werden.

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
quelle
1

Sie können die Zufälligkeit steuern, wenn Sie foldid explizit festlegen. Hier ein Beispiel für einen 5-fachen Lebenslauf

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

Führen Sie nun cv.glmnet mit diesen Foldids aus.

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

Sie erhalten jedes Mal die gleichen Ergebnisse.

Brigitte
quelle