El gradiente descendente es un algoritmo para encontrar los puntos donde una función alcanza su valor mínimo. Recuerde que definimos el aprendizaje como la mejora de los parámetros del modelo para minimizar la pérdida a través de una serie de pasos en el entrenamiento. Con ese concepto, aplicar un gradiente descendente para encontrar el mínimo de una función de pérdida, resultará en nuestro modelo de aprendizaje a partir de nuestros datos de entrada.
Definamos qué es un gradiente, en caso de que no lo sepas. El gradiente es una operación matemática, generalmente representada con el símbolo
(letra griega nabla). Es análogo a una derivada, pero se aplica a funciones que ingresan un vector y generan un solo valor; al igual que nuestras funciones de pérdida hacen.
La salida del gradiente es un vector de derivadas parciales, una por posición del vector de entrada de la función.
Debe pensar en una derivada parcial como si su función recibiera solo una variable, reemplazando todas las demás por constantes y luego aplicando el procedimiento habitual de derivación de una sola variable.
Las derivadas parciales miden la tasa de cambio de la salida de la función con respecto a una variable de entrada particular. En otras palabras, cuánto aumentará el valor de salida si aumentamos ese valor de variable de entrada.
Aquí hay una advertencia antes de continuar. Cuando hablamos de las variables de entrada de la función de pérdida, nos referimos a los pesos del modelo, no a las entradas de características del conjunto de datos real. Esos están fijados por nuestro conjunto de datos y no se pueden optimizar. Las derivadas parciales que calculamos son con respecto a cada peso individual en el modelo de inferencia.
Nos importa el gradiente porque su vector de salida indica la dirección de crecimiento máximo para la función de pérdida. Podrías considerarlo como una pequeña flecha que indicará en cada punto de la función a dónde debes moverte para aumentar su valor:
Supongamos que el cuadro anterior muestra la función de pérdida. El punto rojo representa los valores de peso actuales, donde está parado actualmente. El gradiente representa la flecha, lo que indica que debe ir a la derecha para aumentar la pérdida. Más allá, la longitud de la flecha indica conceptualmente cuánto ganaría si se moviera en esa dirección.
Ahora, si vamos en la dirección opuesta al gradiente, la pérdida también hará lo contrario: disminuirá.
En la tabla, si vamos en la dirección opuesta al gradiente (flecha azul), iremos en la dirección de la pérdida decreciente.
Si nos movemos en esa dirección y calculamos nuevamente el gradiente, y luego repetimos el proceso hasta que la longitud del gradiente sea 0, llegaremos al mínimo de pérdida. Ese es nuestro objetivo, y gráficamente debería verse como:
Eso es todo. Podemos simplemente definir el algoritmo de descenso de gradiente como:
Observe cómo agregamos el valor para escalar el gradiente. Lo llamamos la tasa de aprendizaje. Necesitamos agregar eso porque la longitud del vector de gradiente es en realidad una cantidad medida en las "unidades de función de pérdida", no en "unidades de peso", por lo que necesitamos escalar el gradiente para poder agregarlo a nuestros pesos.
La tasa de aprendizaje no es un valor n que el modelo infiera. Es un hiperparámetro, o una configuración configurable manualmente para nuestro modelo. Tenemos que averiguar el valor correcto para ello. Si es demasiado pequeño, se necesitarán muchos ciclos de aprendizaje para encontrar la pérdida mínima. Si es demasiado grande, el algoritmo puede simplemente "saltar" el mínimo y nunca encontrarlo, saltando cíclicamente. Eso se conoce como overshooting. En nuestro ejemplo de gráfico de funciones de pérdida, se vería así:
En la práctica, no podemos realmente trazar la función de pérdida porque tiene muchas variables. Entonces, para saber que estamos atrapados en el overshooting, tenemos que mirar el gráfico de la pérdida total calculada a través del tiempo, que podemos obtener en Tensorboard mediante el uso de un tf.scalar_summary en la pérdida.
Así es como una pérdida de buen comportamiento debería disminuir con el tiempo, lo que indica una buena tasa de aprendizaje:
La línea azul es el gráfico de Tensorboard, y la roja representa la línea de tendencia de la pérdida.
Esto es lo que parece cuando está superando:
Debes jugar ajustando la velocidad de aprendizaje para que sea lo suficientemente pequeño como para que no se exceda, pero es lo suficientemente grande como para que se deteriore rápidamente, para que puedas lograr un aprendizaje más rápido con menos ciclos.
Además de la velocidad de aprendizaje, otros problemas afectan el descenso del gradiente en el algoritmo. La presencia de optima local está en la función de pérdida. Volviendo al gráfico de la función de pérdida de ejemplo de juguete, así es como funcionaría el algoritmo si tuviéramos nuestros pesos iniciales cerca del "valle/valley" del lado derecho de la función de pérdida:
El algoritmo buscará el valle y luego se detendrá porque pensará que es donde se encuentra el mejor valor posible. El gradiente se valora en 0 en todos los mínimos. El algoritmo no puede distinguir si se detuvo en el mínimo absoluto de la función, el mínimo global o un mínimo local que es el mejor valor solo en el entorno cercano.
Intentamos luchar contra él inicializando los pesos con valores aleatorios. Recuerde que el primer valor para los pesos se establece manualmente. Al usar valores aleatorios, mejoramos la posibilidad de comenzar a descender más cerca del mínimo global.
En un contexto de red profunda como los que veremos en capítulos posteriores, los mínimos locales son muy frecuentes. Una forma sencilla de explicar esto es pensar cómo la misma entrada puede viajar por muchos caminos diferentes a la salida, generando así el mismo resultado. Afortunadamente, hay documentos que muestran que todos esos mínimos son muy equivalentes en términos de pérdida, y en realidad no son mucho peores que el mínimo global.
Hasta ahora no hemos estado calculando explícitamente ningún derivado aquí, porque no teníamos que hacerlo. Tensorflow incluye el método tf.gradients para calcular simbólicamente los gradientes de los pasos del gráfico especificados y generarlos como tensores. Ni siquiera necesitamos llamar manualmente, porque también incluye implementaciones del algoritmo de descenso de gradiente, entre otros. Es por eso que presentamos fórmulas de alto nivel sobre cómo deberían funcionar las cosas sin que sea necesario que profundicemos en los detalles de la implementación y las matemáticas.
Asumamos una red realmente simple, con una entrada, una salida y dos capas ocultas con una sola neurona. Tanto las neuronas ocultas como las de salida serán sigmoides y la pérdida se calculará utilizando la entropía cruzada. Dicha red debería verse como:
Definamos L1 como la salida de la primera capa oculta, L2 la salida de la segunda y L3 la salida final de la red:
Finalmente, la pérdida de la red será:
Para ejecutar un paso de gradiente descendente, necesitamos calcular las derivadas parciales de la función de pérdida con respecto a los tres pesos en la red. Comenzaremos desde los pesos de la capa de salida, aplicando la regla de la cadena:
L2 es solo una constante para este caso, ya que no depende de W3
Para simplificar la expresión podríamos definir:
La expresión resultante para la derivada parcial sería:
Ahora calculemos la derivada para el segundo peso de capa oculta, W2 :
Debes notar un patrón. La derivada en cada capa es el producto de las derivadas de las capas posteriores a la salida de la capa anterior. Esa es la magia de la regla de la cadena y lo que el algoritmo aprovecha.
Avanzamos desde las entradas, calculando las salidas de cada capa oculta hasta la capa de salida. Luego, comenzamos a calcular los derivados yendo hacia atrás a través de las capas ocultas y propagando los resultados para hacer menos cálculos al reutilizar todos los elementos ya calculados. Ese es el origen del nombre backpropagation.
Conclusión
Observe cómo no hemos utilizado la definición de los derivados sigmoides o entropía cruzada. Podríamos haber utilizado una red con diferentes funciones de activación o pérdida y el resultado sería el mismo.
Este es un ejemplo muy simple, pero en una red con miles de pesos para calcular sus derivados, usar este algoritmo puede ahorrar órdenes de magnitud en el tiempo de entrenamiento.
Para cerrar, hay algunos algoritmos de optimización diferentes incluidos en Tensorflow, aunque todos ellos se basan en este método de cálculo de gradientes. Cuál funciona mejor depende de la forma de los datos de entrada y del problema que intenta resolver.
Las capas ocultas sigmoideas, las capas de salida de softmax y el descenso de gradiente con propagación hacia atrás son los bloques más fundamentales que vamos a utilizar para construir los modelos más complejos que veremos en los próximos capítulos.
Comentarios
Publicar un comentario