El cierre del curso: un proyecto tabular completo, de los datos a una predicción real, juntando TODO lo aprendido. Cada salida que verás es de una ejecución real en PyTorch.
Queremos predecir el precio de una vivienda a partir de 4 características: tamaño (m²), habitaciones, antigüedad (años) y ubicación (puntuación 1–10). Es un problema de regresión (predecir un número continuo) — el tipo más común en datos de negocio.
# cada fila: [tamaño, habitaciones, antigüedad, ubicación] → precio
X.shape = (600, 4) y.shape = (600, 1)
La regla de oro de la generalización: separar antes de tocar nada (70% / 15% / 15%):
n1, n2 = int(0.7*N), int(0.85*N)
Xtr, Xva, Xte = X[:n1], X[n1:n2], X[n2:]
ytr, yva, yte = y[:n1], y[n1:n2], y[n2:]
Las features están en escalas muy distintas: el tamaño ronda 100, la ubicación 1–10. Sin normalizar, la red da más "peso" a las magnitudes grandes y entrena mal. Las estandarizamos (media 0, desviación 1) usando solo las estadísticas del train:
mu, sd = Xtr.mean(0), Xtr.std(0) # stats SOLO de train
Xtr = (Xtr - mu) / sd # aplicar a los tres conjuntos
Xva = (Xva - mu) / sd
Xte = (Xte - mu) / sd
mu y sd con el train y aplícalos a
val/test. Si usaras las estadísticas de test, estarías "filtrando" información del test al
entrenamiento (data leakage) y tus resultados serían mentira. También normalizamos el
precio para estabilizar el entrenamiento, y lo des-normalizamos al final.
Una red de 2 capas ocultas con ReLU. Entrada 4 features → salida 1 número (el precio):
net = nn.Sequential(
nn.Linear(4, 32), nn.ReLU(),
nn.Linear(32, 16), nn.ReLU(),
nn.Linear(16, 1), # 1 salida: sin activación (regresión)
)
opt = torch.optim.Adam(net.parameters(), lr=0.01)
loss_fn = nn.MSELoss() # regresión → MSE
dl = DataLoader(TensorDataset(Xtr, ytr), batch_size=32, shuffle=True)
for epoca in range(1, 51):
net.train()
for xb, yb in dl:
opt.zero_grad()
loss_fn(net(xb), yb).backward()
opt.step()
net.eval()
with torch.no_grad():
val = loss_fn(net(Xva), yva)
Train y val bajan juntas y se estabilizan: la red aprendió y generaliza bien (la val no se dispara, así que no hay overfitting serio). Reconoces cada línea de este bucle: son los 5 pasos de la lección 7, con lotes (lección 8) y train/eval (lección 9).
La pérdida MSE normalizada no dice mucho a un humano. Calculamos el error absoluto medio (MAE) en euros reales, des-normalizando las predicciones:
net.eval()
with torch.no_grad():
pred = net(Xte).numpy() * ysd + ymu # des-normalizar a euros
mae = np.abs(pred - yte).mean()
El objetivo final: estimar el precio de una vivienda que no está en los datos. Hay que aplicarle la misma normalización y des-normalizar la salida:
nueva = [[120, 3, 5, 8]] # 120m², 3 hab, 5 años, ubicación 8
nueva = (nueva - mu) / sd # misma normalización que el train
with torch.no_grad():
precio = net(nueva).item() * ysd + ymu
Los precios se crearon con: precio = 1500·tamaño + 20000·hab − 800·antigüedad + 15000·ubicación + 50000 Para [tamaño=120, hab=3, antigüedad=5, ubicación=8]: 1500·120 = 180.000 20000·3 = 60.000 −800·5 = −4.000 15000·8 = 120.000 + base = 50.000 ───────────────────────── precio "real" = 406.000 € La red, que NUNCA vio esta fórmula (solo ejemplos con ruido), predijo 412.361 € → error de solo ~6.000 € (<2%). ¡Dedujo la relación casi exacta!
| Paso del proyecto | Lección |
|---|---|
| Tensores y operaciones | 2, 3 |
| El modelo con nn.Module / nn.Linear | 5 |
| ReLU y MSELoss | 6 |
| Optimizador Adam y bucle de 5 pasos | 7 |
| Dataset y DataLoader (lotes) | 8 |
| train/eval, no_grad, validación | 9 |
| Vigilar overfitting con validación | 9 + curso de redes |