English · Español
Lab 02 — Log-sum-exp y entropía cruzada estable desde logits¶
Lee
theory/04-log-sum-exp-and-stability.md. No consultessolutions/.
Objetivo¶
Implementar logsumexp, log_softmax y cross_entropy_from_logits con disciplina completa de estabilidad numérica. Demostrar que las implementaciones ingenuas fallan en entradas adversariales mientras que las estables tienen éxito.
Configuración¶
Continúa en src/phase05/probability.py.
Tareas¶
Tarea 1 — implementaciones ingenuas (para que las veas fallar)¶
Implementa primero las versiones ingenuas:
def logsumexp_naive(z): return np.log(np.exp(z).sum())
def log_softmax_naive(z): return np.log(np.exp(z) / np.exp(z).sum())
def cross_entropy_naive(z, y_star): return -np.log(np.exp(z) / np.exp(z).sum())[y_star]
Pruébalas sobre las siguientes entradas y documenta qué ocurre:
| Entrada \(z\) | Resultado esperado | Resultado ingenuo |
|---|---|---|
[0, 0, 0] |
sensato | debería funcionar |
[1, 2, 3] |
sensato | debería funcionar |
[1000, 1001, 1002] |
debería ser sensato pero no lo será | overflow → inf / NaN |
[-1000, -999, -998] |
debería ser sensato pero no lo será | underflow → 0 → log -inf |
Tarea 2 — logsumexp estable¶
Implementa la versión estable (restar el máximo antes de exp). Vuelve a ejecutar las 4 entradas de la Tarea 1; las 4 deberían producir ahora salidas finitas y correctas. Verifica contra scipy.special.logsumexp.
Tarea 3 — log_softmax estable¶
Mismo ejercicio para log_softmax. Referencia: scipy.special.log_softmax.
Tarea 4 — cross_entropy_from_logits estable¶
def cross_entropy_from_logits(z, y_star):
"""Stable CE from raw logits. Equivalent to PyTorch's F.cross_entropy on a single example."""
return -log_softmax(z)[y_star]
Verifica sobre un pequeño batch sintético.
Tarea 5 — property tests¶
Añade en tests/test_phase05_logsumexp.py:
- Invarianza por desplazamiento. Para cualquier \(c \in \mathbb{R}\):
logsumexp(z + c) == logsumexp(z) + cdentro de tolerancia. - Invarianza por desplazamiento del softmax. Para cualquier \(c\):
log_softmax(z + c)es igual alog_softmax(z)(porque la constante se cancela). - Sanidad de reducción.
log_softmax(z).sum() == log_softmax([z, z]).sum() / 2 * 2— es decir, el resultado está bien definido por fila. - Paridad con referencia. Compara contra
scipy.special.log_softmaxen una batería de entradas (uniforme, puntiaguda, grande, pequeña, negativa).
Tarea 6 — mide velocidad¶
logsumexp sobre forma (B, V) = (64, 600):
- Mide el tiempo de la versión estable en NumPy.
- Mide
scipy.special.logsumexp. - Mide la versión ingenua rota (sólo por contexto — aunque produciría NaN en logits reales, es una comparación útil sobre entradas seguras).
Guarda las mediciones en experiments/<date>-phase-05-logsumexp/timings.csv.
Aceptación¶
- Las 4 entradas de la Tarea 1 documentadas (la ingenua falla como se predijo).
- Las implementaciones estables pasan sobre las 4 entradas.
- Los property tests pasan.
- Paridad con referencia de scipy dentro de
1e-12. - Tiempos capturados.
Escollos esperados¶
np.exp(1002) == np.infen float64; verásRuntimeWarning: overflow encountered in exp— ese es el punto. No silencies el warning; es diagnóstico.np.log(0.0) == -np.inf; la multiplicación río abajo por 0 daNaN. La versión estable evita esto por completo al no calcular nuncanp.logde exponentes subfluidos.- Al restar el máximo, vigila la semántica de ejes:
z.max(axis=-1, keepdims=True)parazen batch. - El
cross_entropy_from_logitsestable está fusionado — nunca calcules log-softmax luego indexes luego log; sólo-log_softmax(z)[y_star].F.cross_entropyde PyTorch hace la misma fusión bajo el capó.
Siguiente: 03-calibration.md