English · Español
Lab 00 — Derivar ∂ softmax/∂x y ∂ CE/∂x en papel, luego verificar numéricamente¶
🇪🇸 La página más importante de la Fase 4. Derivas a mano la Jacobiana de softmax (
diag(p) - p p^T) y el resultado limpiosoftmax(x) - one_hot(y)para cross-entropy. Después, comparas con diferencias finitas.
Objetivo¶
Dos derivaciones en papel, luego una verificación numérica de una pantalla. Al final no deberías tener que "buscar" ∂ CE/∂x nunca más — es solo softmax(x) - one_hot(y), y habrás construido ese resultado tú mismo.
Planteamiento¶
- Una página de cuaderno en blanco.
numpy, el log-sum-exp de la Fase 05.- Un vector de logits sintético de 5 elementos
z = np.array([2.0, 1.0, 0.5, -1.0, 0.0]).
Tareas¶
Parte A — Derivar en papel (sin código)¶
- Derivada de softmax. Partiendo de
p_i = exp(z_i) / Σ_k exp(z_k), deriva∂p_i / ∂z_jtanto parai = jcomo parai ≠ j. Llega a:
$\(\frac{\partial p_i}{\partial z_j} = p_i (\delta_{ij} - p_j)\)$
Escribe la forma matricial explícitamente: J = diag(p) - p p^T.
- Cross-entropy + softmax compuestas. Sea
L = -log p_ydondeyes el índice de la clase verdadera. Deriva∂L / ∂z_j. Pista: usa∂L/∂z_j = Σ_i (∂L/∂p_i)(∂p_i/∂z_j). La mayoría de términos se anulan (∂L/∂p_i = 0parai ≠ y;= -1/p_yparai = y). Tras sustitución y simplificación, deberías llegar a:
$\(\frac{\partial L}{\partial z_j} = p_j - \mathbb{1}[j = y]\)$
Es decir: ∂CE/∂z = softmax(z) - one_hot(y). Bello.
- Comprobación en papel. Para
z = [2, 1, 0]ey = 0, calculap = softmax(z)numéricamente a mano (solo las proporciones; no calcules los valores exp). Verifica quep_0 - 1 < 0yp_1, p_2 > 0— el gradiente empujaz_0hacia arriba yz_1, z_2hacia abajo, que es lo que queremos.
Parte B — Verificación numérica¶
- Implementa softmax (usa el log-sum-exp de la Fase 05):
- Calcula la Jacobiana analíticamente:
- Calcula la Jacobiana numéricamente mediante diferencias finitas centradas:
def softmax_jacobian_fd(z, h=1e-5):
n = len(z)
J = np.zeros((n, n))
for j in range(n):
e = np.zeros(n); e[j] = h
J[:, j] = (softmax(z + e) - softmax(z - e)) / (2 * h)
return J
- Compara. Para
z = [2.0, 1.0, 0.5, -1.0, 0.0]:
J_analytical = softmax_jacobian(z)
J_numerical = softmax_jacobian_fd(z)
max_err = np.max(np.abs(J_analytical - J_numerical))
assert max_err < 1e-7, f"Jacobian mismatch: {max_err}"
- Mismo ejercicio para CE:
def ce_loss(z, y):
return -np.log(softmax(z)[y])
def ce_grad(z, y):
p = softmax(z)
g = p.copy()
g[y] -= 1.0
return g
def ce_grad_fd(z, y, h=1e-5):
g = np.zeros(len(z))
for j in range(len(z)):
e = np.zeros(len(z)); e[j] = h
g[j] = (ce_loss(z + e, y) - ce_loss(z - e, y)) / (2 * h)
return g
for y in range(5):
diff = np.max(np.abs(ce_grad(z, y) - ce_grad_fd(z, y)))
assert diff < 1e-7
- Imprime la Jacobiana. Para refuerzo visual, imprime
J_analyticalcomo matriz y verifica a ojo que la diagonal esp_i (1 - p_i)(máximo en elpmás grande) y los off-diagonales son-p_i p_j(negativos pequeños).
Entregable¶
learners/borja/phase-04/lab-00-softmax-gradient.md que contiene:
- Una foto o transcripción de la derivación en papel (ambas partes de la Parte A).
- La salida del script de verificación (los mensajes assert o los máximos impresos).
- Una reflexión de 3 frases: ¿la derivación se sintió mecánica o reveladora? ¿Dónde te atascaste?
Aceptación¶
- Ambas derivaciones completadas en papel antes de escribir cualquier código.
softmax_jacobiancoincide con diferencias finitas dentro de1e-7.ce_gradcoincide con diferencias finitas dentro de1e-7.- Reflexión escrita.
Escollos¶
- Saltarse la parte en papel. El código es trivial una vez interiorizada la derivación; el valor de este lab es la derivación. Hazlo en papel.
- Diferencias hacia adelante en lugar de centradas. Hacia adelante es
O(h); centradas esO(h²). Conh = 1e-5, hacia adelante da ~1e-5de error, centradas ~1e-10. Usa centradas. hdemasiado pequeño.h = 1e-12desencadena cancelación catastrófica; obtendrás gradientes peores.1e-5es el punto óptimo para fp64; para fp32, usa1e-3.- Usar
np.log(softmax(z))para cross-entropy. Numéricamente inestable. Usalog_softmax(forma logsumexp) de la Fase 05. - Olvidar que la Jacobiana de softmax es simétrica.
diag(p) - p p^Tes simétrica; si tu código produce una matriz no simétrica, tienes un bug.
Siguiente: 01-jacobian-by-hand.md