12

Secuencias y memoria

¿Cómo procesa una red algo que ocurre en orden, como una frase?

10 min

Secuencias y memoria

Con voz. Puedes pausar y saltar a cualquier paso.

Lección completa

Subtítulos

Mira esta frase: «Las flores que compré en el mercado están marchitas». Para acertar con «están» y no «está», hay que recordar «flores», que quedó seis palabras atrás.

Voz

Sin voz: la lección avanza sola con subtítulos.

La voz de esta lección está en preparación: por ahora avanza sola con subtítulos.

0:00 / 0:00

Qué lograrás

  • Explicar cómo una red recurrente lee una secuencia paso a paso y qué guarda su estado oculto.
  • Predecir qué le pasa a la culpa del error al retroceder por muchos pasos, y por qué se apaga o se vuelve errática.
  • Describir qué hacen las compuertas de una LSTM (olvidar, escribir, leer) y qué límites siguen en pie.

Ideas clave

  1. Una secuencia es un dato cuyo orden importa y cuyo largo varía. Una red recurrente la lee paso a paso con la misma celda y pasa de un paso al siguiente un estado oculto: su memoria.
  2. Desenrollada en el tiempo, una RNN es una red profunda con una capa por paso y los mismos pesos en todas. Entrenarla es retropropagar a través del tiempo.
  3. La culpa del error se multiplica por un factor en cada paso que retrocede. Con factores menores que uno se desvanece y lo lejano no se aprende; con pesos grandes se vuelve errática y el estado se satura.
  4. La LSTM agrega una cinta de memoria y tres compuertas aprendidas (olvido, entrada y salida, con valores entre cero y uno). Con la de olvido cerca de uno, la culpa viaja casi intacta por la cinta.
  5. Aun así, la LSTM va en fila (los pasos no se calculan a la vez), comprime todo en un vector de tamaño fijo y su memoria útil sigue siendo corta: esos límites motivan la atención.

Lo que mucha gente cree

Creencia común Una red recurrente recuerda toda la frase palabra por palabra, como un cuaderno.

Lo correcto Guarda un resumen de tamaño fijo, un puñado de números que se reescribe en cada paso, y lo temprano se desvanece. La LSTM alarga esa memoria con compuertas, pero sigue siendo corta.

Compruébalo

Cuatro preguntas. Sin nota ni cuenta: sirven para ver si la idea quedó.

ConceptoUna red recurrente lee una frase palabra por palabra. ¿Qué es su estado oculto?
Concepto¿Por qué se desvanece el gradiente en una RNN que lee secuencias largas?
CálculoEn cada paso la culpa se multiplica por 0.8. ¿Cuánto queda tras diez pasos?
AplicaciónUn proveedor propone una red recurrente para revisar contratos de cientos de páginas y dice que «recuerda todo el documento». ¿Qué conviene pedirle?

Por dentroPara quien quiere las fórmulas y el código. No hace falta para seguir el curso.

La celda recurrente

Con una entrada xt∈RDx_t\in\mathbb{R}^{D} y un estado ht−1∈RHh_{t-1}\in\mathbb{R}^{H}, la RNN de Elman calcula

ht=tanh⁡ ⁣(Wx xt+Wh ht−1+b),h0=0,h_t=\tanh\!\left(W_x\,x_t+W_h\,h_{t-1}+b\right),\qquad h_0=\mathbf 0,

con Wx∈RH×DW_x\in\mathbb{R}^{H\times D}, Wh∈RH×HW_h\in\mathbb{R}^{H\times H} y b∈RHb\in\mathbb{R}^{H}. Son H(H+D)+HH(H+D)+H parámetros, los mismos en todos los pasos: no crecen con el largo de la frase. Para predecir la palabra siguiente se agrega una capa de salida, y^t=softmax(Wyht)\hat y_t=\mathrm{softmax}(W_y h_t) (lección 19). En la lección, H=6H=6, D=4D=4, sin sesgo (6060 parámetros) y con pesos al azar sin entrenar: solo se ve la mecánica.

La culpa a través del tiempo

La pérdida total es L=∑tLtL=\sum_t L_t. La culpa que llega al estado hkh_k desde el paso TT es la regla de la cadena (lección 8) aplicada a lo largo del tiempo:

∂LT∂hk=∂LT∂hT∏t=k+1T∂ht∂ht−1,∂ht∂ht−1=diag ⁣(1−ht2)Wh.\frac{\partial L_T}{\partial h_k}=\frac{\partial L_T}{\partial h_T}\prod_{t=k+1}^{T}\frac{\partial h_t}{\partial h_{t-1}},\qquad \frac{\partial h_t}{\partial h_{t-1}}=\mathrm{diag}\!\left(1-h_t^{2}\right)W_h .

Es un producto de T−kT-k matrices. La escena muestra el tamaño de ese producto, ∥∂hT/∂hk∥F/H\|\partial h_T/\partial h_k\|_F/\sqrt{H} (la ganancia típica ante un empujón en una dirección al azar; 11 = no se pierde nada). Como ∣1−h2∣≤1|1-h^2|\le 1, si la norma de WhW_h (su mayor valor singular) es g<1g<1, el producto cae como una exponencial: ∥∂hT/∂hk∥≤g T−k\|\partial h_T/\partial h_k\|\le g^{\,T-k}. Con una sigmoide el techo de la derivada es 1/41/4 y todo cae más rápido (lección 8). En general, Pascanu, Mikolov y Bengio (2013) muestran, para una no linealidad con ∣σ′∣≤γ|\sigma'|\le\gamma (γ=1\gamma=1 para tanh, 14\tfrac14 para sigmoide), que λ1<1/γ\lambda_1<1/\gamma **basta** para que la contribución lejana se desvanezca y que λ1>1/γ\lambda_1>1/\gamma es necesario para que explote (λ1\lambda_1: el autovalor mayor en valor absoluto de WhW_h).

Con λ1>1/γ\lambda_1>1/\gamma no hay garantía de explosión: la tanh se satura (1−h2≈01-h^2\approx0) y apaga unidades, como se ve en la escena, donde con pesos grandes la culpa se vuelve errática, enorme en unas frases y casi nula en otras. Dos remedios clásicos: recortar la norma del gradiente contra la explosión (Pascanu et al., 2013) y rediseñar la celda contra el desvanecimiento. Bengio, Simard y Frasconi (1994) añaden un dilema: para guardar un dato de forma estable ante el ruido hace falta que la dinámica contraiga, y eso hace que los gradientes de largo plazo se desvanezcan.

La LSTM

Con compuertas ft,it,ot∈(0,1)Hf_t,i_t,o_t\in(0,1)^H, un candidato c~t\tilde c_t y el producto elemento a elemento ⊙\odot:

ft=σ(Wf[ht−1,xt]+bf),it=σ(Wi[ht−1,xt]+bi),ot=σ(Wo[ht−1,xt]+bo),c~t=tanh⁡(Wc[ht−1,xt]+bc),ct=ft⊙ct−1+it⊙c~t,ht=ot⊙tanh⁡(ct).\begin{aligned} f_t&=\sigma(W_f[h_{t-1},x_t]+b_f), & i_t&=\sigma(W_i[h_{t-1},x_t]+b_i),\\ o_t&=\sigma(W_o[h_{t-1},x_t]+b_o), & \tilde c_t&=\tanh(W_c[h_{t-1},x_t]+b_c),\\ c_t&=f_t\odot c_{t-1}+i_t\odot\tilde c_t, & h_t&=o_t\odot\tanh(c_t). \end{aligned}

La cinta ctc_t se actualiza por suma, no por reescritura. A lo largo de ella, ∂ct/∂ct−1=diag(ft)\partial c_t/\partial c_{t-1}=\mathrm{diag}(f_t) (más caminos indirectos por hh), de modo que la culpa se multiplica solo por la compuerta de olvido: con f≈1f\approx1 llega casi intacta, el «carrusel de error constante» del artículo de 1997. La LSTM original tenía compuertas de entrada y de salida; la de olvido la añadieron Gers, Schmidhuber y Cummins (2000). Un truco práctico: iniciar el sesgo bfb_f cerca de 11 para que la cinta empiece abierta (Jozefowicz et al., 2015). Una capa tiene 4H(H+D)+4H4H(H+D)+4H parámetros: cuatro veces los de la RNN. Con H=D=1000H=D=1000 son 8 004 0008\,004\,000. Las LSTM apiladas llegaron lejos: el sistema de traducción neuronal de Google (Wu et al., 2016) usaba ocho capas LSTM en el codificador y ocho en el decodificador, con atención y conexiones residuales.

En la lección las compuertas son números fijos y hay una sola celda: ct=f ct−1+ittanh⁡(vt)c_t=f\,c_{t-1}+i_t\tanh(v_t), ht=otanh⁡(ct)h_t=o\tanh(c_t), donde vtv_t son pistas de número gramatical ilustrativas (plural para «flores», singular para «mercado») y it=1i_t=1 en la palabra clave. Una red entrenada calcula ftf_t e iti_t de la palabra y del estado. En 2019, Lakretz et al. hallaron que, en una LSTM entrenada como modelo de lenguaje, la información de número de largo alcance la manejan sobre todo dos «unidades de número», justo lo que hace nuestra celda de juguete.

En código

import numpy as np

def influencia(g, pasos):
    """Cuánto de un empujón en el estado inicial sobrevive tras `pasos` pasos (RNN de 2 unidades con tanh)."""
    c, s = np.cos(0.8), np.sin(0.8)
    Wh = g * np.array([[c, -s], [s, c]])        # un giro de 0,8 rad, escalado por g
    x = np.array([0.5, -0.3])                   # la «palabra» que llega en cada paso
    h, P = np.zeros(2), np.eye(2)
    for _ in range(pasos):
        h = np.tanh(Wh @ h + x)
        P = ((1 - h**2)[:, None] * Wh) @ P      # P = J_t · P, con J_t = diag(1 − h²) · Wh
    return np.linalg.norm(P) / np.sqrt(2)

for g in (0.5, 0.9, 1.5, 2.0):
    print(g, [round(float(influencia(g, n)), 4) for n in (1, 5, 10, 20)])
# 0.5 [0.4266, 0.0083, 0.0001, 0.0]
# 0.9 [0.7679, 0.0989, 0.0152, 0.0004]
# 1.5 [1.2798, 0.8562, 1.218, 1.154]
# 2.0 [1.7064, 2.2703, 2.4696, 1.3574]

Con g=0.5g=0.5 o 0.90.9 el empujón se apaga; con g=2g=2 se amplifica; en torno a g=1.5g=1.5 y con esta entrada fija hay una ventana estrecha en que se mantiene. Basta moverse un poco para salir de ella.

import math

pistas = [0, 1.2, 0.3, -0.5, 0.2, -0.1, -0.9]    # pista de número de cada palabra (ilustrativa)

def celda(f, i, o):
    c = 0.0
    for t, v in enumerate(pistas, start=1):
        entrada = 1.0 if t == 2 else i               # «flores» abre la compuerta de entrada
        c = f * c + entrada * math.tanh(v)           # la cinta: olvidar un poco, escribir un poco
    return o * math.tanh(c)                          # lo que se lee al final (positivo = plural)

print(round(celda(0.6, 0.8, 1.0), 3))    # -0.5   (la pista se pierde y gana «mercado»: singular)
print(round(celda(1.0, 0.1, 1.0), 3))    # 0.638  (plural)
print(round(0.97 ** 5, 2))                # 0.86   (culpa que llega a «flores» por la cinta)

Lo que viene

La RNN y la LSTM leen en fila y comprimen todo en un vector; la atención (lección 21) deja que cada palabra mire directamente a las demás, en paralelo. Antes, la lección 13 cuenta por qué el deep learning despegó hacia 2012: datos, GPUs y algunos trucos, entre ellos las conexiones residuales, que en profundidad cumplen un papel parecido al de la cinta de la LSTM.

Lente de decisión

Qué significa esta idea si tu trabajo es decidir, no programar.

  • Memoria teórica no es memoria útil. Cuando un proveedor diga que su modelo «recuerda todo el contexto», pide una prueba con textos tan largos como los tuyos: en modelos de lenguaje LSTM se midió que usaban unas doscientas palabras de contexto.
  • Lo que va en fila cuesta tiempo. Una arquitectura recurrente procesa los pasos de una secuencia uno tras otro: el tiempo de entrenamiento y de respuesta crece con el largo del texto y la GPU se aprovecha mal. Pregunta cómo escala la latencia con la longitud.
  • Los modelos de secuencia siguen siendo razonables en problemas chicos: series de tiempo cortas, sensores, equipos con poco cómputo. Si te proponen una LSTM, pregunta por qué esa y no otra arquitectura, y con qué datos se comparó.
  • El síntoma de una memoria corta es acertar lo reciente y fallar lo que dependía de algo lejano: concordancias, referencias, una cláusula del inicio. Diseña pruebas que pongan el dato clave lejos de la pregunta.

Términos de la lección

Transcripción completa
ComentariosConversación pública de esta lección: dudas, datos por revisar, ideas.

Los comentarios viven en GitHub Discussions, en el repositorio público del curso; para escribir se necesita una cuenta de GitHub. Se cargan desde giscus.app al abrir esta sección. Las personas que revisan el curso los leen, y si un cambio se acepta, se aplica en los dos idiomas. Abrir el hilo en GitHub

Fuentes