El optimizador predeterminado de lme4 requiere muchas iteraciones para datos de alta dimensión

12

TL; DR: la lme4optimización parece ser lineal en la cantidad de parámetros del modelo por defecto, y es mucho más lenta que un glmmodelo equivalente con variables ficticias para grupos. ¿Hay algo que pueda hacer para acelerarlo?


Estoy tratando de ajustar un modelo logit jerárquico bastante grande (~ 50k filas, 100 columnas, 50 grupos). El ajuste de un modelo logit normal a los datos (con variables ficticias para el grupo) funciona bien, pero el modelo jerárquico parece estar estancado: la primera fase de optimización se completa bien, pero la segunda pasa por muchas iteraciones sin que nada cambie y sin detenerse .

EDITAR: sospecho que el problema es principalmente porque tengo tantos parámetros, porque cuando trato de establecer maxfnun valor más bajo, me da una advertencia:

Warning message:
In commonArgs(par, fn, control, environment()) :
  maxfun < 10 * length(par)^2 is not recommended.

Sin embargo, las estimaciones de los parámetros no cambian en absoluto a lo largo de la optimización, por lo que todavía estoy confundido sobre qué hacer. Cuando intenté configurar maxfnlos controles del optimizador (a pesar de la advertencia), pareció bloquearse después de finalizar la optimización.

Aquí hay un código que reproduce el problema de los datos aleatorios:

library(lme4)

set.seed(1)

SIZE <- 50000
NGRP <- 50
NCOL <- 100

test.case <- data.frame(i=1:SIZE)
test.case[["grouping"]] <- sample(NGRP, size=SIZE, replace=TRUE, prob=1/(1:NGRP))
test.case[["y"]] <- sample(c(0, 1), size=SIZE, replace=TRUE, prob=c(0.05, 0.95))

test.formula = y ~ (1 | grouping)

for (i in 1:NCOL) {
    colname <- paste("col", i, sep="")
    test.case[[colname]] <- runif(SIZE)
    test.formula <- update.formula(test.formula, as.formula(paste(". ~ . +", colname)))
}

print(test.formula)

test.model <- glmer(test.formula, data=test.case, family='binomial', verbose=TRUE)

Esto produce:

start par. =  1 fn =  19900.78 
At return
eval:  15 fn:      19769.402 par:  0.00000
(NM) 20: f = 19769.4 at           0     <other numbers>
(NM) 40: f = 19769.4 at           0     <other numbers>

Intenté establecer ncolotros valores, y parece que el número de iteraciones realizadas es (aproximadamente) 40 por columna. Obviamente, esto se convierte en un gran dolor a medida que agrego más columnas. ¿Hay ajustes que pueda hacer al algoritmo de optimización que reducirán la dependencia en el número de columnas?

Ben Kuhn
fuente
1
Sería útil conocer el modelo específico que está tratando de ajustar (especialmente la estructura de efectos aleatorios).
Patrick S. Forscher el
Lamentablemente, el modelo preciso es propietario. Hay un nivel de efectos aleatorios, con tamaños de grupo que varían entre ~ 100 y 5000. Avíseme si puedo proporcionar alguna otra información relevante sobre el modelo.
Ben Kuhn el
OK, agregué un código que reproduce el problema.
Ben Kuhn el
1
No tengo una respuesta completa para usted, así que lo dejaré como comentario. En mi experiencia, glmeres bastante lento, especialmente para modelos que tienen una estructura compleja de efectos aleatorios (por ejemplo, muchas pendientes aleatorias, efectos aleatorios cruzados, etc.). Mi primera sugerencia sería intentar nuevamente con una estructura simplificada de efectos aleatorios. Sin embargo, si está experimentando este problema solo con un modelo de intercepciones aleatorias, su problema puede ser simplemente el número de casos, en cuyo caso deberá probar algunas herramientas especializadas para big data.
Patrick S. Forscher el
Tiene el mismo problema con 2 grupos en lugar de 50. Además, al probar con un número menor de columnas, parece que el número de iteraciones es aproximadamente lineal en el número de columnas ... ¿Hay métodos de optimización que funcionen mejor aquí? ?
Ben Kuhn el

Respuestas:

12

Una cosa que puedes probar es cambiar el optimizador. Vea el comentario de Ben Bolker en este tema de github . La implementación nlopt de bobyqa suele ser mucho más rápida que la predeterminada (al menos cada vez que lo intento).

library(nloptr)
defaultControl <- list(algorithm="NLOPT_LN_BOBYQA",xtol_rel=1e-6,maxeval=1e5)
nloptwrap2 <- function(fn,par,lower,upper,control=list(),...) {
    for (n in names(defaultControl)) 
      if (is.null(control[[n]])) control[[n]] <- defaultControl[[n]]
    res <- nloptr(x0=par,eval_f=fn,lb=lower,ub=upper,opts=control,...)
    with(res,list(par=solution,
                  fval=objective,
                  feval=iterations,
                  conv=if (status>0) 0 else status,
                  message=message))
}

system.time(test.model <- glmer(test.formula, data=test.case, 
family='binomial', verbose=TRUE))

system.time(test.model2 <- update(test.model,
control=glmerControl(optimizer="nloptwrap2"))

Además, consulte esta respuesta para obtener más opciones y este hilo de R-sig-mixed-models (que parece más relevante para su problema).

Editar: te di información desactualizada relacionada con nloptr. En lme4 1.1-7y hacia arriba, nloptrse importa automáticamente (ver ?nloptwrap). Todo lo que tienes que hacer es agregar

control = [g]lmerControl(optimizer = "nloptwrap") # +g if fitting with glmer

a su llamada

alexforrence
fuente
¡Gracias! Estoy probando el código nlopt en este momento. Me pregunto si está ocurriendo algo más que una mala implementación del optimizador, ya que ajustar un glum dummified casi equivalente fue mucho más rápido, pero veré ...
Ben Kuhn
Bueno, fue sin duda más rápido, pero se detuvo con un error: PIRLS step-halvings failed to reduce deviance in pwrssUpdate. ¿Tienes alguna idea de lo que podría estar pasando aquí? El mensaje de error no es exactamente transparente ...
Ben Kuhn
Para las patadas, podría intentar establecer nAGQ = 0 (vea el hilo que vinculé para algunas ideas más). No recuerdo qué causa el error PIRLS, pero miraré a mi alrededor.
alexforrence
¡Muchas gracias! ¿Podría indicarme un recurso en el que pueda aprender más sobre los detalles de estos métodos para poder resolver problemas como este en el futuro? La optimización se parece mucho a la magia negra para mí en este momento.
Ben Kuhn
2
nAGQ = 0 funcionó para mí en su ejemplo de prueba con el bobyqa predeterminado (se ejecutó en ~ 15 segundos), y en 11 segundos con el nloptrbobyqa. Aquí hay una entrevista con John C. Nash (coautor de los paquetes optimy optimx) donde hace una explicación de alto nivel de optimización. Si busca optimxo nloptrutiliza CRAN, sus respectivos manuales de referencia le darán más información sobre la sintaxis. nloptrTambién tiene una viñeta disponible, que va un poco más en detalle.
alexforrence