Варіативність у результатах cv.glmnet


18

Я використовую cv.glmnetдля пошуку прогнозів. Я використовую наступну настройку:

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

Щоб переконатися, що результати відтворюються я set.seed(1). Результати дуже різняться. Я запустив такий самий код 100, щоб побачити, наскільки результативні. У 98/100 запусках завжди був обраний один конкретний предиктор (іноді просто самостійно); були вибрані інші предиктори (коефіцієнт корисної дії був не нульовим), як правило, 50/100 разів.

Отже, мені кажуть, що кожного разу, коли перехресне підтвердження буде запущене, він, ймовірно, вибере іншу найкращу лямбда, тому що початкова рандомізація складок має значення. Інші бачили цю проблему ( результати CV.glmnet ), але запропонованого рішення не існує.

Я думаю, що, можливо, той, що відображає 98/100, напевно, дуже корелює з усіма іншими? Результати цього стабілізуватися , якщо я просто запустити LOOCV ( ), але мені цікаво , чому вони так змінної при nfold < п .fold-size=nnfold<n


1
Щоб було зрозуміло, ти маєш на увазі, що ти set.seed(1)один раз потім біжиш cv.glmnet()100 разів? Це не велика методологія відтворення; краще set.seed()праворуч перед кожним пробігом, інакше тримати складки постійними протягом пробігу. Кожен ваш дзвінок cv.glmnet()дзвонить sample()N разів. Отже, якщо довжина ваших даних колись змінюється, відтворюваність змінюється.
smci

Відповіді:


14

Суть у тому, що в cv.glmnetK складки ("частини") вибираються випадковим чином.

У перехресній валідації K-складки набір даних розділений на частини, а K - 1 частини використовуються для прогнозування K-ї частини (це робиться K разів, використовуючи іншу K- частину кожен раз). Це робиться для всіх лямбда, і саме той, що дає найменшу помилку перехресної перевірки.KK1KKlambda.min

Ось чому, коли ви використовуєте результати не змінюються: кожна група складається з однієї, тому для K груп немає великого вибору .nfolds=nK

З cv.glmnet()довідкового посібника:

Зауважимо також, що результати cv.glmnet є випадковими, оскільки складки вибираються навмання. Користувачі можуть зменшити цю випадковість, запустивши cv.glmnet багато разів і усереднюючи криві помилок.

### 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 - це кадр даних, що містить усі помилки для всіх лямбда (для 100 запусків), lambda.minце ваша лямбда з мінімальною середньою помилкою.


Що мене найбільше хвилює, це те, що вибір n дійсно іноді має значення. Чи варто довіряти результатам, які можуть бути настільки змінними? Або я повинен крейдувати її як схематичну, навіть якщо я її запускаю кілька разів?
user4673

1
Залежно від розміру вибірки ви повинні вибрати n, щоб у вас було принаймні 10 спостережень на групу. Тому краще зменшити значення за замовчуванням n (= 10), якщо у вас розмір вибірки менше 100. Це сказано, див. Відредаговану відповідь з фрагментом коду: за допомогою цього циклу ви можете повторити cv.glmnet в 100 разів і середнє значення криві помилок Спробуйте кілька разів, і ви побачите, що lambda.min не зміниться.
Аліса

2
Мені подобається, як ти це зробив. У мене однаковий цикл, але з одним винятком в кінці: я дивлюся на те, як часто з’являються різні функції на відміну від найнижчого MSE з усіх ітерацій. Я вибираю довільну точку зрізу (тобто показую 50/100 ітерацій) і використовую ці функції. Цікаво протиставити два підходи.
user4673

1
lambdaerror,sincecv

Як зазначав user4581, ця функція може вийти з ладу через мінливість довжини cv.glmnet(...)$lambda. Моя альтернатива виправляє це: stats.stackexchange.com/a/173895/19676
Max Ghenis

9

Останнім часом я зіткнувся з тією ж проблемою. Я багато разів намагався повторити резюме, наприклад, 100, 200, 1000 у своєму наборі даних, намагаючись знайти найкращеλααλα

αλ

Потім для кожного прогноктора я отримую:

  • середній коефіцієнт
  • стандартне відхилення
  • Підсумок 5 чисел (медіана, квартилі, хв і макс)
  • відсоток разів відрізняється від нуля (тобто має вплив)

Таким чином я отримую досить ґрунтовний опис ефекту провісника. Після розподілу коефіцієнтів ви зможете запустити будь-які статистичні дані, які, на вашу думку, варто отримати значення CI, p, тощо ..., але я цього ще не досліджував.

Цей метод можна використовувати з більш-менш будь-яким методом відбору, про який я можу придумати.


4
Чи можете ви опублікувати свій код тут, будь ласка?
rbm

Так, чи можете ви опублікувати тут свій код?
smci

4

Я додам ще одне рішення, яке обробляє помилку в @ Alice's через відсутні лямбда, але не потребує додаткових пакунків, як @Max Ghenis. Дякуємо за всі інші відповіді - кожен робить корисні моменти!

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

Відповідь Аліси працює в більшості випадків добре, але іноді виникає помилка через те, що cv.glmnet$lambdaіноді повертаються результати різної довжини, наприклад:

Помилка в найменуваннях рядків <- (tmp, значення = c (0.135739830284452, 0.12368107787663,: довжина 'dimnames' [1] не дорівнює кількості масиву).

OptimLambdaнижче слід працювати в загальному випадку, а також швидше за рахунок використання mclapplyпаралельної обробки та уникнення циклів.

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

Ви можете керувати випадковістю, якщо явно встановити кратну кількість. Ось приклад для 5-кратного резюме

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

Тепер запустіть cv.glmnet з цими складками.

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

Ви будете отримувати однакові результати кожного разу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.