LECCIÓN 4 · FUNDAMENTOS

Autograd a fondo

El motor que hace posible el aprendizaje. Autograd calcula gradientes automáticamente — es el backpropagation de tu curso, pero gratis y para cualquier cálculo que escribas.

Recuerda de las lecciones anteriores: aprendiste a crear tensores y a operar con ellos — aritmética, broadcasting (@ para multiplicar matrices). Hoy añadimos el superpoder que hace posible el entrenamiento: que PyTorch calcule derivadas por ti.

1. El problema que resuelve

Para entrenar una red necesitas la derivada de la pérdida respecto a cada peso (dL/dw). Calcularla a mano para millones de pesos es imposible. Autograd lo hace por ti: escribes solo el cálculo "hacia adelante" y PyTorch deriva el "hacia atrás" automáticamente.

La clave: cada tensor con requires_grad=True graba las operaciones que se le hacen, formando un "grafo computacional". Cuando llamas .backward(), PyTorch recorre ese grafo al revés aplicando la regla de la cadena.

2. requires_grad + backward + .grad

El ciclo completo en 3 pasos. Derivemos y = x³ + 2x en x = 2:

x = torch.tensor(2.0, requires_grad=True)   # 1. marcar como derivable
y = x**3 + 2*x                          # 2. operar (se graba el grafo)
y.backward()                            # 3. calcular gradientes
print(x.grad)                          # el resultado queda en .grad
salida realy = 12.0 x.grad = 14.0
✍️ El cálculo a mano, paso por paso
Valor de y en x=2:
   y = x³ + 2x = 2³ + 2·2 = 8 + 4 = 12          ✓

Derivada término a término:
   d(x³)/dx = 3x²          (regla de la potencia)
   d(2x)/dx = 2
   ⇒ dy/dx = 3x² + 2

Evaluada en x=2:
   3·(2²) + 2 = 3·4 + 2 = 12 + 2 = 14           ✓ = x.grad

3. Varias variables a la vez

Una red tiene muchos parámetros. backward() calcula la derivada respecto a todos los tensores derivables de un golpe. Sea L = a² + 3b:

a = torch.tensor(1.0, requires_grad=True)
b = torch.tensor(2.0, requires_grad=True)
L = a**2 + 3*b
L.backward()
print(a.grad, b.grad)
salida realL = 7.0 dL/da = 2.0 (derivada de a² es 2a = 2·1 = 2) dL/db = 3.0 (derivada de 3b es 3)
✍️ A mano (a=1, b=2)
Valor de L:   L = a² + 3b = 1² + 3·2 = 1 + 6 = 7      ✓

Cuando hay varias variables, se deriva cada una por separado tratando
las demás como si fueran números fijos. Eso se llama "derivada parcial"
y se escribe con el símbolo ∂ (se llama "del" y se lee "dee parcial de"):

Derivada parcial respecto a 'a' (b se trata como constante):
   ∂L/∂a  (se lee "dee parcial de L respecto de a") = 2a + 0 = 2·1 = 2  ✓
Derivada parcial respecto a 'b' (a se trata como constante):
   ∂L/∂b = 0 + 3 = 3

Cada variable recibe su propia derivada parcial, en paralelo. Esto escala a millones de pesos sin cambiar nada del código.

4. Apagar autograd: no_grad y detach

Grabar el grafo cuesta memoria y tiempo. Cuando no vas a entrenar (por ejemplo, al hacer predicciones), conviene apagarlo:

w = torch.tensor(5.0, requires_grad=True)

(w * 2).requires_grad              # normal: graba

with torch.no_grad():               # bloque sin grabación
    (w * 2).requires_grad

(w * 2).detach().requires_grad   # desconecta un tensor del grafo
salida realnormal: True dentro de no_grad: False detach(): False
Usa with torch.no_grad(): al evaluar o predecir — ahorra memoria y va más rápido porque no construye el grafo. .detach() te da una copia de un tensor "desconectada" del grafo. Ejemplo concreto: loss.detach().item() extrae el número de la pérdida para imprimirlo sin arrastrar el historial de cálculos.

5. ¡Cuidado! Los gradientes se ACUMULAN

Un detalle que sorprende a todos: si llamas backward() dos veces, los gradientes se suman en vez de reemplazarse:

x = torch.tensor(1.0, requires_grad=True)
(x * 3).backward()
print(x.grad)        # 1er backward
(x * 3).backward()
print(x.grad)        # 2do backward... ¡acumuló!
salida real1er backward → x.grad = 3.0 2do backward → x.grad = 6.0 (3 + 3, ¡no 3!)
⚠️ Por esto el bucle de entrenamiento siempre empieza con optimizer.zero_grad() (lo verás en la lección 7): hay que poner los gradientes a cero antes de cada paso, o se irían sumando los de pasos anteriores y el entrenamiento se rompería. Es el error olvidado más típico.

6. Lo que aprendiste

Lo que aprendiste hoy: con requires_grad=True marcas los tensores a derivar. Con .backward() PyTorch calcula todas las derivadas parciales (∂L/∂a, ∂L/∂b…) de golpe usando la regla de la cadena. Los gradientes se acumulan — por eso el bucle de entrenamiento siempre empieza con optimizer.zero_grad(). Usa with torch.no_grad(): cuando no necesitas entrenar. Autograd no es magia: es el backprop de tu curso de redes, automatizado.

Tienes los 3 fundamentos: tensores, operaciones y autograd. En la lección 5 construimos redes de verdad con nn.Module.