# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# + [markdown] id="Am8YGvbdWzwp" toc=true
# <h1>Table of Contents<span class="tocSkip"></span></h1>
# <div class="toc"><ul class="toc-item"><li><span><a href="#Segunda-práctica:-Aspectos-prácticos-de-las-redes-neuronales" data-toc-modified-id="Segunda-práctica:-Aspectos-prácticos-de-las-redes-neuronales-1"><span class="toc-item-num">1&nbsp;&nbsp;</span>Segunda práctica: Aspectos prácticos de las redes neuronales</a></span><ul class="toc-item"><li><ul class="toc-item"><li><ul class="toc-item"><li><span><a href="#Obtención-de-los-datos-y-pre-processing" data-toc-modified-id="Obtención-de-los-datos-y-pre-processing-1.0.0.1"><span class="toc-item-num">1.0.0.1&nbsp;&nbsp;</span>Obtención de los datos y pre-processing</a></span></li></ul></li></ul></li><li><span><a href="#Consideraciones-iniciales" data-toc-modified-id="Consideraciones-iniciales-1.1"><span class="toc-item-num">1.1&nbsp;&nbsp;</span>Consideraciones iniciales</a></span><ul class="toc-item"><li><span><a href="#Train-validation-test-split" data-toc-modified-id="Train-validation-test-split-1.1.1"><span class="toc-item-num">1.1.1&nbsp;&nbsp;</span>Train-validation-test split</a></span></li><li><span><a href="#Un-error-común-con-modelos-de-Keras" data-toc-modified-id="Un-error-común-con-modelos-de-Keras-1.1.2"><span class="toc-item-num">1.1.2&nbsp;&nbsp;</span>Un error común con modelos de Keras</a></span></li><li><span><a href="#Análisis-de-resultados" data-toc-modified-id="Análisis-de-resultados-1.1.3"><span class="toc-item-num">1.1.3&nbsp;&nbsp;</span>Análisis de resultados</a></span></li></ul></li><li><span><a href="#1.-Unidades-de-activación" data-toc-modified-id="1.-Unidades-de-activación-1.2"><span class="toc-item-num">1.2&nbsp;&nbsp;</span>1. Unidades de activación</a></span></li><li><span><a href="#2.-Inicialización-de-parámetros" data-toc-modified-id="2.-Inicialización-de-parámetros-1.3"><span class="toc-item-num">1.3&nbsp;&nbsp;</span>2. Inicialización de parámetros</a></span></li><li><span><a href="#3.-Optimizadores" data-toc-modified-id="3.-Optimizadores-1.4"><span class="toc-item-num">1.4&nbsp;&nbsp;</span>3. Optimizadores</a></span></li><li><span><a href="#4.-Regularización-y-red-final-(2.5-puntos)" data-toc-modified-id="4.-Regularización-y-red-final-(2.5-puntos)-1.5"><span class="toc-item-num">1.5&nbsp;&nbsp;</span>4. Regularización y red final <em>(2.5 puntos)</em></a></span><ul class="toc-item"><li><span><a href="#Evaluación-del-modelo-en-datos-de-test" data-toc-modified-id="Evaluación-del-modelo-en-datos-de-test-1.5.1"><span class="toc-item-num">1.5.1&nbsp;&nbsp;</span>Evaluación del modelo en datos de test</a></span></li></ul></li></ul></li></ul></div>

# + [markdown] id="4ip9ysnsTFqJ"
# # Segunda práctica: Aspectos prácticos de las redes neuronales
#
# En esta segunda parte, vamos a continuar desarrollando el problema de Fashion MNIST, con el objetivo de entender los aspectos prácticos del entrenamiento de redes neuronales.
#
# El código utilizado para contestar tiene que quedar claramente reflejado en el Notebook. Puedes crear nuevas cells si así lo deseas para estructurar tu código y sus salidas. A la hora de entregar el notebook, **asegúrate de que los resultados de ejecutar tu código han quedado guardados**. 

# + executionInfo={"elapsed": 2491, "status": "ok", "timestamp": 1620419567422, "user": {"displayName": "Jos\u00e9 <NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="yQ1DOKSRTFqK"
# Puedes añadir todos los imports adicionales que necesites aquí
import keras
from keras.datasets import fashion_mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
import tensorflow as tf

import matplotlib
import matplotlib.pyplot as plt

# + [markdown] id="lBY7qt3mTFqM"
# #### Obtención de los datos y pre-processing

# + executionInfo={"elapsed": 2340, "status": "ok", "timestamp": 1620421061754, "user": {"displayName": "Jos\u00e9 <NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="PImY4g9yTFqM"
(x_train, y_train), (x_test, y_test) = fashion_mnist.load_data()
x_train = x_train / 255.0
x_test = x_test / 255.0


# + [markdown] id="zJyX2Bk8TFqO"
# ## Consideraciones iniciales

# + [markdown] id="jRmct5ogTFqO"
# ### Train-validation-test split
#
# En todos los modelos que entrenemos, vamos a partir los datos de training (x_train) en dos sets: training y validación. De este modo, al final tendremos tres datasets distintos: training, validation, y test. Esta es una estrategia común en el aprendizaje automático, en la que los datos de test (o held-out data) se 
# "esconden" hasta el final. Los datos de validación se utilizan para estimar cómo de bien están funcionando nuestros modelos y para observar si estamos cayendo en overfitting. Esto nos permite cambiar hiperparámetros y probar distintas arquitecturas **sabiendo que no estamos utilizando información del test set para "optimizar" los resultados en éste** (si eligiéramos nuestro mejor modelo en base a los resultados de test, estaríamos "haciendo trampas", ya que se ha utilizado la información contenida en éste para elegir el modelo y las métricas reportadas serían optimistas).
#
# Para utilizar un split training-validation data durante el entrenamiento, podemos partir nosotros mismos los datos o dejar que Keras lo haga. Podéis ver cómo hacer estas particiones en la documentación de *fit*.
#
# **Requisito: En todos los entrenamientos de esta práctica, se requiere utilizar el 20% de los datos en x_train como  conjunto de datos de validación**

# + [markdown] id="BttR0CzHTFqP"
# ### Un error común con modelos de Keras
#
# En esta práctica entrenaremos varios modelos para comparar resultados. Un error común en Keras es no instanciar un nuevo modelo cada vez que hacemos un nuevo entrenamiento. Al hacer
#
# *model = Sequential()*
#
# *model.add(lo que sea)  # Definición del modelo*
#
# *model.fit()*
#
# si queremos entrenar un nuevo modelo o el mismo modelo otra vez, es necesario volver a inicializar el modelo con model = Sequential(). Si olvidamos este paso y volvemos a hacer fit(), el modelo seguirá entrenando por donde se quedó en el último fit().

# + [markdown] id="X7REMbqlTFqP"
# ### Análisis de resultados 
#
# A la hora de escribir las respuestas y los análisis pedidos, es importante presentar las conclusiones de manera adecuada a partir de lo visto en nuestros experimentos. Los Jupyter Notebook son una herramienta imprescindible para *data scientists* e ingenieros de Machine Learning para presentar los resultados, incluyendo soporte para incluir gráficas y elementos visuales. Podéis explicar vuestras observaciones del modo que consideréis adecuado, si bien recomendamos la utilización de gráficas para evaluar los entrenamientos y comparar resultados.
#
# Como ayuda, las siguientes funciones pueden resultar interesantes a la hora de evaluar resultados. Todas ellas utilizan el objeto *history* que podéis obtener como salida del método *fit()* de Keras:
#
# history = model.fit(x_train, y_train, ...)
#
# Por supuesto, podéis modificarlas y utilizarlas como prefiráis para crear vuestros propios informes.

# + executionInfo={"elapsed": 3212, "status": "ok", "timestamp": 1620419568160, "user": {"displayName": "Jos\u00e9 <NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="L5epQBRpTFqP"
def plot_acc(history, title="Model Accuracy"):
    """Imprime una gráfica mostrando la accuracy por epoch obtenida en un entrenamiento"""
    plt.plot(history.history['accuracy'])
    plt.plot(history.history['val_accuracy'])
    plt.title(title)
    plt.ylabel('Accuracy')
    plt.xlabel('Epoch')
    plt.legend(['Train', 'Val'], loc='upper left')
    plt.show()
    
def plot_loss(history, title="Model Loss"):
    """Imprime una gráfica mostrando la pérdida por epoch obtenida en un entrenamiento"""
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.title(title)
    plt.ylabel('Loss')
    plt.xlabel('Epoch')
    plt.legend(['Train', 'Val'], loc='upper right')
    plt.show()
    
def plot_compare_losses(history1, history2, name1="Red 1",
                        name2="Red 2", title="Graph title"):
    """Compara losses de dos entrenamientos con nombres name1 y name2"""
    plt.plot(history1.history['loss'], color="green")
    plt.plot(history1.history['val_loss'], 'r--', color="green")
    plt.plot(history2.history['loss'], color="blue")
    plt.plot(history2.history['val_loss'], 'r--', color="blue")
    plt.title(title)
    plt.ylabel('Loss')
    plt.xlabel('Epoch')
    plt.legend(['Train ' + name1, 'Val ' + name1, 
                'Train ' + name2, 'Val ' + name2],
               loc='upper right')
    plt.show()
    
def plot_compare_accs(history1, history2, name1="Red 1",
                      name2="Red 2", title="Graph title"):
    """Compara accuracies de dos entrenamientos con nombres name1 y name2"""
    plt.plot(history1.history['accuracy'], color="green")
    plt.plot(history1.history['val_accuracy'], 'r--', color="green")
    plt.plot(history2.history['accuracy'], color="blue")
    plt.plot(history2.history['val_accuracy'], 'r--', color="blue")
    plt.title(title)
    plt.ylabel('Accuracy')
    plt.xlabel('Epoch')
    plt.legend(['Train ' + name1, 'Val ' + name1, 
                'Train ' + name2, 'Val ' + name2], 
               loc='lower right')
    plt.show()
    
# Nota: podéis cambiar los números aquí presentes y ejecutar esta línea si queréis cambiar el tamaño
# de las gráficas
# matplotlib.rcParams['figure.figsize'] = [8, 8]



# + [markdown] id="M_yZ9B8gTFqR"
# ## 1. Unidades de activación

# + [markdown] id="MuVNxmXSTFqR"
# En este ejercicio, vamos a evaluar la importancia de utilizar las unidades de activación adecuadas. Las funciones de activación como sigmoid han dejado de utilizarse en favor de otras unidades como ReLU.
#
# **Ejercicio 1 *(2.5 puntos)***: Partiendo de una red sencilla como la desarrollada en el Trabajo 1, escribir un breve análisis comparando la utilización de unidades sigmoid y ReLU (por ejemplo, se pueden comentar aspectos como velocidad de convergencia, métricas obtenidas...). Explicar por qué pueden darse estas diferencias. Opcionalmente, comparar con otras activaciones disponibles en Keras.
#
# *Pista: Usando redes más grandes se hace más sencillo apreciar las diferencias. Es mejor utilizar al menos 3 o 4 capas densas.*

# + executionInfo={"elapsed": 3209, "status": "ok", "timestamp": 1620419568162, "user": {"displayName": "Jos\u00e9 <NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="VxaWAsso1Pj0"
## Definición de parámetros generales

hidden_units_1 = 128
hidden_units_2 = 64
hidden_units_3 = 64
dropout_rate = 0.25
batch_size = 64
epochs = 20
validation_split = 0.20
metrics = ['accuracy']

# + executionInfo={"elapsed": 1372, "status": "ok", "timestamp": 1620421069687, "user": {"displayName": "Jos\u00e9 <NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="Sn2wDK6fstja"
## Obteniendo las etiquetas y convertiendo los valores de las categorías en un vector one-hot
import numpy as np
from keras.utils import to_categorical

labels, counts = np.unique(y_train, return_counts=True)
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)


# + executionInfo={"elapsed": 3198, "status": "ok", "timestamp": 1620419568164, "user": {"displayName": "Jos\u00e9 <NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="hoYUajTuTFqS"
## Método que construye un modelo con:
## - 1 capa Flatten de entrada para aplanar las imágenes de entrenamiento
## - 3 capas densas ocultas con función de activación "activation_function"
## - 1 capa Dropout para borrar pesos de forma aleatoria durante el entrenamiento, para evitar el sobreajuste
## - 1 capa densa de salida con función de activación softmax

def build_model(x_train, labels, activation_function='relu', loss_function='categorical_crossentropy', 
                optimizer = 'adam', initializer = 'glorot_uniform'):

  model = Sequential()
  model.add(Flatten(input_shape=(x_train.shape[1] , x_train.shape[2])))
  model.add(Dense(hidden_units_1, activation=activation_function, kernel_initializer=initializer))
  model.add(Dense(hidden_units_2, activation=activation_function))
  model.add(Dense(hidden_units_3, activation=activation_function))
  model.add(Dropout(dropout_rate))
  model.add(Dense(len(labels), activation='softmax'))

  model.compile(loss=loss_function,
                        optimizer=optimizer,
                        metrics=metrics)
  return model


# + colab={"base_uri": "https://localhost:8080/", "height": 1000} executionInfo={"elapsed": 3192, "status": "ok", "timestamp": 1620419568165, "user": {"displayName": "Jos\u00<NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="CkJc8hnqsZWe" outputId="2dea8295-4500-401d-a9c8-0f96dc95fa8b"
## Obteniendo el modelo con función de activación Sigmoid, función de perdidad Binary Crossentropy y optimizador SGD.
from keras.optimizers import SGD
from keras.utils import plot_model

model_sigmoid = build_model(x_train, labels, 'sigmoid', 'binary_crossentropy', SGD(learning_rate = 0.1, momentum=0.9))

model_sigmoid.summary()
plot_model(model_sigmoid, show_shapes=True)

# + [markdown] id="HvBC9mceiTtT"
# **Comentario:** Se muestra una sola vez la información del modelo ya que la estructura de la red, el número de capas y el número nodos por capa, y el número de parámetros por capa y total es el mismo que en todos los modelos que se construyen en esta pregunta.

# + colab={"base_uri": "https://localhost:8080/"} executionInfo={"elapsed": 48930, "status": "ok", "timestamp": 1620419613916, "user": {"displayName": "<NAME>\u00<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="DO_rVdQkiRAX" outputId="01853d6c-9dd6-4896-8106-ce10711647d5"
## Entrenando modelo
history_sigmoid = model_sigmoid.fit(x_train, y_train,
                  validation_split=validation_split,
                  batch_size=batch_size,
                  epochs=epochs)

# + colab={"base_uri": "https://localhost:8080/", "height": 573} executionInfo={"elapsed": 49914, "status": "ok", "timestamp": 1620419614909, "user": {"displayName": "<NAME>\u0<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="FKS0UZVBqepK" outputId="5681a7ed-68f5-4247-f9eb-3033181a7c3b"
## Dibujar gráfica de los valores de Accuracy y Loss del modelo entrenado con Sigmoid

plot_acc(history_sigmoid, "Accuracy en el entrenamiento con Sigmoid")
plot_loss(history_sigmoid, "Loss en el entrenamiento con Sigmoid")

# + colab={"base_uri": "https://localhost:8080/"} executionInfo={"elapsed": 97425, "status": "ok", "timestamp": 1620419662432, "user": {"displayName": "Jos\u00e<NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="uMPgt9GtvWte" outputId="426242aa-c432-4b91-8050-0036a7e8cb54"
## Obteniendo el modelo con función de activación ReLU, función de perdidad Categorical Crossentropy y optimizador Adam.
model_relu = build_model(x_train, labels)

## Entrenando modelo
history_relu = model_relu.fit(x_train, y_train,
                  validation_split=validation_split,
                  batch_size=batch_size,
                  epochs=epochs)

# + colab={"base_uri": "https://localhost:8080/", "height": 573} executionInfo={"elapsed": 97416, "status": "ok", "timestamp": 1620419662434, "user": {"displayName": "Jos\u00e9 <NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="LwtK5079wKUF" outputId="99eaeafb-2876-49da-acaa-0bc7941340b2"
## Dibujar gráfica de los valores de Accuracy y Loss del modelo entrenado con ReLU

plot_acc(history_relu, "Accuracy en el entrenamiento con ReLU")
plot_loss(history_relu, "Loss en el entrenamiento con ReLU")

# + [markdown] id="-2w5MQ2Y6WwE"
# **Respuesta:** En este ejercicio he entrenado con los mismos parámetros globales (números de nodos por cada capa, tasa de dropout para el entrenamiento, entre otros) a dos modelos, donde en uno de ellos la función de activación es *Sigmoid*, y el otro modelo su función de activación es *ReLU*. Además, en cada modelo uso sus **respectivos optimizadores y funciones de perdidas** más adecuados ajustando un poco sus parámetros.
#
# Como resultado del entrenamiento, se observa que el modelo con la función de activación **ReLU tiene un resultado más aceptable si nos referimos al accuracy**, sin embargo, el valor loss respecto de los datos de validación es alto. Una característica negativa vista de este modelo es que parece que sufre de **overfitting** porque el accuracy y loss de los datos de validación se alejan respecto a sus análogos de los datos de entrenamiento, es decir, *esta memorizando muy bien los registros de entrenamiento, pero no es capaz de predecir bien nuevos registros no vistos antes*. Otra observación que se destaca es que para la cantidad de epocas definidas (20) se puede apreciar la **evolución del entrenamiento** llegando a valores casi estables en las últimas epocas.
#
# Respecto al modelo que fue entrenado con la función de activación *Sigmoid*, he tenido que ajustar los parámetros de la función de perdida, porque **no estaba encontrando los mínimos locales**. Con los nuevos parámetros de la tasa de aprendizaje y el momentum, se obtiene un resultado bueno de accuracy pero peor al de ReLU, si embargo, la ventaja se resalta en **los valores de loss que mejoran sustancialmente a los de ReLU**, 0.069 (Sigmoid) vs 0.1903 (ReLU). En este modelo se puede ver también que *no se sufre de overfitting*, es decir, el modelo predice con mucha similitud los datos de entrenamiento y los de validación.

# + colab={"base_uri": "https://localhost:8080/"} executionInfo={"elapsed": 144680, "status": "ok", "timestamp": 1620419709711, "user": {"displayName": "Jos\u00<NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="SeyuAx8SBJVk" outputId="1ecfd772-9d93-43af-c399-9156af9e609a"
## Obteniendo el modelo con función de activación Tangente Hiperbólica, función de perdidad MSE y optimizador Adam.
model_tanh = build_model(x_train, labels, 'tanh', 'mean_squared_error')

## Entrenando modelo
history_tanh = model_tanh.fit(x_train, y_train,
                  validation_split=validation_split,
                  batch_size=batch_size,
                  epochs=epochs)

# + colab={"base_uri": "https://localhost:8080/", "height": 573} executionInfo={"elapsed": 145390, "status": "ok", "timestamp": 1620419710430, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="VXcy1tWpDb1p" outputId="015c5816-2be5-4ecc-8603-85bbd83d455a"
## Dibujar gráfica de los valores de Accuracy y Loss del modelo entrenado con Tanh

plot_compare_losses(history_relu, history_tanh, "ReLU", "Tanh", "Comparación del valor Loss entre ReLU y Tanh")
plot_compare_accs(history_relu, history_tanh, "ReLU", "Tanh", "Comparación del valor Accuracy entre ReLU y Tanh")

# + [markdown] id="a5D8isxTEXn6"
# **Respuesta:** En este nuevo modelo entrenado con la función de activación Tanh vemos que se produce *una tendencia del valor accuracy similar al de ReLU*, sin embargo **mejora considerablemente el valor loss** tanto en datos de entrenamiento, como en datos de validación. Este modelo también tiene tendencia a sufrir de **overfitting** al igual que el modelo de ReLU.

# + colab={"base_uri": "https://localhost:8080/"} executionInfo={"elapsed": 197858, "status": "ok", "timestamp": 1620419762909, "user": {"displayName": "<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="kkthj6y7H662" outputId="3f2fa298-93e1-48d5-cafb-d271d1f6d539"
## Obteniendo el modelo con función de activación Softplus, función de perdidad Categorical Crossentropy y optimizador Adam.
model_softplus = build_model(x_train, labels, 'softplus', 'categorical_crossentropy')

## Entrenando modelo
history_softplus = model_softplus.fit(x_train, y_train,
                  validation_split=validation_split,
                  batch_size=batch_size,
                  epochs=epochs)

# + colab={"base_uri": "https://localhost:8080/", "height": 573} executionInfo={"elapsed": 197850, "status": "ok", "timestamp": 1620419762911, "user": {"displayName": "<NAME>\u0<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="WZhaw_SnITvZ" outputId="59fabaf6-1376-4e02-80c2-61e8d2170416"
## Dibujar gráfica de los valores de Accuracy y Loss del modelo entrenado con Softplus

plot_compare_losses(history_relu, history_softplus, "ReLU", "Softplus", "Comparación del valor Loss entre ReLU y Softplus")
plot_compare_accs(history_relu, history_softplus, "ReLU", "Softplus", "Comparación del valor Accuracy entre ReLU y Softplus")

# + [markdown] id="nDaBc1GmJ8-W"
# **Respuesta:** Usar la función **softplus** (una acercamiento a la funcioón ReLU con una curva suave) **no mejora el resultado de ReLU**, e incluso reproduce casi exactamente sus resultados, pero si se observa más suavidad en la gráfica, es decir, tiene menor varianza respecto a ReLU.

# + [markdown] id="pu6RbUFKTFqT"
# ## 2. Inicialización de parámetros

# + [markdown] id="Abmm05UPTFqU"
# En este ejercicio, vamos a evaluar la importancia de una correcta inicialización de parámetros en una red neuronal.
#
# **Ejercicio 2 *(2.5 puntos)***: Partiendo de una red similar a la del ejercicio anterior (usando ya ReLUs), comentar las diferencias que se aprecian en el entrenamiento al utilizar distintas estrategias de inicialización de parámetros. Para ello, inicializar todas las capas con las siguientes estrategias, disponibles en Keras, y analizar sus diferencias:
#
# * Inicialización con ceros.
# * Inicialización con una variable aleatoria normal.
# * Inicialización con los valores por defecto de Keras para una capa Dense (estrategia *glorot uniform*)

# + colab={"base_uri": "https://localhost:8080/"} executionInfo={"elapsed": 243681, "status": "ok", "timestamp": 1620419808754, "user": {"displayName": "<NAME>\u0<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="qcMt7pSkTFqU" outputId="20b896bc-47c1-4341-973e-daffdec142e4"
from tensorflow.keras import initializers

## Obteniendo el modelo con función de activación ReLU, función de perdidad Categorical Crossentropy, optimizador Adam e inicializador de ceros.
model_relu_zeros = build_model(x_train, labels, initializer=initializers.Zeros())

## Entrenando modelo
history_relu_zeros = model_relu_zeros.fit(x_train, y_train, 
                  validation_split=validation_split,
                  batch_size=batch_size,
                  epochs=epochs)

# + colab={"base_uri": "https://localhost:8080/"} executionInfo={"elapsed": 292349, "status": "ok", "timestamp": 1620419857434, "user": {"displayName": "<NAME>\u00<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="SHLHHokGWy0h" outputId="8c64d20f-e79c-4281-e628-957e81d0b9bd"
## Obteniendo el modelo con función de activación ReLU, función de perdidad Categorical Crossentropy, optimizador Adam e inicializador de ceros.
model_relu_random = build_model(x_train, labels, initializer=initializers.RandomNormal(mean=0.0, stddev=1.0))

## Entrenando modelo
history_relu_random = model_relu_random.fit(x_train, y_train,
                  validation_split=validation_split,
                  batch_size=batch_size,
                  epochs=epochs)

# + colab={"base_uri": "https://localhost:8080/", "height": 573} executionInfo={"elapsed": 292343, "status": "ok", "timestamp": 1620419857438, "user": {"displayName": "Jos\u00e<NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="YXRFBbwJWyn5" outputId="4d5d5de9-d56c-4e20-e89c-56a634b223c8"
## Dibujar gráfica de los valores de Accuracy y Loss para los modelos entrenado con ReLU, el verde con inicializador "glorot_uniform" y el azul con inicializador de pesos en ceros.
plot_compare_losses(history_relu, history_relu_zeros, "ReLU GorotUniform", "ReLU RandomNormal", "Comparación del valor Loss entre inicialización glorot_uniform e inicialización con ceros")
plot_compare_accs(history_relu, history_relu_zeros, "ReLU GorotUniform", "ReLU RandomNormal", "Comparación del valor Accuracy entre inicialización glorot_uniform e inicialización con ceros")

# + colab={"base_uri": "https://localhost:8080/", "height": 573} executionInfo={"elapsed": 293787, "status": "ok", "timestamp": 1620419858895, "user": {"displayName": "<NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="7ntgY7EDgjJv" outputId="77d0a4e9-9246-4d17-b865-c2cce787c306"
## Dibujar gráfica de los valores de Accuracy y Loss para los modelos entrenado con ReLU, el verde con inicializador "glorot_uniform" y el azul con inicializador de la distribución normal.
plot_compare_losses(history_relu, history_relu_random, "ReLU GorotUniform", "ReLU RandomNormal", "Comparación del valor Loss entre inicialización glorot_uniform e inicialización con pesos aleatorios de la distribución normal")
plot_compare_accs(history_relu, history_relu_random, "ReLU GorotUniform", "ReLU RandomNormal", "Comparación del valor Accuracy entre inicialización glorot_uniform e inicialización con pesos aleatorios de la distribución normal")

# + [markdown] id="7yylUfcgjp5j"
# **Respuesta**: 

# + [markdown] id="NqIAyVWrTFqV"
# ## 3. Optimizadores

# + [markdown] id="lcYj29hYTFqW"
# **Ejercicio 3 *(2.5 puntos)***: Partiendo de una red similar a la del ejercicio anterior (utilizando la mejor estrategia de inicialización observada), comparar y analizar las diferencias que se observan  al entrenar con varios de los optimizadores vistos en clase, incluyendo SGD como optimizador básico (se puede explorar el espacio de hiperparámetros de cada optimizador, aunque para optimizadores más avanzados del estilo de adam y RMSprop es buena idea dejar los valores por defecto provistos por Keras).

# + colab={"base_uri": "https://localhost:8080/"} executionInfo={"elapsed": 336873, "status": "ok", "timestamp": 1620419901993, "user": {"displayName": "Jos\u00e<NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="0fWDiqXvTFqW" outputId="e369241d-6b76-4fbc-d63e-ce94c7ae81cb"
## Obteniendo el modelo con función de activación ReLU, función de perdidad Categorical Crossentropy y optimizador SGD.
model_relu_sgd = build_model(x_train, labels, optimizer=SGD(learning_rate = 0.01, momentum=0.9))

## Entrenando modelo
history_relu_sgd = model_relu_sgd.fit(x_train, y_train,
                  validation_split=validation_split,
                  batch_size=batch_size,
                  epochs=epochs)

# + colab={"base_uri": "https://localhost:8080/", "height": 573} executionInfo={"elapsed": 337598, "status": "ok", "timestamp": 1620419902730, "user": {"displayName": "Jos\u00e9 <NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="sTQ9ndzscpch" outputId="3ff447c6-8e7f-4b6c-c20d-80f2bf7c77b3"
## Dibujar gráfica de los valores de Accuracy y Loss para los modelos entrenado con ReLU, el verde con inicializador "glorot_uniform" y el azul con inicializador de la distribución normal.
plot_compare_losses(history_relu, history_relu_sgd, "ReLU Adam", "ReLU SGD", "Comparación del valor Loss entre optimizador Adam y SGD")
plot_compare_accs(history_relu, history_relu_sgd, "ReLU Adam", "ReLU SGD", "Comparación del valor Accuracy entre optimizador Adam y SGD")

# + colab={"base_uri": "https://localhost:8080/"} executionInfo={"elapsed": 393404, "status": "ok", "timestamp": 1620419958553, "user": {"displayName": "Jos\u00e9 <NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="h9Id6v8MlFfB" outputId="455a6de0-6b53-43b7-851a-6ae38b5fddb2"
from keras.optimizers import RMSprop

## Obteniendo el modelo con función de activación ReLU, función de perdidad Categorical Crossentropy y optimizador RMSprop.
model_relu_rmsprop = build_model(x_train, labels, optimizer=RMSprop())

## Entrenando modelo
history_relu_rmsprop = model_relu_rmsprop.fit(x_train, y_train,
                  validation_split=validation_split,
                  batch_size=batch_size,
                  epochs=epochs)

# + colab={"base_uri": "https://localhost:8080/", "height": 573} executionInfo={"elapsed": 393983, "status": "ok", "timestamp": 1620419959142, "user": {"displayName": "Jos\u00e9 <NAME>0<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="IQ2vGO5ilFWi" outputId="2c580cfd-a98e-4d8c-bbba-48c6d8a11ae4"
## Dibujar gráfica de los valores de Accuracy y Loss para los modelos entrenado con ReLU, el verde con optimizador "Adam" y el azul con RMSprop.
plot_compare_losses(history_relu, history_relu_rmsprop, "ReLU Adam", "ReLU RMSpro", "Comparación del valor Loss entre optimizador Adam y RMSpro")
plot_compare_accs(history_relu, history_relu_rmsprop, "ReLU Adam", "ReLU RMSpro", "Comparación del valor Accuracy entre optimizador Adam y RMSpro")

# + colab={"base_uri": "https://localhost:8080/"} executionInfo={"elapsed": 461134, "status": "ok", "timestamp": 1620420026305, "user": {"displayName": "Jos\u00<NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="7wm_uPVLmzai" outputId="aac07373-7f6b-4c14-a4cc-044841a2b9ac"
from keras.optimizers import Nadam

## Obteniendo el modelo con función de activación ReLU, función de perdidad Categorical Crossentropy y optimizador Nadam.
model_relu_nadam = build_model(x_train, labels, optimizer=Nadam())

## Entrenando modelo
history_relu_nadam = model_relu_nadam.fit(x_train, y_train,
                  validation_split=validation_split,
                  batch_size=batch_size,
                  epochs=epochs)

# + colab={"base_uri": "https://localhost:8080/", "height": 573} executionInfo={"elapsed": 461779, "status": "ok", "timestamp": 1620420026959, "user": {"displayName": "Jos\u00e9 <NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="d6C60DZomzU5" outputId="9edb48cc-b890-4c8d-86e4-3a6e09543bf3"
## Dibujar gráfica de los valores de Accuracy y Loss para los modelos entrenado con ReLU, el verde con optimizador Adam y el azul con Nadam.
plot_compare_losses(history_relu, history_relu_nadam, "ReLU Adam", "ReLU Nadam", "Comparación del valor Loss entre optimizador Adam y Nadam")
plot_compare_accs(history_relu, history_relu_nadam, "ReLU Adam", "ReLU Nadam", "Comparación del valor Accuracy entre optimizador Adam y Nadam")


# + [markdown] id="aVVIzQy7tDU1"
# **Respuesta**: He probado tres tipos de optimizadores distintos al usado en el modelo construido con la función de activación ReLU de la pregunta anterior. Comparando los resultados para cada optimizador, se observa que el mejor resultado ha sido con el optimizador **NADAM**, que mejora ligeramente al optimizador ADAM.
#
# Se podría decir que el *peor otimizador* para este modelo y grupo de datos de entrenamiento fue el RMSprop. Para el caso del optimizador SGD, se ha tenido que **buscar nuevos valores para sus parámetros** que mejora el resultado que genera los parámetros por defecto.

# + [markdown] id="BkfTFoJOTFqZ"
# ## 4. Regularización y red final *(2.5 puntos)*

# + [markdown] id="f6CQhK7ZTFqZ"
# **Ejercicio 4.1**: Entrenar una red final que sea capaz de obtener una accuracy en el validation set cercana al 90%. Para ello, combinar todo lo aprendido anteriormente y utilizar técnicas de regularización para evitar overfitting. Algunos de los elementos que pueden tenerse en cuenta son los siguientes.
#
# * Número de capas y neuronas por capa
# * Optimizadores y sus parámetros
# * Batch size
# * Unidades de activación
# * Uso de capas dropout, regularización L2, regularización L1...
# * Early stopping (se puede aplicar como un callback de Keras, o se puede ver un poco "a ojo" cuándo el modelo empieza a caer en overfitting y seleccionar el número de epochs necesarias)
# * Batch normalization
#
# Si los modelos entrenados anteriormente ya se acercaban al valor requerido de accuracy, probar distintas estrategias igualmente y comentar los resultados.
#
# Explicar brevemente la estrategia seguida y los modelos probados para obtener el modelo final, que debe verse entrenado en este Notebook. No es necesario guardar el entrenamiento de todos los modelos que se han probado, es suficiente con explicar cómo se ha llegado al modelo final.

# + executionInfo={"elapsed": 2102, "status": "ok", "timestamp": 1620421344472, "user": {"displayName": "Jos\u00e9 <NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="AUJ5AtunTFqa"
## Método que construye un modelo final de la práctica con:
## - 1 capa Flatten de entrada para aplanar las imágenes de entrenamiento
## - 3 capas densas ocultas con función de activación "relu"
## - 1 capa Dropout para borrar pesos de forma aleatoria durante el entrenamiento, para evitar el sobreajuste
## - 1 capa densa de salida con función de activación softmax

def build_final_model(x_train, labels):

  model = Sequential()
  model.add(Flatten(input_shape=(x_train.shape[1] , x_train.shape[2])))
  model.add(Dense(128, activation='softplus'))
  model.add(Dropout(0.3))
  model.add(Dense(256, activation='softplus'))
  model.add(Dropout(0.3))
  model.add(Dense(256, activation='softplus'))
  model.add(Dropout(0.3))
  model.add(Dense(128, activation='softplus'))
  model.add(Dropout(0.3))
  model.add(Dense(len(labels), activation='softmax'))

  model.summary()
  plot_model(model, show_shapes=True)

  model.compile(loss='categorical_crossentropy',
                        optimizer='nadam',
                        metrics=['accuracy'])
  return model


# + colab={"base_uri": "https://localhost:8080/"} executionInfo={"elapsed": 438977, "status": "ok", "timestamp": 1620421786799, "user": {"displayName": "Jos\u00e9 <NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="u0o3wgGHwwnm" outputId="17eed6f9-50a7-47a2-8c84-cb21ac07649a"
from keras.callbacks import EarlyStopping

## Obteniendo el modelo final.
final_model = build_final_model(x_train, labels)

## Definiendo la parada del entrenamiento
callback = [EarlyStopping(monitor='val_accuracy', patience=10)]

## Entrenando modelo
history_final = final_model.fit(x_train, y_train,
                  validation_split=validation_split,
                  batch_size=64,
                  epochs=80,
                  callbacks=callback)

# + colab={"base_uri": "https://localhost:8080/", "height": 573} executionInfo={"elapsed": 1717, "status": "ok", "timestamp": 1620421115827, "user": {"displayName": "Jos\u00<NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="F8q48VxBHRJL" outputId="c51dfe4f-3305-4581-fe0d-bda9fbf82f06"
## Dibujar gráfica de los valores de Accuracy y Loss del modelo entrenado con Sigmoid

plot_acc(history_final, "Accuracy en el entrenamiento del modelo final")
plot_loss(history_final, "Loss en el entrenamiento del modelo final")

# + [markdown] id="XJa-HeqFXkIg"
# **Respuesta**: Para mejorar los resultados del entrenamiento, concretamente la predicción sobre los valores de validación, se han aplicado algunas técnicas que mejoran la predicción y la reducen algunos problemas como el overfitting.
#
# Entre las principales técnicas o herramientas usadas tenemos, selección de número de capas ocultas y nodos por capa, función de activación, función de perdida, optimizador, incorporación de capas de Dropout, etc.
#
# * Se ha ajustado el número de capas y el número de nodos para mejorar el valor de **val_accuracy**.
# * Se inicio con una función de activación ReLU, pero despúes se obtuvo mejores resultados con **Softplus**.
# * Se mantuvo la función de perdida **categorical_crossentropy** porque el resto no mejoraba el resultado.
# * A partir del resultado de la pregunta anterior se ha decidido usar el optimizador **NADAM**.
# * Dado que se producía overffing con pocas epicas, se ha añadido capas **Dropout** con una tasa de 0.3.
# * También se ha creado un callback de tipo **EarlyStopping** con un valor de paciencia de *10* para detener el entramiento cuando ya no mejore el valor de *val_accuracy*. En el último entrenamiento no se ha detenido antes de las 80 epocas.
# * Finalmente se han ajustado los valores de **batch_size** y **epochs** para conseguir llegar al *90% de val_accuracy*.
#
# Después de aplicar todos los pasos descritos previamente, se ha logrado conseguir pocas iteraciones que superen el *90% de val_accuracy* pero la mayoría estuvieron en **89%**.

# + [markdown] id="B5LcQgwUTFqb"
# ### Evaluación del modelo en datos de test

# + [markdown] id="4ldy0NmtTFqb"
# Una vez elegido el que creemos que es nuestro mejor modelo a partir de la estimación que hemos visto en los datos de validación, es hora de utilizar los datos de test para ver cómo se comporta nuestro modelo ante nuevos datos. Si hemos hecho bien las cosas, este número debería ser parecido al valor de nuestra estimación vista en los datos de validación.
#
# **Pregunta 4.2**. Utilizando nuestro mejor modelo, obtener la accuracy resultante en el dataset de test. Comentar este resultado.

# + colab={"base_uri": "https://localhost:8080/"} executionInfo={"elapsed": 2636, "status": "ok", "timestamp": 1620421091465, "user": {"displayName": "Jos\u00e9 <NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="1AchhaHqTFqc" outputId="f42f4894-c0b2-4653-aeb5-6059bc6b1031"
loss, acc = final_model.evaluate(x_test, y_test, batch_size=64)
print("\nTest loss: %.1f%%" % (100.0 * loss))
print("Test accuracy: %.1f%%" % (100.0 * acc))

# + colab={"base_uri": "https://localhost:8080/"} executionInfo={"elapsed": 767400, "status": "ok", "timestamp": 1620420332648, "user": {"displayName": "Jos\u0<NAME>\u00e1<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="qM4nL1Fc_XJh" outputId="6320256c-1e29-4dc3-fcba-22052805d010"
result = final_model.predict(x_test)

from sklearn.metrics import confusion_matrix
confusion_matrix(np.argmax(y_test, axis=1), np.argmax(result, axis=1))

# + colab={"base_uri": "https://localhost:8080/"} executionInfo={"elapsed": 767388, "status": "ok", "timestamp": 1620420332649, "user": {"displayName": "<NAME>\u0<NAME>", "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14GhaYypRXRBkgZWSMj9q5F9NWoJLp1_U38NusgL4Mw=s64", "userId": "16264495400181037765"}, "user_tz": -120} id="fT7iq3kM_d0V" outputId="ec34bd14-6e67-4c28-bd8c-4eb26fd83f6c"
from sklearn.metrics import classification_report
print(classification_report(np.argmax(y_test, axis=1), np.argmax(result, axis=1)))

# + [markdown] id="8ZkUxA8Fi55O"
# **Respuesta**: El valor del accuracy conseguido con los datos de pruebas es del **89.7%**, muy similar a los valores de val_accuracy conseguidos en el entrenamiento.
# * Las prendas que **mejor predijo** el modelo a partir de los datos de prueba son el **Trouser, Ankle boot y Sandal**, ya que tienen una forma diferentes al resto de prendas.
# * En el tipo de prenda en que **más problemas tuvo al predecir fue Shirt**, porque es el que más tiene parecido con el resto de prendas, por ejemplo *T-Shirt, Pullover, Dress y Coat*.

StarcoderdataJupyterScriptsDedupFiltered