Logotipo de Zephyrnet

Comprender el sobreajuste en ConvNets

Fecha:

Introducción

El sobreajuste en ConvNets es un desafío en el aprendizaje profundo y las redes neuronales, donde un modelo aprende demasiado de los datos de entrenamiento, lo que genera un rendimiento deficiente con datos nuevos. Este fenómeno es especialmente frecuente en arquitecturas neuronales complejas, que pueden modelar relaciones intrincadas. Abordar el sobreajuste en convnet es crucial para construir modelos de redes neuronales confiables. Este artículo proporciona una guía para comprender y mitigar el sobreajuste, examinando las causas fundamentales, como la complejidad del modelo, los datos de entrenamiento limitados y las funciones ruidosas. También analiza técnicas para evitar el sobreajuste, como estrategias de aumento de datos y métodos de regularización.

Recomendaría leer estos artículos para una comprensión básica en sobreajuste, desajuste y compensación de varianza de sesgo.

Objetivos de aprendizaje

  • Comprender las causas, consecuencias y escenarios del sobreajuste en ConvNets.
  • Interpretar curvas de aprendizaje para detectar sobreajuste y desajuste en modelos de redes neuronales.
  • Aprenda varias técnicas para mitigar el sobreajuste, como detención anticipada, abandono, normalización de lotes, regularización y aumento de datos.
  • Implementar estas técnicas usando TensorFlow y Keras para entrenar ConvNets en el conjunto de datos CIFAR-10.
  • Analizar el impacto de diferentes técnicas en el rendimiento y la generalización del modelo.

Tabla de contenidos.

Escenarios comunes de sobreajuste en ConvNet

Veamos algunos escenarios comunes de sobreajuste en ConvNet:

Escenario 1: modelo muy complejo con datos insuficientes

El uso de un modelo muy complejo, como una red neuronal profunda, en un conjunto de datos pequeño puede provocar un sobreajuste. El modelo puede memorizar los ejemplos de entrenamiento en lugar de aprender el patrón general. Por ejemplo, entrenar una red neuronal profunda con sólo unos pocos cientos de imágenes para una tarea compleja como el reconocimiento de imágenes podría provocar un sobreajuste.

Consecuencia

El modelo puede funcionar muy bien con los datos de entrenamiento, pero no puede generalizarse a datos nuevos e invisibles, lo que da como resultado un rendimiento deficiente en aplicaciones del mundo real.

Cómo resolver este problema?

Obtenga más datos de entrenamiento. Aumente la imagen para generalizar nuestro conjunto de datos. Comience con un modelo menos complejo y si la capacidad es menor, aumente la complejidad. 

Escenario 2: Entrenamiento excesivo

Entrenar continuamente un modelo durante demasiadas épocas puede provocar un sobreajuste. A medida que el modelo ve los datos de entrenamiento repetidamente, puede comenzar a memorizarlos en lugar de aprender los patrones subyacentes.

Consecuencia

El rendimiento del modelo puede estabilizarse o incluso degradarse con datos invisibles a medida que se especializa cada vez más en el conjunto de entrenamiento.

Cómo resolver este problema?

Utilice la parada anticipada para evitar que el modelo se sobreajuste y guarde el mejor modelo. 

Escenario 3: Ignorar la regularización

Las técnicas de regularización, como la regularización L1 o L2, se utilizan para evitar el sobreajuste penalizando modelos complejos. Ignorar o ajustar incorrectamente los parámetros de regularización puede provocar un sobreajuste.

Consecuencia

El modelo puede volverse demasiado complejo y no lograr generalizarse bien a datos nuevos, lo que resultará en un rendimiento deficiente fuera del conjunto de entrenamiento.

Cómo resolver este problema?

Implementar regularización, validación cruzada, ajuste de hiperparámetros. 

¿Cuál es la capacidad del modelo?

La capacidad de un modelo se refiere al tamaño y la complejidad de los patrones que es capaz de aprender. En el caso de las redes neuronales, esto estará determinado en gran medida por cuántas neuronas tenga y cómo estén conectadas entre sí. Si parece que su red no tiene suficientes datos, debería intentar aumentar su capacidad.

Puede aumentar la capacidad de una red haciéndola más ancha (más unidades en las capas existentes) o haciéndola más profunda (agregando más capas). A las redes más amplias les resulta más fácil aprender relaciones más lineales, mientras que las redes más profundas prefieren relaciones más no lineales. Cuál es mejor solo depende del conjunto de datos.

Interpretación de las curvas de aprendizaje

Keras proporciona la capacidad de registrar devoluciones de llamadas al entrenar a un modelo de aprendizaje profundo. Una de las devoluciones de llamada predeterminadas registradas al entrenar todos los modelos de aprendizaje profundo es la devolución de llamada de Historial. Registra métricas de entrenamiento para cada época. Esto incluye la pérdida y la precisión (para problemas de clasificación) y la pérdida y precisión del conjunto de datos de validación, si se establece uno.

El objeto histórico se devuelve a partir de llamadas a la función fit() utilizada para entrenar el modelo. Las métricas se almacenan en un diccionario en el miembro del historial del objeto devuelto.

Por ejemplo, puede enumerar las métricas recopiladas en un objeto de historial utilizando el siguiente fragmento de código después de entrenar un modelo:

# list all data in history
print(history.history.keys())

Salida:

['precisión', 'pérdida', 'val_accuracy', 'val_loss']

Tipo de información

Se podría pensar que la información contenida en los datos de entrenamiento es de dos tipos:

  • la señal: La señal es la parte que generaliza, la parte que puede ayudar a nuestro modelo a hacer predicciones a partir de datos nuevos.
  • Ruido: El ruido es esa parte que sólo se aplica a los datos de entrenamiento; el ruido es toda la fluctuación aleatoria que proviene de los datos del mundo real o todos los patrones incidentales y no informativos que en realidad no pueden ayudar al modelo a hacer predicciones. El ruido es la pieza que puede parecer útil pero en realidad no lo es.

Cuando entrenamos un modelo, trazamos la pérdida en el conjunto de entrenamiento época por época. A esto también agregaremos un gráfico de los datos de validación. A estos gráficos los llamamos curvas de aprendizaje. Para entrenar modelos de aprendizaje profundo de forma eficaz, debemos poder interpretarlos.

Curvas de aprendizaje

En la figura anterior podemos ver que la pérdida de entrenamiento disminuye a medida que aumentan las épocas, pero la pérdida de validación disminuye al principio y aumenta a medida que el modelo comienza a capturar el ruido presente en el conjunto de datos. Ahora vamos a ver cómo evitar el sobreajuste en ConvNets mediante varias técnicas. 

Métodos para evitar el sobreajuste

Ahora que hemos visto algunos escenarios y cómo interpretar las curvas de aprendizaje para detectar el sobreajuste. Veamos algunos métodos para evitar el sobreajuste en una red neuronal:

Método 1: utilizar más datos

Aumentar el tamaño de su conjunto de datos puede ayudar a que el modelo se generalice mejor, ya que tiene ejemplos más diversos de los que aprender. El modelo encontrará patrones importantes presentes en el conjunto de datos e ignorará el ruido cuando se dé cuenta de que esos patrones específicos (ruido) no están presentes en todo el conjunto de datos.

Método 2: detenerse temprano

La parada anticipada es una técnica que se utiliza para evitar el sobreajuste mediante el seguimiento del rendimiento del modelo en un conjunto de validación durante el entrenamiento. El entrenamiento se detiene cuando el rendimiento del conjunto de validación comienza a degradarse, lo que indica que el modelo está empezando a sobreajustarse. Normalmente, se utiliza un conjunto de validación separado para monitorear el rendimiento y el entrenamiento se detiene cuando el rendimiento no ha mejorado durante un número específico de épocas.

Desajuste y sobreajuste

Método 3: abandono

Sabemos que el sobreajuste se debe a que la red aprende patrones falsos (ruido) en los datos de entrenamiento. Para reconocer estos patrones espurios, una red a menudo se basa en combinaciones de pesos muy específicas, una especie de “conspiración” de pesos. Al ser tan específicos, tienden a ser frágiles: si se elimina uno, la conspiración se desmorona.

Ésta es la idea detrás del abandono. Para romper estas conspiraciones, eliminamos aleatoriamente una fracción de las unidades de entrada de una capa en cada paso del entrenamiento, lo que hace mucho más difícil para la red aprender esos patrones espurios en los datos de entrenamiento. En cambio, tiene que buscar patrones amplios y generales, cuyos patrones de peso tienden a ser más sólidos. 

También se podría pensar en la deserción como la creación de una especie de conjunto de redes. Las predicciones ya no las hará una gran red, sino un comité de redes más pequeñas. Los individuos en el comité tienden a cometer diferentes tipos de errores, pero al mismo tiempo tienen razón, lo que hace que el comité en su conjunto sea mejor que cualquier individuo. (Si está familiarizado con los bosques aleatorios como un conjunto de árboles de decisión, es la misma idea).

Sobreajuste en ConvNets

Método 4: normalización por lotes

El siguiente método especial que veremos realiza una "normalización por lotes" (o "batchnorm"), que puede ayudar a corregir el entrenamiento que es lento o inestable.

Con las redes neuronales, generalmente es una buena idea poner todos los datos en una escala común, tal vez con algo como StandardScaler o MinMaxScaler de scikit-learn. La razón es que SGD cambiará los pesos de la red en proporción al tamaño de activación que producen los datos. Las características que tienden a producir activaciones de tamaños muy diferentes pueden generar un comportamiento de entrenamiento inestable.

Ahora bien, si es bueno normalizar los datos antes de que entren a la red, ¡tal vez también sería mejor normalizarlos dentro de la red! De hecho, tenemos un tipo especial de capa que puede hacer esto, la capa de normalización por lotes. Una capa de normalización de lotes analiza cada lote a medida que llega, primero normalizando el lote con su propia media y desviación estándar y luego colocando los datos en una nueva escala con dos parámetros de reescalado entrenables. Batchnorm, en efecto, realiza una especie de reescalado coordinado de sus entradas.

La mayoría de las veces, la norma por lotes se agrega como ayuda al proceso de optimización (aunque a veces también puede ayudar a predecir el rendimiento). Los modelos con norma por lotes tienden a necesitar menos épocas para completar el entrenamiento. Además, Batchnorm también puede solucionar varios problemas que pueden provocar que el entrenamiento se "atasque". Considere agregar normalización por lotes a sus modelos, especialmente si tiene problemas durante el entrenamiento.

Método 5: Regularización L1 y L2

La regularización L1 y L2 son técnicas que se utilizan para evitar el sobreajuste penalizando pesos grandes en la red neuronal. La regularización L1 agrega un término de penalización a la función de pérdida proporcional al valor absoluto de los pesos. Fomenta la escasez de pesos y puede conducir a la selección de funciones. La regularización L2, también conocida como disminución de peso, agrega un término de penalización proporcional al cuadrado de los pesos a la función de pérdida. Evita que las pesas se vuelvan demasiado grandes y fomenta que la distribución de las pesas sea más uniforme.

La elección entre la regularización L1 y L2 a menudo depende del problema específico y de las propiedades deseadas del modelo.

Tener valores grandes para la regularización L1/L2 hará que el modelo no aprenda rápidamente y alcance una meseta en el aprendizaje, lo que provocará que el modelo no se ajuste adecuadamente. 

Método 6: aumento de datos

La mejor manera de mejorar el rendimiento de un modelo de aprendizaje automático es entrenarlo con más datos. Cuantos más ejemplos tenga que aprender el modelo, mejor podrá reconocer qué diferencias en las imágenes importan y cuáles no. Más datos ayudan al modelo a generalizar mejor.

Una forma sencilla de obtener más datos es utilizar los datos que ya tiene. Si podemos transformar las imágenes en nuestro conjunto de datos de manera que preserven la clase (ejemplo: clasificación de dígitos MNIST, si intentamos aumentar 6, será difícil distinguir entre 6 y 9), podemos enseñarle a nuestro clasificador a ignorar ese tipo de transformaciones. Por ejemplo, si un automóvil está mirando hacia la izquierda o hacia la derecha en una foto, no cambia el hecho de que sea un automóvil y no un camión. Entonces, si aumentamos nuestros datos de entrenamiento con imágenes invertidas, nuestro clasificador aprenderá que "izquierda o derecha" es una diferencia que debe ignorar.

Y esa es la idea detrás del aumento de datos: agregue algunos datos falsos adicionales que se parezcan razonablemente a los datos reales y su clasificador mejorará. 

Recuerde, la clave para evitar el sobreajuste es asegurarse de que su modelo se generalice bien. Verifique siempre el rendimiento de su modelo en un conjunto de validación, no solo en el conjunto de entrenamiento.

Implementación de los métodos anteriores con datos

Exploremos los pasos de implementación para los métodos anteriores:

Paso 1: cargar las bibliotecas necesarias

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow.keras import datasets, layers, models
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.callbacks import ModelCheckpoint
import keras
from keras.preprocessing import image
from keras import models, layers, regularizers
from tqdm import tqdm
import warnings
warnings.filterwarnings(action='ignore')

Paso 2: carga del conjunto de datos y preprocesamiento

#Here all the images are in the form of a numpy array
cifar10 = tf.keras.datasets.cifar10
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
x_train = x_train / 255.0
x_test = x_test / 255.0

Paso 3: conjunto de datos de aprendizaje

x_train.shape, y_train.shape, x_test.shape, y_test.shape 

Salida:

Salida
np.unique(y_train)

Salida:

Salida
#These labels are in the order and taken from the documentaion
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',
               'dog', 'frog', 'horse', 'ship', 'truck']

Paso 4: visualizar la imagen desde el conjunto de datos

def show_image(IMG_INDEX):
    plt.imshow(x_train[20] ,cmap=plt.cm.binary)
    plt.xlabel(class_names[y_train[IMG_INDEX][0]])
    plt.show()
show_image(20)
Sobreajuste en ConvNets
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)))
model.add(layers.AveragePooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.AveragePooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10))
model.summary()

Ahora inicialicemos los hiperparámetros y compilemos el modelo con optimizador, función de pérdida y métrica de evaluación.


train_hyperparameters_config={'optim':keras.optimizers.Adam(learning_rate=0.001),
                             'epochs':20,
                              'batch_size':16
                             }
model.compile(optimizer=train_hyperparameters_config['optim'],
                  loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
                  metrics=['accuracy'])

Paso 6: modelo de entrenamiento

history = model.fit(x_train, y_train, 
                        epochs=train_hyperparameters_config['epochs'], 
                        batch_size=train_hyperparameters_config['batch_size'], 
                        verbose=1,
                        validation_data=(x_test, y_test))

Paso 7: evaluar el modelo

Estos nos dirán la información contenida en el objeto histórico y los usaremos para crear nuestras curvas de información.

print(history.history.keys()) 
def learning_curves(history):
# Plotting Accuracy
    plt.figure(figsize=(14, 5))  # Adjust the figure size as needed
    plt.subplot(1, 2, 1)  # Subplot with 1 row, 2 columns, and index 1
    plt.plot(history.history['accuracy'], label='train_accuracy', marker='s', markersize=4)
    plt.plot(history.history['val_accuracy'], label='val_accuracy', marker='*', markersize=4)
    plt.xlabel('Epoch')
    plt.ylabel('Accuracy')
    plt.legend(loc='lower right')

    # Plotting Loss
    plt.subplot(1, 2, 2)  # Subplot with 1 row, 2 columns, and index 2
    plt.plot(history.history['loss'], label='train_loss', marker='s', markersize=4)
    plt.plot(history.history['val_loss'], label='val_loss', marker='*', markersize=4)
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.legend(loc='lower right')

    plt.show()
learning_curves(history)
Sobreajuste en ConvNets

De las curvas podemos ver que la precisión de la validación alcanza una meseta después de la cuarta época y el modelo comienza a capturar ruido. Por lo tanto, implementaremos una parada anticipada para evitar el sobreajuste del modelo y restauraremos los mejores pesos basados ​​en val_loss. Usaremos val_loss para monitorear las paradas anticipadas mientras nuestra red neuronal intenta reducir las pérdidas utilizando optimizadores. La exactitud y la exactitud de la validación dependen del umbral (una probabilidad de separar clases, generalmente 4 para la clasificación binaria), por lo que si nuestro conjunto de datos está desequilibrado, sería una pérdida de la que deberíamos preocuparnos en la mayoría de los casos. 

Paso 8: Implementar la parada temprana

Dado que no nos preocupa que nuestro modelo se sobreajuste, una parada anticipada evitará que nuestro modelo suceda. Es una buena opción elegir un mayor número de épocas y tener la paciencia adecuada. Ahora usaremos la misma arquitectura de modelo y entrenaremos con devolución de llamada de parada anticipada. 

model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(32, 32, 3)))
model.add(layers.AveragePooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.AveragePooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.Flatten())
model.add(layers.Dense(128, activation='relu'))
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10))
model.summary()

# Here we have used more epochs than needed since we use patience parameter which we stop the model from overfitting
train_hyperparameters_config = {
    'optim': keras.optimizers.Adam(learning_rate=0.001),
    'patience': 5,
    'epochs': 50,
    'batch_size': 32, 
}
print('Setting the callback and early stopping configurations...')
callback = tf.keras.callbacks.EarlyStopping(
    monitor='val_loss', 
    min_delta=0.001, # minimium amount of change to count as an improvement
    patience=train_hyperparameters_config['patience'], 
    restore_best_weights=True)

def model_train(model, x_train, y_train, x_test, y_test, train_hyperparameters_config):
    model.compile(optimizer=train_hyperparameters_config['optim'],
                      loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
                      metrics=['accuracy'])
    ht = model.fit(x_train, y_train, 
                            epochs=train_hyperparameters_config['epochs'], 
                            batch_size=train_hyperparameters_config['batch_size'],
                            callbacks=[callback],
                            verbose=1,
                            validation_data=(x_test, y_test))
    return ht

ht=model_train(model, x_train, y_train, x_test, y_test, train_hyperparameters_config)
learning_curves(ht)
Sobreajuste en ConvNets

Para conocer nuestros mejores pesos que ha cogido el modelo. 

print('Testing ..................')
test_loss, test_acc = model.evaluate(x_test,  y_test, verbose=2)
print('test_loss : ', test_loss, 'test_accuracy : ', test_acc)

Paso 9: aumento de la complejidad del modelo

Dado que nuestro modelo no funciona bien y no se ajusta bien, ya que no puede capturar suficientes datos. Deberíamos aumentar la complejidad de nuestro modelo y evaluarlo. 

model = models.Sequential()

model.add(layers.Conv2D(128, (3, 3), activation='relu', input_shape=(32, 32, 3)))
model.add(layers.MaxPooling2D((2, 2)))

model.add(layers.Conv2D(256, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))

model.add(layers.Conv2D(256, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))

model.add(layers.Conv2D(512, (3, 3), activation='relu', padding='same'))
model.add(layers.MaxPooling2D((2, 2)))

model.add(layers.Flatten())
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(128, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
model.summary()
Salida

Podemos ver que hay un aumento en los parámetros totales. Esto ayudaría a encontrar relaciones más complejas en nuestro modelo. Nota: Nuestro conjunto de datos consta de imágenes de 32X32; Estas son imágenes relativamente pequeñas. Por lo tanto, el uso de modelos más complejos al principio seguramente sobreajustará el modelo, por lo que tendemos a aumentar la complejidad de nuestro modelo lentamente.

# Here we have used more epochs than needed since we use patience parameter which we stop the model from overfitting
train_hyperparameters_config = {
    'optim': keras.optimizers.Adam(learning_rate=0.001),
    'patience': 5,
    'epochs': 50,
    'batch_size': 32, 
}
print('Setting the callback and early stopping configurations...')
callback = tf.keras.callbacks.EarlyStopping(
    monitor='val_loss', 
    min_delta=0.001, # minimium amount of change to count as an improvement
    patience=train_hyperparameters_config['patience'], 
    restore_best_weights=True)
ht=model_train(model, x_train, y_train, x_test, y_test, train_hyperparameters_config)

learning_curves(ht)
Sobreajuste en ConvNets
print('Testing ..................')
test_loss, test_acc = model.evaluate(x_test,  y_test, verbose=2)
print('test_loss : ', test_loss, 'test_accuracy : ', test_acc)

De los gráficos anteriores podemos decir claramente que el modelo se está sobreajustando, por lo que usaremos otro método llamado Normalización de abandono y normalización por lotes.

Paso 10: uso de capas de abandono y capas de normalización por lotes

model = models.Sequential()

model.add(layers.Conv2D(128, (3, 3), activation='relu', input_shape=(32, 32, 3)))
model.add(layers.BatchNormalization())
model.add(layers.MaxPooling2D((2, 2)))

model.add(layers.Conv2D(256, (3, 3), activation='relu'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPooling2D((2, 2)))

model.add(layers.Conv2D(256, (3, 3), activation='relu'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPooling2D((2, 2)))

model.add(layers.Conv2D(512, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPooling2D((2, 2)))

model.add(layers.Flatten())
model.add(layers.Dense(256, activation='relu'))
model.add(layers.BatchNormalization())
model.add(layers.Dropout(0.3))

model.add(layers.Dense(128, activation='relu'))
model.add(layers.BatchNormalization())
model.add(layers.Dropout(0.3))

model.add(layers.Dense(10, activation='softmax'))
model.summary()
Salida
# Here we have used more epochs than needed since we use patience parameter which we stop the model from overfitting
train_hyperparameters_config = {
    'optim': keras.optimizers.Adam(learning_rate=0.001),
    'patience': 5,
    'epochs': 50,
    'batch_size': 32, 
}
print('Setting the callback and early stopping configurations...')
callback = tf.keras.callbacks.EarlyStopping(
    monitor='val_loss', 
    min_delta=0.001, # minimium amount of change to count as an improvement
    patience=train_hyperparameters_config['patience'], 
    restore_best_weights=True)
ht=model_train(model, x_train, y_train, x_test, y_test, train_hyperparameters_config)
learning_curves(ht)
Sobreajuste en ConvNets
print('Testing ..................')
test_loss, test_acc = model.evaluate(x_test,  y_test, verbose=2)
print('test_loss : ', test_loss, 'test_accuracy : ', test_acc)

En los gráficos de aprendizaje podemos ver que el modelo se está sobreajustando incluso con capas de normalización por lotes y abandono. De ahí que en lugar de aumentar la complejidad sino aumentar el número de filtros. Agregaríamos más capas de convolución para extraer más funciones.

Paso 11: aumento de capas de convolución

Disminuya el parámetro entrenable pero aumente las capas de convolución para extraer más funciones.

model = models.Sequential()

model.add(layers.Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=(32, 32, 3)))
model.add(layers.BatchNormalization())
model.add(layers.Conv2D(32, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPool2D((2, 2)))
model.add(layers.Dropout(0.2))

model.add(layers.Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPool2D((2, 2)))
model.add(layers.Dropout(0.3))

model.add(layers.Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPool2D((2, 2)))
model.add(layers.Dropout(0.4))

model.add(layers.Flatten())
model.add(layers.Dense(128, activation='relu'))
model.add(layers.BatchNormalization())
model.add(layers.Dropout(0.5))

model.add(layers.Dense(10, activation='softmax'))

model.summary()
# Here we have used more epochs than needed since we use patience parameter which we stop the model from overfitting
train_hyperparameters_config = {
    'optim': keras.optimizers.Adam(learning_rate=0.001),
    'patience': 5,
    'epochs': 50,
    'batch_size': 32, 
}
print('Setting the callback and early stopping configurations...')
callback = tf.keras.callbacks.EarlyStopping(
    monitor='val_loss', 
    min_delta=0.001, # minimium amount of change to count as an improvement
    patience=train_hyperparameters_config['patience'], 
    restore_best_weights=True)
ht=model_train(model, x_train, y_train, x_test, y_test, train_hyperparameters_config)
salida
learning_curves(ht)
Sobreajuste en ConvNets
print('Testing ..................')
test_loss, test_acc = model.evaluate(x_test,  y_test, verbose=2)
print('test_loss : ', test_loss, 'test_accuracy : ', test_acc)

A partir del resultado y la curva de aprendizaje anteriores, podemos inferir que el modelo ha funcionado muy bien y ha evitado el sobreajuste. La precisión del entrenamiento y la precisión de la validación están muy cerca. En este escenario no necesitaremos más métodos para disminuir el sobreajuste. Sin embargo, exploraremos la regularización L1/L2. 

Paso 12: Usar la regularización L1/L2

from tensorflow.keras import regularizers

model = models.Sequential()

model.add(layers.Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=(32, 32, 3)))
model.add(layers.BatchNormalization())
model.add(layers.Conv2D(32, (3, 3), activation='relu', padding='same', kernel_regularizer=regularizers.l1(0.0005)))
model.add(layers.BatchNormalization())
model.add(layers.MaxPool2D((2, 2)))
model.add(layers.Dropout(0.2))

model.add(layers.Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.Conv2D(64, (3, 3), activation='relu', padding='same', kernel_regularizer=regularizers.l2(0.0005)))
model.add(layers.BatchNormalization())
model.add(layers.MaxPool2D((2, 2)))
model.add(layers.Dropout(0.3))

model.add(layers.Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPool2D((2, 2)))
model.add(layers.Dropout(0.4))

model.add(layers.Flatten())
model.add(layers.Dense(128, activation='relu', kernel_regularizer=regularizers.l1_l2(0.0005, 0.0005)))
model.add(layers.BatchNormalization())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(10, activation='softmax'))

model.summary()
# Here we have used more epochs than needed since we use patience parameter which we stop the model from overfitting
train_hyperparameters_config = {
    'optim': keras.optimizers.Adam(learning_rate=0.001),
    'patience': 7,
    'epochs': 70,
    'batch_size': 32, 
}
print('Setting the callback and early stopping configurations...')
callback = tf.keras.callbacks.EarlyStopping(
    monitor='val_loss', 
    min_delta=0.001, # minimium amount of change to count as an improvement
    patience=train_hyperparameters_config['patience'], 
    restore_best_weights=True)
ht=model_train(model, x_train, y_train, x_test, y_test, train_hyperparameters_config)
learning_curves(ht)
Sobreajuste en ConvNets
print('Testing ..................')
test_loss, test_acc = model.evaluate(x_test,  y_test, verbose=2)
print('test_loss : ', test_loss, 'test_accuracy : ', test_acc)

Ahora podemos ver que la regularización L1/L2, incluso después de usar una puntuación de penalización baja de 0.0001, hizo que nuestro modelo no se ajustara bien en un 4%. Por lo tanto, es aconsejable utilizar todos los métodos juntos con precaución. Como la normalización y regularización por lotes afectan al modelo de manera similar, no necesitaríamos la regularización L1/L2. 

Paso 13: aumento de datos

Usaremos ImageDataGenerator de tensorflow keras.

# creates a data generator object that transforms images
datagen = ImageDataGenerator(
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest')

# pick an image to transform
test_img = x_train[20]
img = image.img_to_array(test_img)  # convert image to numpy arry
img = img.reshape((1,) + img.shape)  # reshape image

i = 0

for batch in datagen.flow(img, save_prefix='test', save_format='jpeg'):  # this loops runs forever until we break, saving images to current directory with specified prefix
    plt.figure(i)
    plot = plt.imshow(image.img_to_array(batch[0]))
    i += 1
    if i > 4:  # show 4 images
        break

plt.show()
Sobreajuste en ConvNets

Se trata de cuatro imágenes aumentadas y una imagen original.

# Create an instance of the ImageDataGenerator
datagen = ImageDataGenerator(
    rotation_range=40,
    width_shift_range=0.2,
    height_shift_range=0.2,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True,
    fill_mode='nearest'
)

# Create an iterator for the data generator
data_generator = datagen.flow(x_train, y_train, batch_size=32)

# Create empty lists to store the augmented images and labels
augmented_images = []
augmented_labels = []

# Loop over the data generator and append the augmented data to the lists
num_batches = len(x_train) // 32
progress_bar = tqdm(total=num_batches, desc="Augmenting data", unit="batch")

for i in range(num_batches):
    batch_images, batch_labels = next(data_generator)
    augmented_images.append(batch_images)
    augmented_labels.append(batch_labels)
    progress_bar.update(1)

progress_bar.close()

# Convert the lists to NumPy arrays
augmented_images = np.concatenate(augmented_images, axis=0)
augmented_labels = np.concatenate(augmented_labels, axis=0)

# Combine the original and augmented data
x_train_augmented = np.concatenate((x_train, augmented_images), axis=0)
y_train_augmented = np.concatenate((y_train, augmented_labels), axis=0)
salida

Hemos utilizado la biblioteca tqdm para conocer el progreso de nuestro aumento.

x_train_augmented.shape, y_train_augmented.shape
Salida

Este es nuestro conjunto de datos después del aumento. Ahora usemos este conjunto de datos y entrenemos nuestro modelo.

model = models.Sequential()

model.add(layers.Conv2D(32, (3, 3), activation='relu', padding='same', input_shape=(32, 32, 3)))
model.add(layers.BatchNormalization())
model.add(layers.Conv2D(32, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPool2D((2, 2)))
model.add(layers.Dropout(0.2))

model.add(layers.Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPool2D((2, 2)))
model.add(layers.Dropout(0.3))

model.add(layers.Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(layers.BatchNormalization())
model.add(layers.MaxPool2D((2, 2)))
model.add(layers.Dropout(0.4))

model.add(layers.Flatten())
model.add(layers.Dense(128, activation='relu'))
model.add(layers.BatchNormalization())
model.add(layers.Dropout(0.5))

model.add(layers.Dense(10, activation='softmax'))

model.summary()

# Here we have used more epochs than needed since we use patience parameter which we stop the model from overfitting
train_hyperparameters_config = {
    'optim': keras.optimizers.Adam(learning_rate=0.001),
    'patience': 10,
    'epochs': 70,
    'batch_size': 32, 
}
print('Setting the callback and early stopping configurations...')
callback = tf.keras.callbacks.EarlyStopping(
    monitor='val_loss', 
    min_delta=0.001, # minimium amount of change to count as an improvement
    patience=train_hyperparameters_config['patience'], 
    restore_best_weights=True)

ht=model_train(model, x_train_augmented, y_train_augmented, x_test, y_test, train_hyperparameters_config)


learning_curves(ht)
Sobreajuste en ConvNets
print('Testing ..................')
test_loss, test_acc = model.evaluate(x_test,  y_test, verbose=2)
print('test_loss : ', test_loss, 'test_accuracy : ', test_acc)

Podemos ver que el modelo es más generalizado y una disminución de las pérdidas. También tenemos una mejor precisión de validación. Por lo tanto, el aumento de datos ha aumentado la precisión de nuestro modelo. 

Conclusión

El sobreajuste es un problema común en el aprendizaje profundo, especialmente con arquitecturas de redes neuronales complejas como ConvNets. Los profesionales pueden evitar el sobreajuste en ConvNets comprendiendo sus causas fundamentales y reconociendo los escenarios en los que ocurre. Técnicas como la parada anticipada, el abandono, la normalización de lotes, la regularización y el aumento de datos pueden ayudar a mitigar este problema. La implementación de estas técnicas en el conjunto de datos CIFAR-10 mostró mejoras significativas en la generalización y el rendimiento del modelo. Dominar estas técnicas y comprender sus principios puede conducir a modelos de redes neuronales robustos y confiables.

Preguntas frecuentes

P1. ¿Qué es el sobreajuste y por qué es un problema en el aprendizaje profundo? 

R. El sobreajuste ocurre cuando un modelo aprende demasiado bien los datos de entrenamiento, incluido su ruido y patrones irrelevantes, lo que resulta en un rendimiento deficiente con datos nuevos e invisibles. Es un problema porque los modelos sobreajustados no logran generalizarse de manera efectiva, lo que limita su utilidad práctica.

P2. ¿Cómo puedo detectar un sobreajuste en mi modelo de red neuronal?

R. Puede detectar el sobreajuste en ConvNets interpretando las curvas de aprendizaje, que trazan las métricas de entrenamiento y validación (por ejemplo, pérdida, precisión) a lo largo de épocas. Si las métricas de validación dejan de mejorar o comienzan a degradarse mientras las métricas de entrenamiento continúan mejorando, es una señal de sobreajuste.

P3. ¿Qué es la interrupción temprana y cómo ayuda a prevenir el sobreajuste?

R. La detención anticipada es una técnica que monitorea el desempeño del modelo en un conjunto de validación durante el entrenamiento y detiene el proceso de entrenamiento cuando el desempeño en el conjunto de validación comienza a degradarse, lo que indica un sobreajuste. Ayuda a evitar que el modelo se sobreajuste al detener el entrenamiento en el momento adecuado.

P4. ¿Cómo ayuda el aumento de datos a mitigar el sobreajuste? 

R. El aumento de datos es el proceso de generar nuevos datos de entrenamiento sintéticos mediante la aplicación de transformaciones (p. ej., voltear, rotar, escalar) a los datos existentes. Ayuda a que el modelo se generalice mejor al exponerlo a ejemplos más diversos, lo que reduce el riesgo de sobreajuste en ConvNets a los datos de entrenamiento limitados.

punto_img

Información más reciente

punto_img