Skip to content

English · Español

Break 00 — Desactivar el sigmoide de las puertas del LSTM (reemplazar por la identidad)

🇪🇸 Rompemos las puertas: el σ de las puertas forget, input y output se reemplaza por la identidad. Las puertas dejan de estar en (0, 1) y pueden tomar valores no acotados. Predice: la celda c_t crece sin control; el gradiente explota; la pérdida es NaN en pocos pasos.

Anclas: LYNX_CORTEX.md §4 / PHASE 14; theory §02 recurrencia de la RNN; theory §04 flujo del gradiente; .claude/commands/break.md.


La rotura

En src/minimodel/nn/lstm.py:

class LSTM(Module):
    def forward(self, x_t: Tensor, h_prev: Tensor, c_prev: Tensor) -> tuple[Tensor, Tensor]:
        z = self.input_to_hidden(x_t) + self.hidden_to_hidden(h_prev)
        i, f, g, o = z.split(4, dim=-1)
        # BUG: eliminados los sigmoides de las puertas.
        i = i               # era: i.sigmoid()
        f = f               # era: f.sigmoid()
        o = o               # era: o.sigmoid()
        g = g.tanh()        # el candidato mantiene tanh (sin cambios)

        c_t = f * c_prev + i * g
        h_t = o * c_t.tanh()
        return h_t, c_t

Tres cambios de una línea. Fíjate en que mantenemos g.tanh() para que la actualización de la celda candidata siga acotada. Las puertas forget/input/output pierden su acotación.

Predice, luego ejecuta

El rango correcto de la puerta forget es (0, 1). Con la identidad, f_t puede valer -3, 5, -100, lo que sea. La actualización de la celda:

\[ c_t = f_t \odot c_{t-1} + i_t \odot g_t \]

Ahora f_t no está acotada, así que c_t ya no está garantizado acotado. Peor: en el backward,

\[ \frac{\partial c_t}{\partial c_{t-1}} = f_t \quad (\text{no acotado}) \]

así que la autopista del gradiente a través de c_t es ahora Π_{t} f_t — un producto de reales no acotados. El gradiente explosivo es el modo de fallo dominante.

Predicciones

  1. NaN en la pérdida de entrenamiento en 5-20 pasos cuando c_t desborde fp32 (>3.4e38).
  2. Si reduces el LR por 1000×, el modelo aguanta un poco más pero converge a una solución degenerada donde f_t satura a ~0 (olvidándolo todo en la práctica — el LSTM se ha vuelto un mapeo feedforward).
  3. Las normas del gradiente disparan por encima de 1e10 antes del NaN.

Escribe predicciones en learners/borja/phase-14/notes/breaks.md antes de ejecutar.

Observa

just exp 14-train-lstm --tag broken-no-gate-sigmoid

Diagnósticos:

  1. Dibuja ||c_t||_∞ por paso. Debería crecer sin acotarse.
  2. Dibuja las normas del gradiente — >1e6 es la zona de peligro.
  3. Tras el NaN: imprime los valores i, f, o del último paso correcto. Deberían estar muy fuera de (0, 1).

Síntoma que Borja verá

  • loss = NaN en <20 pasos.
  • c_t.max() > 1e30 en el paso anterior al NaN.
  • Norma del gradiente >1e8 en los pasos previos al NaN.

Causa oculta (en una frase)

Se eliminó el sigmoide de las puertas forget/input/output, dejándolas no acotadas — c_t = f_t * c_{t-1} + i_t * g_t desborda porque f_t ya no está en (0, 1).

Cascada de pistas

  1. ¿Cuál es el rango de cada puerta del LSTM, y por qué? Imprime el rango empírico durante la ejecución.
  2. Calcula c_t durante unos pocos pasos a mano — ¿qué pasa si f_t > 1?
  3. Mira el forward del LSTM en nn/lstm.py. ¿Pasan todas las cuatro activaciones pre-puerta por su no-linealidad correcta?

Diff del arreglo

i = i.sigmoid()
f = f.sigmoid()
o = o.sigmoid()

Por qué esto enseña el concepto

El sigmoide en las puertas del LSTM no es estético — es el requisito matemático del mecanismo de gating. f_t ∈ (0, 1) es lo que convierte c_t = f_t · c_{t-1} + ... en una operación controlable de olvido. Sin el sigmoide, ya no tienes "un LSTM" — tienes una recurrencia lineal arbitraria no acotada, que es exactamente la clase de modelo que los LSTM se inventaron para evitar (Hochreiter & Schmidhuber 1997, sección de motivación). Esta rotura es una demostración con las manos de por qué las funciones de activación en arquitecturas con puertas (LSTM, GRU, el softmax de la attention, las puertas de expertos en MoE) están restringidas por razones de flujo del gradiente — no por razones estilísticas.


Siguiente: el /break de la Fase 15 sobre el escalado sqrt(d_k) ausente.