Red multicapa con TensorFlow

Construiremos una red neuronal para clasificar dígitos escritos a mano en el conjunto de datos MNIST, que es un problema de clasificación de imágenes.

Importamos los módulos necesarios

import numpy as np 
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.optimizers import Adam
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import confusion_matrix, recall_score
import seaborn as sns
import matplotlib.pyplot as plt

MNIST Dataset

Cargar el conjunto de datos MNIST de dígitos escritos a mano

digits = load_digits()

Exploramos el dataset

digits.keys()

El resultado es:

dict_keys([
    'data', 'target', 'frame',
    'feature_names', 'target_names', 'images', 
    'DESCR'
])

Imprimimos la llave DESCR para obtener la información del conjuto de datos

print(digits.DESCR)

Visualización de una imagen

Es necesario fijar el índice de la imagen que vamos a visualizar

index = 0

Obtener la imagen del dígito y su etiqueta

image = digits.images[index]
label = digits.target[index]

Imprimimos la representación matricial (numérica) de la imagen

print(image)

Mostramos la imagen con Matplotlib

plt.figure(figsize=(3, 3))
plt.imshow(image, cmap=plt.cm.gray)
plt.title(f'Dígito: {label}')
plt.show()

Preprocesamiento de datos

Dividir los datos en conjuntos de entrenamiento y prueba

X_train, X_test, y_train, y_test = train_test_split(
    digits.data, digits.target,
    test_size=0.2, random_state=42
)

Notemos que la longitud del conjunto de entrenamiento y de test son respectivamente:

print(X_train.shape)
print(X_test.shape)

Notamos como se ve el primer elemento de la variable X_train

reshaped_tensor = tf.reshape(X_train[0], shape=(8, 8))
print(reshaped_tensor)

Escalar las imágenes para normalizar los valores de píxeles

scaler = MinMaxScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

Ahora notamos como se ve el primer elemento de la variable X_train después de la normalización

reshaped_tensor = tf.reshape(X_train[0], shape=(8, 8))
# Redondeamos para tener una mejor visualizacion 
reshaped_tensor =  tf.floor(reshaped_tensor * 100) / 100
print(reshaped_tensor)

Red neuronal multicapa

Definir el modelo de red neuronal

modelo = Sequential([
    # Capa de aplanamiento para convertir imágenes en vectores
    Flatten(input_shape=(X_train.shape[1],)),
    # Capa oculta con 128 neuronas y activación ReLU  
    Dense(128, activation='relu'), 
    # Capa de salida con 10 neuronas y activación softmax para clasificación de 10 clases 
    Dense(10, activation='softmax')  
])

Se utiliza Sequential para definir el modelo de red neuronal.

Se agrega una capa de aplanamiento (Flatten) para convertir las imágenes en vectores unidimensionales. Se añaden capas ocultas densamente conectadas (Dense) con activación ReLU y una capa de salida con activación softmax para clasificar los dígitos en 10 clases.

Compilar el modelo

Crear un optimizador Adam con una tasa de aprendizaje del 0.001

# Tasa de aprendizaje deseada
learning_rate = 0.001
adam_optimizer = Adam(learning_rate=learning_rate)
modelo.compile(
    optimizer='adam',
    loss='sparse_categorical_crossentropy',
    metrics=['accuracy']
)

Se compila el modelo utilizando el optimizador Adam y la función de pérdida de entropía cruzada categórica dispersa (sparse_categorical_crossentropy) para la clasificación.

También se puede usar directamente el nombre del optimizador ('adam') en el argumento optimizer de la función compile. En lugar de definir un optimizador personalizado.

La función de pérdida sparse_categorical_crossentropy es adecuada cuando las etiquetas de clase son enteros (como 0, 1, 2, ...) en lugar de codificación one-hot.

Otras funciones de pérdida comunes para clasificación incluyen categorical_crossentropy para etiquetas one-hot y binary_crossentropy para problemas de clasificación binaria.

Entrenar el modelo

modelo.fit(X_train, y_train, epochs=10, batch_size=32, validation_split=0.2)

Se entrena el modelo en los datos de entrenamiento utilizando fit. Se especifica el número de épocas, el tamaño del lote y la proporción de validación para monitorear el rendimiento del modelo durante el entrenamiento.

Evaluación

Evaluar el modelo en el conjunto de prueba

loss, accuracy = modelo.evaluate(X_test, y_test)
print(f'Loss: {loss}, Accuracy: {accuracy}')

Se evalúa el rendimiento del modelo en el conjunto de prueba utilizando evaluate. Se obtienen la pérdida y la precisión del modelo en los datos de prueba.

Calcular la matriz de confusión y la sensibilidad (recall)

y_pred = modelo.predict(X_test)
y_pred_classes = np.argmax(y_pred, axis=1)

conf_matrix = confusion_matrix(y_test, y_pred_classes)
sensitivity = recall_score(y_test, y_pred_classes, average=None)

Se calculan las predicciones del modelo en el conjunto de prueba y se obtienen las clases predichas. Se calcula la matriz de confusión utilizando confusion_matrix. Se calcula la sensibilidad (recall) para cada clase utilizando recall_score.

Visualizar la matriz de confusión y mostrar la sensibilidad para cada clase

# Visualizar la matriz de confusión como una imagen de colores
plt.figure(figsize=(10, 8))
sns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues')
plt.xlabel('Predicted Label')
plt.ylabel('True Label')
plt.title('Confusion Matrix')
plt.show()

# Mostrar la sensibilidad (recall) para cada clase
print('Sensitivity (Recall) for each class:')
for i in range(10):
    print(f'Class {i}: {sensitivity[i]}')

Se visualiza la matriz de confusión como una imagen de colores utilizando sns.heatmap. Se muestra la sensibilidad (recall) para cada clase.

El recall mide la proporción de instancias positivas que fueron correctamente identificadas por el modelo sobre el total de instancias positivas en el conjunto de datos. En otras palabras, el recall indica qué tan efectivo es el modelo para encontrar todas las instancias positivas en comparación con el total de instancias positivas reales en el conjunto de datos.

  • Un recall de 1.0 (100%) significa que el modelo identifica todas las instancias positivas correctamente, es decir, no hay falsos negativos.

  • Un recall de 0.0 (0%) indica que el modelo no identifica ninguna instancia positiva correctamente, es decir, todos los casos positivos son clasificados como negativos (falsos negativos).

En resumen, un recall alto es deseable en problemas donde la detección de instancias positivas es crítica, mientras que un recall bajo puede indicar que el modelo necesita mejorar su capacidad para identificar verdaderos positivos.

Last updated