Hay un enemigo que llevamos esquivando desde el módulo 2: cuando entrenamos la red densa de MNIST en 02-05, las curvas mostraban un sobreajuste leve — la pérdida de entrenamiento seguía bajando mientras la de validación se estancaba y empezaba a subir. Entonces lo dejamos anotado; ahora lo resolvemos. Esta lección presenta el arsenal completo de regularización: el conjunto de técnicas que hacen que un modelo generalice a datos nuevos en lugar de memorizar los de entrenamiento. Son transversales — mejoran la CNN del catálogo, el clasificador de reseñas, la LSTM de ventas, el transfer learning de la lección anterior y hasta el discriminador de la GAN — y por eso son, probablemente, las técnicas que más veces aplicarás en tu vida profesional.

Contenido

  1. El sobreajuste a fondo: diagnóstico con curvas
  2. Bias vs. variance en términos prácticos
  3. Regularización L1 y L2 (weight decay)
  4. Dropout
  5. Batch Normalization
  6. Early Stopping
  7. Aumento de datos para imágenes
  8. Tabla resumen del arsenal
  9. Ejemplo integrador: MNIST de 02-05, mejorado
  10. Mención: learning rate schedules

El sobreajuste a fondo: diagnóstico con curvas

Sobreajustar es memorizar en lugar de aprender. Un modelo sobreajustado rinde muy bien con los datos de entrenamiento y mal con datos nuevos: ha aprendido las peculiaridades accidentales del conjunto (el ruido) además de —o en lugar de— los patrones reales.

El instrumento de diagnóstico ya lo conoces: las curvas train/val que fit() devuelve en history. Los tres cuadros clínicos:

Cuadro Curva de entrenamiento Curva de validación Diagnóstico
Subajuste (underfitting) Pérdida alta, estancada Alta, pegada a la de train El modelo no da más de sí: falta capacidad o entrenamiento
Ajuste sano Baja y se estabiliza Baja y se estabiliza cerca de train Objetivo conseguido
Sobreajuste Sigue bajando y bajando Se estanca y empieza a subir El modelo memoriza: hay que regularizar

La señal inequívoca del sobreajuste es la divergencia de las curvas: el hueco (gap) entre train y val crece con las épocas. En 02-05 ese hueco era pequeño; en el transfer learning de 05-03, con solo 1600 imágenes, puede ser un abismo si no se hace nada.

Bias vs. variance en términos prácticos

La teoría llama a esto el compromiso sesgo-varianza (bias-variance). Sin formalismos, la versión de trabajo:

  • Bias alto = el modelo es demasiado simple o rígido para el patrón real → subajuste. Se nota en que ni siquiera el entrenamiento va bien.
  • Variance alta = el modelo es tan flexible que cambia drásticamente con cada muestra concreta de datos: se ajusta al ruido → sobreajuste. Se nota en el hueco train/val.

La receta de decisión práctica, en orden:

  1. ¿La pérdida de entrenamiento es mala? → problema de bias: modelo más grande, entrenar más, mejores rasgos. (La regularización aquí no ayuda; incluso empeora.)
  2. ¿El entrenamiento va bien pero validación diverge? → problema de variance: regularización (esta lección), más datos, o modelo más pequeño.
  3. ¿Ambas van bien? → no toques nada; pasa a evaluar en test.

Todo el arsenal que sigue ataca el caso 2: la varianza.

Regularización L1 y L2 (weight decay)

La primera familia de técnicas actúa sobre la pérdida: se añade una penalización por pesos grandes.

  • L2 (la habitual, también llamada weight decay): añade a la pérdida la suma de los cuadrados de los pesos, multiplicada por un factor pequeño λ. perdida_total = perdida_datos + λ·Σw²
  • L1: igual pero con valores absolutos (λ·Σ|w|); su efecto lateral es empujar muchos pesos exactamente a cero (produce redes "dispersas").

¿Por qué pesos pequeños generalizan mejor? La intuición: un peso enorme significa que la red se apoya muchísimo en un detalle concreto de la entrada — el tipo de dependencia frágil que caracteriza a la memorización. Con la penalización, cada peso debe "ganarse el sueldo": solo se mantiene grande si reduce la pérdida de datos más de lo que cuesta en penalización. El resultado son funciones más suaves, menos capaces de retorcerse para ajustar puntos de ruido individuales. Recuerda el "comité de compras" de 01-04: L2 impide que un solo vocal grite tanto que decida él solo.

En Keras se aplica por capa:

from tensorflow.keras import layers, regularizers

capa = layers.Dense(
    128, activation="relu",
    kernel_regularizer=regularizers.l2(1e-4),  # lambda tipico: 1e-4 a 1e-2
)

Valores típicos de λ: entre 1e-5 y 1e-2. Demasiado alto y la red se vuelve incapaz de aprender (bias); demasiado bajo y no hace nada.

Dropout

Dropout es la técnica de regularización más emblemática del deep learning: durante el entrenamiento, en cada pasada, se apaga aleatoriamente una fracción de las neuronas de la capa (por ejemplo el 30 %, rate=0.3). En cada batch se apagan neuronas distintas.

¿Por qué funciona algo tan aparentemente destructivo?

  • Impide la coadaptación: sin dropout, una neurona puede especializarse en corregir los errores de otra concreta, creando dependencias frágiles y circuitos que memorizan. Si tu compañera puede desaparecer en cualquier momento, no puedes construir tu función sobre ella: cada neurona debe aprender rasgos útiles por sí mismos.
  • Entrena un comité implícito: cada patrón de apagado define una sub-red distinta; el modelo final se comporta como el promedio de un enorme conjunto de redes más pequeñas — y promediar modelos reduce la varianza (la misma lógica del comité de compras de 01-04, ahora dentro de la red).

Detalles operativos:

  • Comportamiento distinto en train e inference: en entrenamiento apaga neuronas; en predicción (predict/evaluate) no apaga nada y compensa las magnitudes automáticamente. Keras lo gestiona solo — pero explica por qué la pérdida de entrenamiento que reporta fit puede parecer peor que la de validación al principio: la de train se mide con dropout activo.
  • Dónde colocarlo: tras las capas densas grandes (el lugar clásico) y tras el pooling global en cabezas de CNN, como hicimos en 05-03. En convoluciones intermedias es menos habitual; en RNN se usan variantes específicas (los argumentos dropout/recurrent_dropout de la capa LSTM).
  • Tasa típica: 0.2–0.5. Empieza por 0.2–0.3; 0.5 es agresivo y propio de capas densas muy grandes.
modelo = keras.Sequential([
    layers.Dense(256, activation="relu", input_shape=(784,)),
    layers.Dropout(0.3),   # apaga el 30% de las 256 salidas EN CADA BATCH
    layers.Dense(128, activation="relu"),
    layers.Dropout(0.3),
    layers.Dense(10, activation="softmax"),
])

Batch Normalization

Batch Normalization (BN) normaliza las activaciones de una capa dentro de cada batch: les resta la media del batch y las divide por su desviación, dejándolas centradas y con escala controlada (y luego aplica una escala y desplazamiento aprendibles, para no perder expresividad).

Por qué ayuda:

  • Acelera y estabiliza el entrenamiento: sin BN, cada capa recibe entradas cuya distribución cambia constantemente a medida que las capas anteriores aprenden — es como apuntar a un blanco móvil. BN mantiene esas distribuciones estables, lo que permite learning rates mayores y hace la red mucho menos sensible a la inicialización. También mitiga el vanishing/exploding gradient de 02-03, porque evita que las activaciones se vayan a las zonas planas de las funciones de activación (el "grifo" que se cierra).
  • Efecto regularizador secundario: como media y desviación se calculan sobre el batch, cada ejemplo se normaliza de forma ligeramente distinta según con quién comparta batch. Ese pequeño ruido actúa como regularización suave — un beneficio colateral, no su propósito principal.

Dónde va: entre la parte lineal de la capa y la activación es la receta clásica (Dense/Conv2D → BatchNormalization → Activation), aunque colocarla tras la activación también es común y funciona; en la práctica verás ambas. Ya la usaste sin saberlo dos veces: en el generador de la DCGAN (05-01) y dentro de MobileNetV2 (05-03).

modelo = keras.Sequential([
    layers.Dense(256, use_bias=False, input_shape=(784,)),  # BN ya centra:
    layers.BatchNormalization(),                            # el bias sobra
    layers.Activation("relu"),
    layers.Dense(10, activation="softmax"),
])

Igual que Dropout, BN se comporta distinto en inferencia: en predict usa medias y desviaciones acumuladas durante el entrenamiento (no las del batch actual, que podría ser un solo ejemplo). De ahí el training=False que pasamos a la base congelada en 05-03.

Early Stopping

La técnica más simple y quizá la más rentable: dejar de entrenar cuando la validación deja de mejorar. Si el sobreajuste aparece a partir de cierta época, no entrenes más allá — el mejor modelo es el de antes de que las curvas diverjan.

En Keras es un callback:

parada = keras.callbacks.EarlyStopping(
    monitor="val_loss",        # que metrica vigilar
    patience=5,                # epocas de gracia sin mejora antes de parar
    restore_best_weights=True, # al parar, RECUPERA los pesos de la mejor epoca
)

historia = modelo.fit(x_train, y_train,
                      validation_split=0.1,
                      epochs=100,            # un maximo generoso: parara antes
                      callbacks=[parada])

Tres detalles que marcan la diferencia:

  • patience: la validación oscila de forma natural; sin paciencia, pararías en el primer bache. 5–10 épocas es lo habitual.
  • restore_best_weights=True: sin esto, te quedas con los pesos de la última época (que ya era peor), no de la mejor. Actívalo siempre.
  • Con early stopping puedes poner epochs alto sin miedo: el número de épocas deja de ser un hiperparámetro crítico que adivinar.

Aumento de datos para imágenes

Contra la varianza, nada funciona mejor que más datos. Cuando no los hay, se fabrican variaciones plausibles de los existentes: es el aumento de datos (data augmentation) que prometimos en 03-04 al hablar del clasificador de fotos de producto.

La idea: una tostadora sigue siendo una tostadora si la foto está espejada, ligeramente girada, con más zoom o con otra iluminación. Aplicando esas transformaciones aleatoriamente en cada época, la red nunca ve dos veces exactamente la misma imagen — memorizar se vuelve imposible y la red aprende invarianzas reales (la esencia "tostadora" que sobrevive a los cambios).

En Keras moderno, el aumento son capas dentro del modelo, activas solo durante el entrenamiento:

aumento = keras.Sequential([
    layers.RandomFlip("horizontal"),      # espejo izquierda-derecha
    layers.RandomRotation(0.05),          # giros de hasta ±5% de vuelta (±18 grados)
    layers.RandomZoom(0.15),              # acercar/alejar hasta un 15%
    layers.RandomContrast(0.2),           # variar el contraste (iluminacion)
    layers.RandomTranslation(0.1, 0.1),   # desplazar hasta un 10%
], name="aumento_de_datos")

# Se inserta al principio del modelo (tras el Input, antes de la base):
entradas = keras.Input(shape=(160, 160, 3))
x = aumento(entradas)                     # solo actua cuando training=True
x = keras.applications.mobilenet_v2.preprocess_input(x)
# ... base + cabeza como en 05-03

Regla de sensatez: las transformaciones deben conservar la etiqueta y ser plausibles en producción. Espejar una cafetera, bien; espejar un dígito de MNIST convierte algunos en basura (un 2 espejado no es un 2), y poner una tostadora boca abajo no ayuda si ninguna foto real vendrá así.

Tabla resumen del arsenal

Técnica Cuándo usarla Coste / contrapartida
L2 (weight decay) Sobreajuste general en capas densas/conv; casi siempre inofensiva en dosis bajas Un hiperparámetro más (λ); dosis alta causa subajuste
L1 Cuando además interesa dispersar (pesos a cero, selección de rasgos) Menos usada; misma precaución con λ
Dropout Capas densas grandes; cabezas de clasificación Alarga el entrenamiento (la red "efectiva" es menor); no mezclar alegremente con BN en la misma capa
Batch Normalization Redes profundas: acelera, estabiliza y regulariza de propina Ligero coste de cómputo; cuidado con batches muy pequeños (estadísticas ruidosas)
Early Stopping Prácticamente siempre Ninguno relevante; requiere conjunto de validación
Aumento de datos Imágenes con datasets pequeños/medianos (el caso TecnoMarket) Entrenamiento más largo; exige elegir transformaciones plausibles
Más datos reales Siempre que sea viable El más caro… y el más efectivo

Estrategia recomendada: early stopping siempre; aumento de datos si trabajas con imágenes; después dropout o L2 si el hueco train/val persiste; BN si además el entrenamiento es lento o inestable.

Ejemplo integrador: MNIST de 02-05, mejorado

Cerremos la deuda pendiente. Esta es la red densa de 02-05 (~97,7 % y sobreajuste leve), reforzada con BN + Dropout + Early Stopping:

from tensorflow import keras
from tensorflow.keras import layers

(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
x_train = x_train.reshape(-1, 784).astype("float32") / 255.0
x_test = x_test.reshape(-1, 784).astype("float32") / 255.0

def construir(regularizada: bool) -> keras.Sequential:
    """La misma red de 02-05, con o sin el arsenal de regularizacion."""
    capas = [layers.Input(shape=(784,))]
    for unidades in (256, 128):
        if regularizada:
            capas += [layers.Dense(unidades, use_bias=False),
                      layers.BatchNormalization(),
                      layers.Activation("relu"),
                      layers.Dropout(0.3)]
        else:
            capas += [layers.Dense(unidades, activation="relu")]
    capas.append(layers.Dense(10, activation="softmax"))
    return keras.Sequential(capas)

parada = keras.callbacks.EarlyStopping(monitor="val_loss", patience=5,
                                       restore_best_weights=True)

resultados = {}
for nombre, reg in [("original", False), ("regularizada", True)]:
    modelo = construir(regularizada=reg)
    modelo.compile(optimizer="adam",
                   loss="sparse_categorical_crossentropy",
                   metrics=["accuracy"])
    hist = modelo.fit(x_train, y_train, validation_split=0.1,
                      epochs=50, batch_size=128,
                      callbacks=[parada] if reg else [],
                      verbose=0)
    perdida, acc = modelo.evaluate(x_test, y_test, verbose=0)
    resultados[nombre] = (hist, acc)
    print(f"{nombre}: test accuracy = {acc:.4f}, "
          f"epocas entrenadas = {len(hist.history['loss'])}")

Y la comparación de curvas, el veredicto visual:

import matplotlib.pyplot as plt

fig, ejes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)
for eje, (nombre, (hist, _)) in zip(ejes, resultados.items()):
    eje.plot(hist.history["loss"], label="train")
    eje.plot(hist.history["val_loss"], label="validacion")
    eje.set_title(nombre); eje.set_xlabel("epoca"); eje.legend()
ejes[0].set_ylabel("perdida")
plt.show()

Qué esperar al ejecutarlo:

  • Original: las curvas de 02-05 — train baja sin parar, val se despega y sube. El hueco crece con las épocas.
  • Regularizada: las curvas viajan juntas; la de validación se mantiene igual o incluso por debajo de la de train al principio (recuerda: la de train se mide con dropout activo). El entrenamiento se detiene solo cuando deja de mejorar, y restore_best_weights te devuelve el mejor punto.
  • En test, la versión regularizada suele arañar unas décimas (hacia el ~98 %+) — pero la ganancia importante no es esa: es que ahora el rendimiento de validación predice el de test, porque el modelo generaliza. En problemas con menos datos que MNIST (como el catálogo de TecnoMarket), la diferencia no son décimas: son muchos puntos.

Mención: learning rate schedules

Última pieza, solo como mención (los optimizadores en sí ya se vieron en 02-04): en lugar de un learning rate fijo, es habitual reducirlo durante el entrenamiento — pasos grandes al principio para avanzar, pequeños al final para afinar. Dos formas frecuentes en Keras: keras.callbacks.ReduceLROnPlateau (divide el lr cuando la validación se estanca — combina de maravilla con early stopping) y los schedules predefinidos (decaimiento exponencial, coseno). No es regularización en sentido estricto, pero es de las "técnicas de mejora" más rentables por línea de código.

Errores Comunes y Consejos

  • Regularizar un modelo que subajusta. Si la pérdida de entrenamiento ya es mala, dropout/L2 la empeorarán. Diagnostica primero (bias vs. variance); regulariza solo la varianza.
  • Interpretar mal las curvas con dropout. Que la pérdida de validación quede por debajo de la de entrenamiento no es un error de Keras: la de train se calcula con neuronas apagadas y la de val con la red completa.
  • Aumentar los datos de validación/test. Las capas de aumento deben actuar solo en entrenamiento (las capas Random* de Keras ya lo hacen automáticamente); si aumentas la validación, tus métricas dejan de ser comparables entre épocas.
  • Apilarlo todo a la vez. Si añades L2 + dropout 0.5 + BN + aumento agresivo de golpe y la red no aprende, no sabrás qué sobra. Añade técnicas de una en una, midiendo su efecto en las curvas.
  • Elegir aumentos que cambian la etiqueta. El espejo horizontal destroza dígitos y texto; una rotación de 45° no ocurre en fotos reales de catálogo. Piensa siempre "¿podría llegar así una imagen real, y seguiría siendo la misma clase?".
  • Olvidar restore_best_weights=True. Early stopping sin él detiene el entrenamiento pero te deja los pesos de una época ya degradada.

Ejercicios

Ejercicio 1. Para cada escenario, di si el problema es de bias o de variance y qué dos medidas del arsenal aplicarías primero: (a) el clasificador de reseñas de 04-03 da 99,5 % en train y 78 % en validación; (b) la LSTM de ventas de 04-04 da error alto tanto en train como en validación; (c) el transfer learning de 05-03 con base congelada da curvas train/val pegadas y buenas, pero tras el fine-tuning la de validación empeora época a época.

Ejercicio 2. Diseña la capa Sequential de aumento de datos para las fotos de producto de TecnoMarket (fotos de catálogo: producto centrado, fondo claro, siempre derecho). Justifica cada transformación incluida y nombra una transformación que no incluirías y por qué.

Ejercicio 3. Un compañero entrena con EarlyStopping(monitor="val_loss", patience=0) y se queja de que el entrenamiento "para casi al empezar", en la época 4, aunque la tendencia general de la validación era descendente. Explica qué ocurre y cómo corregirlo.

Soluciones

Solución 1.

  • (a) Hueco enorme train/val → variance (sobreajuste claro). Primeras medidas: dropout en las capas densas/recurrentes del clasificador y early stopping; si persiste, reducir el tamaño del modelo o conseguir más reseñas etiquetadas.
  • (b) Mal en ambas curvas → bias (subajuste). La regularización no ayuda aquí: aumentar la capacidad (más unidades/capas), entrenar más épocas, o mejorar las características de entrada (ventanas más informativas, variables de calendario como el Black Friday).
  • (c) El fine-tuning ha introducido variance: al descongelar, la capacidad efectiva entrenable se disparó con los mismos datos. Medidas: early stopping con restore_best_weights (volver al mejor punto) y aumento de datos; también vale reducir cuántas capas se descongelan o bajar aún más el learning rate.

Solución 2.

aumento = keras.Sequential([
    layers.RandomFlip("horizontal"),   # un producto espejado sigue siendo el
                                       # mismo producto y es plausible
    layers.RandomZoom(0.15),           # el encuadre varia entre fotos reales
    layers.RandomTranslation(0.1, 0.1),# el producto no siempre esta perfectamente centrado
    layers.RandomContrast(0.2),        # iluminacion variable entre sesiones de foto
    layers.RandomRotation(0.02),       # inclinaciones muy leves (camara no perfectamente recta)
])

No incluiría rotaciones grandes (p. ej. RandomRotation(0.25), hasta 90°): las fotos de catálogo muestran siempre el producto derecho, así que la red gastaría capacidad en una invarianza que nunca necesitará en producción — y algunas categorías podrían confundirse giradas. (También sería defendible excluir el flip para productos con texto visible en el frontal, como cajas: el texto espejado no es plausible.)

Solución 3.

Con patience=0, el entrenamiento se detiene en cuanto la val_loss empeora una sola época respecto a la mejor vista. Pero la pérdida de validación oscila de forma natural (batches, dropout, azar), así que un pequeño repunte en la época 4 —aunque la tendencia de fondo sea descendente— dispara la parada. Corrección: dar margen con patience=5 (o 10 en entrenamientos largos) y mantener restore_best_weights=True, de modo que se exploren varias épocas más allá de cada bache y, al parar de verdad, se recuperen los pesos de la mejor época.

Conclusión

Has cerrado la deuda abierta en 02-05: ahora sabes diagnosticar el sobreajuste en las curvas (hueco train/val creciente), distinguirlo del subajuste con la lente bias/variance, y atacarlo con un arsenal ordenado — L2 para pesos comedidos, dropout como comité interno, Batch Normalization para entrenar rápido y estable, early stopping como red de seguridad universal y aumento de datos como fábrica de ejemplos plausibles. Son técnicas transversales: aplícalas retroactivamente a cualquier modelo del curso y, muy especialmente, a los proyectos del módulo 7.

Queda la última pieza del módulo, y es la más influyente de la década: en 04-03 dejamos señalado el cuello de botella del vector único en los modelos secuencia-a-secuencia. La solución —la atención— no solo resolvió ese problema: reorganizó el deep learning entero alrededor de una nueva arquitectura, el Transformer. Es la próxima lección.

Curso de Deep Learning

Módulo 1: Introducción a Deep Learning

Módulo 2: Fundamentos de Redes Neuronales

Módulo 3: Redes Neuronales Convolucionales (CNN)

Módulo 4: Redes Neuronales Recurrentes (RNN)

Módulo 5: Técnicas Avanzadas en Deep Learning

Módulo 6: Herramientas y Frameworks

Módulo 7: Proyectos Prácticos

Módulo 8: Consideraciones Éticas y Futuro del Deep Learning

© Copyright 2026. Todos los derechos reservados