clase:iabd:pia:2eval:tema07.backpropagation_descenso_gradiente
Diferencias
Muestra las diferencias entre dos versiones de la página.
| — | clase:iabd:pia:2eval:tema07.backpropagation_descenso_gradiente [2025/11/03 11:37] (actual) – creado - editor externo 127.0.0.1 | ||
|---|---|---|---|
| Línea 1: | Línea 1: | ||
| + | ====== 7.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 $x$ e $y$ para que obtenga o aprenda los mejores parámetros posibles de pesos (// | ||
| + | |||
| + | Sabemos que: | ||
| + | $$y= Salida \: verdadera. \: La \: que \: debería \: haber \: generado \: la \: red \: neuronal. Son \: datos \: que \: tenemos \: reales \: de \: salida$$ | ||
| + | $$\hat{y}= Salida \: predicha. \: La \: que \: ha \: generado \: la \: red \: neuronal$$ | ||
| + | |||
| + | $$loss=\frac{1}{N} \sum_{i=1}^{N} |y_i-\hat{y}_i|$$ | ||
| + | |||
| + | Seguimos con la red neuronal que usamos de ejemplo: | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | |||
| + | Dedujimos que corresponde a la siguiente fórmula: | ||
| + | |||
| + | $$ | ||
| + | \large \hat{y}=\frac{1}{1 + e^{-( w_{5, | ||
| + | $$ | ||
| + | |||
| + | 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 $y$ la salida de la red neuronal $\hat{y}$ es decir $y-\hat{y}$ | ||
| + | |||
| + | $$ | ||
| + | \large loss(x, | ||
| + | $$ | ||
| + | |||
| + | $$ | ||
| + | loss(x, | ||
| + | $$ | ||
| + | |||
| + | 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 ($w_2, | ||
| + | |||
| + | |||
| + | 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: | ||
| + | * Todas las combinaciones: | ||
| + | * 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 '' | ||
| + | <sxh python> | ||
| + | def sigmoid(z): | ||
| + | return 1/(1 + np.exp(-z)) | ||
| + | |||
| + | |||
| + | def predict_formula(x, | ||
| + | part1=w_52*sigmoid(w_2*x+b_2) | ||
| + | part2=w_53*sigmoid(w_3*x+b_3) | ||
| + | part3=w_54*sigmoid(w_4*x+b_4) | ||
| + | part4=b_5 | ||
| + | z=part1+part2+part3+part4 | ||
| + | |||
| + | return sigmoid(z) | ||
| + | |||
| + | |||
| + | def loss_mae(y_true, | ||
| + | error=np.abs(np.subtract(y_true, | ||
| + | mean_error=np.sum(error)/ | ||
| + | |||
| + | return mean_error | ||
| + | |||
| + | |||
| + | |||
| + | def loss(x, | ||
| + | y_pred=predict_formula(x, | ||
| + | |||
| + | return loss_mae(y_true, | ||
| + | </ | ||
| + | |||
| + | |||
| + | Lo siguiente que vamos a hacer es una gráfica para ver como variaría el valor de '' | ||
| + | |||
| + | <sxh python> | ||
| + | iris=load_iris() | ||
| + | x=iris.data[0: | ||
| + | y_true=iris.target[0: | ||
| + | |||
| + | |||
| + | model=get_model(" | ||
| + | w_2_original, | ||
| + | perdida=loss(x, | ||
| + | |||
| + | rango_w_2=np.linspace(-5, | ||
| + | perdidas_w_2=[] | ||
| + | for w_2 in rango_w_2: | ||
| + | perdidas_w_2.append( loss(x, | ||
| + | |||
| + | |||
| + | figure=plt.figure(figsize=(5.5, | ||
| + | axes = figure.add_subplot() | ||
| + | axes.plot(rango_w_2, | ||
| + | axes.scatter(w_2_original, | ||
| + | axes.set_xlabel(" | ||
| + | axes.set_ylabel(' | ||
| + | </ | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | En la gráfica , el punto rojo es el valor actual de '' | ||
| + | La curva nos permite ver como evoluciona la pérdida, si vamos variando el valor de '' | ||
| + | En nuestro caso, viendo la gráfica podemos encontrar rápidamente el valor de '' | ||
| + | pero debemos tener en cuenta que si variamos mucho '' | ||
| + | En nuestro ejemplo, para hacer que la pérdida sea la mínima, ¿que es mejor que '' | ||
| + | |||
| + | 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 '' | ||
| + | |||
| + | Veamos ahora un ejemplo de una función de '' | ||
| + | {{: | ||
| + | |||
| + | <note tip>Las dos imágenes son la misma función pero la de la derecha está representada en 2D, donde al "// | ||
| + | |||
| + | Como vemos no podríamos encontrar el mínimo para '' | ||
| + | |||
| + | 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 // | ||
| + | |||
| + | La siguiente imagen ilustra la idea. | ||
| + | {{ : | ||
| + | |||
| + | Y podemos jugar con un simulador de descenso de gradiente en [[https:// | ||
| + | |||
| + | [[https:// | ||
| + | |||
| + | Y ver un video de como funciona en [[https:// | ||
| + | |||
| + | [[https:// | ||
| + | |||
| + | |||
| + | ¿Como hacemos eso matemáticamente? | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | $$ | ||
| + | gradiente \: de \: f(x) = derivada \: de \: f(x)=\frac{\Delta f}{\Delta x}=\lim_{h \to 0} \frac {f(x+h)-f(x)}{h}=\frac{\partial \: f(x)}{\partial \: x} | ||
| + | $$ | ||
| + | |||
| + | |||
| + | 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 $x$ 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 $x$ proporcionalmente al valor del gradiente. | ||
| + | |||
| + | |||
| + | |||
| + | <note tip> | ||
| + | Una explicación mas detallada de la fórmula se puede ver en el siguiente video: [[https:// | ||
| + | </ | ||
| + | |||
| + | <note tip>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//</ | ||
| + | |||
| + | Para entender la derivada podemos usar estos 2 recursos de Gecebra: | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | |||
| + | |||
| + | La siguiente fórmula del gradiente queda así con respecto $w_0$ en el punto $(w_0, | ||
| + | |||
| + | |||
| + | $$ | ||
| + | gradiente \: de \: w_0 = \lim_{h \to 0} \frac {loss(w_0+h, | ||
| + | $$ | ||
| + | |||
| + | La siguiente fórmula es lo mismo pero para $w_1$ | ||
| + | |||
| + | |||
| + | $$ | ||
| + | gradiente \: de \: w_1 = \lim_{h \to 0} \frac {loss(w_0, | ||
| + | $$ | ||
| + | |||
| + | |||
| + | Ya sabemos si crece pues en ese caso soy hay que moverse justo para el //lado// contrario de ahí el " | ||
| + | |||
| + | $$ | ||
| + | w_0=w_0-\alpha \cdot gradiente \: de \: w_0=w_0-\alpha \cdot \frac{\partial \: loss(w_0, | ||
| + | $$ | ||
| + | |||
| + | y | ||
| + | |||
| + | $$ | ||
| + | w_1=w_1-\alpha \cdot gradiente \: de \: w_1=w_1-\alpha \cdot \frac{\partial | ||
| + | $$ | ||
| + | |||
| + | 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í: | ||
| + | |||
| + | $$ | ||
| + | \huge w_i^{t+1}=w_i^{t}-\alpha \cdot \frac{\partial \: loss(w_i^{t})}{\partial \: w_i^{t}} | ||
| + | $$ | ||
| + | |||
| + | Expliquemos un poco la fórmula: | ||
| + | * $w_i$ es cada uno de los parámetros es decir '' | ||
| + | * El superíndice $t$ el instante de tiempo,ya que vamos a aplicar varias veces la fórmula. Es realmente cada una de las épocas o '' | ||
| + | * La variable | ||
| + | ===== 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: | ||
| + | |||
| + | <sxh python> | ||
| + | import numpy as np | ||
| + | import pandas as pd | ||
| + | import tensorflow as tf | ||
| + | from tensorflow.keras.models import Sequential | ||
| + | from tensorflow.keras.layers import Dense | ||
| + | from sklearn.datasets import load_iris | ||
| + | |||
| + | |||
| + | def get_model(loss, | ||
| + | np.random.seed(5) | ||
| + | tf.random.set_seed(5) | ||
| + | random.seed(5) | ||
| + | |||
| + | model=Sequential() | ||
| + | model.add(Dense(3, | ||
| + | model.add(Dense(1, | ||
| + | model.compile(loss=loss, | ||
| + | |||
| + | return model | ||
| + | |||
| + | |||
| + | def get_w(model, | ||
| + | layer=model.layers[layer] | ||
| + | return layer.get_weights()[0][index, | ||
| + | |||
| + | |||
| + | |||
| + | def get_b(model, | ||
| + | layer=model.layers[layer] | ||
| + | return layer.get_weights()[1][neuron] | ||
| + | | ||
| + | |||
| + | def get_parameters_from_model(model): | ||
| + | w_2 =get_w(model, | ||
| + | w_3 =get_w(model, | ||
| + | w_4 =get_w(model, | ||
| + | w_52=get_w(model, | ||
| + | w_53=get_w(model, | ||
| + | w_54=get_w(model, | ||
| + | b_2 =get_b(model, | ||
| + | b_3 =get_b(model, | ||
| + | b_4 =get_b(model, | ||
| + | b_5 =get_b(model, | ||
| + | |||
| + | return w_2, | ||
| + | |||
| + | def print_params(cabecera, | ||
| + | decimals=8 | ||
| + | |||
| + | print(cabecera) | ||
| + | print(" | ||
| + | print(" | ||
| + | print(" | ||
| + | print(" | ||
| + | |||
| + | |||
| + | def sigmoid(x): | ||
| + | return 1/(1 + np.exp(-x)) | ||
| + | | ||
| + | def predict_formula(x, | ||
| + | part1=w_52*sigmoid(w_2*x+b_2) | ||
| + | part2=w_53*sigmoid(w_3*x+b_3) | ||
| + | part3=w_54*sigmoid(w_4*x+b_4) | ||
| + | part4=b_5 | ||
| + | z=part1+part2+part3+part4 | ||
| + | | ||
| + | return sigmoid(z) | ||
| + | |||
| + | def loss_mae(y_true, | ||
| + | error=np.abs(np.subtract(y_true, | ||
| + | mean_error=np.sum(error)/ | ||
| + | |||
| + | return mean_error | ||
| + | |||
| + | def loss(x, | ||
| + | y_pred=predict_formula(x, | ||
| + | |||
| + | return loss_mae(y_true, | ||
| + | |||
| + | </ | ||
| + | |||
| + | y ahora añadimos el código de los métodos '' | ||
| + | |||
| + | <sxh python> | ||
| + | |||
| + | |||
| + | def descenso_gradiente(x, | ||
| + | h=0.000003 | ||
| + | |||
| + | gradiente_w_2 =(loss(x, | ||
| + | gradiente_w_3 =(loss(x, | ||
| + | gradiente_w_4 =(loss(x, | ||
| + | gradiente_w_52=(loss(x, | ||
| + | gradiente_w_53=(loss(x, | ||
| + | gradiente_w_54=(loss(x, | ||
| + | gradiente_b_2 =(loss(x, | ||
| + | gradiente_b_3 =(loss(x, | ||
| + | gradiente_b_4 =(loss(x, | ||
| + | gradiente_b_5 =(loss(x, | ||
| + | |||
| + | w_2 =w_2 -learning_rate*gradiente_w_2 | ||
| + | w_3 =w_3 -learning_rate*gradiente_w_3 | ||
| + | w_4 =w_4 -learning_rate*gradiente_w_4 | ||
| + | w_52=w_52-learning_rate*gradiente_w_52 | ||
| + | w_53=w_53-learning_rate*gradiente_w_53 | ||
| + | w_54=w_54-learning_rate*gradiente_w_54 | ||
| + | b_2 =b_2 -learning_rate*gradiente_b_2 | ||
| + | b_3 =b_3 -learning_rate*gradiente_b_3 | ||
| + | b_4 =b_4 -learning_rate*gradiente_b_4 | ||
| + | b_5 =b_5 -learning_rate*gradiente_b_5 | ||
| + | |||
| + | return w_2, | ||
| + | |||
| + | |||
| + | |||
| + | def fit(x, y_true, | ||
| + | | ||
| + | for epoch in range(epochs): | ||
| + | w_2, | ||
| + | |||
| + | return w_2, | ||
| + | |||
| + | </ | ||
| + | |||
| + | |||
| + | Por último usamos todo el código para ver si entrena igual con nuestro código que en Keras. | ||
| + | |||
| + | <sxh python> | ||
| + | learning_rate=0.001 | ||
| + | epochs=50 | ||
| + | |||
| + | iris=load_iris() | ||
| + | x=iris.data[0: | ||
| + | y_true=iris.target[0: | ||
| + | |||
| + | model=get_model(' | ||
| + | |||
| + | w_2_original, | ||
| + | print_params(" | ||
| + | |||
| + | history=model.fit(x, | ||
| + | w_2, | ||
| + | print_params(" | ||
| + | |||
| + | w_2, | ||
| + | print_params(" | ||
| + | </ | ||
| + | |||
| + | <sxh python> | ||
| + | Valor de los pesos antes del entrenamiento: | ||
| + | w_2= 0.30959857 | ||
| + | w_3= 0.07310068 | ||
| + | w_4= 0.63308823 | ||
| + | w_52= 0.49408045 | ||
| + | Valor de los pesos tras entrenamiento usando Keras: | ||
| + | w_2= 0.31066534 | ||
| + | w_3= 0.06847349 | ||
| + | w_4= 0.6330435 | ||
| + | w_52= 0.49511436 | ||
| + | Valor de los pesos tras entrenamiento Manualmente: | ||
| + | w_2= 0.31066538 | ||
| + | w_3= 0.06847351 | ||
| + | w_4= 0.63304377 | ||
| + | w_52= 0.49511434 | ||
| + | </ | ||
| + | |||
| + | Mas información: | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | |||
| + | ===== Entrenamiento en Keras ===== | ||
| + | Ya hemos visto como el método '' | ||
| + | |||
| + | Para el entrenamiento debemos establecer parámetros tanto en '' | ||
| + | |||
| + | <sxh python> | ||
| + | model.compile(loss=loss, | ||
| + | model.fit(x, | ||
| + | </ | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | Cuando entrenamos por defecto '' | ||
| + | <sxh base> | ||
| + | Epoch 1/50 | ||
| + | 1/1 [==============================] - 0s 282ms/step - loss: 0.4918 | ||
| + | Epoch 2/50 | ||
| + | 1/1 [==============================] - 0s 7ms/step - loss: 0.4917 | ||
| + | Epoch 3/50 | ||
| + | 1/1 [==============================] - 0s 5ms/step - loss: 0.4917 | ||
| + | ... | ||
| + | ... | ||
| + | ... | ||
| + | Epoch 48/50 | ||
| + | 1/1 [==============================] - 0s 3ms/step - loss: 0.4913 | ||
| + | Epoch 49/50 | ||
| + | 1/1 [==============================] - 0s 4ms/step - loss: 0.4913 | ||
| + | Epoch 50/50 | ||
| + | 1/1 [==============================] - 0s 4ms/step - loss: 0.4913 | ||
| + | </ | ||
| + | |||
| + | |||
| + | Tanto el método '' | ||
| + | |||
| + | Mas información: | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | |||
| + | ===== 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: | ||
| + | |||
| + | $$loss(w_0, | ||
| + | |||
| + | y en Python: | ||
| + | |||
| + | <sxh python> | ||
| + | def loss_function(w_0, | ||
| + | return | ||
| + | </ | ||
| + | |||
| + | Cuya gráfica es: | ||
| + | {{: | ||
| + | |||
| + | |||
| + | |||
| + | En los apéndices de este tema está el código que vamos a usar que se usa así: | ||
| + | <sxh python> | ||
| + | figure=plt.figure(figsize=(16, | ||
| + | axes = figure.add_subplot() | ||
| + | plot_loss_function(axes) | ||
| + | |||
| + | epochs=5 | ||
| + | learning_rate=0.03 | ||
| + | w_0_original=-0.35 | ||
| + | w_1_original=-0.67 | ||
| + | plot_descenso_gradiente(axes, | ||
| + | </ | ||
| + | |||
| + | La función '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | Que genera la siguiente gráfica | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | |||
| + | |||
| + | < | ||
| + | * La estrella roja es desde donde empieza el algoritmo.Es decir, el valor inicial de los parámetros $w_0,w_1$ | ||
| + | * La estrella azul es donde acaba el algoritmo.Es decir, el valor tras el entrenamiento de los parámetros $w_0,w_1$ | ||
| + | * 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. | ||
| + | </ | ||
| + | |||
| + | ==== Optimizadores de Keras ==== | ||
| + | | ||
| + | |||
| + | |||
| + | <sxh python> | ||
| + | def loss_tf(w_0, | ||
| + | return | ||
| + | |||
| + | def get_puntos_descenso_gradiente_optimizer(epochs, | ||
| + | |||
| + | puntos_descenso_gradiente=np.array([[w_0_init, | ||
| + | |||
| + | w_0=w_0_init | ||
| + | w_1=w_1_init | ||
| + | for epoch in range(epochs): | ||
| + | var_w_0=tf.Variable(w_0) | ||
| + | var_w_1=tf.Variable(w_1) | ||
| + | |||
| + | optimizer_function.minimize(lambda: | ||
| + | optimizer_function.minimize(lambda: | ||
| + | |||
| + | w_0=var_w_0.numpy() | ||
| + | w_1=var_w_1.numpy() | ||
| + | |||
| + | puntos_descenso_gradiente=np.append(puntos_descenso_gradiente, | ||
| + | |||
| + | return puntos_descenso_gradiente | ||
| + | </ | ||
| + | |||
| + | Lo que ha cambiado principalmente es la función '' | ||
| + | |||
| + | |||
| + | Si usamos '' | ||
| + | <sxh python> | ||
| + | get_puntos_descenso_gradiente_optimizer(5, | ||
| + | </ | ||
| + | <sxh base> | ||
| + | array([[-0.35 | ||
| + | | ||
| + | | ||
| + | [ 0.06544246, -1.06462073], | ||
| + | [ 0.24086528, -1.40752137], | ||
| + | [ 0.26578248, -1.59573841]]) | ||
| + | </ | ||
| + | Vamos que el resultado es prácticamente el mismo que cuando lo hicimos manualmente. | ||
| + | |||
| + | Y podemos generar de la misma forma el gráfico: | ||
| + | <sxh python> | ||
| + | figure=plt.figure(figsize=(16, | ||
| + | axes = figure.add_subplot() | ||
| + | plot_loss_function(axes) | ||
| + | |||
| + | plot_descenso_gradiente(axes, | ||
| + | </ | ||
| + | |||
| + | |||
| + | {{ : | ||
| + | |||
| + | Y obviamente el resultado es el mismo | ||
| + | |||
| + | ==== Learning rate y epochs ==== | ||
| + | Veamos ahora como influye el '' | ||
| + | |||
| + | Comparemos la gráfica anterior con un '' | ||
| + | |||
| + | <sxh python> | ||
| + | plot_descenso_gradiente(get_puntos_descenso_gradiente(5, | ||
| + | </ | ||
| + | |||
| + | |||
| + | {{ : | ||
| + | |||
| + | |||
| + | Podemos ver que ahora que damos //pasos// demasiado largos y no llegamos hasta el mínimo de la función. | ||
| + | |||
| + | Sigamos con otro ejemplo: | ||
| + | |||
| + | <sxh python> | ||
| + | plot_descenso_gradiente(get_puntos_descenso_gradiente(11, | ||
| + | </ | ||
| + | |||
| + | |||
| + | {{ : | ||
| + | |||
| + | El valor de '' | ||
| + | Es decir, si el '' | ||
| + | |||
| + | |||
| + | Sigamos con otro ejemplo en el que el '' | ||
| + | |||
| + | <sxh python> | ||
| + | plot_descenso_gradiente(get_puntos_descenso_gradiente(11, | ||
| + | </ | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | 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. | ||
| + | |||
| + | <sxh python> | ||
| + | plot_descenso_gradiente(get_puntos_descenso_gradiente(30, | ||
| + | </ | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | Y ahora ya ha llegado hasta el mínimo. | ||
| + | |||
| + | <note tip> | ||
| + | En general nos interesa valores bajos de '' | ||
| + | </ | ||
| + | |||
| + | ==== Mínimos locales y mínimos globales ==== | ||
| + | Ahora vamos a simular distintos valores iniciales de $w_0$ y $w_1$ 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. | ||
| + | |||
| + | <sxh python> | ||
| + | plot_descenso_gradiente(axes, | ||
| + | plot_descenso_gradiente(axes, | ||
| + | plot_descenso_gradiente(axes, | ||
| + | plot_descenso_gradiente(axes, | ||
| + | </ | ||
| + | |||
| + | |||
| + | {{ : | ||
| + | |||
| + | 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. | ||
| + | |||
| + | ==== Punto de silla ==== | ||
| + | Al entrenar redes neuronales, existe el concepto de "Punto de silla" | ||
| + | Es un punto en el que para cada parámetro estaría en un mínimo o máximo local. | ||
| + | Su nombre viene de que parece una silla de montar a caballo. | ||
| + | |||
| + | {{: | ||
| + | |||
| + | No debe preocuparnos los puntos de silla ya que al actualizar los parámetros, | ||
| + | |||
| + | ===== 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 [[https:// | ||
| + | |||
| + | 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 '' | ||
| + | |||
| + | |||
| + | * Mediante la clase que retorna la función | ||
| + | |||
| + | <sxh python> | ||
| + | model.compile(loss=" | ||
| + | </ | ||
| + | |||
| + | |||
| + | * Mediante un String | ||
| + | |||
| + | <sxh python> | ||
| + | model.compile(loss=" | ||
| + | </ | ||
| + | |||
| + | <note tip> | ||
| + | Notar que el optimizador se define en el método '' | ||
| + | </ | ||
| + | |||
| + | ===== Tipos de optimizadores en Keras ===== | ||
| + | Veamos ahora los distintos tipos de optimizadores que hay en Keras. | ||
| + | |||
| + | Mas información: | ||
| + | * [[https:// | ||
| + | |||
| + | ==== Stochastic gradient descent o SGD ==== | ||
| + | Es el que hemos usado hasta ahora. Se llama " | ||
| + | |||
| + | * Tiene además los siguientes parámetros: | ||
| + | * '' | ||
| + | * '' | ||
| + | | ||
| + | * Su uso en Keras: | ||
| + | <sxh python> | ||
| + | model.compile(loss=" | ||
| + | model.compile(loss=" | ||
| + | </ | ||
| + | |||
| + | Mas información: | ||
| + | * [[https:// | ||
| + | |||
| + | |||
| + | ==== Adagrad ==== | ||
| + | Es como SGD pero intenta aprender el '' | ||
| + | |||
| + | * Su uso en Keras: | ||
| + | <sxh python> | ||
| + | model.compile(loss=" | ||
| + | model.compile(loss=" | ||
| + | </ | ||
| + | |||
| + | Más información: | ||
| + | * [[https:// | ||
| + | |||
| + | ==== RMSprop ==== | ||
| + | Es como AdraGrad pero aprende el '' | ||
| + | |||
| + | * Tiene además los siguientes parámetros: | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | * Su uso en Keras: | ||
| + | <sxh python> | ||
| + | model.compile(loss=" | ||
| + | model.compile(loss=" | ||
| + | </ | ||
| + | |||
| + | Más información: | ||
| + | * [[https:// | ||
| + | |||
| + | ==== Adadelta ==== | ||
| + | Es como AdraGrad pero '' | ||
| + | |||
| + | |||
| + | * Tiene además los siguientes parámetros: | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | * Su uso en Keras: | ||
| + | <sxh python> | ||
| + | model.compile(loss=" | ||
| + | model.compile(loss=" | ||
| + | </ | ||
| + | |||
| + | |||
| + | Más información: | ||
| + | * [[https:// | ||
| + | |||
| + | ==== Adam ==== | ||
| + | RMSprop + Momentum | ||
| + | |||
| + | * Tiene además los siguientes parámetros: | ||
| + | * '' | ||
| + | |||
| + | |||
| + | * Su uso en Keras: | ||
| + | <sxh python> | ||
| + | model.compile(loss=" | ||
| + | model.compile(loss=" | ||
| + | </ | ||
| + | |||
| + | |||
| + | Más información: | ||
| + | * [[https:// | ||
| + | * {{ : | ||
| + | |||
| + | |||
| + | ==== Adamax ==== | ||
| + | Como Adam pero mas estable a ruido del gradiente | ||
| + | |||
| + | * Su uso en Keras: | ||
| + | <sxh python> | ||
| + | model.compile(loss=" | ||
| + | model.compile(loss=" | ||
| + | </ | ||
| + | |||
| + | |||
| + | Más información: | ||
| + | * [[https:// | ||
| + | |||
| + | ==== Nadam ==== | ||
| + | Como Adam pero se utiliza el algoritmo de Nesterov para el // | ||
| + | |||
| + | * Su uso en Keras: | ||
| + | <sxh python> | ||
| + | model.compile(loss=" | ||
| + | model.compile(loss=" | ||
| + | </ | ||
| + | |||
| + | Más información: | ||
| + | * [[https:// | ||
| + | |||
| + | |||
| + | <note tip> | ||
| + | En nuestros proyectos, solemos necesitar mostrar por pantalla el optimizador que estamos usando y normalmente pasa ésto: | ||
| + | |||
| + | <sxh python> | ||
| + | print(tf.keras.optimizers.Adam(learning_rate=0.001)) | ||
| + | print(tf.keras.optimizers.Adamax(learning_rate=0.00000001)) | ||
| + | </ | ||
| + | |||
| + | <sxh base> | ||
| + | < | ||
| + | < | ||
| + | </ | ||
| + | |||
| + | Pero hay un truco para que se muestre de forma más amigable, que es sobre escribir la función '' | ||
| + | |||
| + | <sxh python> | ||
| + | tf.keras.optimizers.Optimizer.__str__=lambda self: f' | ||
| + | </ | ||
| + | |||
| + | Y si volvemos a ejecutar de nuevo el código: | ||
| + | |||
| + | <sxh python> | ||
| + | print(tf.keras.optimizers.Adam(learning_rate=0.001)) | ||
| + | print(tf.keras.optimizers.Adamax(learning_rate=0.00000001)) | ||
| + | </ | ||
| + | |||
| + | <sxh base> | ||
| + | Adam lr=0.001 | ||
| + | Adamax lr=0.00000001 | ||
| + | </ | ||
| + | |||
| + | |||
| + | </ | ||
| + | |||
| + | |||
| + | ===== Elección del optimizador. ===== | ||
| + | Al igual que pasaba con las funciones de activación, | ||
| + | |||
| + | 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 '' | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | 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 | ||
| + | |||
| + | <sxh python> | ||
| + | figure=plt.figure(figsize=(16, | ||
| + | |||
| + | learning_rate=0.1 | ||
| + | epochs=10 | ||
| + | optimizers=[ | ||
| + | [tf.keras.optimizers.SGD(learning_rate=learning_rate)," | ||
| + | [tf.keras.optimizers.Adagrad(learning_rate=learning_rate)," | ||
| + | [tf.keras.optimizers.RMSprop(learning_rate=learning_rate)," | ||
| + | [tf.keras.optimizers.Adadelta(learning_rate=1)," | ||
| + | [tf.keras.optimizers.Adam(learning_rate=learning_rate)," | ||
| + | [tf.keras.optimizers.Adam(learning_rate=learning_rate, | ||
| + | [tf.keras.optimizers.Adamax(learning_rate=learning_rate)," | ||
| + | [tf.keras.optimizers.Nadam(learning_rate=learning_rate)," | ||
| + | ] | ||
| + | |||
| + | for index, | ||
| + | axes = figure.add_subplot(3, | ||
| + | plot_loss_function(axes, | ||
| + | plot_descenso_gradiente(axes, | ||
| + | </ | ||
| + | |||
| + | {{: | ||
| + | |||
| + | |||
| + | Mas información: | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | |||
| + | ===== Backpropagation ===== | ||
| + | El Backpropagation es el algoritmo que optimiza el entrenamiento de la red. Calcular el gradiente (o derivada) de toda la red es muy costoso. Se basa en la idea de que los parámetros de una capa no dependen de la capa anterior. | ||
| + | |||
| + | |||
| + | Si volvemos a ver nuestra red neuronal de ejemplo, podemos calcular los pesos de la neurona 5 sin que influya en como van a ser los pesos de las neuronas 2, 3 y 4. Es decir que empezamos con las neuronas de las capas más hacía la salida y una vez calculados sus pesos , calculamos los parámetros de las capa anterior (más hacia la entrada) , y eso significa ir hacia atrás o // | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | {{ : | ||
| + | |||
| + | Con backpropagation acabamos de ver el orden en el que se calculan los parámetros de cada neurona y a continuación vamos a ver con el descenso de gradiente como calculamos los parámetros de una neurona. | ||
| + | |||
| + | Junto con el backpropagation | ||
| + | |||
| + | En los siguientes videos está explicado perfectamente el backpropagation y la //chain rule//: | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | |||
| + | |||
| + | |||
| + | ===== Hardware entrenamiento ===== | ||
| + | {{ : | ||
| + | |||
| + | Mas información | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | * [[https:// | ||
| + | |||
| + | |||
| + | |||
| + | ===== 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: | ||
| + | |||
| + | $$f(x) = \frac{x}{1 + e^{-x}}$$ | ||
| + | |||
| + | |||
| + | * 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 " | ||
| + | * Averigua en qué valor la '' | ||
| + | |||
| + | ==== Ejercicio 3 ==== | ||
| + | Copia el siguiente código python: | ||
| + | |||
| + | <sxh python> | ||
| + | #Código base de varios ejercicios. | ||
| + | |||
| + | import numpy as np | ||
| + | import matplotlib.pyplot as plt | ||
| + | from sklearn.datasets import load_iris | ||
| + | from matplotlib.ticker import MaxNLocator | ||
| + | from matplotlib.ticker import MultipleLocator | ||
| + | |||
| + | def sigmoid(z): | ||
| + | return 1/(1 + np.exp(-z)) | ||
| + | | ||
| + | def predict_formula(x, | ||
| + | return sigmoid(w*x+b) | ||
| + | |||
| + | |||
| + | def loss_mae(y_true, | ||
| + | error=np.abs(np.subtract(y_true, | ||
| + | mean_error=np.sum(error)/ | ||
| + | | ||
| + | return mean_error | ||
| + | | ||
| + | | ||
| + | | ||
| + | def loss(x, | ||
| + | y_pred=predict_formula(x, | ||
| + | |||
| + | return loss_mae(y_true, | ||
| + | |||
| + | |||
| + | def plot_loss(x, | ||
| + | axes.set_ylim(ymin=0.0, | ||
| + | axes.plot(rango, | ||
| + | axes.scatter(valor_parametro_inicial, | ||
| + | axes.set_xlabel(xlabel) | ||
| + | axes.set_ylabel(' | ||
| + | |||
| + | axes.vlines(x = valor_parametro_inicial, | ||
| + | axes.text(valor_parametro_inicial+0.1, | ||
| + | |||
| + | |||
| + | min_x=rango[np.argmin(perdidas)] | ||
| + | min_y=np.min(perdidas) | ||
| + | axes.vlines(x = min_x, ymin = 0, ymax = min_y, | ||
| + | axes.text(min_x+0.1, | ||
| + | |||
| + | |||
| + | def plot_simple_metrics(axes, | ||
| + | |||
| + | axes.plot(history, | ||
| + | |||
| + | axes.set_xlabel(' | ||
| + | axes.xaxis.set_major_locator(MaxNLocator(integer=True)) | ||
| + | |||
| + | axes.set_ylabel(' | ||
| + | axes.set_ylim(ymin=0, | ||
| + | axes.yaxis.set_major_locator(MultipleLocator(0.1)) | ||
| + | |||
| + | axes.set_title(title) | ||
| + | axes.set_facecolor("# | ||
| + | axes.grid(visible=True, | ||
| + | axes.legend() | ||
| + | |||
| + | |||
| + | def plot_losses(x, | ||
| + | perdida_original=loss(x, | ||
| + | |||
| + | kk=subfigure | ||
| + | subfigure.suptitle(f' | ||
| + | |||
| + | axes_w=subfigure.add_subplot(1, | ||
| + | axes_b=subfigure.add_subplot(1, | ||
| + | |||
| + | rango=np.linspace(-5, | ||
| + | perdidas_w=[] | ||
| + | perdidas_b=[] | ||
| + | |||
| + | for parametro in rango: | ||
| + | perdidas_w.append(loss(x, | ||
| + | perdidas_b.append(loss(x, | ||
| + | |||
| + | |||
| + | plot_loss(x, | ||
| + | plot_loss(x, | ||
| + | |||
| + | return perdida_original | ||
| + | |||
| + | def plot_evolucion_parametros(axes, | ||
| + | axes.plot(ws, | ||
| + | axes.plot(bs, | ||
| + | |||
| + | axes.set_xlabel(' | ||
| + | axes.xaxis.set_major_locator(MaxNLocator(integer=True)) | ||
| + | |||
| + | axes.set_ylabel(' | ||
| + | axes.set_ylim(ymin=-5, | ||
| + | axes.yaxis.set_major_locator(MultipleLocator(1)) | ||
| + | |||
| + | axes.set_title(" | ||
| + | axes.set_facecolor("# | ||
| + | axes.grid(visible=True, | ||
| + | axes.legend() | ||
| + | |||
| + | |||
| + | |||
| + | def plot_parametros(x, | ||
| + | figure=plt.figure(figsize=(8, | ||
| + | figure.suptitle(" | ||
| + | subfigures = figure.subfigures(nrows=len(parametros)+1, | ||
| + | |||
| + | ws=[] | ||
| + | bs=[] | ||
| + | history=[] | ||
| + | for index,(w,b) in enumerate(parametros): | ||
| + | subfigure=subfigures[index] | ||
| + | |||
| + | loss=plot_losses(x, | ||
| + | ws.append(w) | ||
| + | bs.append(b) | ||
| + | history.append(loss) | ||
| + | |||
| + | axes=subfigures[-1].add_subplot(1, | ||
| + | plot_simple_metrics(axes, | ||
| + | axes=subfigures[-1].add_subplot(1, | ||
| + | plot_evolucion_parametros(axes, | ||
| + | |||
| + | def descenso_gradiente(x, | ||
| + | h=0.000003 | ||
| + | | ||
| + | gradiente_w =(loss(x, | ||
| + | gradiente_b =(loss(x, | ||
| + | |||
| + | w=w-learning_rate*gradiente_w | ||
| + | b=b-learning_rate*gradiente_b | ||
| + | return w,b | ||
| + | |||
| + | |||
| + | def plot_descenso_gradiente(x, | ||
| + | figure=plt.figure(figsize=(8, | ||
| + | figure.suptitle(" | ||
| + | |||
| + | subfigures = figure.subfigures(nrows=epochs+1, | ||
| + | w=w_inicial | ||
| + | b=b_inicial | ||
| + | |||
| + | ws=[] | ||
| + | bs=[] | ||
| + | history=[] | ||
| + | for epoch in range(epochs): | ||
| + | if (epochs> | ||
| + | subfigure=subfigures[epoch] | ||
| + | else: | ||
| + | subfigure=subfigures | ||
| + | |||
| + | loss=plot_losses(x, | ||
| + | ws.append(w) | ||
| + | bs.append(b) | ||
| + | history.append(loss) | ||
| + | w, | ||
| + | |||
| + | axes=subfigures[-1].add_subplot(1, | ||
| + | plot_simple_metrics(axes, | ||
| + | axes=subfigures[-1].add_subplot(1, | ||
| + | plot_evolucion_parametros(axes, | ||
| + | </ | ||
| + | |||
| + | |||
| + | 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: | ||
| + | |||
| + | $$ | ||
| + | \large | ||
| + | y=\frac{1}{1 + e^{-( w \cdot x+b )}} | ||
| + | $$ | ||
| + | |||
| + | Lo que debe hacer es entrenar tu manualmente la red , es decir, encontrar los mejores valores de '' | ||
| + | |||
| + | Para ello ejecuta el siguiente código: | ||
| + | <sxh python> | ||
| + | iris=load_iris() | ||
| + | x=iris.data[0: | ||
| + | y_true=iris.target[0: | ||
| + | </ | ||
| + | |||
| + | <sxh python> | ||
| + | parametros=[(-0.3, | ||
| + | plot_parametros(x, | ||
| + | </ | ||
| + | |||
| + | Que muestra la siguiente figura: | ||
| + | |||
| + | {{: | ||
| + | |||
| + | Cada par de valores del array '' | ||
| + | |||
| + | En las 2 primeras gráficas se muestra en rojo que con los valores de '' | ||
| + | Mientras que en verde se muestra los valores que harían mínima cada una de las funciones '' | ||
| + | Y así sucesivamente con el resto de parámetros | ||
| + | |||
| + | En las últimas gráficas se muestra a la izquierda cada uno de los " | ||
| + | |||
| + | Modifica el array '' | ||
| + | |||
| + | ==== Ejercicio 4 ==== | ||
| + | Entrena ahora la red neuronal usando el descenso de gradiente. Para ello ejecuta el siguiente código: | ||
| + | |||
| + | <sxh python> | ||
| + | iris=load_iris() | ||
| + | x=iris.data[0: | ||
| + | y_true=iris.target[0: | ||
| + | |||
| + | w_inicial=-0.3 | ||
| + | b_inicial=0.1 | ||
| + | learning_rate=0.3 | ||
| + | epochs=5 | ||
| + | plot_descenso_gradiente(x, | ||
| + | </ | ||
| + | |||
| + | Modifica los valores de '' | ||
| + | |||
| + | ==== 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: | ||
| + | * É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: | ||
| + | |||
| + | <sxh python> | ||
| + | from sklearn.datasets import load_digits | ||
| + | |||
| + | def get_datos(): | ||
| + | datos=load_digits() | ||
| + | x=datos.data | ||
| + | y=datos.target | ||
| + | label_binarizer = LabelBinarizer() | ||
| + | label_binarizer.fit(range(max(y)+1)) | ||
| + | y = label_binarizer.transform(y) | ||
| + | |||
| + | x_train, x_test, y_train, y_test = train_test_split(x, | ||
| + | |||
| + | return x_train, x_test, y_train, y_test | ||
| + | </ | ||
| + | |||
| + | <note important> | ||
| + | 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: '' | ||
| + | * Función de activación de las capas ocultas: '' | ||
| + | |||
| + | 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/ | ||
| + | |||
| + | |||
| + | ==== 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? | ||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
| + | |||
