Posit AI Weblog: Destacado de la comunidad: Diversión con torchopt


Desde el principio, ha sido emocionante observar el creciente número de paquetes que se desarrollan en el torch ecosistema. Lo asombroso es la variedad de cosas que la gente hace con torch: ampliar su funcionalidad; integrar y poner en uso específico de dominio su infraestructura de diferenciación automática de bajo nivel; arquitecturas de redes neuronales de puertos… y por último, pero no menos importante, responder preguntas científicas.

Esta publicación de weblog presentará, de forma breve y bastante subjetiva, uno de estos paquetes: torchopt. Antes de comenzar, una cosa que probablemente deberíamos decir mucho más a menudo: si desea publicar una publicación en este weblog, sobre el paquete que está desarrollando o la forma en que emplea los marcos de trabajo de aprendizaje profundo del lenguaje R, infórmenos. – ¡eres más que bienvenido!

torchopt

torchopt es un paquete desarrollado por Gilberto Camara y colegas en Instituto Nacional de Investigaciones Espaciales, Brasil.

Por lo que parece, la razón de ser del paquete es bastante evidente. torch en sí mismo no implementa, ni debería implementar, todos los algoritmos de optimización recientemente publicados y potencialmente útiles para sus propósitos. Los algoritmos ensamblados aquí, entonces, son probablemente exactamente aquellos con los que los autores estaban más ansiosos por experimentar en su propio trabajo. A la fecha de este escrito, comprenden, entre otros, varios miembros de la in style ADA* y *ADÁN* familias. Y podemos asumir con seguridad que la lista crecerá con el tiempo.

Voy a presentar el paquete destacando algo que, técnicamente, es “simplemente” una función de utilidad, pero que para el usuario puede ser extremadamente útil: la capacidad de, para un optimizador arbitrario y una función de prueba arbitraria, trazar los pasos tomados en optimización.

Si bien es cierto que no tengo la intención de comparar (y mucho menos analizar) diferentes estrategias, hay una que, para mí, se destaca en la lista: ADAHESSIAN (Yao et al. 2020), un algoritmo de segundo orden diseñado para escalar a grandes redes neuronales. Tengo especial curiosidad por ver cómo se comporta en comparación con L-BFGS, el “clásico” de segundo orden disponible desde la base. torch hemos tenido un publicación de weblog dedicada sobre el año pasado.

la forma en que funciona

La función de utilidad en cuestión se denomina test_optim(). El único argumento requerido se refiere al optimizador para probar (optim). Pero es possible que también desee modificar otros tres:

  • test_fn: Para utilizar una función de prueba diferente de la predeterminada (beale). Puede elegir entre los muchos proporcionados en torchopt, o puede pasar por su cuenta. En este último caso, también debe proporcionar información sobre el dominio de búsqueda y los puntos de partida. (Lo veremos en un instante.)
  • steps: Para configurar el número de pasos de optimización.
  • opt_hparams: Para modificar los hiperparámetros del optimizador; más notablemente, la tasa de aprendizaje.

Aquí, voy a usar el flower() función que ya ocupaba un lugar destacado en el mencionado publish sobre L-BFGS. Se acerca a su mínimo a medida que se acerca más y más a (0,0) (pero no está definido en el origen mismo).

Aquí lo tienes:

flower <- operate(x, y) {
  a <- 1
  b <- 1
  c <- 4
  a * torch_sqrt(torch_square(x) + torch_square(y)) + b * torch_sin(c * torch_atan2(y, x))
}

Para ver cómo se ve, simplemente desplácese hacia abajo un poco. La trama se puede modificar de muchas maneras, pero me quedaré con el diseño predeterminado, con colores de longitud de onda más corta asignados a valores de función más bajos.

Comencemos nuestras exploraciones.

¿Por qué siempre dicen que la tasa de aprendizaje es importante?

Cierto, es una pregunta retórica. Pero aún así, a veces las visualizaciones son la evidencia más memorable.

Aquí, usamos un optimizador de primer orden in style, AdamW (Loshchilov y Hutter 2017). Lo llamamos con su tasa de aprendizaje predeterminada, 0.01, y deje que la búsqueda se ejecute durante doscientos pasos. Como en esa publicación anterior, comenzamos desde lejos: el punto (20,20)muy fuera de la región rectangular de interés.

library(torchopt)
library(torch)

test_optim(
    # name with default studying fee (0.01)
    optim = optim_adamw,
    # move in self-defined check operate, plus a closure indicating beginning factors and search area
    test_fn = checklist(flower, operate() (c(x0 = 20, y0 = 20, xmax = 3, xmin = -3, ymax = 3, ymin = -3))),
    steps = 200
)
Minimizando la función floral con AdamW.  Configuración no.  1: tasa de aprendizaje predeterminada, 200 pasos.

Vaya, ¿qué pasó? ¿Hay un error en el código de trazado? – De nada; es solo que después del número máximo de pasos permitidos, aún no hemos ingresado a la región de interés.

A continuación, aumentamos la tasa de aprendizaje por un issue de diez.

test_optim(
    optim = optim_adamw,
    # scale default fee by an element of 10
    opt_hparams = checklist(lr = 0.1),
    test_fn = checklist(flower, operate() (c(x0 = 20, y0 = 20, xmax = 3, xmin = -3, ymax = 3, ymin = -3))),
    steps = 200
)
Minimizando la función floral con AdamW.  Configuración no.  1: tasa de aprendizaje predeterminada, 200 pasos.

¡Qué cambio! Con una tasa de aprendizaje de diez veces, el resultado es óptimo. ¿Significa esto que la configuración predeterminada es mala? Por supuesto que no; el algoritmo se ha ajustado para que funcione bien con redes neuronales, no con alguna función que se haya diseñado a propósito para presentar un desafío específico.

Naturalmente, también tenemos que ver qué sucede con una tasa de aprendizaje aún mayor.

test_optim(
    optim = optim_adamw,
    # scale default fee by an element of 70
    opt_hparams = checklist(lr = 0.7),
    test_fn = checklist(flower, operate() (c(x0 = 20, y0 = 20, xmax = 3, xmin = -3, ymax = 3, ymin = -3))),
    steps = 200
)
Minimizando la función floral con AdamW.  Configuración no.  3: lr = 0,7, 200 pasos.

Vemos el comportamiento sobre el que siempre nos han advertido: la optimización salta salvajemente, antes de que aparentemente se vaya para siempre. (Aparentemente, porque en este caso, esto no es lo que sucede. En cambio, la búsqueda se alejará y retrocederá continuamente).

Ahora, esto podría hacer que uno tenga curiosidad. ¿Qué sucede realmente si elegimos la “buena” tasa de aprendizaje, pero no dejamos de optimizar en doscientos pasos? Aquí, intentamos trescientos en su lugar:

test_optim(
    optim = optim_adamw,
    # scale default fee by an element of 10
    opt_hparams = checklist(lr = 0.1),
    test_fn = checklist(flower, operate() (c(x0 = 20, y0 = 20, xmax = 3, xmin = -3, ymax = 3, ymin = -3))),
    # this time, proceed search till we attain step 300
    steps = 300
)
Minimizando la función floral con AdamW.  Configuración no.  3: lr

Curiosamente, vemos que aquí ocurre el mismo tipo de vaivén que con una tasa de aprendizaje más alta: solo se retrasa en el tiempo.

Otra pregunta divertida que me viene a la mente es: ¿Podemos rastrear cómo el proceso de optimización “explora” los cuatro pétalos? Con un poco de experimentación rápida, llegué a esto:

Minimizando la función floral con AdamW, lr = 0.1: “exploración” sucesiva de pétalos.  Pasos (sentido horario): 300, 700, 900, 1300.

¿Quién cube que necesitas caos para producir una trama hermosa?

Un optimizador de segundo orden para redes neuronales: ADAHESSIAN

En el único algoritmo que me gustaría revisar específicamente. Luego de un poco de experimentación con la tasa de aprendizaje, pude llegar a un resultado excelente después de solo treinta y cinco pasos.

test_optim(
    optim = optim_adahessian,
    opt_hparams = checklist(lr = 0.3),
    test_fn = checklist(flower, operate() (c(x0 = 20, y0 = 20, xmax = 3, xmin = -3, ymax = 3, ymin = -3))),
    steps = 35
)
Minimizando la función floral con AdamW.  Configuración no.  3: lr

Sin embargo, dadas nuestras experiencias recientes con AdamW, lo que significa que “simplemente no se está instalando” muy cerca del mínimo, es posible que también deseemos realizar una prueba equivalente con ADAHESSIAN. ¿Qué sucede si seguimos optimizando un poco más, digamos doscientos pasos?

test_optim(
    optim = optim_adahessian,
    opt_hparams = checklist(lr = 0.3),
    test_fn = checklist(flower, operate() (c(x0 = 20, y0 = 20, xmax = 3, xmin = -3, ymax = 3, ymin = -3))),
    steps = 200
)
Minimizando la función floral con ADAHESSIAN.  Configuración no.  2: lr = 0,3, 200 pasos.

Al igual que AdamW, ADAHESSIAN pasa a “explorar” los pétalos, pero no se aleja tanto del mínimo.

¿Es esto sorprendente? Yo no diría que lo es. El argumento es el mismo que con AdamW, arriba: su algoritmo se ha ajustado para funcionar bien en grandes redes neuronales, no para resolver una tarea clásica de minimización hecha a mano.

Ahora que ya hemos escuchado ese argumento dos veces, es hora de verificar la suposición explícita: que un algoritmo clásico de segundo orden maneja esto mejor. En otras palabras, es hora de volver a visitar L-BFGS.

Lo mejor de los clásicos: revisitando L-BFGS

Usar test_optim() con L-BFGS, necesitamos tomar un pequeño desvío. Si has leído la publicación en L-BFGS, recordará que con este optimizador, es necesario envolver tanto la llamada a la función de prueba como la evaluación del gradiente en un cierre. (La razón es que ambos deben poder llamarse varias veces por iteración).

Ahora, viendo cómo L-BFGS es un caso muy especial, y es possible que pocas personas usen test_optim() con él en el futuro, no parece que valga la pena hacer que esa función maneje diferentes casos. Para esta prueba de encendido y apagado, simplemente copié y modifiqué el código según se requería. El resultado, test_optim_lbfgs()se encuentra en el apéndice.

Al decidir qué número de pasos probar, tenemos en cuenta que L-BFGS tiene un concepto de iteraciones diferente al de otros optimizadores; es decir, puede refinar su búsqueda varias veces por paso. De hecho, por la publicación anterior, sé que tres iteraciones son suficientes:

test_optim_lbfgs(
    optim = optim_lbfgs,
    opt_hparams = checklist(line_search_fn = "strong_wolfe"),
    test_fn = checklist(flower, operate() (c(x0 = 20, y0 = 20, xmax = 3, xmin = -3, ymax = 3, ymin = -3))),
    steps = 3
)
Minimización de la función floral con L-BFGS.  Configuración no.  1: 3 pasos.

En este punto, por supuesto, debo apegarme a mi regla de probar lo que sucede con “demasiados pasos”. (Aunque esta vez, tengo fuertes razones para creer que no pasará nada).

test_optim_lbfgs(
    optim = optim_lbfgs,
    opt_hparams = checklist(line_search_fn = "strong_wolfe"),
    test_fn = checklist(flower, operate() (c(x0 = 20, y0 = 20, xmax = 3, xmin = -3, ymax = 3, ymin = -3))),
    steps = 10
)
Minimización de la función floral con L-BFGS.  Configuración no.  2: 10 pasos.

Hipótesis confirmada.

Y aquí termina mi introducción lúdica y subjetiva a torchopt. Ciertamente espero que les haya gustado; pero en cualquier caso, creo que debería haber tenido la impresión de que aquí hay un paquete útil, extensible y con probabilidades de crecer, del que se debe tener cuidado en el futuro. ¡Como siempre, gracias por leer!

Apéndice

test_optim_lbfgs <- operate(optim, ...,
                       opt_hparams = NULL,
                       test_fn = "beale",
                       steps = 200,
                       pt_start_color = "#5050FF7F",
                       pt_end_color = "#FF5050FF",
                       ln_color = "#FF0000FF",
                       ln_weight = 2,
                       bg_xy_breaks = 100,
                       bg_z_breaks = 32,
                       bg_palette = "viridis",
                       ct_levels = 10,
                       ct_labels = FALSE,
                       ct_color = "#FFFFFF7F",
                       plot_each_step = FALSE) {


    if (is.character(test_fn)) {
        # get beginning factors
        domain_fn <- get(paste0("domain_",test_fn),
                         envir = asNamespace("torchopt"),
                         inherits = FALSE)
        # get gradient operate
        test_fn <- get(test_fn,
                       envir = asNamespace("torchopt"),
                       inherits = FALSE)
    } else if (is.checklist(test_fn)) {
        domain_fn <- test_fn((2))
        test_fn <- test_fn((1))
    }

    # place to begin
    dom <- domain_fn()
    x0 <- dom(("x0"))
    y0 <- dom(("y0"))
    # create tensor
    x <- torch::torch_tensor(x0, requires_grad = TRUE)
    y <- torch::torch_tensor(y0, requires_grad = TRUE)

    # instantiate optimizer
    optim <- do.name(optim, c(checklist(params = checklist(x, y)), opt_hparams))

    # with L-BFGS, it's essential to wrap each operate name and gradient analysis in a closure,
    # for them to be callable a number of occasions per iteration.
    calc_loss <- operate() {
      optim$zero_grad()
      z <- test_fn(x, y)
      z$backward()
      z
    }

    # run optimizer
    x_steps <- numeric(steps)
    y_steps <- numeric(steps)
    for (i in seq_len(steps)) {
        x_steps(i) <- as.numeric(x)
        y_steps(i) <- as.numeric(y)
        optim$step(calc_loss)
    }

    # put together plot
    # get xy limits

    xmax <- dom(("xmax"))
    xmin <- dom(("xmin"))
    ymax <- dom(("ymax"))
    ymin <- dom(("ymin"))

    # put together knowledge for gradient plot
    x <- seq(xmin, xmax, size.out = bg_xy_breaks)
    y <- seq(xmin, xmax, size.out = bg_xy_breaks)
    z <- outer(X = x, Y = y, FUN = operate(x, y) as.numeric(test_fn(x, y)))

    plot_from_step <- steps
    if (plot_each_step) {
        plot_from_step <- 1
    }

    for (step in seq(plot_from_step, steps, 1)) {

        # plot background
        picture(
            x = x,
            y = y,
            z = z,
            col = hcl.colours(
                n = bg_z_breaks,
                palette = bg_palette
            ),
            ...
        )

        # plot contour
        if (ct_levels > 0) {
            contour(
                x = x,
                y = y,
                z = z,
                nlevels = ct_levels,
                drawlabels = ct_labels,
                col = ct_color,
                add = TRUE
            )
        }

        # plot place to begin
        factors(
            x_steps(1),
            y_steps(1),
            pch = 21,
            bg = pt_start_color
        )

        # plot path line
        strains(
            x_steps(seq_len(step)),
            y_steps(seq_len(step)),
            lwd = ln_weight,
            col = ln_color
        )

        # plot finish level
        factors(
            x_steps(step),
            y_steps(step),
            pch = 21,
            bg = pt_end_color
        )
    }
}

Loshchilov, Ilya y Frank Hutter. 2017. “Reparación de la regularización de la disminución del peso en Adam”. CoRR abs/1711.05101. http://arxiv.org/abs/1711.05101.

Yao, Zhewei, Amir Gholami, Sheng Shen, Kurt Keutzer y Michael W. Mahoney. 2020. “ADAHESSIAN: un optimizador adaptativo de segundo orden para el aprendizaje automático”. CoRR abs/2006.00719. https://arxiv.org/abs/2006.00719.

Related Articles

WebAssembly nativo de la nube con Matt Butcher

Cuando se creó Net Meeting, se suponía que period un...

Comments

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Same Category

WebAssembly nativo de la nube con Matt Butcher

Cuando se creó Net Meeting, se suponía que...

Historia de dos expertos: una lista aparte

Todo el mundo quiere ser un experto. ...

Una guía sobre el puntaje de IELTS para estudiar en los EE. UU.

Estados Unidos atrae a una gran cantidad de...
spot_img

Stay in touch!

Follow our Instagram