Mecanismos de Atención
1 Repaso a a los conceptos anteriores
1.1 RNN, LSTM, GRU
Las Redes Neuronales Recurrentes (RNN) tienen un bucle interno que les permite procesar datos secuenciales —como texto, audio o series temporales— manteniendo información de los pasos anteriores.
Cada paso temporal (\(t\)) de una red recurrente funciona mediante los siguientes componentes clave:
- Entrada (\(x_t\)): El dato que recibe la red en el momento actual, por ejemplo, una palabra de una frase o el precio de una acción en un día determinado.
- Estado oculto anterior (\(h_{t-1}\)): La memoria o contexto acumulado de los pasos anteriores. Antes de procesar el primer elemento (\(t=1\)), al no existir información previa, el estado oculto inicial \(h_0\) se inicializa habitualmente como un vector de ceros.
- Estado oculto actual (\(h_t\)): La nueva representación de la información disponible hasta el instante \(t\). Se obtiene combinando la entrada actual con el estado oculto anterior mediante una transformación matemática. Este estado cumple una doble función: actúa como memoria que se transmite al siguiente paso (\(t+1\)) y puede utilizarse como base para generar una predicción.
- Salida visible (\(y_t\)): El resultado que genera la red en ese paso concreto, por ejemplo, predecir la siguiente palabra de un texto o la tendencia de una serie temporal.
En una RNN sencilla, el estado oculto se puede expresar mediante la siguiente ecuación:
Donde \(W_{xh}\) transforma la entrada actual, \(W_{hh}\) transforma el estado oculto anterior y \(b_h\) es el término de sesgo (bias). La función \(\tanh\) introduce la no linealidad necesaria para que la red pueda aprender relaciones complejas.
Una característica fundamental de las RNN es que utilizan los mismos pesos sinápticos en cada paso temporal. No importa si la red está procesando el paso 1 o el paso 100; las matrices de pesos que transforman la entrada y el estado oculto son las mismas. Esto reduce considerablemente el número de parámetros que la red necesita aprender y permite procesar secuencias de longitud variable.
La salida visible puede obtenerse a partir del estado oculto mediante otra transformación:
donde \(W_{hy}\) y \(b_y\) son los parámetros que transforman el estado oculto en la salida, y \(f\) depende del tipo de problema.
Aunque el concepto es potente, las RNN estándar sufren de un problema importante cuando las secuencias son muy largas: el desvanecimiento o explosión del gradiente.
Durante el entrenamiento, el error debe propagarse hacia atrás a través de muchos pasos temporales. Debido a las sucesivas multiplicaciones que aparecen durante este proceso, el gradiente puede hacerse extremadamente pequeño, dificultando que la red aprenda dependencias a largo plazo. Este fenómeno se conoce como desvanecimiento del gradiente (vanishing gradient).
En el caso contrario, el gradiente puede crecer excesivamente, provocando el exploding gradient o explosión del gradiente.
Para mitigar estas limitaciones se desarrollaron arquitecturas recurrentes como LSTM (Long Short-Term Memory) y GRU (Gated Recurrent Unit), que incorporan mecanismos de puertas (gates) capaces de controlar qué información conservar, actualizar u olvidar.
Las LSTM y las GRU son evoluciones de las RNN porque mitigan especialmente el problema del desvanecimiento del gradiente y permiten modelar dependencias más largas mediante un sistema de puertas que actúan como filtros de información.
En lugar de transformar toda la información anterior de la misma manera, estas arquitecturas aprenden dinámicamente qué información merece la pena conservar y qué información debe descartarse en cada instante.
- LSTM (Long Short-Term Memory): Utiliza un estado de celda (cell state) que permite transportar información a lo largo de la secuencia y tres puertas principales: la puerta de olvido (forget gate), que determina qué información del estado anterior debe descartarse; la puerta de entrada (input gate), que determina qué información nueva debe incorporarse; y la puerta de salida (output gate), que determina qué información del estado de celda se utiliza para generar el estado oculto actual.
- GRU (Gated Recurrent Unit): Es una arquitectura recurrente más sencilla que una LSTM. Combina algunas de sus estructuras internas y utiliza principalmente dos mecanismos de control: la puerta de actualización (update gate) y la puerta de reseteo (reset gate). En muchas tareas puede obtener resultados comparables a una LSTM utilizando menos parámetros y, habitualmente, con una computación más eficiente.
1.1.1 LSTM:
1.1.2 GRU
De la misma forma:
El símbolo \(\odot\) significa que cada componente de un vector se multiplica por el componente correspondiente del otro vector.
Por ejemplo:
1.2 Redes Neuronales Recurrentes Bidireccionales (Bi-RNN)
Mientras que una RNN normal procesa la secuencia siguiendo una única dirección y, por tanto, en cada posición utiliza principalmente el contexto disponible hasta ese momento, una Bi-RNN procesa la secuencia en ambas direcciones.
- Camino hacia adelante (Forward): Una subred lee la secuencia desde el primer elemento hasta el último (\(x_1\) hasta \(x_T\)), capturando información del contexto anterior.
- Camino hacia atrás (Backward): La otra subred lee la secuencia en orden inverso, desde el último elemento hasta el primero (\(x_T\) hasta \(x_1\)), capturando información del contexto posterior.
- Fusión: En cada posición temporal, las representaciones obtenidas por ambas direcciones se combinan —habitualmente mediante concatenación— para formar un vector enriquecido que contiene información tanto de lo que aparece antes como de lo que aparece después.
De esta forma, cada posición de la secuencia dispone de información procedente de ambos lados.
Esta arquitectura resulta especialmente útil en tareas donde el contexto completo de una frase o secuencia importa para comprender correctamente un elemento concreto. Por ejemplo, en tareas de procesamiento del lenguaje natural como el etiquetado de palabras, conocer tanto las palabras anteriores como las posteriores puede aportar información importante.
1.3 Encoder-Decoder
La arquitectura Encoder-Decoder (Codificador-Descodificador) revolucionó campos como la traducción automática y el resumen de textos.
Su idea fundamental consiste en dividir el problema en dos redes:
1.3.1 1. El Codificador (Encoder)
El codificador es una RNN —que puede ser una RNN estándar, LSTM o GRU— que procesa la secuencia de entrada paso a paso:
A medida que avanza, va actualizando su estado oculto:
En la arquitectura clásica de Encoder-Decoder, el vector de contexto se obtiene a partir del estado final del codificador.
En el caso más sencillo:
Este vector único intenta condensar en una representación de tamaño fijo toda la información relevante de la secuencia de entrada.
En un encoder bidireccional, el contexto puede construirse a partir de los estados finales de ambas direcciones, por ejemplo:
Esto es válido siempre que estemos hablando de la arquitectura clásica de codificador y descodificador sin el mecanismo de atención.
1.3.2 2. El Descodificador (Decoder)
En los modelos clásicos de Encoder-Decoder, el vector de contexto se utiliza habitualmente para inicializar el estado oculto inicial del descodificador. De esta forma, el decoder comienza su proceso disponiendo de una representación resumida de la secuencia que acaba de leer el encoder.
En cada paso, el descodificador recibe principalmente dos elementos:
- La salida anterior (\(y_{t-1}\)): Para generar la salida actual, el decoder utiliza la salida producida en el paso anterior. Durante el entrenamiento, normalmente se utiliza la salida correcta del conjunto de entrenamiento en lugar de la salida que el propio modelo acaba de generar. Esta técnica se denomina Teacher Forcing.
En el primer paso (\(t=1\)), como todavía no existe una salida anterior, se utiliza un token especial de inicio, habitualmente <SOS> (Start of Sequence).
- Su propio estado oculto anterior (\(s_{t-1}\)): Al igual que cualquier RNN, el descodificador mantiene un estado interno que recoge la información que ha procesado hasta ese momento.
Podemos representar el funcionamiento del decoder mediante:
donde \(E(y_{t-1})\) representa el embedding de la salida anterior.
Posteriormente, el estado oculto se utiliza para generar una distribución de probabilidad sobre las posibles salidas:
Por tanto, el descodificador utiliza la memoria global proporcionada por el encoder mientras va generando la nueva secuencia paso a paso y manteniendo su propio estado recurrente.
Aunque esta estructura tuvo un gran éxito, presentaba un problema importante: comprimir una secuencia muy larga en un único vector de contexto de tamaño fijo es difícil.
A medida que aumenta la longitud de la secuencia, el vector de contexto tiene que representar cada vez más información. Como consecuencia, puede resultar difícil conservar con precisión los detalles relevantes de las primeras posiciones.
Este problema se conoce habitualmente como el cuello de botella del vector de contexto.
Para solucionar este cuello de botella surgió el siguiente gran avance: el Mecanismo de Atención (Attention Mechanism).
La idea consiste en permitir que el descodificador, en lugar de depender exclusivamente de un único vector de contexto estático, pueda consultar directamente las representaciones generadas por el codificador y decidir qué partes son más relevantes en cada paso.
En el primer paso del descodificador (\(t=1\)), en la arquitectura clásica sin atención, el decoder comienza con dos elementos:
-
Estado oculto inicial (\(s_0\)): Se obtiene a partir del vector de contexto generado por el encoder.
-
Entrada externa: Recibe el token especial
<SOS>, que indica que debe comenzar a generar la secuencia de salida.
A partir de ahí, el descodificador genera su primera salida \(y_1\), que podrá utilizarse como entrada en el siguiente paso.
2 Mecanismos de atención
El siguiente gran avance en la evolución de los modelos Encoder-Decoder fue el Mecanismo de Atención (Attention Mechanism).
2.1 La solución de la Atención (Bahdanau / Luong)
Introducido alrededor de 2014-2015, el mecanismo de atención cambió radicalmente la forma en que el descodificador utiliza la información proporcionada por el codificador.
En lugar de depender de un único vector de contexto estático, el codificador conserva los estados ocultos de todos sus pasos:
El descodificador puede consultar estas representaciones en cada paso y calcular qué posiciones de la secuencia de entrada son más relevantes para generar la siguiente salida.
El proceso puede resumirse en tres pasos fundamentales:
2.1.1 1. El codificador guarda toda la información
En lugar de utilizar únicamente el último estado oculto, se conservan todos los estados:
Cada uno de estos vectores contiene información relacionada con una posición concreta de la secuencia de entrada.
2.1.2 2. El descodificador calcula una puntuación de atención
En cada paso \(t\) del descodificador, se compara su estado actual con cada uno de los estados del codificador.
Esto produce una puntuación para cada posición:
donde \(e_{t,i}\) indica cuánto debería prestar atención el descodificador a la posición \(i\) de la entrada cuando está generando la salida en el paso \(t\).
Por ejemplo, si la secuencia de entrada tiene 10 palabras, obtendremos 10 puntuaciones.
2.1.3 3. Las puntuaciones se convierten en pesos de atención
Las puntuaciones se pasan por una función Softmax para obtener pesos que suman 1:
Estos valores se denominan pesos de atención (attention weights).
Por ejemplo:
- Vector de la palabra 1: 2%
- Vector de la palabra 2: 85%
- Vector de la palabra 3: 10%
- Resto de palabras: 3% en total.
Los pesos indican qué importancia relativa asigna el modelo a cada posición de la entrada en ese paso concreto.
2.1.4 4. Se construye el vector de contexto dinámico
Finalmente, los estados del codificador se combinan mediante una suma ponderada:
El resultado es un vector de contexto específico para el paso \(t\).
A diferencia del modelo Encoder-Decoder clásico, donde teníamos un único contexto:
ahora tenemos un contexto que cambia en cada paso:
Por eso hablamos de un vector de contexto dinámico.
El descodificador utiliza este contexto junto con su propio estado para generar la siguiente salida:
De esta forma, el modelo puede centrarse en diferentes partes de la secuencia de entrada dependiendo de lo que necesite generar en cada momento.
Imagina que estamos traduciendo la frase del inglés al español:
“The cat sat on the mat”
y el descodificador está generando la palabra “gato”.
Con el modelo Encoder-Decoder clásico, el descodificador dependía principalmente del resumen global contenido en un único vector de contexto.
Con el mecanismo de atención, el modelo puede asignar un peso elevado al estado del codificador asociado a “cat” cuando está generando “gato”.
No existe una regla programada explícitamente que diga cat → gato. El modelo aprende durante el entrenamiento a asignar estos pesos de atención en función de las relaciones que encuentra en los datos.
En el siguiente paso del descodificador, los pesos pueden ser diferentes porque la red necesita prestar atención a otra parte de la secuencia. Por tanto, el vector de contexto también cambia:
La atención permite así superar en gran medida el cuello de botella que suponía intentar condensar toda la secuencia de entrada en un único vector de tamaño fijo.
Este mecanismo fue uno de los avances fundamentales que posteriormente contribuyeron al desarrollo de la arquitectura Transformer, presentada en 2017 en el trabajo Attention Is All You Need.
En la siguiente sección veremos cómo los Transformers llevan la idea de la atención mucho más lejos y eliminan la necesidad de utilizar redes recurrentes como mecanismo principal para procesar las secuencias.
2.2 Atención con un Encoder bidireccional
Cuando el codificador utilizado en un modelo Encoder-Decoder con atención es bidireccional, cada posición de la secuencia contiene información procedente de las dos direcciones:
Por tanto, si la frase de entrada tiene 10 palabras, el codificador genera una secuencia de 10 vectores enriquecidos:
El mecanismo de atención puede consultar todos estos vectores en cada paso del descodificador.
El descodificador, por su parte, sigue siendo una red recurrente unidireccional, ya que genera la secuencia de salida de izquierda a derecha y únicamente puede utilizar la información disponible hasta ese momento.
En el primer paso, el descodificador necesita un estado oculto inicial \(s_0\). Cuando el encoder es bidireccional, este estado puede construirse combinando los estados finales de las dos direcciones.
2.2.1 1. Los ingredientes de los extremos
La subred que procesa la secuencia hacia adelante termina en el último elemento (\(T\)), produciendo:
La subred que procesa la secuencia hacia atrás comienza en el último elemento y termina en el primero, produciendo como estado final:
Por tanto, podemos combinar ambos estados mediante concatenación:
Esta representación contiene información procedente de las dos direcciones de procesamiento.
2.2.2 2. Combinación y transformación
El vector resultante puede tener una dimensión diferente de la que necesita el descodificador. Por eso, se puede utilizar una transformación aprendida para convertirlo al tamaño adecuado:
Donde:
- \([\vec{h}_T;\overleftarrow{h}_1]\) es la concatenación de los estados finales de las dos direcciones del encoder.
- \(W\) es una matriz de pesos aprendida durante el entrenamiento que transforma la representación al tamaño requerido por el decoder.
- \(b\) es el término de sesgo (bias).
- \(\tanh\) introduce una transformación no lineal y produce valores dentro del intervalo \((-1,1)\).
De este modo, el estado inicial \(s_0\) incorpora información procedente de ambas direcciones del encoder.
A partir de ahí, el descodificador comienza a generar sus propios estados paso a paso, utilizando la salida anterior y, gracias al mecanismo de atención, consultando dinámicamente todos los estados generados por el codificador.
El flujo completo puede resumirse así:
y posteriormente, para cada paso del decoder:
La gran diferencia respecto al Encoder-Decoder clásico es que el descodificador ya no depende de un único vector de contexto fijo. En cada paso puede seleccionar dinámicamente qué partes de la secuencia de entrada son más relevantes para producir la siguiente salida.