Módulo 5 · Redes neuronales

Lección 5 de 7

Regularización y normalización

En esta páginaEl problema

El problema

Tienes 100 ejemplos etiquetados para entrenar y 500 para validar. Cada ejemplo tiene 20 columnas: las dos primeras son las coordenadas de make_moons con ruido 0.3 y las otras 18 son ruido gaussiano sin relación con la clase. Un MLP 20 → 128 → 128 → 2 con ReLU se entrena con Adam (lr=1e-3) en batches de 25 durante 100 épocas. Estas son sus pérdidas de entropía cruzada:

épocaentrenamientovalidación
10.6490.667
1000.0001.035

El modelo termina con una pérdida de entrenamiento casi nula y una pérdida de validación más alta que después de la primera época. Memorizó los 100 ejemplos, incluido el ruido de las 18 columnas.

Early stopping

La celda plegada genera los datos del problema: 600 puntos de make_moons con 18 columnas de ruido, 100 para entrenamiento y 500 para validación con la misma proporción de clases. StandardScaler se ajusta solo con el entrenamiento, estandariza las dos partes y el resultado se convierte en tensores.

Mostrar códigoOcultar códigoPython
Python 3 · ejemplo reproducible
import numpy as np
import torch
from sklearn.datasets import make_moons
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

lunas, etiquetas = make_moons(n_samples=600, noise=0.3, random_state=0)
ruido = np.random.default_rng(0).normal(size=(600, 18))
X = np.hstack([lunas, ruido])
X_ent, X_val, y_ent, y_val = train_test_split(
    X, etiquetas, train_size=100, stratify=etiquetas, random_state=0
)
escala = StandardScaler().fit(X_ent)
X_ent = torch.tensor(escala.transform(X_ent), dtype=torch.float32)
X_val = torch.tensor(escala.transform(X_val), dtype=torch.float32)
y_ent = torch.tensor(y_ent)
y_val = torch.tensor(y_val)

X_ent tiene forma (100, 20) y X_val, (500, 20): una fila por ejemplo y 20 columnas estandarizadas. y_ent y y_val guardan la clase, 0 o 1. La proporción de clase 1 es 0.5 en entrenamiento y 0.5 en validación.

mlp construye la red 20 → 128 → 128 → 2. perdida_fn es la entropía cruzada, y evaluar la calcula en modo eval y sin registrar gradientes.

Python 3 · ejemplo reproducible
from torch import nn


def mlp():
    return nn.Sequential(
        nn.Linear(20, 128),
        nn.ReLU(),
        nn.Linear(128, 128),
        nn.ReLU(),
        nn.Linear(128, 2),
    )


perdida_fn = nn.CrossEntropyLoss()


def evaluar(modelo, X, y):
    modelo.eval()
    with torch.no_grad():
        return perdida_fn(modelo(X), y).item()

La celda plegada define dos auxiliares del loop de entrenamiento. preparar fija la semilla con torch.manual_seed, construye el modelo, crea torch.optim.Adam con lr=1e-3 y el weight_decay recibido, y un DataLoader con batches de 25 cuyo orden depende de la misma semilla. entrenar_epoca recorre los batches una vez con zero_grad, forward, pérdida, backward y step.

Mostrar códigoOcultar códigoPython
Python 3 · ejemplo reproducible
from torch.utils.data import DataLoader, TensorDataset


def preparar(construir, weight_decay, semilla):
    torch.manual_seed(semilla)
    modelo = construir()
    optimizador = torch.optim.Adam(
        modelo.parameters(), lr=1e-3, weight_decay=weight_decay
    )
    lotes = DataLoader(
        TensorDataset(X_ent, y_ent),
        batch_size=25,
        shuffle=True,
        generator=torch.Generator().manual_seed(semilla),
    )
    return modelo, optimizador, lotes


def entrenar_epoca(modelo, optimizador, lotes):
    modelo.train()
    for xb, yb in lotes:
        optimizador.zero_grad()
        perdida_fn(modelo(xb), yb).backward()
        optimizador.step()

entrenar repite entrenar_epoca y guarda en historial, al final de cada época, la pérdida de entrenamiento y la de validación. Cada vez que la pérdida de validación alcanza un mínimo nuevo, guarda una copia de los pesos con copy.deepcopy(modelo.state_dict()). Con paciencia=p, el entrenamiento se detiene cuando pasan p épocas sin un mínimo nuevo, y load_state_dict devuelve el modelo a los pesos guardados. Esa regla se llama early stopping.

Python 3 · ejemplo reproducible
import copy


def entrenar(construir, epocas=100, weight_decay=0.0, paciencia=None, semilla=0):
    modelo, optimizador, lotes = preparar(construir, weight_decay, semilla)
    historial = []
    mejor_val, mejor_epoca, mejores_pesos = float("inf"), 0, None
    for epoca in range(1, epocas + 1):
        entrenar_epoca(modelo, optimizador, lotes)
        historial.append((evaluar(modelo, X_ent, y_ent), evaluar(modelo, X_val, y_val)))
        if historial[-1][1] < mejor_val:
            mejor_val, mejor_epoca = historial[-1][1], epoca
            mejores_pesos = copy.deepcopy(modelo.state_dict())  # copia, no referencia
        elif paciencia is not None and epoca - mejor_epoca >= paciencia:
            break
    if paciencia is not None:
        modelo.load_state_dict(mejores_pesos)
    return modelo, historial, mejor_epoca

Pregunta. Sin early stopping, ¿en qué época aproximada queda la pérdida de validación más baja? En esa época, ¿la pérdida de entrenamiento ya es casi cero?

Ver resultado

La tabla muestra, para algunas épocas de entrenar(mlp), la pérdida de entrenamiento y la de validación al final de esa época. Por ejemplo, la fila con 10, 0.194 y 0.418 dice que al terminar la época 10 la pérdida de entrenamiento es 0.194 y la de validación es 0.418.

épocaentrenamientovalidación
10.6490.667
50.4340.528
100.1940.418
150.0670.477
250.0090.668
500.0010.870
1000.0001.035

El mínimo de validación está en la época 10.

Lectura. La validación baja durante las primeras épocas y después sube sin volver. La pérdida de entrenamiento sigue bajando todo el tiempo. En el mínimo de validación, el modelo todavía no ha memorizado el conjunto de entrenamiento. A partir de ahí, cada época ajusta mejor los 100 ejemplos y predice peor los 500 nuevos.

Pregunta. Con paciencia=10 y la misma semilla, ¿cuántas épocas se ejecutan? ¿Qué pérdida de validación tiene el modelo devuelto?

Ver resultado

La corrida es entrenar(mlp, paciencia=10). len(historial) cuenta las épocas ejecutadas y evaluar(modelo, X_val, y_val) mide el modelo devuelto:

Se ejecutan 20 épocas, la mejor es la 10 y el modelo devuelto tiene pérdida de validación 0.418.

Lectura. Con la misma semilla, las primeras épocas son idénticas a las de la corrida anterior. El mínimo no se supera en las 10 épocas siguientes, así que el loop se detiene y el modelo vuelve a los pesos de la mejor época. La pérdida de validación del modelo devuelto es la del mínimo, no la de la última época ejecutada.

Weight decay y dropout

Early stopping elige cuándo parar. Otras técnicas cambian lo que el modelo aprende mientras se entrena.

El argumento weight_decay de torch.optim.Adam suma weight_decay · w al gradiente de cada parámetro w. Eso equivale a sumar (weight_decay / 2) · Σ w² a la pérdida: la misma penalización L2 que Ridge controla con alpha. torch.optim.AdamW aplica el decaimiento directamente a los pesos, fuera del gradiente, y su efecto por unidad de weight_decay es distinto.

La celda plegada define resumen, que imprime en una línea la mejor pérdida de validación con su época, las pérdidas de la última época y la norma de los parámetros del modelo.

Mostrar códigoOcultar códigoPython
Python 3 · ejemplo reproducible
def resumen(nombre, modelo, historial, mejor_epoca):
    mejor_val = historial[mejor_epoca - 1][1]
    ent_final, val_final = historial[-1]
    print(
        f"{nombre:<13} mejor val {mejor_val:.3f} (época {mejor_epoca:>2})  "
        f"final: ent {ent_final:.3f}  val {val_final:.3f}  norma {norma(modelo):.1f}"
    )

norma junta todos los parámetros del modelo en un solo vector con torch.cat y calcula su longitud euclídea con .norm(). En resumen("weight decay", *entrenar(...)), el * pasa las tres salidas de entrenar como argumentos.

Pregunta. Con weight_decay=0.05 y la misma semilla, ¿qué cambia más: la mejor pérdida de validación o la pérdida de validación de la época 100? ¿La norma de los parámetros al final será mayor o menor?

Python 3 · ejemplo reproducible
def norma(modelo):
    return torch.cat([p.detach().flatten() for p in modelo.parameters()]).norm().item()


resumen("base", *entrenar(mlp))
resumen("weight decay", *entrenar(mlp, weight_decay=0.05))
Salida esperada

La salida esperada está oculta.

nn.Dropout(p) actúa distinto según el modo del módulo. En modo train, pone en cero cada valor con probabilidad p y multiplica los que sobreviven por 1 / (1 − p).

capa.train() y capa.eval() cambian el modo de la capa, y torch.manual_seed(0) fija qué valores se apagan.

Pregunta. Con p=0.5 y un vector de diez unos, ¿qué valores puede tener la salida en modo train? ¿Qué devuelve en modo eval?

Python 3 · ejemplo reproducible
capa = nn.Dropout(p=0.5)
unos = torch.ones(10)

torch.manual_seed(0)
capa.train()
print(capa(unos))
capa.eval()
print(capa(unos))
Salida esperada

La salida esperada está oculta.

Durante el entrenamiento, cada batch usa una red con neuronas apagadas al azar. Ninguna neurona puede depender de que otra específica esté presente.

mlp_dropout(p) es mlp() con nn.Dropout(p) después de cada ReLU. entrenar llama a construir() sin argumentos, así que lambda: mlp_dropout(0.5) le pasa una función sin argumentos que construye esa red con p=0.5.

Pregunta. Con nn.Dropout(0.5) después de cada ReLU, ¿la mejor época llega antes o después que en la base? ¿La pérdida de validación final baja tanto como con weight decay?

Python 3 · ejemplo reproducible
def mlp_dropout(p):
    return nn.Sequential(
        nn.Linear(20, 128),
        nn.ReLU(),
        nn.Dropout(p),
        nn.Linear(128, 128),
        nn.ReLU(),
        nn.Dropout(p),
        nn.Linear(128, 2),
    )


resumen("dropout 0.5", *entrenar(lambda: mlp_dropout(0.5)))
Salida esperada

La salida esperada está oculta.

Inicialización de pesos

Antes de la primera época, cada capa nn.Linear ya tiene pesos aleatorios. Su escala decide cuánto crecen o se encogen las activaciones al pasar por varias capas. Por defecto, PyTorch toma los pesos de una distribución uniforme con desviación estándar 1 / √(3 · entradas). La inicialización de He (nn.init.kaiming_normal_) usa una normal con desviación estándar √(2 / entradas).

iniciar reescribe los pesos de una capa Linear según el nombre de la inicialización. nn.init.zeros_ pone los sesgos en cero y nn.init.normal_ toma los pesos de una normal con la desviación estándar std. nonlinearity="relu" elige la fórmula de He para ReLU. Con "defecto", la capa queda como la creó PyTorch.

Python 3 · ejemplo reproducible
def iniciar(capa, inicio):
    if inicio == "defecto":
        return
    nn.init.zeros_(capa.bias)
    if inicio == "normal 0.01":
        nn.init.normal_(capa.weight, std=0.01)
    elif inicio == "normal 0.5":
        nn.init.normal_(capa.weight, std=0.5)
    elif inicio == "He":
        nn.init.kaiming_normal_(capa.weight, nonlinearity="relu")

La celda plegada define pila(inicio, entradas, capas, ancho), que encadena capas bloques Linear + ReLU de ancho neuronas y aplica iniciar a cada Linear.

Mostrar códigoOcultar códigoPython
Python 3 · ejemplo reproducible
def pila(inicio, entradas, capas, ancho):
    modulos = []
    for _ in range(capas):
        lineal = nn.Linear(entradas, ancho)
        iniciar(lineal, inicio)
        modulos += [lineal, nn.ReLU()]
        entradas = ancho
    return nn.Sequential(*modulos)

Una pila de diez capas Linear(128, 128) con ReLU recibe 512 vectores con desviación estándar 1. Con cada inicialización y la misma semilla, la desviación estándar de la salida de cada ReLU mide la escala de las activaciones.

Pregunta. Para cada inicialización, ¿la desviación estándar de las activaciones en la capa 10 será cercana a 1, mucho menor o mucho mayor?

Ver resultado

La tabla tiene una fila por inicialización. Cada columna da la desviación estándar de las activaciones después de la ReLU de la capa 1, la 5 y la 10. Por ejemplo, la fila de He dice que esa desviación estándar es 0.829 en la capa 1, 0.752 en la capa 5 y 0.644 en la capa 10.

inicializacióncapa 1capa 5capa 10
defecto0.3410.03230.0318
normal 0.010.06632.46e-066.91e-12
normal 0.53.327706.75e+05
He0.8290.7520.644
Lectura. Con desviación estándar 0.01, cada capa multiplica la escala por un factor pequeño y en la capa 10 las activaciones son casi cero. Con 0.5, cada capa la multiplica por un factor grande y las activaciones explotan. He mantiene la escala en el mismo orden en las diez capas, de 0.829 en la capa 1 a 0.644 en la capa 10: su desviación estándar √(2 / entradas) compensa que la ReLU anule la mitad de los valores y conserva, en promedio, la media de los cuadrados de las activaciones. La desviación estándar queda por debajo de 1 porque las salidas de la ReLU no están centradas en cero. La inicialización por defecto encoge la señal en las primeras capas y después se estabiliza en un valor pequeño, sostenida por los sesgos.

La escala de las activaciones también fija la escala de los gradientes, porque backward() recorre las mismas capas en sentido inverso. La comparación usa una red de ocho capas ocultas de 64 neuronas sobre los datos de las lunas: profunda(inicio) es pila(inicio, entradas=20, capas=8, ancho=64) seguida de una capa de salida Linear(64, 2). Con red = profunda(inicio), tras un backward() sobre los 100 ejemplos de entrenamiento, weight.grad.norm() mide el tamaño del gradiente de los pesos de la primera capa:

Python 3 · ejemplo reproducible
perdida = perdida_fn(red(X_ent), y_ent)
perdida.backward()
red[0][0].weight.grad.norm()  # primera Linear de la pila

Después, entrenar(lambda: profunda(inicio), epocas=20) entrena una red con la misma inicialización.

Pregunta. ¿Qué inicialización tiene el gradiente más pequeño en la primera capa? ¿Cuál tendrá una pérdida inicial muy lejos de ln 2 ≈ 0.693, la de un modelo que asigna 0.5 a cada clase?

Ver resultado

La tabla tiene una fila por inicialización. Las columnas dan la pérdida antes de entrenar, la norma del gradiente de los pesos de la primera capa en ese punto y las pérdidas de entrenamiento y validación al terminar la época 20. Por ejemplo, la fila de He dice que la pérdida inicial es 0.711, el gradiente de la primera capa mide 1.3e-01 y, en la época 20, la pérdida de entrenamiento es 0.000 y la de validación 1.643.

inicializaciónpérdida inicialgradiente capa 1entrenamiento época 20validación época 20
defecto0.7055.6e-040.0701.033
normal 0.010.6932.6e-100.6930.693
normal 0.5464.3872.6e+020.00096.251
He0.7111.3e-010.0001.643
Lectura. Con 0.01, el gradiente de la primera capa es prácticamente cero y la pérdida se queda en 0.693: la red no aprende nada en 20 épocas. Con 0.5, la pérdida inicial es enorme; el modelo llega a pérdida de entrenamiento cero, pero la de validación queda muy alta porque sus salidas son extremas. He y la inicialización por defecto empiezan cerca de 0.693. Con la inicialización por defecto, el gradiente de la primera capa es unas 200 veces menor que con He, y en la época 20 su pérdida de entrenamiento sigue por encima de la de He. Las dos ya sobreajustan: su pérdida de validación pasa de 1.

BatchNorm y LayerNorm

Una capa de normalización resta una media y divide entre una desviación estándar. Después aplica una escala weight y un desplazamiento bias que se aprenden y empiezan en 1 y 0. Las dos capas más comunes difieren en qué valores promedian. En un batch de forma (ejemplos, características), nn.BatchNorm1d normaliza cada característica con los ejemplos del batch. nn.LayerNorm normaliza cada ejemplo con sus propias características.

lote es un batch de cuatro ejemplos con tres características.

Python 3 · ejemplo reproducible
lote = torch.tensor(
    [
        [1.0, 10.0, 100.0],
        [2.0, 20.0, 300.0],
        [3.0, 30.0, 200.0],
        [4.0, 40.0, 400.0],
    ]
)

nn.BatchNorm1d(3) y nn.LayerNorm(3) reciben el número de características. mean(dim=0) promedia cada columna y mean(dim=1), cada fila.

Pregunta. En el batch de cuatro ejemplos, la segunda columna es 10 veces la primera. ¿Qué relación tendrán esas dos columnas en la salida de BatchNorm? ¿Qué salida tendrá media 0 en cada columna y cuál en cada fila? ¿Cuál cambiaría si se agregara un quinto ejemplo al batch?

Python 3 · ejemplo reproducible
bn = nn.BatchNorm1d(3)
ln = nn.LayerNorm(3)

with torch.no_grad():
    salida_bn = bn(lote)
    salida_ln = ln(lote)

print(salida_bn.round(decimals=2))
print(salida_ln.round(decimals=2))
print("medias por columna, BatchNorm:", salida_bn.mean(dim=0).abs().round(decimals=2))
print("medias por fila, LayerNorm:  ", salida_ln.mean(dim=1).abs().round(decimals=2))
Salida esperada

La salida esperada está oculta.

Al evaluar, un modelo puede recibir un solo ejemplo, y BatchNorm necesita aun así una media y una varianza por característica. En modo train, usa las del batch y además actualiza running_mean y running_var con momentum=0.1: nuevo = 0.9 · anterior + 0.1 · estadística del batch. En modo eval, usa esos promedios acumulados en lugar de las estadísticas del batch. torch.equal devuelve True si dos tensores tienen la misma forma y los mismos valores.

Pregunta. Después de la única pasada en modo train, ¿cuánto vale running_mean de la primera columna, cuya media en el batch es 2.5? En modo eval, ¿la primera columna de la salida seguirá teniendo media 0?

Python 3 · ejemplo reproducible
print("running_mean:", bn.running_mean)
print("running_var: ", bn.running_var.round(decimals=2))

bn.eval()
ln.eval()
with torch.no_grad():
    print(bn(lote).round(decimals=2))
    print(torch.equal(ln(lote), salida_ln))
Salida esperada

La salida esperada está oculta.

Comparación controlada

Una comparación atribuye una diferencia a un cambio solo si ese cambio es lo único distinto. En estas variantes, la partición, el número de épocas, el optimizador, el learning rate, el tamaño de batch y las semillas son los mismos. mlp_normalizada inserta una capa de normalización después de cada Linear oculta. Esa variante tiene más capas que mlp(). Recibe la clase de la capa, nn.BatchNorm1d o nn.LayerNorm, y la construye con 128 características.

Python 3 · ejemplo reproducible
def mlp_normalizada(Capa):
    return nn.Sequential(
        nn.Linear(20, 128),
        Capa(128),
        nn.ReLU(),
        nn.Linear(128, 128),
        Capa(128),
        nn.ReLU(),
        nn.Linear(128, 2),
    )

La celda siguiente fija la misma semilla antes de construir cada red y compara los pesos de su primera capa.

Pregunta. ¿Los pesos iniciales de la primera capa de mlp() y de mlp_normalizada(nn.LayerNorm) serán iguales con torch.manual_seed(0)?

Python 3 · ejemplo reproducible
torch.manual_seed(0)
pesos_base = mlp()[0].weight
torch.manual_seed(0)
pesos_ln = mlp_normalizada(nn.LayerNorm)[0].weight
print(torch.equal(pesos_base, pesos_ln))
Salida esperada

La salida esperada está oculta.

Una sola semilla tampoco basta. Otra semilla cambia los pesos iniciales, el orden de los batches y las máscaras de dropout, y eso ya mueve el resultado.

variantes guarda, para cada nombre, los argumentos de entrenar que definen la variante. Todo lo que no aparece toma el valor por defecto de entrenar.

Python 3 · ejemplo reproducible
variantes = {
    "base": dict(construir=mlp),
    "weight decay": dict(construir=mlp, weight_decay=0.05),
    "dropout 0.5": dict(construir=lambda: mlp_dropout(0.5)),
    "BatchNorm": dict(construir=lambda: mlp_normalizada(nn.BatchNorm1d)),
    "LayerNorm": dict(construir=lambda: mlp_normalizada(nn.LayerNorm)),
}

En el loop, entrenar(semilla=semilla, **ajustes) pasa el diccionario de cada variante como argumentos con nombre. La tabla entrena cada variante con las semillas 0, 1 y 2, y reporta la media y el rango (mínimo–máximo) de la mejor pérdida de validación y de la pérdida de validación final.

Pregunta. El rango de la base mide cuánto cambia el resultado solo por la semilla. ¿Qué variantes tendrán una mejor pérdida de validación claramente fuera de ese rango? ¿Y una pérdida final fuera de él?

Python 3 · ejemplo reproducible
print(f"{'variante':<13} {'mejor val':>21}   {'val final':>21}")
for nombre, ajustes in variantes.items():
    mejores, finales = [], []
    for semilla in [0, 1, 2]:
        _, hist, epoca = entrenar(semilla=semilla, **ajustes)
        mejores.append(hist[epoca - 1][1])  # validación en la mejor época
        finales.append(hist[-1][1])
    print(
        f"{nombre:<13} {np.mean(mejores):.3f} ({min(mejores):.3f}–{max(mejores):.3f})   "
        f"{np.mean(finales):.3f} ({min(finales):.3f}–{max(finales):.3f})"
    )
Salida esperada

La salida esperada está oculta.

Práctica

Traza guiada

Antes de procesar, escribe la salida de ln_2(x) fila por fila, la salida de bn_2(x) columna por columna y el valor de bn_2.running_mean después de la llamada.

Python 3 · ejemplo reproducible
x = torch.tensor([[0.0, 4.0], [2.0, 8.0]])
bn_2 = nn.BatchNorm1d(2)
ln_2 = nn.LayerNorm(2)

Comprueba con print dentro de torch.no_grad(). Cada fila de la salida de LayerNorm y cada columna de la de BatchNorm debe tener media 0. Si tu cálculo a mano difiere solo en los últimos decimales, la causa es el pequeño eps que ambas capas suman a la varianza. Para running_mean, aplica momentum=0.1 a la media de cada columna de x.

Intensidad de weight decay

Repite la comparación controlada con la base y weight_decay en [0.01, 0.05, 0.2], con las semillas 0, 1 y 2. Antes de procesar, predice cómo cambian la pérdida de entrenamiento final y la pérdida de validación final al aumentar weight_decay. Comprueba con assert que la norma final de los parámetros baja cuando weight_decay sube. Si la pérdida de validación final deja de bajar con el valor más alto, explica qué muestra la pérdida de entrenamiento final.

Inicialización en la comparación

Escribe mlp_he(), igual que mlp() pero con nn.init.kaiming_normal_(..., nonlinearity="relu") en las dos capas Linear ocultas. Compárala contra mlp() con la misma función entrenar, las semillas 0 a 4 y sin ningún otro cambio. Reporta:

  1. la media y el rango de la mejor pérdida de validación de cada una
  2. la media de la época del mínimo
  3. si la diferencia entre medias es mayor que el rango de la base
  4. una conclusión de una oración que solo afirme lo que muestran los números

Reto opcional

Escribe una función batchnorm_manual(x, media_corrida, var_corrida) que reproduzca nn.BatchNorm1d en modo train con weight=1, bias=0 y eps=1e-5: normaliza con la varianza sesgada del batch y actualiza las estadísticas corridas con la varianza insesgada. Comprueba con torch.allclose que la salida y running_var coinciden con las de nn.BatchNorm1d sobre lote.

Errores que revisar

  • Si BatchNorm lanza Expected more than 1 value per channel when training, un batch tiene un solo ejemplo. Suele ser el último batch del DataLoader; usa drop_last=True o cambia batch_size.
  • Si el mismo modelo da predicciones distintas para la misma entrada, sigue en modo train: dropout apaga neuronas distintas en cada llamada y BatchNorm usa las estadísticas del batch. Llama a modelo.eval() antes de evaluar.
  • Si la pérdida de dos clases se queda en 0.693 desde la primera época, la red predice 0.5 para todo. Revisa la escala de la inicialización y la norma del gradiente de la primera capa.
  • Si una variante cambia a la vez el dropout y el learning rate, la diferencia no se puede atribuir a ninguno. Cambia una cosa y conserva semillas, partición y épocas.
  • Si reportas como resultado final la pérdida de validación de la época que elegiste con esa misma validación, la cifra es optimista. Esa validación ya participó en la decisión.

Cierre

Una variante agrega dropout 0.3 y a la vez sube el learning rate de 1e-3 a 1e-2. Con una sola semilla, su pérdida de validación final es 0.60 y la de la base es 1.10. Explica por qué ese resultado no demuestra que el dropout causó la mejora y describe la comparación que sí lo pondría a prueba.

Lista de comprobación

  • ¿Qué parte de la curva de validación cambia early stopping y cuál cambia weight decay?
  • ¿Por qué nn.Dropout(0.5) multiplica por 2 los valores que conserva en modo train?
  • ¿Sobre qué eje de un batch (ejemplos, características) normaliza BatchNorm y sobre cuál LayerNorm?
  • ¿Qué estadísticas usa BatchNorm en modo eval y de dónde salen?
  • ¿Qué desviación estándar de la inicialización hace que las activaciones se encojan capa tras capa y cuál las conserva con ReLU?

Documentación oficial

  • torch.optim.Adam: algoritmo de Adam y cómo weight_decay modifica el gradiente.
  • torch.nn.Dropout: probabilidad p, escalado por 1 / (1 − p) y comportamiento en eval.
  • torch.nn.init: funciones de inicialización, incluidas normal_ y kaiming_normal_.
  • torch.nn.BatchNorm1d: normalización por característica, momentum y estadísticas corridas en eval.
  • torch.nn.LayerNorm: normalización por ejemplo sobre las últimas dimensiones.
Cargando progreso…