Supervisa tu entrenamiento de redes neuronales con TensorBoard

Enrique Blanco    11 noviembre, 2020

TensorBoard es una herramienta que permite monitorizar métricas como la precisión y la función de pérdida durante el entrenamiento de modelos profundos tanto en el conjunto de entrenamiento como en los conjuntos de validación y/o testeo. Esta herramienta también resulta bastante fácil de usar y se puede visualizar e interactuar con ella a través del navegador.

A continuación, resumimos algunos de los aspectos que se van a cubrir en este artículo:

  • Monitorización de escalares durante el entrenamiento;
  • Visualización de imágenes;
  • Comprobación de los pesos de los modelos y los sesgos;
  • Visualización de la arquitectura del modelo;
  • Análisis de la matriz de confusión del modelo.

Con TensorBoard ya instalado, podemos usarlo en un Notebook Jupyter, en Google Colab o en el entorno virtual de vuestro proyecto. Nosotros trabajaremos en un Jupyter Notebook en Colab.

%load_ext tensorboard

Una vez cargada la extensión de TensorBoard, establecemos la ruta del directorio de logs. Aquí es donde el TensorBoard almacenará todos los registros durante el entrenamiento.

En el caso de que se necesite recargar la extensión de TensorBoard, el comando indicado a continuación resultará útil.

%reload_ext tensorboard

Cargando un dataset

Vamos a empezar a explorar TensorBoard con un ejemplo de clasificación de imágenes simple haciendo uso de Deep Learning. Usaremos uno de los datasets más sencillos y conocidos que hay: fashion-MNIST y trabajaremos con Keras.

Se nos ofrecen imágenes de 28×28 de 10 tipos distintos de prendas en color blanco y negro.

import numpy as np
import random
import matplotlib.pyplot as plt
import os
import datetime

import tensorflow as tf
(X_train, y_train), (X_test, y_test) =  tf.keras.datasets.fashion_mnist.load_data()

X_train, X_test = X_train / 255., X_test / 255.
  • Tamaño del training dataset: (60000, 28, 28)
  • Tamaño del test dataset: (10000, 28, 28)
  • Tamaño de cada imagen: (28, 28)
  • Número de clases: 10

Si le echamos un vistazo al dataset, podemos ver que el mismo está formado por un conjunto de prendas:

for i in range(0, 9):
    plt.subplot(331+i) # plot of 3 rows and 3 columns
    plt.imshow(X_train[i], cmap='gray') # gray scale

Cada entero indicado en las etiquetas del dataset corresponde a las siguientes categorías:

“top”, “trouser”, “pullover”, “dress”, “coat”,  “sandal”, “shirt”, “sneaker”, “bag”, “ankle boot”

Figura 1. Muestra de 9 imágenes del Fashin-MNIST dataset.
Figura 1. Muestra de 9 imágenes del Fashin-MNIST dataset.

Necesitaremos hacer un reshape de las muestras de entrenamiento y testeo para poder alimentar un modelo convolucional de clasificación. Veremos a continuación cómo construirlo.

Creando un modelo muy sencillo con Keras

Vamos a crear un modelo convolucional simple que tenga 10 unidades de salida con activación softmax. No es nuestra intención conseguir precisiones muy altas en este artículo.

# Model
model = tf.keras.models.Sequential()
# Add convolution 2D
model.add(tf.keras.layers.Conv2D(32, 
                                 kernel_size=(3, 3),
                                 activation='relu',
                                 input_shape=(28, 28, 1)))
model.add(tf.keras.layers.MaxPooling2D((2, 2)))
model.add(tf.keras.layers.Conv2D(64, 
                                 kernel_size=(3, 3), 
                                 activation='relu'))
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2)))
model.add(tf.keras.layers.Conv2D(128, 
                                 kernel_size=(3, 3), 
                                 activation='relu'))
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(128, activation='relu'))
model.add(tf.keras.layers.Dense(10, activation='softmax'))
model.summary()

Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 26, 26, 32)        320       
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 13, 13, 32)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 11, 11, 64)        18496     
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 5, 5, 64)          0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 3, 3, 128)         73856     
_________________________________________________________________
flatten (Flatten)            (None, 1152)              0         
_________________________________________________________________
dense (Dense)                (None, 128)               147584    
_________________________________________________________________
dense_1 (Dense)              (None, 10)                1290      
=================================================================
Total params: 241,546
Trainable params: 241,546
Non-trainable params: 0

Compilamos el modelo

model.compile(optimizer='Adam', 
   loss='sparse_categorical_crossentropy',
   metrics=['accuracy'])

Cómo usar el callback de TensorBoard

El siguiente paso es definir el callback TensorBoard durante el método de ajuste del modelo.

Esta llamada es responsable de registrar eventos como Histogramas de Activación, Gráficos de Resumen de Métricas, Perfiles y Visualizaciones de Gráficos de Entrenamiento.

from tensorflow.keras.callbacks import TensorBoard

Ahora podemos crear el callback del TensorBoard y especificar el directorio de registro usando log_dir. El callback de TensorBoard también toma otros parámetros:

  • histogram_freq es la frecuencia con la que se calculan los histogramas de activación y peso para las capas del modelo. Poner esto a 0 significa que los histogramas no serán calculados. Para que esto funcione, hay que definir los datos de validación.
  • write_graph determina si el gráfico será visualizado en TensorBoard.
  • write_images cuando se ajusta a True, los pesos del modelo se visualizan como una imagen en TensorBoard.
  • update_freq determina cómo las pérdidas y las métricas se escriben en el TensorBoard. Cuando se establece en un número entero, digamos 100, las pérdidas y métricas se registran cada 100 lotes. Cuando se establece en un bath, las pérdidas y métricas se establecen después de cada lote. Cuando se establece en la época se escriben después de cada época.
  • profile_batch determina qué batches serán perfilados. Por defecto, se perfila el segundo batch. También se puede establecer, por ejemplo de 5 y a 10, para perfilar los lotes de 5 a 10, es decir, profile_batch=’5,10′ . Si se establece profile_batch en 0, se desactiva la creación de perfiles.
  • embeddings_freq la frecuencia con la que se visualizarán las capas de embedding. Ponerlo a cero significa que esos embeddings no se visualizarán.

Podríamos también definir un callback de checkpoint en el caso de que queramos almacenar el aprendizaje de nuestro modelo al final de cada época.

callback = [
            TensorBoard(log_dir=os.path.join("./logs", datetime.datetime.now().strftime("%Y%m%d-%H%M%S")),
                         histogram_freq=1,
                         write_graph=True,
                         write_images=True,
                         update_freq='epoch',
                         embeddings_freq=1)
]

El siguiente paso es ajustar el modelo pasándole el callback.

tf.keras.backend.clear_session()
model.fit(X_train, y_train,
          epochs=15,
          batch_size=32,
          validation_data=(X_test, y_test),
          callbacks=callback)

A partir de la época 5 empezamos a sufrir overfitting, pero lo hacemos con una precisión sobre el conjunto de validación del 90.47%.

Epoch 5/15
1875/1875 [==============================] - 6s 3ms/step - loss: 0.2050 - accuracy: 0.9225 - val_loss: 0.2538 - val_accuracy: 0.9047

En este punto deberíamos haber parado nuestro entrenamiento, de haber implementado un callback de EarlyStopping y otro de ModelCheckpoint.

Visualizando la evolución de los escalares durante el entrenamiento

Ejecutando en nuestro Jupyter:

%tensorboard --logdir logs
Video 1. Explorando escalares, pesos, grafos y distribuciones con TensorBoard.

Escalares TensorBoard

La pestaña Scalars muestra los cambios en la función pérdida y las métricas durante las épocas tanto en entrenamiento como en validación. Se puede usar para rastrear otros valores escalares como la tasa de aprendizaje y la velocidad de entrenamiento.

Imágenes de TensorBoard

También podemos analizar las imágenes que muestran los pesos de la red. La barra deslizante muestra los pesos a lo largo de todas las épocas.

Grafos de TensorBoard

Además, se nos permite comprobar el grafo del modelo, analizando la relación entre capas y la topología global de la red.

Distribuciones e Histogramas en TensorBoard

Podemos consultar las distribuciones e histogramas de los tensores, tanto en pesos como en bias para cada época.

Embedding Projector de TensorBoard Puede utilizar el proyector de TensorBoard para visualizar cualquier representación vectorial. Por ejemplo, Word Embeddings o imágenes.

Sobre esta funcionalidad, que podéis encontrar bajo en menú desplegable Inactivo, ya hablamos en este artículo anterior

Ejecutando TensorBoard en remoto

Cuando se está trabajando con TensorBoard en un servidor remoto, se puede usar SSH tunneling para dirigir un puerto de ese servidor a un puerto en tu máquina local (por ejemplo, el 6006). Esto se podría hacer de la siguiente manera:

ssh -L local_port:localhost:server_port your_username@my_server_ip

De esta forma, podremos ejecutar sin ningún problema en nuestro servidor remoto, suponiendo que nuestro puerto en local es el local_port 8000 y el puerto en remoto expuesto es el server_port 8880:

tensorboard --logdir=logs --port 8880

De forma que, en nuestro navegador, podremos visualizar TensorBoard en:

http://localhost:8000

Representando imágenes de entrenamiento en TensorBoard

Además de visualizar los tensores de imagen, también puede visualizar imágenes de nuestro dataset en TensorBoard. Para ilustrar eso, debe convertir los tensores de Fashion-MNIST en imágenes usando numpy. Después de eso, necesitas usar tf.summary.image para trazar las imágenes en Tensorboard.

Sabemos que las imágeson son de un tamaño de 28 por 28. Debemos especificar que sólo tenemos un canal, pues las imágenes vienen en escala de grises. Luego, usa file_write para escribir las imágenes en TensorBoard.

En este ejemplo, las imágenes en el índice 0 a 20 se escribirán en TensorBoard.

logdir = "./logs/train_data/"

file_writer = tf.summary.create_file_writer(logdir)

with file_writer.as_default():
    images = np.reshape(X_train[0:20], (-1, 28, 28, 1))
    tf.summary.image("20 Clothes", images, max_outputs=25, step=0)

class_names = ["top", "trouser", "pullover", "dress", "coat",
	"sandal", "shirt", "sneaker", "bag", "ankle boot"]

logdir = "logs/plots/"

file_writer = tf.summary.create_file_writer(logdir)

A continuación, creamos una cuadrícula que contenga las imágenes. En este caso, la cuadrícula tendrá 25 prendas.

def image_grid():  
    figure = plt.figure(figsize=(12,8))

    for i in range(25):    
        plt.subplot(5, 5, i + 1)
        plt.xlabel(class_names[y_train[i]])
        plt.xticks([])
        plt.yticks([])
        plt.grid(False)
        plt.imshow(X_train[i].reshape(28,28), cmap=plt.cm.coolwarm)

    return figure

figure = image_grid()
Figura 2. Cuadrícula de muestras del dataset.
Figura 2. Cuadrícula de muestras del dataset.

Ahora convertimos las muestras en una sola imagen para su visualización.

def plot_to_image(figure):    
    buf = io.BytesIO()
    plt.savefig(buf, format='png')
    plt.close(figure)
    buf.seek(0)

    clothes = tf.image.decode_png(buf.getvalue(), channels=4)
    clothes  = tf.expand_dims(digit, 0)

    return clothes
with file_writer.as_default():    
    tf.summary.image("Fashion MNIST", plot_to_image(figure), step=0)
Vídeo 2. Representando muestras del dataset en TensorBoard.

Representando la matriz de confusión en TensorBoard

Se puede generar la matriz de confusión para todas las épocas. Primero, definimos una función que devolverá una figura de Matplotlib que contenga la matriz de confusión.

import itertools 

def plot_confusion_matrix(cm, class_names): 
    figure = plt.figure(figsize=(8, 8)) 
    plt.imshow(cm, interpolation='nearest', cmap=plt.cm.Accent) 
    plt.title("Confusion matrix") 
    plt.colorbar() 
    tick_marks = np.arange(len(class_names)) 
    plt.xticks(tick_marks, class_names, rotation=45) 
    plt.yticks(tick_marks, class_names)

    cm = np.around(cm.astype('float') / cm.sum(axis=1)[:, np.newaxis], decimals=2)  
    threshold = cm.max() / 2. 

    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):   
        color = "white" if cm[i, j] > threshold else "black"   
        plt.text(j, i, cm[i, j], horizontalalignment="center", color=color)  
    
    plt.tight_layout() 
    plt.ylabel('True label') 
    plt.xlabel('Predicted label') 

    return figure
!rm -rf "./logs/*"

logdir = "logs"
file_writer_cm = tf.summary.create_file_writer(logdir)

from sklearn import metrics

class_names = ["top", "trouser", "pullover", "dress", "coat",
	"sandal", "shirt", "sneaker", "bag", "ankle boot"]

def log_confusion_matrix(epoch, logs):
    predictions = model.predict(X_test)
    predictions = np.argmax(predictions, axis=1)

    cm = metrics.confusion_matrix(y_test, predictions)
    figure = plot_confusion_matrix(cm, class_names=class_names)
    cm_image = plot_to_image(figure)
    
    with file_writer_cm.as_default():
        tf.summary.image("Confusion Matrix", cm_image, step=epoch)

A continuación debemos definir un callback LambdaCallback.

LambdaCallback generará la matriz de confusión en cada época.

Dado que el modelo ya se ha entrenado con anterioridad, es recomendable reiniciar la ejecución y asegurarse de que volvemos a hacer un fitting desde el inicio.

callbacks = [
   TensorBoard(log_dir=logdir, 
               histogram_freq=1, 
               write_graph=True,
               write_images=True,
               update_freq='epoch',
               profile_batch=2,
               embeddings_freq=1),
          keras.callbacks.LambdaCallback(on_epoch_end=log_confusion_matrix)
]

tf.keras.backend.clear_session()

model.fit(X_train, y_train,
          epochs=15,
          batch_size=128, 
          validation_data=(X_test, y_test),
          callbacks=callbacks)
Vídeo 3. Matriz de confusión de un modelo de clasificación con TensorBoard.

Como podemos observar, resulta muy útil usar está herramienta como ayuda para mantener un registro de todos las métricas relacionadas con el entrenamiento de nuestras redes neuronales.

En un artículo posterior trataremos la optimización de hiperparámetros de nuestra red. TensorBoard también se puede usar para visualizar la evolución de la precisión y métricas de performance de los modelos con la finalidad de poder elegir los parámetros que se pueden considerar como óptimos para el dataset que tenemos disponible y el problema que queremos resolver.

Para mantenerte al día con LUCA visita nuestra página web,  suscríbete a LUCA Data Speaks o síguenos en TwitterLinkedIn YouTube.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *