7. Entrenamiento de redes neuronales b) Descenso de gradiente

El Descenso de gradiente es el algoritmo que usamos para entrenar la red neuronal.

¿Que era entrenar la red neuronal? Pues simplemente pasarle muchos e para que obtenga o aprenda los mejores parámetros posibles de pesos (weight) y sesgos (bias) de forma que la función de coste sea mínima.

Sabemos que:

Seguimos con la red neuronal que usamos de ejemplo:

Dedujimos que corresponde a la siguiente fórmula:

Ya hemos visto que la siguiente función matemática nos dice los buena que es nuestra red neuronal, al resta el valor real de la salida de la red neuronal es decir

Por lo que los parámetros a entrenar son los siguientes: . Es decir que realmente entrenar consiste en averiguar los valores de los parámetros (, etc) que hacen que el valor de loss tenga un valor mínimo.

Lo siguiente es saber el algoritmo para averiguar los parámetros. De forma didáctica expongo 3 métodos aunque solo se gasta el último.

Aleatoriamente: Se podrían elegir aleatoriamente y ver si son bueno y así probar muchas veces hasta encontrar los bueno. Obviamente es una forma malísima ya que la probabilidad de que todos a la vez fueran buenos es extremadamente baja. Por lo que tardaríamos muchísimo tiempo en obtener los parámetros adecuados.

Todas las combinaciones: Se podrían probar todas las posibles combinaciones, pero hay tantísimas que el coste es prohibitivo y también los hace inviable.

Descenso de gradiente: El algoritmo consiste en buscar el mínimo de la función ajustando siempre los parámetros un poco de forma que el valor de la función disminuya un poco y repetirlo muchas veces.

Durante este tema la función a minimizar va a ser loss y siguiendo con nuestro ejemplo el código es el siguiente:

Lo siguiente que vamos a hacer es una gráfica para ver como variaría el valor de loss , es decir la pérdida, en caso de ir variando el valor del peso w_2.

En la gráfica , el punto rojo es el valor actual de w_2 y la pérdida de la red, es decir el resultado de la función loss. La curva nos permite ver como evoluciona la pérdida, si vamos variando el valor de w_2. En nuestro caso, viendo la gráfica podemos encontrar rápidamente el valor de w_2 que haría que fuera mínimo y podríamos darle ese valor pero debemos tener en cuenta que si variamos mucho w_2 cambiaría totalmente la pérdida para el resto de pesos por lo que hacemos es ir variando poco a poco el valor de cada peso. En nuestro ejemplo, para hacer que la pérdida sea la mínima, ¿que es mejor que w_2 se haga un poquito más grande o un poquito más pequeño? La respuesta es que lo mejor es que se haga un poquito más grande.

En la siguiente gráfica podemos ver como varia la pérdida para todos los pesos de nuestro ejemplo.

Es decir que como nuestra función de loss tiene muchos parámetros, no podemos encontrar el mínimo de cada parámetro por separado sino que hay que buscar poco a poco el mínimo de cada parámetro.

Veamos ahora un ejemplo de una función de loss con dos parámetros:

Las dos imágenes son la misma función pero la de la derecha está representada en 2D, donde al "altura" se representa con colores.

Como vemos no podríamos encontrar el mínimo para w_0 y luego para w_1.Así que los haremos poco a poco usando el algoritmo de descenso de gradiente.

Otro ejemplo más sencillo sería:

Matemáticas en el descenso de gradiente

Ahora que vemos la necesidad de encontrar el mínimo de una función pasemos a ver como se encuentra el mínimo. Pues es tan sencillo como imaginarse que estamos en una montaña y para encontrar la parte mas baja solo hay que ir descendiendo por la montaña. Es decir que hay que ver si la función (la montaña) crece y en ese caso ir hacia el lado contrario.

La siguiente imagen ilustra la idea.

¿Como hacemos eso matemáticamente? Pues lo primero es pensar que cuando se inicializa la red neuronal, a los parámetros ( y ) se les da un valor aleatorio, por lo que estamos en un punto cualquiera de esa montaña. ¿Como sabemos en que dirección debemos movernos? Pues muy fácil, incrementamos un poquito el valor del parámetro (a ese incremento lo llamaremos o ) y vemos como se incrementa con respecto del valor original, (a ese incremento de la función lo llamamos ) . Es decir los restamos (aunque también lo dividimos entre el valor del incremento).

Ese valor es lo que se llama la derivada de una función y si:

Si el resultado de la derivada el positivo, es que la función crece en se punto. Y como estamos buscando el mínimo, deberemos decrementar el valor de proporcionalmente al valor del gradiente.

Si el resultado de la derivada el negativo, es que la función decrece en se punto. Y como estamos buscando el mínimo, deberemos incrementar el valor de proporcionalmente al valor del gradiente.

Una explicación mas detallada de la fórmula se puede ver en el siguiente video: DERIVADAS - Clase Completa: Explicación Desde Cero | El Traductor
En el contexto del descenso de gradiente no se suele hablar que el resultado de la fórmula es la derivada sino el gradiente. Ya que para eso estamos en el descenso de gradente

La siguiente fórmula del gradiente queda así con respecto en el punto . La segunda fórmula, es simplemente lo mismo pero expresado de forma mas compacta visto como derivadas.

La siguiente fórmula es lo mismo pero para

Ya sabemos si crece pues en ese caso soy hay que moverse justo para el lado contrario de ahí el "menos" de la siguiente fórmula.

y

Si ésto lo repetimos muchas veces es justamente el algoritmo del descenso de gradiente.

La formula general de lo que es el descenso de gradiente se podría expresar así:

Expliquemos un poco la fórmula:

es cada uno de los parámetros es decir w_0, w_1, etc.

El superíndice el instante de tiempo,ya que vamos a aplicar varias veces la fórmula. Es realmente cada una de las épocas o epochs.

La variable es lo que llamamos la tasa de apredizaje o learning_rate e indica cuanto queremos que varíen los parámetros en cada época.Este valor es muy importante ya que lo que nos dice es "como" de largo vamos a dar los pasos en nuestro descenso de la montaña ya que si damos pasos muy largos en una dirección puede que nos pasemos de largo. En este caso podemos imaginar que podemos ser gigantes que si damos pasos muy largos, nos llegaremos a la parte interior de la montaña. Pero si damos los pasos muy pequeños, no llegaremos a la parte inferior de la montaña antes de acabar las épocas.

Descenso de gradiente en Python

Pasemos ahora a ver como se programa todo ésto en Python.Siguiendo con nuestro ejemplo de:

Ya teníamos el siguiente código:

y ahora añadimos el código de los métodos descenso_gradiente y fit.

Por último usamos todo el código para ver si entrena igual con nuestro código que en Keras.

Mas información:

¿Qué es el Descenso del Gradiente? Algoritmo de Inteligencia Artificial | DotCSV

IA NOTEBOOK #3 | Descenso del Gradiente (Gradient Descent) | Programando IA

Todo lo que Necesitas Saber sobre el Descenso del Gradiente Aplicado a Redes Neuronales

Entrenamiento en Keras

Ya hemos visto como el método fit de Keras realiza el entrenamiento pero veamos un poco más en detalle lo que hay que hacer.

Para el entrenamiento debemos establecer parámetros tanto en fit como en compile. Veamos un ejemplo.

compile

loss: Ya vimos en el tema anterior que es la fórmula que se usa para calcular la pérdida o coste de la red neuronal

optimizer: Indica el algoritmo de descenso de gradiente que vamos a usar junto con la tasa de aprendizaje o learning_rate. Más adelante en este tema vamos a ver que hay variaciones del algoritmo del descenso de gradiente.

fit

epochs: Indicamos cuantas veces se va a aplicar el algoritmo del descenso de gradiente.

batch_size: En este tema aun no vamos a ver el significado de este parámetro pero simplemente decir que lo hemos incluido para que el resultado fuera el mismo que con Python.

shuffle: Este parámetro no se suele usar y solo lo hemos incluido para que el resultado fuera el mismo que con Python.

Cuando entrenamos por defecto fit nos muestra el resultado del entrenamiento de cada época, indicando el valor de loss en cada una de ellas.

Tanto el método fit como compile tienen muchos más parámetros pero aquí hemos visto únicamente los básicos.

Mas información:

compile method

fit method

Profundizando en el descenso de gradiente

Vamos ahora a ver de una forma más gráfica como funciona el descenso de gradiente y algunos problemas que tiene.

En este apartado a modo de ejemplo, vamos a usar como función de coste:

y en Python:

Cuya gráfica es:

En los apéndices de este tema está el código que vamos a usar que se usa así:

La función get_puntos_descenso_gradiente tiene los siguientes parámetros:

epochs: El nº de épocas a aplicar

learning_rate: La tasa de aprendizaje a aplicar.

w_0_original: El peso inicial de w_0

w_1_original: El peso inicial de w_1

Que genera la siguiente gráfica

La estrella roja es desde donde empieza el algoritmo.Es decir, el valor inicial de los parámetros La estrella azul es donde acaba el algoritmo.Es decir, el valor tras el entrenamiento de los parámetros Los puntos amarillo son los pasos por lo que se va moviendo el algoritmo. Cada uno de los valores intermedios de los parámetros durante el entrenamiento.

Learning rate y epochs

Veamos ahora como influye el learning_rate en el funcionamiento del algoritmo de gradiente.

Comparemos la gráfica anterior con un learning_rate de 0.03 con este otro ejemplo con learning_rate de 0.07

Podemos ver que ahora que damos pasos demasiado largos y no llegamos hasta el mínimo de la función.

Sigamos con otro ejemplo:

El valor de learning_rate es mayor aun , siendo de 0.1 por lo que damos pasos tan grandes que nos salimos de mínimo de la función y eso que hemos pasado de 5 épocas a 11 épocas. Es decir, si el learning_rate es elevado, no llegará al mínimo aunque pongamos muchas épocas.

Sigamos con otro ejemplo en el que el learning_rate es pequeño, concretamente de 0.006

Vemos que al dar pasos tan pequeños nos quedamos a mitad de camino y eso que también hemos puesto 11 épocas. En este caso para solucionarlo solo habría que aumentar el número de épocas.

Así que en el siguiente ejemplo, vamos a aumentar el número de épocas de 11 a 30.

Y ahora ya ha llegado hasta el mínimo.

En general nos interesa valores bajos de learning_rate y altos de epochs.

Mínimos locales y mínimos globales

Ahora vamos a simular distintos valores iniciales de y es decir, suponiendo que el generador de números aleatorios genera unos valores iniciales distintos. Y vamos a comparar que ocurre con los distintos valores iniciales.

Esta última gráfica es muy interesante. No hay que perder de vista que las estrellas rojas es el valor inicial de los pesos que tenemos en nuestra red neuronal. Es decir , son esos pesos aleatorios con los que de inicializa la red neuronal. Pues lo interesante es que según los valores iniciales aleatorios que elijamos, puede que no lleguemos al mínimo de la función (mínimo global), sino a lo que llamamos un mínimo local.

Se puede ver mejor en esta gráfica:

Es decir que el algoritmo del descenso de gradiente no nos garantiza que encontremos el mínimo global de la función sino solo un mínimo local. Lo que conlleva que quizás no encontramos los mejores valores de los parámetros (weight y bias) para nuestra red neuronal.

Para intentar solventar los problemas del algoritmo del descenso de gradiente existen diversas variaciones del mismo que vamos a ver en el siguiente apartado.

Usando optimizadores en Keras

Ya hemos visto como funciona el algoritmo del descenso de gradiente pero resulta que existe muchas variaciones sobre el algoritmo básico. Es lo que en Keras se llaman Optimizers.

Lo que pretenden estos optimizadores es hacer que se llegue lo más rápido posible al mínimo además de evitar mínimos locales, etc. El descenso de gradiente que hemos visto es lo que en Keras se llama Stochastic gradient descent (SGD).

Al igual que pasaba otras veces en Keras, los optimizadores se pueden usar de 2 formas distintas y se usan en el método compile con el argumento optimizer:

Mediante la clase que retorna la función

Mediante un String

Notar que el optimizador se define en el método compile pero donde se usa es con el método fit.

Tipos de optimizadores en Keras

Veamos ahora los distintos tipos de optimizadores que hay en Keras.

Mas información:

Keras Available optimizers

Stochastic gradient descent o SGD

Es el que hemos usado hasta ahora. Se llama "Stochastic gradient descent".

Tiene además los siguientes parámetros:

momentum: Se usa para que tenga en cuenta valores anteriores del gradiente. Valor adecuado si de desea momentum es de 0.9

nesterov: Si vale True se utiliza el algoritmo de Nesterov para el momentum

Su uso en Keras:

Mas información:

SGD

Adagrad

Es como SGD pero intenta aprender el learning_rate

Su uso en Keras:

Más información:

Adagrad

RMSprop

Es como AdraGrad pero aprende el learning_rate.

Tiene además los siguientes parámetros:

rho: Se usa para ver como se aprende el learning_rate

momentum: Es la tasa de decaimiento del learning_rate

Su uso en Keras:

Más información:

RMSprop

Adadelta

Es como AdraGrad pero learning_rate la aprende sola.

Tiene además los siguientes parámetros:

learning_rate: Debería valor 1 o un valor elevado ya que siempre va bajando.

rho: Se usa para ver como se aprende el learning_rate

Su uso en Keras:

Más información:

Adadelta

Adam

RMSprop + Momentum

Tiene además los siguientes parámetros:

amsgrad: Si vale True se comporta como el algoritmo de AMSGrad que es bastante moderno (2018)

Su uso en Keras:

Más información:

Adam

Adamax

Como Adam pero mas estable a ruido del gradiente

Su uso en Keras:

Más información:

Adamax

Nadam

Como Adam pero se utiliza el algoritmo de Nesterov para el momentum

Su uso en Keras:

Más información:

Nadam

Elección del optimizador.

Al igual que pasaba con las funciones de activación, según el problema, puede ser mejor uno u otro.

En el siguiente esquema se puede ver la relación entre unos y otros y así ver cual es más moderno.

Usando las funciones que ya tenemos de get_puntos_descenso_gradiente, plot_loss_function, etc. he creado a crear un video para que se vea de la velocidad a la que se mueve cada optimizador.

descenso_gradiente_animado.mp4

El objetivo del vídeo no es decir que optimizador es mejor sino hacer ver que hay mejores y peores. En otro problema y con otros parámetros el resultado puede ser distinto.

Veamos ahora un ejemplo con código de como funcionan

Mas información:

Introducción al Deep Learning III: Métodos de optimización II

Optimizers in Machine Learning and Deep Learning.

10 Stochastic Gradient Descent Optimisation Algorithms + Cheat Sheet

Keras optimizers

Quick Notes on How to choose Optimizer In Keras

11. Optimization Algorithms -- Dive into Deep Learning 0.17.0 documentation

Dissecting Optimization Algorithms: Contiene gráficas de rendimiento de los distintos algoritmo

Optimizer: diving deep into Neural Networks: Gráficas buenas.

Deep study of a not very deep neural network. Part 3a: Optimizers overview: Con gráficas

Deep study of a not very deep neural network. Part 3b: Choosing an optimizer: Dice que en cada problema hay que elegir un optimizador adecuado.

Gradient Descent Algorithm — a deep dive: Explicación del descenso de gradiente

RMSprop saviour of Adagrad: Lo que intenta mejorar RMSprop de Adagrad

Adagrad and Adadelta Optimizer: In-Depth Explanation

A 2021 Guide to improving CNNs-Optimizers: Adam vs SGD

Hardware entrenamiento

Mas información

Deep Learning GPU Benchmarks 2022

AIME A4000 - Multi GPU HPC Rack Server

AIME T600 - Workstation

Ejercicios

Ejercicio 1

Dado las gráficas de las funciones de coste de una red neuronal:

Indica para cada parámetro: Si la función crece o decrece, el valor de su derivada (positiva o negativa) y por lo tanto si se debe incrementar el valor del parámetro o decremenarlo.

Ejercicio 2

Dado la siguiente función matemática:

Indica en valor de la función en los puntos -2,-1 y 1

Indica el valor de la derivada en los puntos anteriores

Según el valor de la derivada de los puntos anteriores indica si la función crece o decrece y "cuanto"

Averigua en qué valor la x tendría el valor mínimo.

Ejercicio 3

Copia el siguiente código python:

Vamos a trabajar con una red neuronal correspondiente a una única neurona y función de activación sigmoide, que corresponde a la siguiente fórmula:

Lo que debe hacer es entrenar tu manualmente la red , es decir, encontrar los mejores valores de w y b.

Para ello ejecuta el siguiente código:

Que muestra la siguiente gráfica:

Esta gráfica muestra en rojo que con los valores de w=-0.3 y b=0.1 el valor de la pérdida de la red es 0.59. loss=0.59. Mientras que en verde se muestra los valores que harían mínima cada ua de las funciones w=0.55 y b=-4.

Modifica el array parametros añadiendo mas pares de valores de w y b para ver si consigues obtener un valor de loss lo más cercano a 0 que sea posible. Deberás ir modificando poco a poco los valores de w y b.

Ejercicio 4

Entrena ahora la red neuronal usando el descenso de gradiente. Para ello ejecuta el siguiente código:

Modifica los valores de learning_rate y epochs para que la red se entrene sola para obtener un valor de loss lo más cercano a 0 que sea posible.

Ejercicio 5.A

Crea una red neuronal para entrenar las flores. La red tendrá las siguientes características:

Capas: [4,8,3]

Función de activación: selu

Épocas: 400

Y con todas las combinaciones de:

Optimizadores

tf.keras.optimizers.SGD

tf.keras.optimizers.Adagrad

tf.keras.optimizers.RMSprop

tf.keras.optimizers.Adam

tf.keras.optimizers.Adamax

tf.keras.optimizers.Nadam

Tasa de aprendizaje

0.1

0.01

0.001

0.0001

Muestra las gráficas de la pérdida en función de las épocas de forma que por filas estén los optimizadores y por columnas las tasas de aprendizaje.

Responde las siguientes cuestiones:

¿Cual ha resultado ser el mejor optimizador?

¿Cual ha sido la mejor tasa de aprendizaje para el mejor optimizador?

Indica para cada optimizador cual es la mejor tasa de aprendizaje

Explica que problema tiene en general la tasa de aprendizaje de 0.1

¿Que problema tiene en general tasas de aprendizaje altas?

Ejercicio 5.B

Repite el ejercicio anterior pero ahora solo con 20 épocas.

¿Que tasas de aprendizaje y optimizadores se ve que van a generar redes inestables?

Ejercicio 6.A

Haz una red neuronal que averigüe un dígito que se ha escrito a mano. Para ello obtén los datos con el siguiente código:

La red debería ser realmente una red convolucional pero en este ejemplo usaremos una red neuronal complemente conectada

La red tendrá las siguientes características:

Neuronas por capa: 64,128,64,32,16,10

Función de activación de las capas ocultas: selu

Prueba únicamente con 20 épocas con todas las combinaciones de lo siguiente:

Optimizadores

tf.keras.optimizers.SGD

tf.keras.optimizers.Adagrad

tf.keras.optimizers.RMSprop

tf.keras.optimizers.Adam

tf.keras.optimizers.Adamax

tf.keras.optimizers.Nadam

Tasa de aprendizaje

0.1

0.01

0.001

0.0005

Muestra las gráficas de la pérdida en función de las épocas de forma que por filas estén los optimizadores y por columnas las tasas de aprendizaje.

Indica para cada tasa de aprendizaje y para cada optimizador si la entrenarías o no y el motivo.

Ejercicio 6.B

Siguiendo con el ejercicio anterior, entrena ahora la red con 300 épocas pero ahora solo con los optimizadores/tasas de aprendizaje que seleccionaste en el ejercicio anterior

Ejercicio 6.C

Repite el ejercicio anterior pero ahora con TODAS las combinaciones de optimizadores y tasas de aprendizaje.

¿Fue adecuada la selección que hiciste en el ejercicio anterior?