8. Optimización: descenso, momento, Adam y búsquedas sin gradiente
Secciones
Ajustes de lectura
Ninguna voz para este idioma en este navegador.
Más voces
Código y términos técnicos en inglés
Desactivado: la voz de la página lo lee todo, sin pausa entre dos voces.
Ninguna voz para este idioma en este navegador.
Más voces
Más voces: Microsoft Edge ofrece voces naturales en línea, y Windows (Configuración › Hora e idioma › Voz), macOS y Android instalan otras.
La lección 2 ajustaba una recta por descenso de gradiente y descubría que un paso demasiado grande diverge. La lección 7 descubría que el tamaño del paso no siempre es el número que pasaste. Esta lección mira la regla de actualización en sí, sobre una función elegida para hacer visibles las diferencias.
La función de Rosenbrock, (1 - x)² + 100(y - x²)², tiene su mínimo en (1, 1), donde vale 0. Alrededor de ese mínimo discurre un valle largo, curvo, casi plano, de paredes empinadas. El descenso simple rebota entre las paredes y se arrastra por el fondo; es la forma habitual de ver qué compran el momento y los tamaños de paso por coordenada.
| ML.NET | Tribuo | PyTorch | IX | |
|---|---|---|---|---|
| Descenso simple | OnlineGradientDescent |
SGD |
optim.SGD |
ix_optimize::gradient::SGD |
| Momento | — | SGD.getLinearDecaySGD con momento |
optim.SGD(momentum=…) |
ix_optimize::gradient::Momentum |
| Adam | — | Adam |
optim.Adam |
ix_optimize::gradient::Adam |
| Sin gradiente | — | — | — | annealing::SimulatedAnnealing, pso::ParticleSwarm |
El programa es examples/l08_optimization.rs, partiendo de (-1.2, 1.0), el punto que usa el artículo de Rosenbrock.
Tres reglas
Sección titulada «Tres reglas»El descenso simple resta el gradiente: p ← p - α g. Tiene un problema en un valle — el gradiente apunta a través del valle mucho más fuerte que a lo largo de él, así que la mayor parte del paso se desperdicia yendo y viniendo.
El momento mantiene una velocidad corriente, v ← βv + αg, y resta esa. Los pasos que siguen apuntando en la misma dirección se acumulan; los que alternan se cancelan. En un valle es exactamente el instinto correcto.
Adam mantiene dos medias corrientes: la media del gradiente y la media de su cuadrado. Divide una por la raíz de la otra, lo que da a cada coordenada su propio tamaño de paso — grande donde el gradiente ha sido pequeño y constante, pequeño donde ha sido grande y ruidoso. Ambas medias parten de cero, así que los primeros pasos serían demasiado pequeños; dividir por 1 - βᵗ lo corrige (src/optimize.rs, líneas 101-125):
let mean_hat = &mean / (1.0 - self.beta1.powf(t));let square_hat = &square / (1.0 - self.beta2.powf(t));let next = params - &(self.learning_rate * &mean_hat / &(square_hat.mapv(f64::sqrt) + self.epsilon));== by hand, 5000 steps at most SGD 5000 steps: last [0.9387, 0.8810] f 0.003761, best f 0.003761 Momentum 4129 steps: last [1.0000, 1.0000] f 0.000000, best f 0.000000 Adam 2822 steps: last [1.0000, 1.0000] f 0.000000, best f 0.000000
== ix_optimize::gradient::minimize, the same 5000 steps SGD 5000 steps: best [0.9387, 0.8810] f 0.003761, converged false Momentum 4129 steps: best [1.0000, 1.0000] f 0.000000, converged true Adam 2822 steps: best [1.0000, 1.0000] f 0.000000, converged trueEl descenso simple agota sus 5000 pasos sin llegar al mínimo. El momento, con la misma tasa de aprendizaje, llega en 4129. Adam llega en 2822 con una tasa cincuenta veces mayor, que es la razón práctica por la que es el valor por defecto casi en todas partes: es mucho menos sensible al número que elijas.
La versión a mano e ix_optimize coinciden exactamente — mismos recuentos de pasos, mismos puntos finales y el mismo primer paso desde el mismo gradiente:
== the first Adam step from the same gradient gradient [-215.6000, -88.0000] hand [-1.15000000, 1.05000000] ix [-1.15000000, 1.05000000]Ese primer paso merece leerse. El gradiente es [-215.6, -88], tremendamente distinto en las dos coordenadas, y Adam mueve ambas exactamente 0,05 — la tasa de aprendizaje. En el primer paso la corrección de sesgo hace que mean_hat sea igual al gradiente y square_hat igual al gradiente al cuadrado, así que la razón es el signo del gradiente y nada más. Adam empieza ignorando por completo el tamaño del gradiente.
Un gradiente que nadie escribió
Sección titulada «Un gradiente que nadie escribió»ObjectiveFunction::gradient tiene una implementación por defecto: si no proporcionas una, se mide con diferencias centradas (traits.rs, líneas 10-13):
fn gradient(&self, x: &Array1<f64>) -> Array1<f64> { ix_math::calculus::numerical_gradient(&|p: &Array1<f64>| self.evaluate(p), x, 1e-7)}ClosureObjective, el envoltorio de comodidad, nunca lo redefine. Así que envolver una closure cuesta dos evaluaciones extra del objetivo por coordenada y por paso — y compra un gradiente exacto hasta unos ocho dígitos:
== the gradient at the start, written and measured written [-215.600000000, -88.000000000] measured [-215.600000093, -87.999999998] largest gap 9.30e-8, and two extra evaluations of f per coordinate per step Adam on ClosureObjective: best f 1.061e-16 against 1.061e-16 with the written gradientLas dos ejecuciones aterrizan en el mismo valor hasta el último dígito impreso. Esa es la conclusión honesta aquí: para un objetivo suave en dos dimensiones, el gradiente medido es suficientemente bueno, y el coste está en evaluaciones, no en precisión. El ejercicio las cuenta — 1001 frente a 201 para 200 pasos — y la razón crece con el número de parámetros, que es por lo que nadie entrena una red neuronal así.
Una respuesta finita para una ejecución que divergió
Sección titulada «Una respuesta finita para una ejecución que divergió»minimize recuerda el mejor punto por el que ha pasado y devuelve ese, no donde terminó (gradient.rs, líneas 124-165). Normalmente es una gentileza. Cuando la ejecución diverge es una trampa:
== a step size that diverges ix SGD lr 0.01: best f 24.200000 after 5000 steps, converged false the same run by hand ends at f NaN — the best point hides the divergence24,200000 es f(-1.2, 1.0) — el punto de partida. Con una tasa de aprendizaje de 0,01 el primerísimo paso se pasa, cada uno posterior se pasa más, los valores se disparan al infinito y luego a NaN, y como ningún valor fue nunca menor que el primero, best_params sigue siendo el punto que pasó el llamante. El resultado parece una respuesta corriente: un valor finito, un punto finito, 5000 iteraciones.
El único campo que protesta es converged: false — y el descenso simple devuelve converged: false también para una ejecución que terminó perfectamente bien en 0,003761. Así que converged no separa ambos casos, y best_value tampoco. Quien quiera saber si una optimización funcionó tiene que comparar el objetivo en el punto devuelto con el objetivo en el inicio, y rechazar un resultado que no haya mejorado.
Dos búsquedas que nunca piden un gradiente
Sección titulada «Dos búsquedas que nunca piden un gradiente»Algunos objetivos no tienen gradiente — una configuración discreta, una simulación, una puntuación de una caja negra. IX incluye dos de las respuestas clásicas.
El recocido simulado propone un vecino aleatorio y siempre lo acepta si es mejor; si es peor lo acepta de todos modos con probabilidad exp(-Δ/T), donde la temperatura T cae con el tiempo. Caliente, deambula libremente y escapa de mínimos locales; frío, solo baja.
El enjambre de partículas hace volar una población de puntos, cada uno atraído hacia el mejor punto que ha visto personalmente y hacia el mejor punto que ha visto cualquiera.
== without a gradient simulated annealing, seed 42: best [0.9934, 0.9881] f 0.000193 after 3216 iterations particle swarm, 40 particles, seed 42: best [1.0000, 1.0000] f 0.000000El recocido se detiene en la iteración 3216, cuando el programa exponencial lleva la temperatura por debajo de min_temp, y obtiene cuatro dígitos de la respuesta. El enjambre, con 40 partículas en 200 iteraciones — 8000 evaluaciones — aterriza en el mínimo. Ambos se comportan dignamente en un problema donde el gradiente escrito necesitó 2822 pasos; ambos serían mala elección precisamente porque el gradiente existe.
De vuelta a la recta
Sección titulada «De vuelta a la recta»Las mismas tres reglas sobre la pérdida de la lección 2, estandarizada, donde la respuesta se conoce:
== the build-time line, standardized: closed form slope 0.667448, intercept 0 SGD 106 steps: slope 0.667448, intercept -0.000000, loss 0.554513 Momentum 366 steps: slope 0.667448, intercept -0.000000, loss 0.554513 Adam 384 steps: slope 0.667448, intercept -0.000000, loss 0.554513Los tres alcanzan la forma cerrada hasta seis decimales, y el descenso simple llega primero. En un cuenco redondo con una tasa de aprendizaje bien elegida no hay nada que el momento pueda acumular ni nada que Adam pueda reescalar; ambos solo añaden su propia dinámica encima de un problema que no la necesitaba. El valle de Rosenbrock y este cuenco son los dos extremos de la misma historia, y en qué extremo estás es una propiedad del problema, no del optimizador.
Puntos clave
Sección titulada «Puntos clave»- El descenso simple rebota entre las paredes del valle de Rosenbrock. El momentum acumula los pasos que coinciden, y Adam da a cada coordenada su propio tamaño de paso, lo que lo llevó al mínimo en 2822 pasos.
- Las versiones a mano e
ix_optimizecoinciden paso a paso. El primer paso de Adam mueve cada coordenada exactamente la tasa de aprendizaje, sea cual sea el tamaño del gradiente. - Sin un gradiente escrito, IX mide uno con diferencias centradas: exacto hasta unos ocho dígitos, a costa de dos evaluaciones más por coordenada y por paso.
minimizedevuelve el mejor punto por el que pasó, así que una ejecución que divergió hasta NaN devuelve su punto de partida como una respuesta finita; compara el objetivo en el resultado con el objetivo en el inicio.- El recocido simulado y el enjambre de partículas no necesitan gradiente, pero son una mala elección cuando existe uno, y en un cuenco redondo el descenso simple termina primero: el problema decide qué regla gana.
Ejercicios
Sección titulada «Ejercicios»- ¿Cuál es la mayor tasa de aprendizaje del descenso simple que aún llega al valle en 5000 pasos?
- ¿Cuántas evaluaciones del objetivo cuesta un gradiente ausente a lo largo de 200 pasos de Adam?
- ¿Cómo cambia el coeficiente de momento el número de pasos?
Soluciones
Están en examples/l08_exercises.rs.
1. 0,002 — y el borde es afilado:
== plain descent on Rosenbrock, 5000 steps, by learning rate rate 0.0001 : ends at f 2.305718 rate 0.0005 : ends at f 0.042952 rate 0.001 : ends at f 0.003761 rate 0.002 : ends at f 0.000055 rate 0.005 : ends at f 0.788039 rate 0.01 : ends at f NaNPor debajo de 0,002 la ejecución es estable y lenta; en 0,002 es estable y mucho más rápida; en 0,005 es inestable pero aún acotada; en 0,01 se ha ido. La ventana utilizable abarca un factor de unos cinco, y nada salvo probar te dice dónde está. Adam a 0,05 estaba cómodamente en medio de una mucho más ancha.
2. Cinco veces más:
== evaluations of f for 200 Adam steps written gradient true : 201 evaluations, best f 2.588e0 written gradient false: 1001 evaluations, best f 2.588e0201 es una por paso más la primera. 1001 es eso más 2 × 2 coordenadas × 200 pasos. Con d parámetros el factor es 1 + 2d, así que un modelo con mil parámetros pagaría dos mil veces más — y llegaría al mismo punto, como muestra la columna best f.
3. Más momento ayuda hasta que deja de hacerlo:
== momentum coefficient against steps to reach f < 1e-10 0 : 20000 steps, last [0.9999, 0.9997] f 1.938e-8 0.5 : 20000 steps, last [1.0000, 1.0000] f 2.130e-15 0.9 : 4129 steps, last [1.0000, 1.0000] f 1.244e-16 0.95 : 1790 steps, last [1.0000, 1.0000] f 1.242e-16 0.99 : 4014 steps, last [1.0000, 1.0000] f 4.696e-18Con β = 0 esto es descenso simple, y 20 000 pasos no bastan para disparar la prueba sobre la norma del gradiente. 0,95 es el mejor de estos, con 1790 pasos. Con 0,99 la velocidad lleva tan lejos más allá del giro que la ejecución tarda más del doble — y aterriza en el punto más preciso de los cinco, tras haber espiraleado hacia dentro. Los valores por defecto habituales, 0,9 y 0,99, quedan a ambos lados del óptimo aquí, lo que da una imagen justa de cuánto merece la pena ajustar este parámetro.
Fuentes
Sección titulada «Fuentes»- Rosenbrock, An automatic method for finding the greatest or least value of a function, 1960
- Kingma y Ba, Adam: A Method for Stochastic Optimization, 2015
- Goodfellow, Bengio, Courville, Deep Learning, capítulo 8
- Kirkpatrick, Gelatt y Vecchi, Optimization by Simulated Annealing, 1983
- PyTorch:
torch.optim - IX en
490c395:ix-optimize/src/gradient.rs,ix-optimize/src/traits.rs,ix-optimize/src/annealing.rs,ix-optimize/src/pso.rs,ix-math/src/calculus.rs