Redes recurrentes y modelos para secuencias

Capítulo 10

Tiempo estimado de lectura: 9 minutos

+ Ejercicio

¿Qué hace que un dato sea secuencial?

En un dato secuencial, el orden importa: el elemento en la posición t depende (total o parcialmente) de lo que ocurrió antes. Esto aparece en:

  • Texto: una frase es una secuencia de tokens; el significado de una palabra depende del contexto previo.
  • Series temporales: sensores, finanzas o demanda eléctrica; el valor actual suele depender de valores pasados y estacionalidades.
  • Eventos: clics, logs, historial de compras; la secuencia de acciones es informativa.

El reto central es que el modelo debe “recordar” información relevante del pasado para predecir o etiquetar el presente. En redes recurrentes, ese recuerdo se formaliza como un estado oculto.

Idea de estado oculto (hidden state)

En cada paso temporal t, el modelo recibe una entrada x_t y mantiene un vector h_t que resume lo visto hasta ese momento. Operacionalmente, h_t es una memoria comprimida: no guarda todo, sino una representación aprendida útil para la tarea.

Una forma típica de pensarlo es: h_t = f(h_{t-1}, x_t). Si necesitas clasificar una secuencia completa (por ejemplo, sentimiento), el modelo puede producir una salida final y a partir de h_T. Si necesitas etiquetar cada elemento (por ejemplo, POS tagging), produce y_t en cada paso.

RNN básica: cómo funciona y por qué falla en secuencias largas

Definición operacional

Una RNN “clásica” (Elman) actualiza su estado con una transformación afín y una no linealidad:

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

h_t = tanh(W_x x_t + W_h h_{t-1} + b)

y puede producir una salida:

o_t = W_o h_t + c

Lo importante es que W_h se reutiliza en todos los pasos: el mismo bloque se aplica repetidamente a lo largo del tiempo (parámetros compartidos).

Entrenamiento: Backpropagation Through Time (BPTT)

Para entrenar, se “desenrolla” la RNN en el tiempo: se crea una copia conceptual del bloque por cada t, se calcula la pérdida (en el último paso o en todos) y se retropropaga el gradiente a través de esa cadena temporal. Eso hace que el gradiente hacia pasos tempranos sea un producto repetido de derivadas y matrices.

Limitaciones: gradientes que se desvanecen o explotan (visión práctica)

En la práctica, al retropropagar desde t=T hacia t=1, el gradiente se multiplica muchas veces por términos relacionados con W_h y la derivada de la activación (por ejemplo, tanh'), que suele ser menor que 1 en magnitud. Dos escenarios típicos:

  • Gradiente que se desvanece: las multiplicaciones sucesivas reducen el gradiente hasta casi cero. Resultado operativo: el modelo no aprende dependencias lejanas; “olvida” lo que pasó hace muchos pasos.
  • Gradiente que explota: si el producto crece, los gradientes se vuelven enormes. Resultado operativo: entrenamiento inestable, pérdidas que se disparan, NaN, necesidad de recortar gradientes.

Señales comunes en entrenamiento: si al aumentar la longitud de secuencia el rendimiento cae drásticamente o el modelo solo usa información reciente, suele haber desvanecimiento; si aparecen picos en la norma del gradiente o inestabilidad, suele haber explosión.

LSTM y GRU: compuertas para conservar información

La idea clave es introducir mecanismos de compuertas (gates) que controlan qué información se guarda, qué se olvida y qué se expone como estado. Esto crea rutas de gradiente más estables y permite memorias más largas.

LSTM (Long Short-Term Memory)

LSTM mantiene dos estados: estado oculto h_t (lo que se “expone”) y estado de celda c_t (memoria interna). Usa compuertas sigmoides (valores entre 0 y 1) para controlar el flujo:

  • Compuerta de olvido f_t: cuánto de c_{t-1} se conserva.
  • Compuerta de entrada i_t y candidato g_t: qué nueva información se escribe en la celda.
  • Compuerta de salida o_t: qué parte de la celda se proyecta a h_t.
f_t = sigmoid(W_f x_t + U_f h_{t-1} + b_f)  # olvidar/retener memoria previa (0..1)
i_t = sigmoid(W_i x_t + U_i h_{t-1} + b_i)  # cuánto escribir
g_t = tanh(   W_g x_t + U_g h_{t-1} + b_g)  # contenido candidato
c_t = f_t * c_{t-1} + i_t * g_t            # memoria acumulada
o_t = sigmoid(W_o x_t + U_o h_{t-1} + b_o)  # cuánto exponer
h_t = o_t * tanh(c_t)

Interpretación práctica: si f_t se acerca a 1 e i_t a 0 durante varios pasos, la celda mantiene información casi intacta, facilitando dependencias largas.

GRU (Gated Recurrent Unit)

GRU simplifica LSTM: combina memoria y estado en un solo vector h_t y usa dos compuertas principales:

  • Update gate z_t: cuánto mantener del estado anterior.
  • Reset gate r_t: cuánto “resetear” al calcular el candidato.
z_t = sigmoid(W_z x_t + U_z h_{t-1} + b_z)
r_t = sigmoid(W_r x_t + U_r h_{t-1} + b_r)
h~_t = tanh(W_h x_t + U_h (r_t * h_{t-1}) + b_h)
h_t = (1 - z_t) * h_{t-1} + z_t * h~_t

En la práctica, GRU suele entrenar bien con menos parámetros y puede ser una buena primera opción si buscas eficiencia. LSTM puede ser preferible cuando necesitas una memoria más explícita (por su celda c_t), aunque depende del problema.

Esquemas de entrenamiento para tareas con secuencias

Many-to-one: clasificación de secuencia

Objetivo: una etiqueta por secuencia completa. Ejemplos: sentimiento de una reseña, detección de anomalía en una ventana temporal, clasificación de intención en una frase.

Patrón típico:

  • Entrada: x_1, x_2, ..., x_T
  • Modelo recurrente produce estados h_1...h_T
  • Se toma h_T (o un pooling sobre todos los h_t) y se pasa a una capa de clasificación.
# many-to-one (ejemplo conceptual)
for t in 1..T:
  h_t = RNN(x_t, h_{t-1})
y_hat = softmax(W * h_T + b)

Consejo operativo: si la señal relevante puede estar en cualquier parte de la secuencia, a veces funciona mejor usar un resumen como mean/max pooling sobre h_t en lugar de solo h_T, especialmente si T es grande.

Many-to-many: etiquetado por paso (sequence labeling)

Objetivo: una etiqueta por elemento. Ejemplos: etiquetado gramatical (POS), detección de entidades (NER), clasificación de cada instante en una serie temporal (normal/anómalo por tiempo).

Patrón típico:

  • Entrada: x_1...x_T
  • Salida: y_1...y_T
  • Se aplica una capa de salida en cada h_t.
# many-to-many (etiquetado)
for t in 1..T:
  h_t = RNN(x_t, h_{t-1})
  y_hat_t = softmax(W * h_t + b)

En tareas de texto, es común usar variantes bidireccionales (procesar de izquierda a derecha y de derecha a izquierda) para que cada y_t use contexto pasado y futuro, pero incluso en ese caso siguen aplicando las mismas ideas de padding, máscaras y BPTT truncado.

Estrategias prácticas imprescindibles: padding, máscaras y BPTT truncado

1) Padding: hacer lotes (batches) con longitudes distintas

En la práctica, las secuencias tienen longitudes variables. Para entrenar eficientemente en GPU, se agrupan en lotes con una longitud común T_max y se rellena con un token/valor especial (PAD) hasta igualar longitudes.

Ejemplo (texto) con IDs:

SecuenciaOriginalCon padding a T_max=6
A[5, 18, 9][5, 18, 9, 0, 0, 0]
B[4, 7, 2, 11, 3][4, 7, 2, 11, 3, 0]

En series temporales, el padding puede ser ceros u otro valor; lo importante es que el modelo no “aprenda” del relleno como si fuera señal real.

2) Máscaras: evitar que el padding afecte la pérdida y (a veces) el estado

El padding debe ignorarse al calcular la pérdida. Para eso se usa una máscara m_t que vale 1 en posiciones reales y 0 en padding.

En many-to-many, la pérdida total suele ser una suma/medio ponderado por máscara:

# loss por paso (ejemplo)
loss = sum_t ( m_t * CE(y_t, y_hat_t) ) / sum_t m_t

Además, en algunas implementaciones conviene enmascarar la actualización del estado para que, cuando m_t=0, el estado no cambie:

# actualización enmascarada (conceptual)
h_t = m_t * RNN(x_t, h_{t-1}) + (1 - m_t) * h_{t-1}

Esto es especialmente útil si procesas lotes con padding al final y quieres evitar que el modelo “evolucione” sobre tokens inexistentes.

3) Truncated BPTT: entrenar en secuencias largas sin retropropagar hasta el inicio

En secuencias muy largas (por ejemplo, miles de pasos en series temporales), desenrollar toda la secuencia es costoso en memoria y puede empeorar la estabilidad. Truncated BPTT consiste en:

  • Procesar la secuencia por fragmentos de longitud K (por ejemplo, 50 o 100 pasos).
  • Retropropagar solo dentro de cada fragmento.
  • Pasar el estado final del fragmento como estado inicial del siguiente, pero deteniendo el gradiente entre fragmentos (no se retropropaga a través del límite).

Guía paso a paso (operacional):

  • Paso 1: elige K (tamaño de truncamiento) según memoria y dependencia temporal esperada.
  • Paso 2: inicializa h_0 (cero o aprendido) para cada secuencia del lote.
  • Paso 3: para el fragmento 1, procesa x_1...x_K, calcula la pérdida del fragmento y actualiza parámetros.
  • Paso 4: toma el estado h_K como inicio del siguiente fragmento, pero aplica “detach/stop_gradient” para que el grafo no crezca.
  • Paso 5: repite con x_{K+1}...x_{2K}, etc., hasta cubrir la secuencia.
# esquema conceptual de truncated BPTT
h = h0
for chunk in chunks(x, K):
  h = stop_gradient(h)
  for x_t in chunk:
    h = RNN(x_t, h)
    ... acumular pérdida ...
  backprop_y_update()

Trade-off: reduces coste y estabilizas entrenamiento, pero limitas cuánto puede “aprender” el gradiente sobre dependencias más allá de K. Aun así, el estado puede transportar información hacia adelante; lo que se recorta es la señal de aprendizaje a través de muchos pasos.

Checklist práctico para implementar un modelo recurrente en un proyecto real

  • Define el formato de entrada: tokens (texto) o vectores numéricos por paso (series temporales). Asegura normalización/estandarización en series.
  • Elige arquitectura: empieza con GRU si buscas simplicidad; usa LSTM si sospechas dependencias largas y quieres más control.
  • Selecciona esquema de salida: many-to-one para etiqueta global; many-to-many para etiqueta por paso.
  • Gestiona longitudes variables: aplica padding y usa máscaras en la pérdida (y opcionalmente en el estado).
  • Si la secuencia es larga: usa truncated BPTT con un K razonable.
  • Estabilidad: si observas explosión de gradientes, aplica recorte (gradient clipping) y revisa la escala de entradas; si hay olvido, considera LSTM/GRU, ajustar K o usar representaciones más informativas.

Ahora responde el ejercicio sobre el contenido:

¿Cuál es el propósito principal de usar máscaras al entrenar con secuencias rellenadas (padding) en un esquema many-to-many?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

Las máscaras indican qué pasos son reales (1) y cuáles son padding (0), permitiendo excluir el relleno del cálculo de la pérdida y, opcionalmente, evitar que el estado se actualice cuando la entrada es padding.

Siguiente capítulo

Atención y transformadores en deep learning

Arrow Right Icon
Portada de libro electrónico gratuitaIntroducción a las Redes Neuronales: Del Perceptrón al Deep Learning
77%

Introducción a las Redes Neuronales: Del Perceptrón al Deep Learning

Nuevo curso

13 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.