← Todo el material
Curso Intermedio 23 de agosto de 2026

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

ComponenteQué esDe qué módulo viene
DatasetEl acceso a los datos crudos (imágenes + etiquetas)Datos de entrada
DataLoaderSirve los datos en mini-batches y los barajaMódulo 6 (batches)
nn.ModuleLa clase base de todo modelo; define las capasMódulos 1–3 (la red)
loss (CrossEntropy)Mide el error de la predicciónMódulo 4 (pérdida)
optimizer (Adam)Ajusta los pesos con los gradientesMódulo 6 (optimizers)
backward() / step()Calcula gradientes y actualizaMó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() o torch.no_grad() al evaluar. Lo primero deja dropout activo; lo segundo desperdicia memoria armando gradientes inútiles. Ambos ensucian la evaluación.
  • Flatten mal puesto. La imagen entra como 28×28; si no la aplanas a un vector de 784 antes del Linear, 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

Redes neuronalesDeep learningPyTorchMNISTPráctica

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.