2012-07-24 20 views
10

Tengo un problema de optimización que resolverá el método Nelder-Mead, pero que también me gustaría resolver usando BFGS o Newton-Raphson, o algo que tome una función de degradado, para obtener más velocidad y, con suerte, estimaciones más precisas. Escribí tal función de degradado siguiendo (pensé) el ejemplo en la documentación optim/optimx, pero cuando lo uso con BFGS mis valores iniciales no se mueven (optim()), o la función no se ejecuta (optimx()) , que devuelve Error: Gradient function might be wrong - check it!). Lo siento, hay un poco de código involucrado en la reproducción de esto, pero aquí va:cómo especificar correctamente una función de degradado para usar en optim() u otro optimizador

Esta es la función para la que quiero obtener estimaciones de parámetros (esto es para suavizar las tasas de mortalidad en la vejez, donde x es la edad, a partir de los 80 años):

KannistoMu <- function(pars, x = .5:30.5){ 
     a <- pars["a"] 
     b <- pars["b"] 
     (a * exp(b * x))/(1 + a * exp(b * x)) 
    } 

Y aquí hay una función de probabilidad logarítmica para estimar desde tasas observadas (definida como muertes, .Dx sobre la exposición, .Exp):

KannistoLik1 <- function(pars, .Dx, .Exp, .x. = .5:30.5){ 
     mu <- KannistoMu(exp(pars), x = .x.) 
     # take negative and minimize it (default optimizer behavior) 
     -sum(.Dx * log(mu) - .Exp * mu, na.rm = TRUE) 
    } 

que se ven exp(pars) allí beca uso doy log(pars) para optimizar, para limitar el a final y el b para que sea positivo.

Datos de ejemplo (1962 hembras Japón, si alguien tiene curiosidad):

.Dx <- structure(c(10036.12, 9629.12, 8810.11, 8556.1, 7593.1, 6975.08, 
     6045.08, 4980.06, 4246.06, 3334.04, 2416.03, 1676.02, 1327.02, 
     980.02, 709, 432, 350, 217, 134, 56, 24, 21, 10, 8, 3, 1, 2, 
     1, 0, 0, 0), .Names = c("80", "81", "82", "83", "84", "85", "86", 
     "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", 
     "98", "99", "100", "101", "102", "103", "104", "105", "106", 
     "107", "108", "109", "110")) 
    .Exp <- structure(c(85476.0333333333, 74002.0866666667, 63027.5183333333, 
     53756.8983333333, 44270.9, 36749.85, 29024.9333333333, 21811.07, 
     16912.315, 11917.9583333333, 7899.33833333333, 5417.67, 3743.67833333333, 
     2722.435, 1758.95, 1043.985, 705.49, 443.818333333333, 223.828333333333, 
     93.8233333333333, 53.1566666666667, 27.3333333333333, 16.1666666666667, 
     10.5, 4.33333333333333, 3.16666666666667, 3, 2.16666666666667, 
     1.5, 0, 1), .Names = c("80", "81", "82", "83", "84", "85", "86", 
     "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", 
     "98", "99", "100", "101", "102", "103", "104", "105", "106", 
     "107", "108", "109", "110")) 

las siguientes obras para el método Nelder-Mead:

NMab <- optim(log(c(a = .1, b = .1)), 
     fn = KannistoLik1, method = "Nelder-Mead", 
     .Dx = .Dx, .Exp = .Exp) 
    exp(NMab$par) 
    # these are reasonable estimates 
     a   b 
    0.1243144 0.1163926 

Ésta es la función del gradiente que se me ocurrió:

Kannisto.gr <- function(pars, .Dx, .Exp, x = .5:30.5){ 
     a <- exp(pars["a"]) 
     b <- exp(pars["b"]) 
     d.a <- (a * exp(b * x) * .Exp + (-a * exp(b * x) - 1) * .Dx)/
     (a^3 * exp(2 * b * x) + 2 * a^2 * exp(b * x) + a) 
     d.b <- (a * x * exp(b * x) * .Exp + (-a * x * exp(b * x) - x) * .Dx)/
     (a^2 * exp(2 * b * x) + 2 * a * exp(b * x) + 1) 
     -colSums(cbind(a = d.a, b = d.b), na.rm = TRUE) 
    } 

La salida es un vector de longitud 2, el cambio con respecto a la p los parámetros a y b. También tengo una versión más fea al explotar el resultado de deriv(), que devuelve la misma respuesta y que no publico (solo para confirmar que los derivados son correctos).

Si suministrarla a optim() de la siguiente manera, con BFGS como el método, las estimaciones no se mueven a partir de los valores de partida:

BFGSab <- optim(log(c(a = .1, b = .1)), 
     fn = KannistoLik1, gr = Kannisto.gr, method = "BFGS", 
     .Dx = .Dx, .Exp = .Exp) 
    # estimates do not change from starting values: 
    exp(BFGSab$par) 
     a b 
    0.1 0.1 

Cuando miro el elemento $counts de la salida, se dice que KannistoLik1() se llamó 31 veces y Kannisto.gr() solo 1 vez. $convergence es 0, así que creo que cree que convergió (si doy menos arranques razonables, también se quedan). Reduje la tolerancia, etc., y nada cambia. Cuando intento la misma llamada en optimx() (no se muestra), recibo el waring que mencioné anteriormente, y no se devuelve ningún objeto. Obtengo los mismos resultados al especificar gr = Kannisto.gr con el "CG".Con el método "L-BFGS-B" consigo los mismos valores de partida atrás como estimación, sino que también ha informado de que tanto la función y el gradiente se llamaron 21 veces, y no hay un mensaje de error: "ERROR: BNORMAL_TERMINATION_IN_LNSRCH"

Estoy esperando que hay una cierta detalles menores en la forma en que se escribe la función de gradiente resolverán esto, ya que esta advertencia posterior y el comportamiento optimx sugieren sin rodeos que la función simplemente no es correcta (creo). También probé el maximizador maxNR() del paquete maxLik y observé un comportamiento similar (los valores iniciales no se mueven). ¿Alguien puede darme un puntero? Muy agradecido

[Editar] @Vincent sugirió comparo con la salida de una aproximación numérica:

library(numDeriv) 
    grad(function(u) KannistoLik1(c(a=u[1], b=u[2]), .Dx, .Exp), log(c(.1,.1))) 
    [1] -14477.40 -7458.34 
    Kannisto.gr(log(c(a=.1,b=.1)), .Dx, .Exp) 
    a  b 
    144774.0 74583.4 

signo de modo diferente, y por un factor de 10? Cómo cambio el gradiente de la función a hacer lo mismo:

Kannisto.gr2 <- function(pars, .Dx, .Exp, x = .5:30.5){ 
     a <- exp(pars["a"]) 
     b <- exp(pars["b"]) 
     d.a <- (a * exp(b * x) * .Exp + (-a * exp(b * x) - 1) * .Dx)/
     (a^3 * exp(2 * b * x) + 2 * a^2 * exp(b * x) + a) 
     d.b <- (a * x * exp(b * x) * .Exp + (-a * x * exp(b * x) - x) * .Dx)/
     (a^2 * exp(2 * b * x) + 2 * a * exp(b * x) + 1) 
     colSums(cbind(a=d.a,b=d.b), na.rm = TRUE)/10 
    } 
    Kannisto.gr2(log(c(a=.1,b=.1)), .Dx, .Exp) 
    # same as numerical: 
     a   b 
    -14477.40 -7458.34 

probarlo en el optimizador:

BFGSab <- optim(log(c(a = .1, b = .1)), 
     fn = KannistoLik1, gr = Kannisto.gr2, method = "BFGS", 
     .Dx = .Dx, .Exp = .Exp) 
    # not reasonable results: 
    exp(BFGSab$par) 
     a b 
    Inf Inf 
    # and in fact, when not exp()'d, they look oddly familiar: 
    BFGSab$par 
     a   b 
    -14477.40 -7458.34 

siguiente respuesta de Vincent, que reajustarán la función del gradiente, y se utiliza abs() en lugar de exp() para mantener los parámetros positivo. Los más recientes, y más funcional objetivos y funciones de gradiente:

KannistoLik2 <- function(pars, .Dx, .Exp, .x. = .5:30.5){ 
     mu <- KannistoMu.c(abs(pars), x = .x.) 
     # take negative and minimize it (default optimizer behavior) 
     -sum(.Dx * log(mu) - .Exp * mu, na.rm = TRUE) 
    } 

    # gradient, to be down-scaled in `optim()` call 
    Kannisto.gr3 <- function(pars, .Dx, .Exp, x = .5:30.5){ 
     a <- abs(pars["a"]) 
     b <- abs(pars["b"]) 
     d.a <- (a * exp(b * x) * .Exp + (-a * exp(b * x) - 1) * .Dx)/
     (a^3 * exp(2 * b * x) + 2 * a^2 * exp(b * x) + a) 
     d.b <- (a * x * exp(b * x) * .Exp + (-a * x * exp(b * x) - x) * .Dx)/
     (a^2 * exp(2 * b * x) + 2 * a * exp(b * x) + 1) 
     colSums(cbind(a = d.a, b = d.b), na.rm = TRUE) 
    } 

    # try it out: 
    BFGSab2 <- optim(
     c(a = .1, b = .1), 
     fn = KannistoLik2, 
     gr = function(...) Kannisto.gr3(...) * 1e-7, 
     method = "BFGS", 
     .Dx = .Dx, .Exp = .Exp 
    ) 
    # reasonable: 
    BFGSab2$par 
      a   b 
    0.1243249 0.1163924 

    # better: 
    KannistoLik2(exp(NMab1$par),.Dx = .Dx, .Exp = .Exp) > KannistoLik2(BFGSab2$par,.Dx = .Dx, .Exp = .Exp) 
    [1] TRUE 

Esto se resolvió mucho más rápido de lo que esperaba, y he aprendido más de un par de trucos. Gracias Vincent!

+3

Para comprobar si su gradiente es correcto, puede comparar con una aproximación numérica, por ejemplo, 'library (numDeriv); grad (función (u) KannistoLik1 (c (a = u [1], b = u [2]), .Dx, .Exp), c (1,1)); Kannisto.gr (c (a = 1, b = 1), .Dx, .Exp) '. Los signos son incorrectos: el algoritmo no ve ninguna mejora cuando se mueve en esta dirección, y por lo tanto no se mueve. –

+0

Gracias Vincent. Intentó, publicará resultados por encima de –

Respuesta

9

Para comprobar si el gradiente es correcta, se puede comparar con una aproximación numérica:

library(numDeriv); 
grad(function(u) KannistoLik1(c(a=u[1], b=u[2]), .Dx, .Exp), c(1,1)); 
Kannisto.gr(c(a=1,b=1), .Dx, .Exp) 

Los signos están equivocados: el algoritmo no ve ninguna mejora cuando se mueve en esta dirección, y por lo tanto, no se mueve.

Se puede utilizar algún sistema de álgebra computacional (en este caso, Maxima) hacer los cálculos para usted:

display2d: false; 
f(a,b,x) := a * exp(b*x)/(1 + a * exp(b*x)); 
l(a,b,d,e,x) := - d * log(f(a,b,x)) + e * f(a,b,x); 
factor(diff(l(exp(a),exp(b),d,e,x),a)); 
factor(diff(l(exp(a),exp(b),d,e,x),b)); 

acabo de copiar y pegar el resultado en R:

f_gradient <- function(u, .Dx, .Exp, .x.=.5:30.5) { 
    a <- u[1] 
    b <- u[1] 
    x <- .x. 
    d <- .Dx 
    e <- .Exp 
    c(
    sum((e*exp(exp(b)*x+a)-d*exp(exp(b)*x+a)-d)/(exp(exp(b)*x+a)+1)^2), 
    sum(exp(b)*x*(e*exp(exp(b)*x+a)-d*exp(exp(b)*x+a)-d)/(exp(exp(b)*x+a)+1)^2) 
) 
} 

library(numDeriv) 
grad(function(u) KannistoLik1(c(a=u[1], b=u[2]), .Dx, .Exp), c(1,1)) 
f_gradient(c(a=1,b=1), .Dx, .Exp) # Identical 

Si poner a ciegas el gradiente en la optimización, hay un problema de inestabilidad numérica: la solución dada es (Inf,Inf) ... Para evitarlo, puede volver a escalar el gradiente (una mejor solución sería usar una transformación menos explosiva que la exponencial, para asegurar que los parámetros permanezcan positivos).

BFGSab <- optim(
    log(c(a = .1, b = .1)), 
    fn = KannistoLik1, 
    gr = function(...) f_gradient(...) * 1e-3, 
    method = "BFGS", 
    .Dx = .Dx, .Exp = .Exp 
) 
exp(BFGSab$par) # Less precise than Nelder-Mead 
+1

Gracias Vincent por los consejos.Siguiendo sus 3 consejos: cambie el signo (duh), reduzca el degradado y cambie el 'exp()' al 'abs()', obtengo un mejor estimado que antes. Es posible que necesite publicar otra pregunta más tarde sobre el reajuste. –

Cuestiones relacionadas