Juan Diego Andrés PRADA··RAMÍREZ Entrar
Lección 3 de 7

Tensores y Autograd

¿Qué es?

Un tensor es la estructura de datos central de PyTorch: un arreglo n-dimensional, prácticamente un ndarray de NumPy con dos superpoderes. Primero, puede vivir en la GPU para cálculos masivos en paralelo. Segundo, puede recordar las operaciones que lo crearon para calcular derivadas automáticamente. Ese segundo poder se llama autograd.

¿Cómo funciona?

Cuando marcas un tensor con requires_grad=True, PyTorch construye en silencio un grafo computacional: anota cada operación que haces con él. Al final llamas a .backward() sobre un escalar (normalmente la loss) y PyTorch recorre ese grafo hacia atrás aplicando la regla de la cadena, dejando la derivada de la loss respecto a cada parámetro en su atributo .grad.

Esto es exactamente lo que necesita el descenso de gradiente: para minimizar la loss, movemos cada parámetro un pasito en la dirección opuesta a su gradiente. Autograd nos regala los gradientes; nosotros solo aplicamos el paso.

¿Para qué sirve?

Sirve para no tener que derivar a mano. En una red con millones de parámetros, calcular las derivadas manualmente sería imposible. Autograd lo hace por ti, exacto y automático. Es el motor que hace posible entrenar cualquier modelo.

La pieza del proyecto que construimos aquí

Construimos el motor de optimización a mano: una regresión logística (un modelo lineal con sigmoide) que predice el riesgo académico, entrenada con un bucle de descenso de gradiente escrito paso a paso. Entender esto a bajo nivel hará que las capas de alto nivel del próximo módulo no sean magia.

Paso 1: tensores y operaciones básicas

import torch

a = torch.tensor([[1., 2.], [3., 4.]])
b = torch.ones(2, 2)
print(a @ b) # producto matricial
print(a.shape, a.dtype) # torch.Size([2, 2]) torch.float32

# Puente con NumPy
import numpy as np
n = np.array([1.0, 2.0, 3.0])
t = torch.from_numpy(n).float()
print(t)

@ es producto matricial; * es multiplicación elemento a elemento. Confundirlos es una fuente clásica de bugs silenciosos: no fallan, dan el número equivocado.

Paso 2: autograd en acción

x = torch.tensor(3.0, requires_grad=True)
y = x**2 + 2*x + 1 # y = (x+1)^2
y.backward() # calcula dy/dx
print(x.grad) # 2*x + 2 = 8.0 -> tensor(8.)

PyTorch derivó solo. La derivada de x**2 + 2x + 1 es 2x + 2, y en x=3 eso da 8. Coincide.

Paso 3: entrenar la regresión logística a mano

Retomamos los tensores del módulo anterior (X_train_t, y_train_t). Definimos pesos y entrenamos con un bucle explícito.

torch.manual_seed(0)
n_features = X_train_t.shape[1]

W = torch.zeros(n_features, 1, requires_grad=True)
b = torch.zeros(1, requires_grad=True)
lr = 0.1

for epoch in range(200):
 # Forward: predicción
 logits = X_train_t @ W + b
 probs = torch.sigmoid(logits)

 # Loss: entropía cruzada binaria (estable con clamp)
 eps = 1e-7
 probs = probs.clamp(eps, 1 - eps)
 loss = -(y_train_t * torch.log(probs)
 + (1 - y_train_t) * torch.log(1 - probs)).mean()

 # Backward: autograd calcula gradientes
 loss.backward()

 # Paso de descenso de gradiente (sin rastrear este update)
 with torch.no_grad():
 W -= lr * W.grad
 b -= lr * b.grad
 W.grad.zero_() # ¡imprescindible!
 b.grad.zero_()

 if epoch % 40 == 0:
 print(f"epoch {epoch:3d} loss {loss.item():.4f}")

Si olvidas W.grad.zero_(), PyTorch acumula los gradientes de cada iteración sobre los anteriores. El entrenamiento se vuelve inestable o diverge. Poner los gradientes a cero en cada paso es obligatorio.

El bloque with torch.no_grad() le dice a PyTorch: "esto es un ajuste manual, no lo metas en el grafo". Sin él, el propio update generaría gradientes y se haría un lío.

Paso 4: evaluar en el test

with torch.no_grad():
 test_probs = torch.sigmoid(X_test_t @ W + b)
 test_pred = (test_probs > 0.5).float()
 acc = (test_pred == y_test_t).float().mean()
print(f"Accuracy en test: {acc.item():.3f}")

Tech English: tensor, gradient (gradiente), backpropagation (retropropagación), forward/backward pass, learning rate (tasa de aprendizaje), epoch (época, una pasada por los datos).

Ejercicios

  1. Lee la curva de loss. Guarda la loss de cada época en una lista e imprímela (o grafícala). ¿Baja de forma sostenida? Ahora sube el learning rate a 5.0 y vuelve a entrenar. Describe qué le pasa a la loss y explica por qué un paso demasiado grande puede impedir que el modelo converja.
  2. Interpreta los pesos. Tras entrenar, imprime W. Tienes dos features: horas de estudio y asistencia. ¿Qué signo tiene cada peso y qué significa eso para el "riesgo"? ¿Es coherente con la intuición de que más estudio y más asistencia reducen el riesgo?
Tu progreso se guarda en este navegador. Inicia sesión para guardarlo en tu cuenta y verlo desde cualquier dispositivo.