Redes Neuronales — Módulo 8: tu primera red en PyTorch, de principio a fin
Octavo módulo del curso de Redes Neuronales: un programa completo que entrena de verdad, juntando todo lo anterior — cargar MNIST con Dataset y DataLoader, definir el modelo con nn.Module, elegir loss y optimizer, el training loop con batches y la evaluación honesta en el test set.
☰ Índice del contenido
Llevamos siete módulos armando piezas: la neurona, las activaciones, la red feed-forward, la loss, el descenso, backpropagation, el training loop y la regularización. Hasta ahora vimos cada pieza por separado. Hoy las juntamos en un solo programa que entrena de verdad: una red que aprende a reconocer dígitos escritos a mano. Al final vas a tener código completo que corre, y — más importante — vas a reconocer en cada línea un concepto que ya entiendes.
La tesis del módulo
Toda la teoría de este bloque cabe en unas 40 líneas de PyTorch. No hay magia oculta: cargar datos, definir el modelo, elegir loss y optimizer, iterar el loop, evaluar. Si entendiste los módulos anteriores, este código no te enseña nada nuevo — te muestra dónde vivía cada cosa.
El problema: MNIST
Vamos a usar MNIST, el “hola mundo” del deep learning: 70.000 imágenes de dígitos manuscritos (0 al 9), de 28×28 píxeles en blanco y negro. La tarea: dada una imagen, decir qué dígito es. Es un problema de clasificación con 10 clases posibles, chico y limpio, perfecto para ver el pipeline completo sin ahogarse en detalles.
Las piezas de PyTorch que vas a usar
| Componente | Qué es | De qué módulo viene |
|---|---|---|
| Dataset | El acceso a los datos crudos (imágenes + etiquetas) | Datos de entrada |
| DataLoader | Sirve los datos en mini-batches y los baraja | Módulo 6 (batches) |
| nn.Module | La clase base de todo modelo; define las capas | Módulos 1–3 (la red) |
| loss (CrossEntropy) | Mide el error de la predicción | Módulo 4 (pérdida) |
| optimizer (Adam) | Ajusta los pesos con los gradientes | Módulo 6 (optimizers) |
| backward() / step() | Calcula gradientes y actualiza | Módulos 5 y 6 |
El pipeline completo
Antes del código, el mapa de lo que vamos a construir:
START
│
▼
┌──────────────┐ Dataset + DataLoader
│ DATOS │ MNIST en mini-batches
└──────┬───────┘
▼
┌──────────────┐ nn.Module: capas
│ MODELO │ 784 ──► 128 ──► 10
└──────┬───────┘
▼
┌──────────────┐ loop por épocas y batches
│ ENTRENAR │ forward ──► loss ──► backward ──► step
└──────┬───────┘
▼
┌──────────────┐ test set, una sola vez
│ EVALUAR │ ¿cuántos dígitos acierta?
└──────┬───────┘
▼
END: accuracy
Cada caja de este pipeline es un bloque de código abajo, y cada bloque conecta con un módulo anterior. Vamos.
1. Los datos: Dataset y DataLoader
import torch
import torch.nn as nn
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# transforma cada imagen a tensor y normaliza sus valores a un rango estable
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,)), # media y desvío de MNIST
])
# Dataset: descarga MNIST y da acceso a (imagen, etiqueta)
train_ds = datasets.MNIST(root="./data", train=True, download=True, transform=transform)
test_ds = datasets.MNIST(root="./data", train=False, download=True, transform=transform)
# DataLoader: entrega mini-batches. shuffle mezcla el orden cada época (Módulo 6)
train_loader = DataLoader(train_ds, batch_size=64, shuffle=True)
test_loader = DataLoader(test_ds, batch_size=64, shuffle=False)
El Dataset sabe qué datos hay y cómo leer uno. El DataLoader los agrupa en batches de 64 y los baraja — justo lo que el Módulo 6 dijo que necesitábamos para entrenar bien.
2. El modelo: nn.Module
class RedMNIST(nn.Module):
def __init__(self):
super().__init__()
self.red = nn.Sequential(
nn.Flatten(), # 28x28 ──► vector de 784
nn.Linear(784, 128), # capa oculta (Módulos 1 y 3)
nn.ReLU(), # activación (Módulo 2)
nn.Dropout(0.2), # regularización (Módulo 7)
nn.Linear(128, 10), # capa de salida: 10 dígitos
)
def forward(self, x):
return self.red(x) # el forward pass del Módulo 5
modelo = RedMNIST()
Reconoces cada línea: Linear son las neuronas del Módulo 1, ReLU la activación del Módulo 2, Dropout la regularización del Módulo 7. La salida son 10 números, uno por dígito; el más alto es la apuesta de la red.
3. Loss y optimizer
loss_fn = nn.CrossEntropyLoss() # Módulo 4
optimizer = torch.optim.Adam(modelo.parameters(), lr=0.001) # Módulo 6
CrossEntropyLoss es la loss estándar para clasificación: castiga fuerte cuando la red apuesta con confianza por el dígito equivocado. Adam es el default moderno que ya conoces.
4. El training loop
EPOCAS = 5
for epoca in range(EPOCAS):
modelo.train() # modo train: dropout activo (Módulo 7)
for x, y in train_loader: # un mini-batch por iteración
optimizer.zero_grad() # borra gradientes previos (Módulo 5)
pred = modelo(x) # FORWARD
loss = loss_fn(pred, y) # error del batch
loss.backward() # BACKWARD: gradientes (Módulo 5)
optimizer.step() # ajusta los pesos (Módulo 6)
print(f"Época {epoca+1}: loss = {loss.item():.4f}")
Estos son los cinco pasos del Módulo 6, exactos. No hay nada nuevo aquí: es el loop que ya diagramamos, corriendo sobre MNIST de verdad.
5. Evaluar en el test set
modelo.eval() # modo eval: dropout apagado (Módulo 7)
aciertos, total = 0, 0
with torch.no_grad(): # no calculamos gradientes al evaluar
for x, y in test_loader:
pred = modelo(x) # 10 números por imagen
elegido = pred.argmax(dim=1) # el índice del número más alto = dígito
aciertos += (elegido == y).sum().item()
total += y.size(0)
print(f"Accuracy en test: {100 * aciertos / total:.2f}%")
Dos detalles clave, ambos del Módulo 7 y 5: modelo.eval() apaga el dropout para evaluar limpio, y torch.no_grad() le dice a autograd que no arme el grafo de gradientes — no vamos a entrenar, solo a mirar, y así ahorramos memoria y tiempo.
Con esto, una red tan simple llega a ~97% de accuracy en MNIST en pocos minutos. Acaba de aprender a leer dígitos sin que le programaras una sola regla sobre cómo se ve un “7”.
Ojo: el orden de los modos importa.
modelo.train()va antes de entrenar cada época;modelo.eval()va antes de evaluar. Olvidarlos deja dropout y batchnorm en el modo equivocado y ensucia los resultados — el error del Módulo 7 que ahora ves aparecer en código real.
Errores comunes
- Mezclar train y test. Evaluar sobre datos de entrenamiento infla el accuracy y miente. El test set se mira una vez, al final, y nunca se usa para decidir.
- Olvidar
optimizer.zero_grad()en el loop. Los gradientes se acumulan (Módulo 5) y sin borrarlos la red entrena con basura. Silencioso y letal. - Olvidar
modelo.eval()otorch.no_grad()al evaluar. Lo primero deja dropout activo; lo segundo desperdicia memoria armando gradientes inútiles. Ambos ensucian la evaluación. Flattenmal puesto. La imagen entra como 28×28; si no la aplanas a un vector de 784 antes delLinear, las dimensiones no calzan y PyTorch reclama. Es el primer error de dimensiones que todos cometen.- Learning rate al voleo. Si la loss no baja o explota a
NaN, revisa el lr antes que nada (Módulo 6). - Esperar 99% a la primera. MNIST es fácil, pero llegar del 97% al 99% requiere redes más sofisticadas (convolucionales, curso de imágenes). Un 97% con esta red simple ya es un éxito rotundo.
La frase para llevarse
“Cuarenta líneas de PyTorch y siete módulos de teoría: cada import es un concepto que ya entiendes. Aprender el framework fue fácil porque primero entendiste la máquina.”
Qué viene
Cierras el bloque intermedio con una red que entrena de verdad. Ahora arranca el bloque avanzado. Módulo 9: por qué las redes profundas — qué gana una red al tener muchas capas en vez de una gorda, qué representa cada capa, y por qué “deep” en deep learning no es marketing sino el motivo de que todo esto funcione.
Material relacionado
Preguntas sobre este curso
Regístrate gratis para hacer preguntas y guardar tu progreso.
✉ Entérate de lo nuevo
Recibe un aviso cuando publique nuevos cursos, workshops o investigaciones. Sin spam.