LECCIÓN 9 · CONSTRUIR Y ENTRENAR

Entrenamiento completo

Juntamos todo lo aprendido en un entrenamiento de verdad: datos, lotes, bucle, validación para vigilar el overfitting, y cómo guardar y recargar el modelo entrenado.

Recuerda de las lecciones anteriores: tienes modelo (nn.Module), pérdida (MSELoss/CrossEntropyLoss), optimizador (Adam), y DataLoader para lotes. El bucle es zero_grad → forward → loss → backward → step. Hoy lo ensamblamos todo en un entrenamiento real con validación.

1. Las piezas que ya tenemos

Un entrenamiento real combina todo el bloque intermedio:

2. El código completo (regresión real)

Predeciremos y = 2·x₁ − 3·x₂ + 1 (con ruido) a partir de 200 ejemplos, 160 para entrenar y 40 para validar:

# datos: 200 ejemplos con 2 features
X = torch.randn(200, 2)
y = (2*X[:,0] - 3*X[:,1] + 1).reshape(-1, 1)  # reshape(-1,1): convierte vector de 200 a 200 filas de 1 columna
Xtr, Ytr = X[:160], y[:160]      # entrenamiento
Xva, Yva = X[160:], y[160:]      # validación

tr_dl = DataLoader(TensorDataset(Xtr, Ytr), batch_size=32, shuffle=True)

net = nn.Sequential(nn.Linear(2, 16), nn.ReLU(), nn.Linear(16, 1))
opt = torch.optim.Adam(net.parameters(), lr=0.05)
loss_fn = nn.MSELoss()

for epoca in range(1, 41):
    net.train()                          # modo entrenamiento
    for xb, yb in tr_dl:                  # bucle interno: un paso por lote; xb e yb son el mini-tensor del lote actual
        opt.zero_grad()
        loss = loss_fn(net(xb), yb)
        loss.backward()
        opt.step()

    net.eval()                           # modo evaluación
    with torch.no_grad():                  # sin gradientes al validar
        val_loss = loss_fn(net(Xva), Yva)
salida real (pérdidas por época)epoca 1: train_loss=4.3737 val_loss=8.4813 epoca 5: train_loss=0.3235 val_loss=0.7114 epoca 10: train_loss=0.0511 val_loss=0.0980 epoca 20: train_loss=0.0179 val_loss=0.0147 epoca 40: train_loss=0.0134 val_loss=0.0127
Ambas pérdidas bajan juntas de ~8 a ~0.013: la red aprendió la relación y generaliza (val_loss baja igual que train_loss, sin dispararse → no hay overfitting). Esa vigilancia del val_loss es exactamente la lección de generalización aplicada en PyTorch.

3. Dos detalles nuevos: train() y eval()

LlamadaCuándoQué hace
net.train()antes de entrenarActiva dropout y BatchNorm en modo entrenamiento
net.eval()antes de validar/predecirLos pone en modo evaluación (comportamiento determinista)
🔑 Combina siempre net.eval() con with torch.no_grad(): al validar o predecir: eval() ajusta capas como dropout, y no_grad() ahorra memoria al no construir el grafo. Olvidar eval() da resultados peores e inconsistentes.

4. Guardar y cargar el modelo entrenado

Entrenar cuesta tiempo; no quieres repetirlo cada vez. Se guarda el state_dict (un diccionario con todos los pesos):

# guardar
torch.save(net.state_dict(), 'modelo.pt')

# cargar (hay que recrear la MISMA arquitectura primero)
net2 = nn.Sequential(nn.Linear(2,16), nn.ReLU(), nn.Linear(16,1))
net2.load_state_dict(torch.load('modelo.pt'))

¿Qué hay dentro del state_dict? Una entrada por cada peso y bias de cada capa:

salida real['0.weight', '0.bias', '2.weight', '2.bias'] (los números son índices de las capas en el Sequential: 0=Linear, 1=ReLU, 2=Linear)

Tras cargar, el modelo predice igual que el original:

salida realpredicción = 0.7903 valor real = 0.9180
💾 Se guarda el state_dict (solo los pesos), no el objeto entero. Por eso al cargar debes recrear la arquitectura idéntica y luego inyectarle los pesos. Es la práctica recomendada en PyTorch.

5. Lo que aprendiste

Lo que aprendiste hoy: un entrenamiento completo = DataLoader de lotes + modelo + pérdida + optimizador + doble bucle (épocas/lotes). Alterna net.train() al entrenar y net.eval() + torch.no_grad() al validar. Vigila el val_loss para detectar overfitting. Guarda con torch.save(net.state_dict(), 'modelo.pt') y carga recreando la arquitectura idéntica.

¡Ya puedes construir y entrenar redes funcionales! Las lecciones avanzadas (GPU, regularización, CNNs, transfer learning) son mejoras sobre esta misma base.