Módulo 5 · Redes neuronales
Derivadas y gradientes
En esta páginaEl problema
El problema
Un modelo tiene un solo parámetro w y su pérdida es L(w) = (w − 3)². El mínimo está en w = 3. Tres ejecuciones empiezan en w = 0 y aplican la misma regla de descenso por gradiente. Solo cambia el learning rate η:
η | w tras 3 pasos | tras 20 pasos | L tras 20 pasos |
|---|---|---|---|
0.1 | 1.464 | 2.965 | 0.001 |
1.0 | 6.000 | 0.000 | 9.000 |
1.1 | 8.184 | −112.013 | 13227.944 |
La primera ejecución se acerca a 3. La segunda salta entre 0 y 6 sin avanzar. La tercera se aleja cada vez más, aunque la regla es la misma.
Derivada como pendiente
La derivada de L en un punto mide cuánto cambia L por cada unidad que cambia w, cerca de ese punto. Una forma de aproximarla es la diferencia finita: se mueve w una distancia pequeña h, se mide el cambio de L y se divide entre h.
Pregunta. En w = 0, ¿el cociente es positivo o negativo? Al reducir h, ¿hacia qué número se acerca?
Ver resultado
La tabla da el cociente (L(0 + h) − L(0)) / h para cuatro valores de h: la primera columna es el desplazamiento y la segunda el cambio de L dividido entre ese desplazamiento. Por ejemplo, la fila con 0.1 y −5.9 dice que al pasar de w = 0 a w = 0.1 la pérdida baja de 9 a 8.41, un cambio de −0.59 que equivale a −5.9 por unidad de w.
h | cociente |
|---|---|
| 1.0 | −5.0 |
| 0.1 | −5.9 |
| 0.01 | −5.99 |
| 0.001 | −5.999 |
−6 cuando h se reduce. Ese límite es la derivada en w = 0. Para (w − 3)², la derivada exacta es L'(w) = 2(w − 3), que vale −6 en w = 0. El signo negativo indica que L baja si w aumenta un poco.La diferencia centrada evalúa L a ambos lados de w: (L(w + h) − L(w − h)) / (2h). Con el mismo h suele quedar más cerca de la derivada exacta.
Pregunta. Escribe el signo de la pendiente en w = 0, 2, 3 y 5. ¿En cuál de esos puntos vale cero?
def perdida(w):
return (w - 3) ** 2
def pendiente(f, w, h=1e-4):
return (f(w + h) - f(w - h)) / (2 * h)
for w in [0.0, 2.0, 3.0, 5.0]:
print(w, round(pendiente(perdida, w), 3), 2 * (w - 3))
La salida esperada está oculta.
Gradiente en dos variables
Con dos parámetros, la pérdida es una superficie. Una derivada parcial mide la pendiente respecto a un parámetro mientras el otro queda fijo. El gradiente es el vector que reúne todas las derivadas parciales.
La superficie de esta sección es S(w₁, w₂) = w₁² + 3·w₂². Su mínimo está en (0, 0).
Pregunta. En el punto (2, 1), escribe el valor de S y las dos derivadas parciales. ¿Cuál es mayor?
import numpy as np
def superficie(p):
return p[0] ** 2 + 3 * p[1] ** 2
def gradiente_numerico(f, p, h=1e-4):
grad = np.zeros_like(p)
for i in range(len(p)):
paso = np.zeros_like(p)
paso[i] = h
grad[i] = (f(p + paso) - f(p - paso)) / (2 * h)
return grad
punto = np.array([2.0, 1.0])
g = gradiente_numerico(superficie, punto)
print(superficie(punto))
print(np.round(g, 3))
La salida esperada está oculta.
El gradiente también tiene una dirección. Desde (2, 1), el punto se mueve una distancia de 0.1 en cuatro direcciones: a lo largo de cada eje, en la dirección de g y en la contraria. En cada caso se mide el cambio de S.
Pregunta. ¿Qué dirección produce el mayor aumento de S? ¿Cuál produce la mayor disminución?
Ver resultado
La tabla da el cambio de S al mover el punto (2, 1) una distancia de 0.1 en cada dirección: la primera columna es la dirección y la segunda la diferencia S(nuevo punto) − S(2, 1). Por ejemplo, la fila eje w₂ con +0.630 dice que S sube de 7 a 7.630 al pasar a (2, 1.1).
| dirección | cambio de S |
|---|---|
eje w₁ | +0.410 |
eje w₂ | +0.630 |
| gradiente | +0.745 |
| contra el gradiente | −0.697 |
S y la contraria la baja más. Esa propiedad vale en general: el gradiente apunta hacia el mayor aumento local y −g hacia la mayor disminución local. Por eso el descenso por gradiente resta el gradiente.Regla de la cadena
Una neurona con activación sigmoide calcula su pérdida en tres etapas:
z = w·x + ba = σ(z) = 1 / (1 + e^(−z))L = (a − y)²
La regla de la cadena obtiene dL/dw como producto de las derivadas locales de cada etapa: dL/dw = dL/da · da/dz · dz/dw. Las derivadas locales son dL/da = 2(a − y), da/dz = a(1 − a) y dz/dw = x.
La celda plegada guarda los datos de la neurona (x, b, y y peso) y define sigmoide.
Mostrar códigoOcultar código
x, b, y = 1.5, 0.0, 1.0
peso = 0.8
def sigmoide(z):
return 1 / (1 + np.exp(-z))
Pregunta. Con x = 1.5, b = 0, y = 1 y w = 0.8, escribe el signo de cada factor y el signo del producto. ¿Conviene aumentar o disminuir w?
Ver resultado
La tabla recorre la neurona en w = 0.8: la primera columna es una etapa o una derivada local y la segunda su valor. Por ejemplo, la fila dL/da = 2(a − y) con −0.463 dice que, cerca de a = 0.769, la pérdida cambia −0.463 por cada unidad que aumenta a.
| cantidad | valor en w = 0.8 |
|---|---|
z = w·x + b | 1.2 |
a = σ(z) | 0.769 |
dL/da = 2(a − y) | −0.463 |
da/dz = a(1 − a) | 0.178 |
dz/dw = x | 1.5 |
dL/dw, producto de los tres factores | −0.1235 |
dL/da es negativo porque la salida a queda por debajo de y. da/dz y dz/dw son positivos. El producto es negativo, así que L baja si w aumenta. El valor −0.1235 necesita los tres factores. El signo, en este ejemplo, lo decide solo dL/da: a(1 − a) siempre es positivo y x = 1.5 también.Pregunta. ¿La diferencia centrada y autograd darán el mismo valor que el producto hasta el cuarto decimal?
import torch
def perdida_neurona(w):
return (sigmoide(w * x + b) - y) ** 2
peso_t = torch.tensor(peso, requires_grad=True)
perdida_t = (torch.sigmoid(peso_t * x + b) - y) ** 2
perdida_t.backward()
print(round(pendiente(perdida_neurona, peso), 4))
print(round(peso_t.grad.item(), 4))
La salida esperada está oculta.
Descenso por gradiente
El descenso por gradiente repite un paso: w ← w − η·L'(w). η es el learning rate y fija qué fracción de la derivada se resta en cada paso.
Pregunta. Con η = 0.1 y w = 0, calcula a mano w después del primer paso. ¿Los pasos siguientes serán más largos o más cortos?
def derivada(w):
return 2 * (w - 3)
w = 0.0
eta = 0.1
for paso in range(1, 6):
w = w - eta * derivada(w)
print(paso, round(w, 3), round(perdida(w), 3))
La salida esperada está oculta.
El comportamiento cambia con η. Para comparar varios valores, descenso guarda la trayectoria completa. La distancia al mínimo es w − 3.
Pregunta. Con w = 0, calcula el primer paso para η = 0.1, 0.9, 1.0 y 1.1. Divide la distancia al mínimo después del paso entre la distancia antes del paso. ¿En qué casos cambia de signo?
def descenso(eta, pasos, w=0.0):
historial = [w]
for _ in range(pasos):
w = w - eta * derivada(w)
historial.append(w)
return np.array(historial)
for eta in [0.1, 0.9, 1.0, 1.1]:
trayectoria = descenso(eta, 20)
distancias = trayectoria - 3
razon = distancias[1] / distancias[0]
print(
f"eta={eta}: razón={razon:+.1f} "
f"w3={trayectoria[3]:.3f} w20={trayectoria[20]:.3f} "
f"L20={perdida(trayectoria[20]):.3f}"
)
La salida esperada está oculta.
- con
η = 0.1, la razón es0.8: la distancia se reduce sin cambiar de lado - con
η = 0.9, la razón es−0.8:wsalta de un lado al otro del mínimo, pero la distancia se reduce - con
η = 1.0, la razón es−1:woscila entre 0 y 6 y la distancia nunca cambia - con
η = 1.1, la razón es−1.2: cada salto es 20 % más largo y la pérdida crece sin límite
El descenso converge solo si |1 − 2η| < 1, es decir, si 0 < η < 1. Para una parábola c·(w − 3)² la razón es 1 − 2cη: cuanto más empinada es la curva, menor es el learning rate que todavía converge.
Momentum
Momentum acumula una velocidad v: cada paso suma la derivada actual a una fracción β de la velocidad anterior y mueve w según esa velocidad.
La regla es v ← β·v + L'(w) y w ← w − η·v. torch.optim.SGD con el argumento momentum=β aplica esta misma regla.
Pregunta. Con η = 0.01, ¿qué trayectoria llega antes cerca de w = 3, la de descenso simple o la de momentum con β = 0.9? ¿Alguna pasa de 3?
def con_momentum(eta, beta, pasos, w=0.0):
historial = [w]
v = 0.0
for _ in range(pasos):
v = beta * v + derivada(w)
w = w - eta * v
historial.append(w)
return np.array(historial)
simple = descenso(0.01, 60)
momentum = con_momentum(0.01, 0.9, 60)
for paso in [10, 20, 30, 40, 50, 60]:
print(paso, round(simple[paso], 3), round(momentum[paso], 3))
w_t = torch.tensor(0.0, requires_grad=True)
optimizador = torch.optim.SGD([w_t], lr=0.01, momentum=0.9)
for _ in range(60):
optimizador.zero_grad()
((w_t - 3) ** 2).backward()
optimizador.step()
print(round(w_t.item(), 3))
La salida esperada está oculta.
Learning rate en un valle
Da pasos con distintos learning rates y observa cuándo el punto baja al fondo del valle, cuándo lo cruza de un lado a otro y cuándo se sale.
Qué probar
Después activa momentum y compara su recorrido y su número de pasos con los del descenso simple.
Arrastra el círculo hueco para cambiar el inicio.
La flecha naranja es −g, el gradiente con el signo cambiado: la dirección en la que la pérdida baja más rápido desde el punto actual. La ✕ marca el mínimo.
Práctica
Traza guiada
Antes de procesar, escribe df(1.0), el valor de w después de cada uno de los tres pasos y la razón entre distancias al mínimo w = −1.
def f(w):
return (w + 1) ** 2
def df(w):
return 2 * (w + 1)
w = 1.0
for paso in range(3):
w = w - 0.25 * df(w)
Comprueba con print dentro del ciclo. Compara también df(1.0) con pendiente(f, 1.0). Si la razón no coincide con 1 − 2η, revisa que la distancia se mida respecto a −1 y no respecto a 0.
Parábola más empinada
Cambia la pérdida a 5·(w − 3)² y su derivada a 10·(w − 3). Antes de procesar, calcula la razón 1 − 10η para η = 0.05, 0.1, 0.15 y 0.25. Predice cuál converge sin cambiar de lado, cuál llega al mínimo en un solo paso, cuál oscila y converge, y cuál diverge. Después ejecuta 20 pasos desde w = 0 y comprueba con assert que la pérdida final es menor que 1e-6 en los tres primeros casos.
Descenso en dos variables
Aplica descenso por gradiente a superficie desde (2, 1). Escribe un cálculo que:
- use
gradiente_numericopara el gradiente de cada paso - ejecute 30 pasos con
η = 0.1e imprimaSal final - calcule la razón entre distancias al mínimo en cada coordenada después del primer paso
- encuentre un
ηcon el quew₁converge yw₂diverge - compruebe con
assertque la razón de cada coordenada coincide con1 − 2ηy1 − 6ηcon tolerancia1e-6
Reto opcional
Con η = 0.01 y 60 pasos, ejecuta con_momentum con β = 0.5, 0.7 y 0.9. Antes de procesar, predice cuál tiene el máximo más alto de la trayectoria y cuál termina más cerca de 3. Registra trayectoria.max() y el valor final de cada una.
Errores que revisar
- Si
wse aleja del mínimo desde el primer paso sin cambiar de lado, revisa el signo de la regla. El paso restaη·L'(w). Sumarlo sube la pérdida. - Si
wcambia de lado en cada paso y la pérdida crece,ηes demasiado grande para la curvatura. Calcula la razón1 − 2cηo reduceηa la mitad y repite. - Si una diferencia finita no coincide con la derivada a mano, usa la diferencia centrada con
h = 1e-4. Unhcomo1e-12también falla: la resta de dos números casi iguales pierde dígitos. - Si al derivar una neurona a mano el producto de los factores no coincide con
peso_t.grad, revisa que no falte una derivada interna, comoxoa(1 − a). - Si la trayectoria con momentum pasa del mínimo, revisa el valor de
βantes que el código. La velocidad acumulada produce ese salto aunque la regla esté bien escrita.
Cierre
Usa la razón 1 − 2η para explicar por qué, en L(w) = (w − 3)², η = 0.1 converge, η = 1.0 oscila sin avanzar y η = 1.1 diverge.
Lista de comprobación
- ¿Qué indican el signo y la magnitud de la derivada en un punto?
- ¿Por qué el paso de descenso va en sentido contrario al gradiente?
- ¿Qué tres derivadas locales se multiplican para obtener
dL/dwen la neurona sigmoide? - ¿Cómo cambia el mayor learning rate que converge cuando la parábola es más empinada?
- ¿Qué gana y qué riesgo añade momentum en el ejemplo de
η = 0.01?
Documentación oficial
- Automatic differentiation with torch.autograd: grafo de operaciones,
backward()y cálculo de gradientes con la regla de la cadena. - Optimizing model parameters: learning rate, optimizador y paso de descenso por gradiente en PyTorch.
- torch.optim.SGD: regla de actualización con
momentumy sus parámetros. - scipy.optimize.check_grad: comparación de un gradiente escrito a mano con su aproximación por diferencias finitas.