Variabilità nei risultati di cv.glmnet


18

Sto usando cv.glmnetper trovare predittori. L'impostazione che utilizzo è la seguente:

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

Per assicurarsi che i risultati sono riproducibili io set.seed(1). I risultati sono molto variabili. Ho eseguito esattamente lo stesso codice 100 per vedere quanto variabili fossero i risultati. Nelle corse 98/100 era sempre stato selezionato un predittore particolare (a volte solo da solo); sono stati selezionati altri predittori (il coefficiente era diverso da zero) di solito 50/100 volte.

Quindi mi dice che ogni volta che la validazione incrociata è in esecuzione, probabilmente selezionerà un diverso lambda migliore, perché la randomizzazione iniziale delle pieghe è importante. Altri hanno riscontrato questo problema ( risultati CV.glmnet ) ma non esiste una soluzione suggerita.

Sto pensando che forse quello che mostra 98/100 è probabilmente abbastanza fortemente correlato a tutti gli altri? I risultati si stabilizzano se eseguo LOOCV ( ), ma sono curioso di sapere perché sono così variabili quando nfold < n .fold-size=nnfold<n


1
Per essere chiari, vuoi dire che set.seed(1)una volta poi corri cv.glmnet()100 volte? Questa non è una grande metodologia per la riproducibilità; meglio a set.seed()destra prima di ogni corsa, oppure mantenere costanti i pieghe tra le corse. Ciascuna delle tue chiamate a cv.glmnet()sta chiamando sample()N volte. Quindi, se la lunghezza dei tuoi dati cambia, la riproducibilità cambia.
smci,

Risposte:


14

Il punto qui è che nelle cv.glmnetpieghe K ("parti") vengono scelte casualmente.

Nella convalida incrociata di K-fold il set di dati è diviso in K parti parti vengono utilizzate per prevedere la parte K-th (questo viene fatto K volte, usando una parte K diversa ogni volta). Questo viene fatto per tutti i lambda, ed è quello che dà il più piccolo errore di convalida incrociata.K-1KKlambda.min

Questo è il motivo per cui quando lo usi i risultati non cambiano: ogni gruppo ne fa uno, quindi non c'è molta scelta per igruppi K.nfoldS=nK

Dal cv.glmnet()manuale di riferimento:

Si noti inoltre che i risultati di cv.glmnet sono casuali, poiché le pieghe sono selezionate a caso. Gli utenti possono ridurre questa casualità eseguendo cv.glmnet molte volte e calcolando la media delle curve di errore.

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

MSE è il frame di dati che contiene tutti gli errori per tutti i lambda (per le 100 esecuzioni), lambda.minè il tuo lambda con un errore medio minimo.


La cosa di cui mi preoccupo di più è che la selezione di n sembra davvero importante a volte. Devo fidarmi di risultati che possono essere così variabili? O dovrei segnarlo come impreciso anche se lo eseguo più volte?
user4673,

1
A seconda della dimensione del campione, dovresti scegliere n in modo da avere almeno 10 osservazioni per gruppo. Quindi è meglio ridurre il valore predefinito n (= 10) se hai una dimensione del campione inferiore a 100. Detto questo, vedi la risposta modificata con il pezzo di codice: con questo per il ciclo puoi ripetere cv.glmnet 100 volte e fare la media del curve di errore. Provalo un paio di volte e vedrai che lambda.min non cambierà.
Alice,

2
Mi piace come l'hai fatto. Ho lo stesso loop ma con un'eccezione alla fine: guardo con che frequenza compaiono diverse funzionalità rispetto al MSE più basso da tutte le iterazioni. Scelgo un punto di taglio arbitrario (es. Mostra 50/100 iterazioni) e utilizzo quelle funzionalità. Curioso contrasto tra i due approcci.
user4673,

1
lun'mBdun'error,Sioncecv

Come notato dall'utente 4581, questa funzione può fallire a causa della variabilità della lunghezza di cv.glmnet(...)$lambda. La mia alternativa risolve questo: stats.stackexchange.com/a/173895/19676
Max Ghenis,

9

λααλα coppie ).

αλ e quindi li trattano come una distribuzione di valori (una variabile casuale).

Quindi, per ogni predittore ottengo:

  • coefficiente medio
  • deviazione standard
  • 5 riepilogo numeri (mediana, quartili, min e max)
  • la percentuale di volte è diversa da zero (cioè ha un'influenza)

In questo modo ottengo una descrizione abbastanza solida dell'effetto del predittore. Una volta che hai distribuzioni per i coefficienti, puoi eseguire qualsiasi roba statistica che ritieni valga la pena ottenere CI, valori p, ecc ... ma non ho ancora indagato su questo.

Questo metodo può essere utilizzato con più o meno qualsiasi metodo di selezione che mi viene in mente.


4
Puoi pubblicare il tuo codice qui per favore?
rbm,

Sì, puoi pubblicare il tuo codice qui?
smci,

4

Aggiungerò un'altra soluzione, che gestisce il bug in @ Alice's a causa della mancanza di lambda, ma non richiede pacchetti extra come @Max Ghenis. Grazie a tutte le altre risposte - tutti fanno punti utili!

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)

3

La risposta di Alice funziona bene nella maggior parte dei casi, ma a volte si verificano errori a causa della cv.glmnet$lambdarestituzione a volte di risultati di diversa lunghezza, ad esempio:

Errore nei rownames <- (tmp, value = c (0.135739830284452, 0.12368107787663,: lunghezza di 'dimnames' [1] non uguale all'estensione dell'array.

OptimLambdasotto dovrebbe funzionare nel caso generale, ed è anche più veloce sfruttando l' mclapplyelaborazione parallela ed evitando i loop.

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

1

È possibile controllare la casualità se si imposta esplicitamente foldid. Ecco un esempio di CV 5 volte

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

Ora esegui cv.glmnet con questi foldids.

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

Otterrai gli stessi risultati ogni volta.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.