Implementación de la red neuronal VGG-16 para clasificación de imágenes de gatos y perros con TensorFlow

Este artículo detalla la construcción y entrenamiento de una red neuronal convolucional VGG-16 para la clasificación binaria de imágenes de gatos y perros. Se utiliza TensorFlow y Keras para el desarrollo, y el conjunto de datos está compuesto por 1700 imágenes por clase.

Configuración del entorno y preparación de datos

El entorno de desarrollo se basa en Python 3.12, con TensorFlow 2.21.0 y Jupyter Notebook. Inicialmente, se importan las bibliotecas necesarias y se configura el uso de la GPU para optimizar el rendimiento.

import tensorflow as tf
import matplotlib.pyplot as plt
import os
import pathlib
import warnings
from tqdm import tqdm
import numpy as np
from tensorflow.keras import layers, models
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dense, Flatten, Dropout

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
warnings.filterwarnings('ignore')

dispositivos_gpu = tf.config.list_physical_devices("GPU")
if dispositivos_gpu:
    tf.config.experimental.set_memory_growth(dispositivos_gpu[0], True)
    tf.config.set_visible_devices([dispositivos_gpu[0]], "GPU")

Se define la ruta al directorio de datos y se calcula el número total de imágenes usando pathlib.

ruta_datos = pathlib.Path("./Datos/conjunto_imagenes")
total_imagenes = len(list(ruta_datos.glob('*/*')))
print(f"Número total de imágenes: {total_imagenes}")

Preprocesamiento y división del conjunto de datos

Las imágenes se redimensionan a 224x224 píxeles, estándar para VGG-16, y se normalizan al rango [0, 1]. El conjunto de datos se divide en entrenamiento (80%) y validación (20%) con un tamaño de lote de 8.

altura_imagen = 224
ancho_imagen = 224
tamano_lote = 8

conjunto_entrenamiento = tf.keras.preprocessing.image_dataset_from_directory(
    ruta_datos,
    validation_split=0.2,
    subset="training",
    seed=456,
    image_size=(altura_imagen, ancho_imagen),
    batch_size=tamano_lote
)

conjunto_validacion = tf.keras.preprocessing.image_dataset_from_directory(
    ruta_datos,
    validation_split=0.2,
    subset="validation",
    seed=456,
    image_size=(altura_imagen, ancho_imagen),
    batch_size=tamano_lote
)

nombres_clases = conjunto_entrenamiento.class_names
print(f"Clases: {nombres_clases}")

Se aplica una función de preprocesamiento para normalizar los píxeles y se optimiza el rendimiento mediante el almacenamiento en caché y la precarga.

def normalizar_imagen(imagen, etiqueta):
    return imagen / 255.0, etiqueta

conjunto_entrenamiento = conjunto_entrenamiento.map(normalizar_imagen, num_parallel_calls=tf.data.AUTOTUNE)
conjunto_validacion = conjunto_validacion.map(normalizar_imagen, num_parallel_calls=tf.data.AUTOTUNE)

conjunto_entrenamiento = conjunto_entrenamiento.cache().shuffle(1000).prefetch(buffer_size=tf.data.AUTOTUNE)
conjunto_validacion = conjunto_validacion.cache().prefetch(buffer_size=tf.data.AUTOTUNE)

plt.figure(figsize=(12, 8))
for imagenes, etiquetas in conjunto_entrenamiento.take(1):
    for idx in range(8):
        ax = plt.subplot(2, 4, idx + 1)
        plt.imshow(imagenes[idx].numpy())
        plt.title(nombres_clases[etiquetas[idx]])
        plt.axis("off")

Construcción del modelo VGG-16

Se define la arquitectura VGG-16 utilizando la API funcional de Keras. La red consta de bloques convolucionales seguidos de capas densas para la clasificación.

def construir_vgg16(num_clases, forma_entrada):
    entrada = tf.keras.Input(shape=forma_entrada)
    # Primer bloque
    x = Conv2D(64, (3, 3), activation='relu', padding='same', name='capa_conv1_1')(entrada)
    x = Conv2D(64, (3, 3), activation='relu', padding='same', name='capa_conv1_2')(x)
    x = MaxPooling2D((2, 2), strides=(2, 2), name='pool1')(x)
    # Segundo bloque
    x = Conv2D(128, (3, 3), activation='relu', padding='same', name='capa_conv2_1')(x)
    x = Conv2D(128, (3, 3), activation='relu', padding='same', name='capa_conv2_2')(x)
    x = MaxPooling2D((2, 2), strides=(2, 2), name='pool2')(x)
    # Tercer bloque
    x = Conv2D(256, (3, 3), activation='relu', padding='same', name='capa_conv3_1')(x)
    x = Conv2D(256, (3, 3), activation='relu', padding='same', name='capa_conv3_2')(x)
    x = Conv2D(256, (3, 3), activation='relu', padding='same', name='capa_conv3_3')(x)
    x = MaxPooling2D((2, 2), strides=(2, 2), name='pool3')(x)
    # Cuarto bloque
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='capa_conv4_1')(x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='capa_conv4_2')(x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='capa_conv4_3')(x)
    x = MaxPooling2D((2, 2), strides=(2, 2), name='pool4')(x)
    # Quinto bloque
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='capa_conv5_1')(x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='capa_conv5_2')(x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='capa_conv5_3')(x)
    x = MaxPooling2D((2, 2), strides=(2, 2), name='pool5')(x)
    # Capas totalmente conectadas
    x = Flatten()(x)
    x = Dense(4096, activation='relu', name='fc1')(x)
    x = Dense(4096, activation='relu', name='fc2')(x)
    salida = Dense(num_clases, activation='softmax', name='predicciones')(x)
    modelo = models.Model(entrada, salida)
    return modelo

modelo_vgg = construir_vgg16(len(nombres_clases), (altura_imagen, ancho_imagen, 3))
modelo_vgg.summary()

Entrenamiento y evaluación del modelo

Se compila el modelo con el optimizador Adam y la función de pérdida de entropía cruzada. El entrenamiento se realiza durante 10 épocas con una tasa de aprendizaje que decae exponencialmente.

modelo_vgg.compile(
    optimizer='adam',
    loss='sparse_categorical_crossentropy',
    metrics=['accuracy']
)

epocas = 10
tasa_aprendizaje_inicial = 1e-4

historial_perdida_entrenamiento = []
historial_exactitud_entrenamiento = []
historial_perdida_validacion = []
historial_exactitud_validacion = []

for epoca in range(epocas):
    total_entrenamiento = len(conjunto_entrenamiento)
    total_validacion = len(conjunto_validacion)
    
    tasa_aprendizaje = tasa_aprendizaje_inicial * (0.92 ** epoca)
    modelo_vgg.optimizer.learning_rate.assign(tasa_aprendizaje)
    
    with tqdm(total=total_entrenamiento, desc=f'Época {epoca + 1}/{epocas}', mininterval=1, ncols=100) as barra:
        for imagenes, etiquetas in conjunto_entrenamiento:
            resultados = modelo_vgg.train_on_batch(imagenes, etiquetas)
            barra.set_postfix({"pérdida": f"{resultados[0]:.4f}", "exactitud": f"{resultados[1]:.4f}", "lr": tasa_aprendizaje})
            barra.update(1)
        historial_perdida_entrenamiento.append(resultados[0])
        historial_exactitud_entrenamiento.append(resultados[1])
    
    with tqdm(total=total_validacion, desc=f'Validación {epoca + 1}/{epocas}', mininterval=0.3, ncols=100) as barra:
        for imagenes, etiquetas in conjunto_validacion:
            resultados_val = modelo_vgg.test_on_batch(imagenes, etiquetas)
            barra.set_postfix({"pérdida": f"{resultados_val[0]:.4f}", "exactitud": f"{resultados_val[1]:.4f}"})
            barra.update(1)
        historial_perdida_validacion.append(resultados_val[0])
        historial_exactitud_validacion.append(resultados_val[1])
    
    print(f"Pérdida en validación: {resultados_val[0]:.4f}, Exactitud: {resultados_val[1]:.4f}")

Se visuailzan las curvas de aprendizaje y se realizan predicciones en el conjunto de validación.

rango_epocas = range(epocas)

plt.figure(figsize=(14, 5))
plt.subplot(1, 2, 1)
plt.plot(rango_epocas, historial_exactitud_entrenamiento, label='Exactitud de entrenamiento')
plt.plot(rango_epocas, historial_exactitud_validacion, label='Exactitud de validación')
plt.xlabel('Épocas')
plt.ylabel('Exactitud')
plt.legend()
plt.title('Exactitud durante el entrenamiento')

plt.subplot(1, 2, 2)
plt.plot(rango_epocas, historial_perdida_entrenamiento, label='Pérdida de entrenamiento')
plt.plot(rango_epocas, historial_perdida_validacion, label='Pérdida de validación')
plt.xlabel('Épocas')
plt.ylabel('Pérdida')
plt.legend()
plt.title('Pérdida durante el entrenamiento')
plt.show()

plt.figure(figsize=(16, 3))
plt.suptitle("Predicciones en el conjunto de validación")
for imagenes, etiquetas in conjunto_validacion.take(1):
    for idx in range(8):
        ax = plt.subplot(1, 8, idx + 1)
        plt.imshow(imagenes[idx].numpy())
        tensor_imagen = tf.expand_dims(imagenes[idx], 0)
        predicciones = modelo_vgg.predict(tensor_imagen)
        plt.title(nombres_clases[np.argmax(predicciones)])
        plt.axis("off")

Etiquetas: VGG-16 TensorFlow keras Redes Convolucionales clasificación de imágenes

Publicado el 7-28 12:02