Saltar a contenido

Transformers: Atención es todo lo que necesitas

Navegación en esta sección

Capítulos anteriores: - Mecanismos de Atención — atención en modelos seq2seq, CTC y detección de palabras clave.

Estás en Transformers (auto-atención, multi-head, codificación posicional, encoder-decoder enmascarado).

1 De las RNN a los Transformers

Hemos recorrido un camino evolutivo: RNN simples (gradientes desvanecientes) → LSTM/GRU (puertas de memoria) → Seq2Seq (codificador-decodificador) → Atención (vectores de contexto dinámicos). Sin embargo, todas estas arquitecturas comparten una limitación fundamental: procesan la información de forma secuencial, palabra por palabra.

Los Transformers (Vaswani et al., 2017, “Attention Is All You Need”) eliminan completamente la recurrencia y procesan todas las palabras simultáneamente mediante mecanismos de atención. Esto permite:

  1. Paralelización completa: El entrenamiento es órdenes de magnitud más rápido.
  2. Vectores de palabras contextuales: Cada palabra obtiene una representación que depende de todas las demás palabras de la frase.

Los Transformers son la base de los modelos más avanzados: BERT, GPT-4, T5, LLaMA y prácticamente todos los LLMs modernos.

La arquitectura mantiene el patrón codificador-decodificador, pero los bloques internos son completamente diferentes a las RNN:

  • Codificador: 6 bloques apilados. Cada bloque contiene una capa de auto-atención multicabeza seguida de una red feed-forward.
  • Decodificador: 6 bloques apilados. Cada bloque contiene auto-atención enmascarada, atención codificador-decodificador y una red feed-forward.

2 Auto-Atención Escalada por Producto Punto (Scaled Dot-Product Attention)

La auto-atención es el corazón del Transformer. Su objetivo es transformar un vector de palabra fijo en un vector contextualizado que capture su significado en relación con todas las demás palabras de la frase.

2.1 Vectores Query, Key y Value

Para cada palabra de la entrada, se generan tres vectores multiplicando su incrustación \(e\) por tres matrices de pesos aprendibles:

\[ Q = e \cdot W_Q, \qquad K = e \cdot W_K, \qquad V = e \cdot W_V \]
  • Query (\(Q\)): “¿Qué estoy buscando?” — representa la palabra para la que calculamos la atención.
  • Key (\(K\)): “¿Qué información tengo?” — representa cada palabra como una etiqueta de contenido.
  • Value (\(V\)): “¿Cuál es mi contenido real?” — contiene la información que se ponderará.

2.2 Cálculo de la atención

El proceso tiene 4 pasos:

  1. Puntuaciones de atención: Producto escalar entre \(Q\) de la palabra actual y \(K\) de cada palabra:
\[ \text{score}(i, j) = Q_i \cdot K_j \]
  1. Escalado: Dividir por \(\sqrt{d_k}\) (dimensión de los vectores \(K\)) para evitar que los productos escalados sean demasiados grandes y saturen el Softmax:
\[ \text{score}_{\text{escalado}} = \frac{Q \cdot K^T}{\sqrt{d_k}} \]
  1. Softmax: Convertir las puntuaciones en pesos de atención que suman 1:
\[ \alpha_{ij} = \text{softmax}\left(\frac{Q_i \cdot K_j^T}{\sqrt{d_k}}\right) \]
  1. Suma ponderada: Multiplicar los pesos de atención por los vectores \(V\):
\[ \text{Atención}(Q, K, V) = \alpha \cdot V \]

Para la frase “el gato se sentó” con vectores de dimensión \(d_k = 4\):

  1. Cada palabra genera sus vectores \(Q\), \(K\), \(V\) (de dimensión 4).
  2. Para la palabra “gato”, se calcula el producto escalar de \(Q_{\text{gato}}\) con \(K_{\text{el}}\), \(K_{\text{gato}}\), \(K_{\text{se}}\), \(K_{\text{sentó}}\).
  3. Se escalan dividiendo por \(\sqrt{4} = 2\).
  4. Se aplica Softmax → pesos de atención (ej. 0.08, 0.46, 0.08, 0.38).
  5. Se multiplican los pesos por los vectores \(V\) correspondientes y se suman → vector contextualizado de “gato”.

El funcionamiento de la autoatención (self-attention) se puede estructurar paso a paso con el ejemplo numérico siguiente.

2.2.1 1. Planteamiento inicial

Tenemos tres palabras de entrada con sus respectivos vectores embebidos, a partir de los cuales, mediante las matrices de proyección \(W_Q\), \(W_K\) y \(W_V\), recordemos que estas matrices de proyección se entrenan, entonces, obtenemos los vectores de Consulta (\(Q\)), Clave (\(K\)) y Valor (\(V\)):

  • Consultas (\(Q\)):

  • \(Q_1 = [1, 0]\)

  • \(Q_2 = [0, 1]\)

  • \(Q_3 = [1, 1]\)

  • Claves (\(K\)):

  • \(K_1 = [1, 1]\)

  • \(K_2 = [2, 1]\)

  • \(K_3 = [1, 2]\)

  • Valores (\(V\)):

  • \(V_1 = [2, 1]\)

  • \(V_2 = [3, 2]\)

  • \(V_3 = [4, 3]\)

Nuestro objetivo es calcular el vector contextualizado para la tercera palabra (\(Z_3\)), utilizando su vector de consulta \(Q_3\) frente a todas las claves.

2.2.2 2. Paso 1: Cálculo de las puntuaciones de compatibilidad (Scores)

Multiplicamos el vector de consulta \(Q_3\) por cada uno de los vectores clave mediante el producto escalar (\(Q \cdot K^T\)):

\[ \text{score}_1 = Q_3 \cdot K_1 = [1, 1] \cdot [1, 1] = (1 \cdot 1) + (1 \cdot 1) = 2 \]
\[ \text{score}_2 = Q_3 \cdot K_2 = [1, 1] \cdot [2, 1] = (1 \cdot 2) + (1 \cdot 1) = 3 \]
\[ \text{score}_3 = Q_3 \cdot K_3 = [1, 1] \cdot [1, 2] = (1 \cdot 1) + (1 \cdot 2) = 3 \]

Obtenemos el vector de puntuaciones brutas: \([2, 3, 3]\).

2.2.3 3. Paso 2: Aplicación de la función Softmax

Nota: en este ejemplo numérico se omite deliberadamente la división por \(\sqrt{d_k}\) para mantener la aritmética simple. En un Transformer real, el paso previo sería \(\text{score}/\sqrt{d_k}\) antes del softmax.

Para convertir estas puntuaciones en pesos de atención normalizados (porcentajes que suman 1), aplicamos la función \(\text{softmax}\) directamente sobre \([2, 3, 3]\):

\[ \text{softmax}(z_i) = \frac{e^{z_i}}{\sum_{j} e^{z_j}} \]

Calculando las exponenciales:

  • \(e^2 \approx 7.39\)
  • \(e^3 \approx 20.09\)
  • \(e^3 \approx 20.09\)

La suma total es:

\[ \text{Suma} = 7.39 + 20.09 + 20.09 = 47.57 \]

Dividiendo cada valor entre la suma para obtener los pesos de atención (\(\alpha\)):

  • \(\alpha_1 = \frac{7.39}{47.57} \approx 0.16\)
  • \(\alpha_2 = \frac{20.09}{47.57} \approx 0.42\)
  • \(\alpha_3 = \frac{20.09}{47.57} \approx 0.42\)

2.2.4 4. Paso 3: Obtención del vector contextualizado (\(Z_3\))

Finalmente, multiplicamos cada vector de valor (\(V\)) por su respectivo peso de atención y sumamos los resultados:

\[ Z_3 = \alpha_1 V_1 + \alpha_2 V_2 + \alpha_3 V_3 \]

Sustituyendo los valores numéricos:

\[ Z_3 = 0.16 \cdot [2, 1] + 0.42 \cdot [3, 2] + 0.42 \cdot [4, 3] \]

Realizamos las multiplicaciones escalares:

  • \(0.16 \cdot [2, 1] = [0.32, 0.16]\)
  • \(0.42 \cdot [3, 2] = [1.26, 0.84]\)
  • \(0.42 \cdot [4, 3] = [1.68, 1.26]\)

Sumamos las componentes:

\[ Z_3 = [0.32 + 1.26 + 1.68, \quad 0.16 + 0.84 + 1.26] = [3.26, 2.26] \]

El vector \([3.26, 2.26]\) es el nuevo vector embebido contextualizado para la palabra \(X_3\), que ahora lleva impregnada información de toda la secuencia según la relevancia calculada. ¿Te cuadra toda la secuencia matemática con los números que habías planteado?

3 Atención Multicabeza (Multi-Head Attention)

En lugar de una sola cabeza de atención, el Transformer ejecuta múltiples cabezas en paralelo (típicamente 8), cada una con sus propias matrices \(W_Q\), \(W_K\), \(W_V\).

Cada cabeza aprende a enfocarse en un tipo diferente de relación: - Una cabeza puede capturar relaciones sujeto-verbo. - Otra puede capturar relaciones sustantivo-adjetivo. - Otra puede capturar dependencias a larga distancia.

El proceso:

  1. Generación paralela: El vector de entrada se multiplica por las matrices \(W_Q^{(h)}\), \(W_K^{(h)}\), \(W_V^{(h)}\) de cada cabeza \(h\).
  2. Atención independiente: Cada cabeza calcula su propia atención escalada por producto punto, produciendo un vector de salida \(Z_h\).
  3. Concatenación: Los vectores de salida de todas las cabezas se concatenan.
  4. Transformación lineal: El vector concatenado se multiplica por una matriz de pesos final \(W_O\).
\[ \text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \dots, \text{head}_h) \cdot W_O \]

La transformación lineal final con la matriz \(W_O\) (Output) es el paso clave que permite fusionar todo lo que descubrieron las diferentes cabezas de atención y devolver los datos al formato que el resto de la red espera.

Cada cabeza de atención ha estado mirando la frase desde su propio punto de vista (una buscando verbos, otra adjetivos, otra la distancia entre palabras). Por separado tienen información valiosa, pero están aisladas. La multiplicación por \(W_O\) actúa como una mesa redonda de síntesis: coge los outputs independientes de todas las cabezas y permite que la red aprenda a combinar esas diferentes perspectivas en una sola representación unificada y coherente.

Supongamos que el vector de entrada tiene una dimensión total de 4 (por ejemplo, \([0.5, 1.2, -0.3, 0.8]\)). Y queremos usar 2 cabezas de atención.

En lugar de dejar que cada cabeza trabaje con una dimensión de 4 (lo que haría que al concatenar 2 cabezas acabaramos con un vector gigante de 8), el Transformer hace una división inteligente:

  1. Se divide la dimensión entre el número de cabezas: Si la dimensión total es 4 y tenemos 2 cabezas, cada cabeza recibe una porción de 2 dimensiones (\(4 / 2 = 2\)).
  2. Trabajo independiente:

  3. La Cabeza 1 recibe sus 2 dimensiones y genera un vector contextualizado de tamaño 2 (por ejemplo, \([3.26, 2.26]\) como el de tu ejemplo anterior).

  4. La Cabeza 2 recibe sus otras 2 dimensiones y genera otro vector contextualizado también de tamaño 2 (por ejemplo, \([1.15, 4.02]\)).

La concatenación por sí sola ya devuelve el vector a su tamaño original de 4.

La matriz \(W_O\) (que es una matriz de \(4 \times 4\) en este ejemplo) multiplica a ese vector concatenado para mezclar la información de ambas cabezas, permitiendo que la red decida qué peso darle a lo que descubrió la primera cabeza en relación con la segunda. El resultado final de esa multiplicación es un vector de dimensión 4 perfectamente integrado y listo para la siguiente capa. Al igual que el resto de matrices es una matriz que se entrena.

4 Codificación Posicional

Los Transformers procesan todas las palabras en paralelo, por lo que no tienen noción del orden. Sin embargo, “el león mata al tigre” y “el tigre mata al león” tienen significados completamente diferentes.

La codificación posicional genera un vector para cada posición de la secuencia que se suma a la incrustación de la palabra, inyectando información sobre su posición.

4.1 Codificación sinusoidal (fija)

El paper original utiliza funciones seno y coseno con frecuencias decrecientes:

\[ PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d}}\right), \qquad PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d}}\right) \]
  • \(pos\): Es la posición de la palabra en la frase (0 para la primera, 1 para la segunda, 2 para la tercera, etc.).
  • \(d\): Es la dimensión total del vector (por ejemplo, 512).
  • \(i\): Es el índice de la columna o dimensión dentro de ese vector (va desde 0 hasta \(d/2\)).

¿Por qué seno y coseno? - Oscilan entre \(-1\) y \(1\), evitando valores que crezcan indefinidamente. - Cada posición tiene un patrón único y distinguible. - La relación entre posiciones se puede capturar mediante operaciones lineales.

La entrada al Transformer es: \(\text{Entrada} = \text{Incrustación}(e) + \text{Codificación Posicional}(PE)\)

Ahora vamos a ver un ejemplo.

  • Queremos calcular la posición \(pos = 1\) (la segunda palabra de la frase).
  • Supongamos que la dimensión total del vector es \(d = 4\) (es decir, el vector tiene 4 casillas).
  • Como \(d = 4\), el índice \(i\) tomará los valores \(0\) y \(1\) (las dos mitades del vector).

4.1.1 Paso 1: Calcular para el índice \(i = 0\) (Casillas 0 y 1 del vector)

Aquí evaluamos la primera mitad del vector con las frecuencias más rápidas.

  1. Calculamos el denominador: \(10000^{(2 \cdot 0) / 4} = 10000^0 = 1\)
  2. Casilla 0 (Seno): \(\sin\left(\frac{1}{1}\right) = \sin(1) \approx \mathbf{0.84}\)
  3. Casilla 1 (Coseno): \(\cos\left(\frac{1}{1}\right) = \cos(1) \approx \mathbf{0.54}\)

4.1.2 Paso 2: Calcular para el índice \(i = 1\) (Casillas 2 y 3 del vector)

Aquí evaluamos la segunda mitad del vector con frecuencias más lentas gracias al denominador con exponente.

  1. Calculamos el denominador: \(10000^{(2 \cdot 1) / 4} = 10000^{2/4} = 10000^{0.5} = \sqrt{10000} = 100\)
  2. Casilla 2 (Seno): \(\sin\left(\frac{1}{100}\right) = \sin(0.01) \approx \mathbf{0.01}\)
  3. Casilla 3 (Coseno): \(\cos\left(\frac{1}{100}\right) = \cos(0.01) \approx \mathbf{1.00}\)

Juntando los valores de todas las casillas, el vector de codificación posicional para la palabra en la posición 1 es:

\[ PE_{pos=1} = [0.84, \quad 0.54, \quad 0.01, \quad 1.00] \]

5 Normalización de Capas (Layer Normalization)

A medida que un vector pasa por varias capas de atención y multiplicaciones de matrices, los valores numéricos pueden descontrolarse: algunos pueden volverse gigantescos y otros tender a cero. La normalización ajusta constantemente esos valores para mantenerlos en una escala estable (media cero y varianza controlada). Esto evita que el entrenamiento se vuelva inestable o que las matemáticas de la red fallen por desbordamiento.

Después de cada sub-capa (atención o feed-forward), se aplica una conexión residual seguida de normalización de capas:

\[ \text{salida} = \text{LayerNorm}(x + \text{SubCapa}(x)) \]

5.1 Conexión residual

Inmediatamente antes de normalizar, ocurre una operación de suma directa conocida como atajo o conexión residual: el vector de entrada original se le suma al resultado de la atención (\(\text{Entrada} + \text{Atención}(\text{Entrada})\)).

Esto significa que la red no borra lo que tenía; coge el significado base de la palabra y le añade la información contextual que acaba de descubrir.

5.2 Normalización de capas

A diferencia de Batch Normalization (que normaliza por lote), Layer Normalization normaliza cada vector de palabra de forma independiente:

  1. Calcular la media \(\mu\) y desviación estándar \(\sigma\) de todos los elementos del vector.
  2. Normalizar: \(\hat{x}_i = \frac{x_i - \mu}{\sigma}\)
  3. Ajustar con parámetros aprendidos \(\gamma\) (escala) y \(\beta\) (desplazamiento):
\[ \text{LayerNorm}(x) = \gamma \cdot \hat{x} + \beta \]

Los parámetros \(\gamma\) y \(\beta\) se aprenden durante el entrenamiento y permiten al modelo revertir la normalización si es beneficioso.

Veamos un ejemplo, la palabra que estamos procesando tiene este vector original antes de entrar a la atención:

\[ \text{Original} = [2.0, \quad 1.0, \quad 3.0, \quad 4.0] \]

La capa de atención hace sus cálculos y genera este nuevo vector de contexto:

\[ \text{Atención} = [0.5, \quad 3.0, \quad 1.0, \quad 2.0] \]

La conexión residual consiste simplemente en sumar ambos vectores elemento a elemento:

\[ \text{Vector}_{residual} = \text{Original} + \text{Atención} \]
\[ \text{Vector}_{residual} = [2.0 + 0.5, \quad 1.0 + 3.0, \quad 3.0 + 1.0, \quad 4.0 + 2.0] \]
\[ \text{Vector}_{residual} = [2.5, \quad 4.0, \quad 4.0, \quad 6.0] \]

Este vector de cuatro números [2.5, 4.0, 4.0, 6.0] es el que acaba de salir de la suma residual y el que entra directamente en la Layer Normalization.

Aquí es donde ocurre la clave: LayerNorm mira únicamente a los números que forman parte del vector de esa palabra, ignorando por completo a las demás palabras de la frase.

\[ \mu = \frac{2.5 + 4.0 + 4.0 + 6.0}{4} = \frac{16.5}{4} = 4.125 \]

Restamos la media a cada uno, los elevamos al cuadrado y hacemos la media:

  • \((2.5 - 4.125)^2 = (-1.625)^2 = 2.64\)
  • \((4.0 - 4.125)^2 = (-0.125)^2 = 0.015\)
  • \((4.0 - 4.125)^2 = (-0.125)^2 = 0.015\)
  • \((6.0 - 4.125)^2 = (1.875)^2 = 3.515\)

Sumando y dividiendo entre 4, la varianza es \(\sigma^2 \approx 1.546\) (y la desviación estándar \(\sigma \approx 1.24\)).

A cada número de nuestro vector residual se le resta la media (\(4.125\)) y se divide entre la desviación estándar (\(1.24\)). Con esto conseguimos que los valores vuelvan a estar equilibrados (con media 0 y varianza 1).

Por último, la red multiplica esos valores normalizados por los parámetros aprendidos \(\gamma\) y les suma \(\beta\) para adaptar la escala a lo que necesite la siguiente capa (la red de propagación hacia adelante).

6 El Bloque Codificador

Cada uno de los 6 bloques del codificador tiene la misma estructura:

  1. Entrada: Vector de incrustación + codificación posicional.
  2. Auto-Atención Multicabeza: Procesa todas las palabras en paralelo, generando vectores contextualizados.
  3. Add & LayerNorm: Conexión residual + normalización.
  4. Red Feed-Forward: Dos capas lineales con activación ReLU, aplicadas independientemente a cada posición.
  5. Add & LayerNorm: Segunda conexión residual + normalización.

La salida del primer bloque se convierte en la entrada del segundo, y así sucesivamente hasta el sexto bloque.

Una vez que la atención multi-cabeza ha cruzado información entre todas las palabras y ha devuelto los vectores contextualizados, estos pasan por una red neuronal densa tradicional (Feed-Forward). Mientras que la atención sirve para que las palabras “hablen entre sí”, esta red de propagación hacia adelante procesa cada vector de manera independiente para aplicarle transformaciones no lineales complejas y digerir mejor la información.

7 El Bloque Decodificador

El decodificador tiene tres sub-capas en lugar de dos:

  1. Auto-Atención Multicabeza Enmascarada: Similar a la del codificador, pero con una máscara que impide atender a tokens futuros. Esto obliga al modelo a generar la secuencia de forma autorregresiva (palabra por palabra).
  2. Atención Codificador-Decodificador: El decodificador usa sus propios vectores \(Q\) y los vectores \(K\), \(V\) del codificador. Esto permite que el decodificador “consulte” la información de la entrada.
  3. Red Feed-Forward: Igual que en el codificador.

Cada sub-capa tiene su propia conexión residual y normalización.

7.1 El enmascaramiento (Masking)

Durante el entrenamiento, el decodificador recibe toda la secuencia de salida esperada. Sin enmascaramiento, podría “ver” las palabras futuras, lo que impediría el aprendizaje.

La máscara se aplica añadiendo \(-\infty\) a las posiciones futuras en la matriz de puntuaciones de atención antes del Softmax. Esto hace que los pesos de atención para esas posiciones sean \(0\) después del Softmax.