Capítulo 3: Atención y el auge de los Transformers
3.3 Autoatención y Atención Multi-Cabeza
Sobre la base de los mecanismos de atención, la autoatención surgió como una innovación revolucionaria en el procesamiento del lenguaje natural (NLP). Este enfoque transformador cambió la forma en que los modelos procesan secuencias de entrada al introducir un mecanismo en el que cada elemento de una secuencia puede interactuar directamente con todos los demás. Esta interacción directa permite a los modelos procesar secuencias de entrada con una eficiencia y conciencia contextual sin precedentes, eliminando los cuellos de botella tradicionales del procesamiento secuencial.
La autoatención permite que cada token consulte y atienda simultáneamente a todos los demás tokens en la secuencia. Por ejemplo, al procesar la oración "The cat sat on the mat", cada palabra puede evaluar directamente su relación con todas las demás, ayudando al modelo a entender tanto relaciones locales (como "the cat") como dependencias a larga distancia (conectar "cat" con "sat").
Cuando se combina con la atención multi-cabeza, esta capacidad se vuelve aún más poderosa. La atención multi-cabeza permite al modelo mantener múltiples patrones de atención diferentes simultáneamente, cada uno enfocado en diferentes aspectos de las relaciones entre los tokens. Este enfoque multifacético es la base de los modelos Transformer, capacitando a los modelos para capturar relaciones complejas entre tokens en una secuencia desde múltiples perspectivas al mismo tiempo.
En esta sección exploraremos la autoatención y su extensión a la atención multi-cabeza, examinando cómo funcionan estos mecanismos y por qué son fundamentales en las arquitecturas Transformer. Analizaremos sus fundamentos matemáticos, sus implementaciones prácticas y demostraremos su efectividad con ejemplos concretos y código.
3.3.1 ¿Qué es la autoatención?
En la autoatención, cada token en una secuencia de entrada atiende a todos los demás tokens (incluyéndose a sí mismo) para calcular una nueva representación. Este mecanismo revolucionario crea conexiones dinámicas entre todos los elementos de una secuencia. Por ejemplo, al procesar una oración, cada palabra mantiene conciencia de todas las demás mediante pesos de atención que determinan cuánto influye cada palabra en la representación de la palabra actual. Estos pesos se aprenden durante el entrenamiento y se adaptan en función del contexto y la tarea.
Para ilustrar este concepto, considera la oración "The cat chased the mouse." Al procesar la palabra "chased", el mecanismo de autoatención considera simultáneamente todas las palabras en la oración:
- Presta mucha atención a "cat" como el sujeto que realiza la acción.
- Mantiene una atención fuerte en "mouse" como el objeto que recibe la acción.
- Puede prestar menos atención a artículos como "the", que aportan menos al significado semántico.
Este procesamiento paralelo permite al modelo construir una comprensión rica y contextual del papel de cada palabra en la oración.
A diferencia de los mecanismos de atención tradicionales, que típicamente trabajan con dos secuencias separadas (como en la traducción automática donde una palabra en inglés atiende a palabras en francés), la autoatención opera completamente dentro de una única secuencia. Este enfoque interno representa un avance significativo en el procesamiento del lenguaje natural.
La eficacia de este enfoque se hace particularmente evidente al manejar fenómenos lingüísticos complejos:
- Dependencias a larga distancia: (por ejemplo, "The cat, which had a brown collar, chased the mouse").
- Resolución de correferencias: (entender que "it" se refiere a "the cat").
- Etiquetado de roles semánticos: (identificar quién hizo qué a quién).
- Comprensión de estructuras sintácticas: (captar las relaciones gramaticales entre palabras).
Cómo funciona:
- Representación de entrada: Cada token (palabra o subpalabra) en la secuencia se convierte primero en un vector numérico mediante un proceso de embeddings. Estos vectores tienen cientos de dimensiones y capturan relaciones semánticas entre palabras. Por ejemplo, palabras similares como "cat" y "kitten" tendrán vectores cercanos en este espacio de alta dimensión.
- Creación de Query, Key y Value: El modelo transforma el vector inicial de cada token en tres vectores distintos mediante transformaciones lineales aprendidas:
- Query (Q): Representa lo que la palabra actual está buscando en la secuencia.
- Key (K): Funciona como un índice, ayudando a otros tokens a encontrar esta palabra cuando es relevante.
- Value (V): Contiene la información significativa que se usará en la representación final.
- Cálculo de puntajes de atención: El modelo calcula los puntajes de atención tomando el producto punto entre cada query y todas las keys. Esto crea una matriz de puntajes donde cada entrada (i, j) representa cuán relevante es el token j para el token i. Los puntajes se escalan dividiéndolos por la raíz cuadrada de la dimensión del key (\sqrt{d_k}) para evitar que los productos punto se vuelvan demasiado grandes, lo que ayuda a mantener gradientes estables durante el entrenamiento.
- Normalización de pesos: Los puntajes de atención se convierten en probabilidades usando la función softmax. Esto asegura que todos los pesos para un token dado sumen 1, creando una distribución de probabilidad adecuada. Por ejemplo, al procesar "ate" en "The hungry cat ate fish", el modelo podría asignar pesos más altos a palabras relevantes como "cat" (0.6) y "fish" (0.3), y pesos más bajos a palabras menos importantes como "the" (0.02).
- Cálculo de la salida: La representación final de cada token se calcula como una suma ponderada de todos los vectores de valor (V), utilizando los pesos de atención normalizados. Este proceso permite que cada token reúna información de todos los demás tokens en la secuencia, ponderada por su relevancia. Las representaciones resultantes son conscientes del contexto y pueden capturar tanto la estructura gramatical local como las dependencias a larga distancia, lo que permite al modelo entender relaciones entre palabras incluso cuando están muy separadas en el texto.
3.3.2 Matemáticas de la autoatención
Para una secuencia de tokens de entrada X = [x_1, x_2, \dots, x_n]:
- Calcular Q = XW_Q, K = XW_K y V = XW_V, donde W_Q, W_K, W_V son matrices de pesos aprendibles.
- Calcular los puntajes de atención:
\text{Scores} = \frac{Q \cdot K^\top}{\sqrt{d_k}}
Aquí, d_k es la dimensión de los vectores K.
- Normalizar los puntajes con softmax:
\text{Weights} = \text{softmax}\left(\text{Scores}\right)
- Calcular la salida:
\text{Output} = \text{Weights} \cdot V
Ejemplo: Implementación de autoatención
Implementemos autoatención para una secuencia sencilla.
Ejemplo de código: Autoatención en NumPy
import numpy as np
def self_attention(X, W_Q, W_K, W_V, mask=None):
"""
Compute self-attention for a sequence with optional masking.
Parameters:
-----------
X: np.ndarray
Input sequence of shape (n_tokens, d_model)
W_Q, W_K, W_V: np.ndarray
Weight matrices for Query, Key, Value transformations
mask: np.ndarray, optional
Attention mask of shape (n_tokens, n_tokens)
Returns:
--------
output: np.ndarray
Attended sequence of shape (n_tokens, d_model)
weights: np.ndarray
Attention weights of shape (n_tokens, n_tokens)
"""
# Linear transformations
Q = np.dot(X, W_Q) # Shape: (n_tokens, d_k)
K = np.dot(X, W_K) # Shape: (n_tokens, d_k)
V = np.dot(X, W_V) # Shape: (n_tokens, d_v)
# Calculate scaled dot-product attention
d_k = K.shape[1]
scores = np.dot(Q, K.T) / np.sqrt(d_k) # Shape: (n_tokens, n_tokens)
# Apply mask if provided
if mask is not None:
scores = scores * mask + -1e9 * (1 - mask)
# Softmax normalization
weights = np.exp(scores - np.max(scores, axis=-1, keepdims=True))
weights /= np.sum(weights, axis=-1, keepdims=True)
# Compute weighted sum
output = np.dot(weights, V) # Shape: (n_tokens, d_v)
return output, weights
# Example usage with a more complex sequence
def create_example():
# Create sample sequence
X = np.array([
[1, 0, 0], # First token
[0, 1, 0], # Second token
[0, 0, 1], # Third token
[1, 1, 0] # Fourth token
])
# Create weight matrices
d_model = 3 # Input dimension
d_k = 2 # Key/Query dimension
d_v = 4 # Value dimension
W_Q = np.random.randn(d_model, d_k) * 0.1
W_K = np.random.randn(d_model, d_k) * 0.1
W_V = np.random.randn(d_model, d_v) * 0.1
# Create attention mask (optional)
mask = np.array([
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 0], # Last token masked for third position
[1, 1, 1, 1]
])
return X, W_Q, W_K, W_V, mask
# Run example
X, W_Q, W_K, W_V, mask = create_example()
output, weights = self_attention(X, W_Q, W_K, W_V, mask)
print("Input Shape:", X.shape)
print("\nQuery Matrix Shape:", W_Q.shape)
print("Key Matrix Shape:", W_K.shape)
print("Value Matrix Shape:", W_V.shape)
print("\nAttention Weights:\n", weights)
print("\nOutput Shape:", output.shape)
print("Output:\n", output)
Explicación del desglose del código:
- Definición de la función y parámetros:
- La función toma como entrada la secuencia X y tres matrices de peso (W_Q, W_K, W_V).
- Incluye un parámetro opcional de enmascaramiento para un control más detallado de la atención.
- Proporciona una docstring completa con descripciones de los parámetros.
- Transformaciones lineales:
- Convierte los tokens de entrada en representaciones de Query (Q), Key (K) y Value (V).
- Utiliza multiplicación matricial (\text{np.dot}) para un cálculo eficiente.
- Mantiene transformaciones de forma adecuadas en todo el proceso.
- Cálculo de puntajes de atención:
- Implementa atención de producto punto escalado con el factor de escalado adecuado.
- Incluye funcionalidad de enmascaramiento para atención selectiva.
- Usa una implementación de softmax numéricamente estable.
- Implementación de ejemplo:
- Crea un ejemplo realista con 4 tokens y 3 características.
- Demuestra la inicialización adecuada de las matrices de peso.
- Muestra cómo usar el enmascaramiento opcional.
- Información sobre formas:
- Documenta claramente las formas de los tensores a lo largo del proceso.
- Ayuda a entender las transformaciones dimensionales.
- Facilita la depuración del código.
3.3.3 ¿Qué es la atención multi-cabeza?
La atención multi-cabeza es una mejora sofisticada del mecanismo de autoatención que ejecuta múltiples cálculos de atención en paralelo, llamados "cabezas." Cada cabeza opera de forma independiente y aprende a enfocarse en diferentes aspectos de las relaciones entre tokens en la secuencia. Por ejemplo, una cabeza puede aprender a centrarse en relaciones sintácticas (como la concordancia sujeto-verbo), otra en relaciones semánticas (como la relevancia temática) y otra en dependencias a largo plazo (como la resolución de correferencias).
Esta arquitectura de procesamiento paralelo ofrece varias ventajas clave:
- Permite al modelo analizar simultáneamente la secuencia de entrada desde múltiples perspectivas, como los humanos que procesan el lenguaje considerando varios aspectos a la vez.
- Tener múltiples mecanismos de atención especializados ayuda al modelo a capturar tanto patrones detallados como generales en los datos.
- Las representaciones diversas aprendidas por las diferentes cabezas se combinan para crear una comprensión más rica y matizada de la secuencia de entrada.
Las salidas de todas las cabezas se combinan finalmente a través de una operación de concatenación seguida de una transformación lineal. Esto permite al modelo sintetizar estas diferentes perspectivas en una representación cohesiva. Este enfoque multifacético mejora significativamente la capacidad del modelo para entender y procesar patrones lingüísticos complejos, haciéndolo particularmente eficaz para tareas que requieren una comprensión sofisticada del lenguaje.
Pasos en la atención multi-cabeza
- Dividir la entrada en múltiples cabezas:
- Divide la secuencia de entrada en subespacios separados.
- Cada cabeza recibe una porción de la dimensionalidad de la entrada.
- Esta división permite el procesamiento paralelo de diferentes aspectos de las características.
- Aplicar autoatención de forma independiente a cada cabeza:
- Cada cabeza calcula sus propias matrices de Query ($Q$), Key ($K$) y Value ($V$).
- Calcula los puntajes de atención usando atención de producto punto escalado.
- Procesa la información enfocándose en diferentes aspectos de la entrada.
- Concatenar las salidas de todas las cabezas:
- Combina los resultados de cada cabeza de atención.
- Preserva los patrones y relaciones únicos aprendidos por cada cabeza.
- Crea una representación integral de la secuencia de entrada.
- Aplicar una transformación lineal final:
- Proyecta las salidas concatenadas a la dimensión deseada.
- Integra la información de todas las cabezas en una representación cohesiva.
- Permite al modelo ponderar la importancia de las salidas de diferentes cabezas.
Beneficios de la Atención Multi-Cabeza
- Representaciones Diversas: Cada cabeza de atención se especializa en capturar diferentes tipos de relaciones dentro de los datos. Por ejemplo, una cabeza puede enfocarse en dependencias sintácticas (como la concordancia sujeto-verbo), otra en relaciones semánticas (como la relevancia temática), y otra en dependencias de largo alcance (como la resolución de correferencias). Esta diversidad permite al modelo construir una comprensión rica y multifacética de la entrada.
- Mayor Expresividad: El modelo puede enfocarse en múltiples aspectos de la entrada simultáneamente, similar a cómo los humanos procesan el lenguaje. Este procesamiento en paralelo permite al modelo:
- Capturar tanto el contexto local como el global.
- Procesar diferentes niveles semánticos (nivel de palabra, frase, oración).
- Aprender relaciones jerárquicas entre los tokens.
- Combinar diferentes perspectivas en una comprensión más completa.
- Capacidad de Aprendizaje Mejorada: Las múltiples cabezas permiten al modelo distribuir la atención a través de diferentes subespacios, incrementando efectivamente su poder representacional sin aumentar significativamente la complejidad computacional.
- Detección Robusta de Características: Al mantener múltiples mecanismos de atención independientes, el modelo se vuelve más robusto, ya que no depende de un único patrón de atención, reduciendo el impacto de ruido o patrones engañosos en los datos.
Ejemplo: Atención Multi-Cabeza
Implementemos una versión simplificada de la atención multi-cabeza.
Ejemplo de Código: Atención Multi-Cabeza en NumPy
import numpy as np
def multi_head_attention(X, W_Q, W_K, W_V, W_O, n_heads, mask=None):
"""
Compute multi-head attention with optional masking.
Parameters:
-----------
X: np.ndarray
Input sequence of shape (n_tokens, d_model)
W_Q, W_K, W_V: np.ndarray
Weight matrices for Query, Key, Value transformations
W_O: np.ndarray
Output projection matrix
n_heads: int
Number of attention heads
mask: np.ndarray, optional
Attention mask of shape (n_tokens, n_tokens)
Returns:
--------
final_output: np.ndarray
Transformed sequence of shape (n_tokens, d_model)
attention_weights: list
List of attention weights for each head
"""
d_model = X.shape[1]
head_dim = W_Q.shape[1] // n_heads
outputs = []
attention_weights = []
# Process each attention head
for i in range(n_heads):
# Split weights for current head
Q = np.dot(X, W_Q[:, i*head_dim:(i+1)*head_dim]) # (n_tokens, head_dim)
K = np.dot(X, W_K[:, i*head_dim:(i+1)*head_dim]) # (n_tokens, head_dim)
V = np.dot(X, W_V[:, i*head_dim:(i+1)*head_dim]) # (n_tokens, head_dim)
# Compute attention scores
scores = np.dot(Q, K.T) / np.sqrt(head_dim) # (n_tokens, n_tokens)
# Apply mask if provided
if mask is not None:
scores = scores * mask + -1e9 * (1 - mask)
# Apply softmax
weights = np.exp(scores - np.max(scores, axis=-1, keepdims=True))
weights = weights / np.sum(weights, axis=-1, keepdims=True)
# Compute weighted sum
output = np.dot(weights, V) # (n_tokens, head_dim)
outputs.append(output)
attention_weights.append(weights)
# Concatenate all heads
concatenated = np.concatenate(outputs, axis=-1) # (n_tokens, d_model)
# Final linear transformation
final_output = np.dot(concatenated, W_O) # (n_tokens, d_model)
return final_output, attention_weights
# Example usage with a more realistic sequence
def create_example_inputs(n_tokens=4, d_model=8, n_heads=2):
"""Create example inputs for multi-head attention."""
# Input sequence
X = np.random.randn(n_tokens, d_model)
# Weight matrices
head_dim = d_model // n_heads
W_Q = np.random.randn(d_model, d_model) * 0.1
W_K = np.random.randn(d_model, d_model) * 0.1
W_V = np.random.randn(d_model, d_model) * 0.1
W_O = np.random.randn(d_model, d_model) * 0.1
# Optional mask (causal attention)
mask = np.tril(np.ones((n_tokens, n_tokens)))
return X, W_Q, W_K, W_V, W_O, mask
# Run example
X, W_Q, W_K, W_V, W_O, mask = create_example_inputs()
output, weights = multi_head_attention(X, W_Q, W_K, W_V, W_O, n_heads=2, mask=mask)
print("Input shape:", X.shape)
print("Output shape:", output.shape)
print("\nAttention weights for first head:\n", weights[0])
print("\nAttention weights for second head:\n", weights[1])
Desglose del Código
- Arquitectura de la Función
- Implementa atención multi-cabeza con documentación detallada.
- Incluye una opción de enmascarado para atención causal.
- Devuelve tanto las salidas como los pesos de atención para análisis.
- Componentes Clave
- Cálculo de la Dimensión por Cabeza: Divide la dimensión de entrada entre las cabezas.
- Procesamiento por Cabeza: Calcula la atención separada para cada cabeza.
- Mecanismo de Atención: Implementa atención de producto punto escalada.
- Agregación de Salidas: Concatenación y proyección de las salidas de las cabezas.
- Características Mejoradas
- Estabilidad Numérica: Utiliza una implementación de softmax estable.
- Soporte de Enmascarado: Permite patrones de atención enmascarados.
- Escalado Correcto: Incluye un factor de escalado para la atención.
- Funciones Auxiliares
- create_example_inputs: Genera datos de prueba realistas.
- Incluye información de forma y lógica de inicialización.
- Demuestra patrones de uso adecuados.
- Análisis de Salida
- Imprime las formas de los datos para verificación.
- Muestra los pesos de atención para interpretación.
- Demuestra la naturaleza multi-cabeza de la atención.
3.3.4 Aplicaciones de la Auto-Atención y Atención Multi-Cabeza
Resumen de Textos
Los modelos aprovechan los mecanismos de atención de maneras sofisticadas para identificar y priorizar las partes más importantes de un documento. El mecanismo de atención funciona asignando diferentes pesos a diferentes partes del texto de entrada, esencialmente creando una jerarquía de importancia. Estos pesos se aprenden durante el entrenamiento y se ajustan dinámicamente según el contenido específico que se procesa.
Los pesos de atención actúan como un mecanismo de filtrado avanzado que ayuda a determinar qué oraciones contienen la información más crítica. Este proceso implica analizar diversas características lingüísticas, incluidas la relevancia semántica, la estructura sintáctica y las relaciones contextuales entre las diferentes partes del texto. El modelo puede así crear resúmenes concisos y significativos, preservando el mensaje principal y manteniendo la coherencia.
Por ejemplo, en el resumen de artículos de noticias, el modelo emplea un enfoque de atención en múltiples capas. Podría enfocarse fuertemente en eventos clave (como las principales acciones o desarrollos), citas significativas de figuras relevantes y datos estadísticos importantes que respaldan la narrativa principal. Mientras tanto, asigna pesos de atención más bajos a detalles complementarios, información de contexto o contenido redundante. Este proceso de atención selectiva refleja el comportamiento humano en la creación de resúmenes, donde naturalmente nos enfocamos en información crucial mientras pasamos por alto detalles menos importantes.
Ejemplo de Código: Resumen de Textos con Auto-Atención
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
class SelfAttentionSummarizer(nn.Module):
def __init__(self, vocab_size, embed_dim, num_heads, hidden_dim, max_length=512):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
self.position_encoding = nn.Parameter(
torch.zeros(max_length, embed_dim)
)
self.multihead_attention = nn.MultiheadAttention(
embed_dim, num_heads, batch_first=True
)
self.layer_norm1 = nn.LayerNorm(embed_dim)
self.feed_forward = nn.Sequential(
nn.Linear(embed_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, embed_dim)
)
self.layer_norm2 = nn.LayerNorm(embed_dim)
self.output_layer = nn.Linear(embed_dim, vocab_size)
def forward(self, x, src_mask=None):
# Add positional encoding to embeddings
seq_length = x.size(1)
x = self.embedding(x) + self.position_encoding[:seq_length]
# Self-attention block
attention_output, attention_weights = self.multihead_attention(
x, x, x,
key_padding_mask=src_mask,
need_weights=True
)
x = self.layer_norm1(x + attention_output)
# Feed-forward block
ff_output = self.feed_forward(x)
x = self.layer_norm2(x + ff_output)
# Generate output probabilities
output = self.output_layer(x)
return output, attention_weights
def generate_summary(model, input_ids, tokenizer, max_length=150):
model.eval()
with torch.no_grad():
output, attention_weights = model(input_ids)
# Get most attended words for summary
attention_scores = attention_weights.mean(dim=1)
top_scores = torch.topk(attention_scores.squeeze(), k=max_length)
# Extract and arrange summary tokens
summary_indices = top_scores.indices.sort().values
summary_tokens = input_ids[0, summary_indices]
# Convert to text
summary = tokenizer.decode(summary_tokens)
return summary, attention_weights
# Example usage
def summarize_text(text, model, tokenizer):
# Tokenize input text
inputs = tokenizer(text, return_tensors="pt", max_length=512, truncation=True)
# Generate summary
summary, attention = generate_summary(
model,
inputs["input_ids"],
tokenizer
)
return summary, attention
Desglose del Código
- Arquitectura del Modelo
- Implementa un resumidor basado en Transformer con auto-atención multi-cabeza.
- Incluye codificación posicional para reconocer la secuencia.
- Utiliza normalización por capas y conexiones residuales para un entrenamiento estable.
- Componentes Clave
- Capa de Embedding: Convierte tokens en vectores densos.
- Atención Multi-Cabeza: Procesa el texto desde múltiples perspectivas.
- Red Feed-Forward: Agrega no linealidad y transforma representaciones.
- Capa de Salida: Genera predicciones finales de tokens.
- Proceso de Resumen
- Analiza los pesos de atención para identificar los tokens importantes.
- Selecciona los tokens con mayor atención para generar el resumen.
- Mantiene el orden original de los tokens seleccionados para asegurar coherencia.
- Características Avanzadas
- Soporta entradas de longitud variable con enmascaramiento.
- Implementa un procesamiento eficiente por lotes.
- Devuelve pesos de atención para análisis y visualización.
Ejemplo de Uso:
# Example setup and usage
vocab_size = 30000
embed_dim = 512
num_heads = 8
hidden_dim = 2048
model = SelfAttentionSummarizer(
vocab_size=vocab_size,
embed_dim=embed_dim,
num_heads=num_heads,
hidden_dim=hidden_dim
)
# Example text
text = """
Climate change poses significant challenges to global ecosystems.
Rising temperatures affect wildlife habitats and agricultural productivity.
Scientists warn that immediate action is necessary to prevent irreversible damage.
"""
# Generate summary (assuming tokenizer is initialized)
summary, attention = summarize_text(text, model, tokenizer)
print("Summary:", summary)
Traducción Automática
Los mecanismos de atención han revolucionado la traducción automática al crear alineaciones dinámicas sofisticadas entre palabras y frases en diferentes idiomas. Este proceso funciona estableciendo conexiones ponderadas entre elementos de los idiomas de origen y destino, permitiendo al modelo comprender relaciones lingüísticas complejas. Por ejemplo, al traducir del inglés al japonés, el mecanismo de atención puede manejar las significativas diferencias en la estructura de las oraciones, donde el inglés sigue un orden Sujeto-Verbo-Objeto, mientras que el japonés generalmente utiliza Sujeto-Objeto-Verbo.
El mecanismo es especialmente poderoso al abordar tres desafíos clave de la traducción:
Primero, maneja variaciones complejas en el orden de las palabras entre idiomas. Por ejemplo, al traducir entre inglés y alemán, donde la posición del verbo puede variar significativamente, el mecanismo de atención puede mantener las relaciones semánticas adecuadas a pesar de las diferencias sintácticas.
Segundo, maneja eficazmente las correspondencias de palabras de muchos a uno y de uno a muchos. Por ejemplo, al traducir la palabra compuesta alemana "Schadenfreude" al inglés, el mecanismo puede mapearla a la frase "placer derivado del infortunio ajeno", manteniendo un significado preciso a pesar de la diferencia estructural.
Tercero, el modelo mantiene la conciencia contextual en oraciones extendidas gracias a su capacidad de referenciar y ponderar la importancia de diferentes partes de la secuencia de entrada. Esto garantiza que las oraciones largas conserven su significado y coherencia en la traducción, evitando problemas comunes como perder la relación sujeto-verbo o mal manejar cláusulas dependientes.
El mecanismo de atención logra esto actualizando continuamente su enfoque en función de la palabra actual que se está traduciendo y su relación con todas las demás palabras en la oración, asegurando que la traducción final preserve tanto el significado como el flujo natural del lenguaje.
Ejemplo de Código: Traducción Automática Neuronal con Auto-Atención
import torch
import torch.nn as nn
import torch.nn.functional as F
class TranslationTransformer(nn.Module):
def __init__(self, src_vocab_size, tgt_vocab_size, d_model=512, nhead=8,
num_encoder_layers=6, num_decoder_layers=6, dim_feedforward=2048):
super().__init__()
# Embedding layers
self.src_embedding = nn.Embedding(src_vocab_size, d_model)
self.tgt_embedding = nn.Embedding(tgt_vocab_size, d_model)
self.positional_encoding = PositionalEncoding(d_model)
# Transformer layers
self.transformer = nn.Transformer(
d_model=d_model,
nhead=nhead,
num_encoder_layers=num_encoder_layers,
num_decoder_layers=num_decoder_layers,
dim_feedforward=dim_feedforward
)
# Output projection
self.output_layer = nn.Linear(d_model, tgt_vocab_size)
def forward(self, src, tgt, src_mask=None, tgt_mask=None):
# Create source embedding
src_embedded = self.positional_encoding(self.src_embedding(src))
# Create target embedding
tgt_embedded = self.positional_encoding(self.tgt_embedding(tgt))
# Generate masks if not provided
if src_mask is None:
src_mask = self.generate_square_subsequent_mask(src.size(1))
if tgt_mask is None:
tgt_mask = self.generate_square_subsequent_mask(tgt.size(1))
# Pass through transformer
output = self.transformer(
src_embedded, tgt_embedded,
src_mask=src_mask,
tgt_mask=tgt_mask
)
# Project to vocabulary
return self.output_layer(output)
@staticmethod
def generate_square_subsequent_mask(sz):
mask = torch.triu(torch.ones(sz, sz), diagonal=1)
mask = mask.masked_fill(mask==1, float('-inf'))
return mask
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=5000):
super().__init__()
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2).float() *
(-math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0)
self.register_buffer('pe', pe)
def forward(self, x):
return x + self.pe[:, :x.size(1)]
# Training function
def train_translation_model(model, train_loader, optimizer, criterion, num_epochs=10):
model.train()
for epoch in range(num_epochs):
total_loss = 0
for batch_idx, (src, tgt) in enumerate(train_loader):
optimizer.zero_grad()
# Forward pass
output = model(src, tgt[:-1]) # exclude last target token
# Calculate loss
loss = criterion(
output.view(-1, output.size(-1)),
tgt[1:].reshape(-1) # exclude first target token (BOS)
)
# Backward pass
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(train_loader)
print(f'Epoch: {epoch+1}, Average Loss: {avg_loss:.4f}')
Desglose del Código
- Arquitectura del Modelo
- Implementa un modelo completo de traducción basado en Transformer.
- Utiliza un codificador y un decodificador con atención multi-cabeza.
- Incluye codificación posicional para reconocer el orden de la secuencia.
- Componentes Clave
- Embeddings de Origen y Destino: Convierte tokens en vectores.
- Codificación Posicional: Agrega información de posición a los embeddings.
- Bloque Transformer: Procesa secuencias utilizando auto-atención.
- Proyección de Salida: Mapea al vocabulario del idioma destino.
- Proceso de Entrenamiento
- Implementa teacher forcing durante el entrenamiento.
- Utiliza atención enmascarada para generación autorregresiva.
- Incluye pasos de cálculo de pérdida y optimización.
- Características Avanzadas
- Soporta secuencias de longitud variable.
- Implementa un procesamiento eficiente por lotes.
- Incluye generación de máscaras para atención causal.
Ejemplo de Uso:
# Initialize model and training components
model = TranslationTransformer(
src_vocab_size=10000,
tgt_vocab_size=10000,
d_model=512,
nhead=8
)
# Setup optimizer and criterion
optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)
criterion = nn.CrossEntropyLoss(ignore_index=pad_idx)
# Example translation
def translate(model, src_sentence, src_tokenizer, tgt_tokenizer, max_len=50):
model.eval()
with torch.no_grad():
# Tokenize source sentence
src_tokens = src_tokenizer.encode(src_sentence)
src_tensor = torch.LongTensor(src_tokens).unsqueeze(0)
# Initialize target with BOS token
tgt_tokens = [tgt_tokenizer.bos_token_id]
# Generate translation
for _ in range(max_len):
tgt_tensor = torch.LongTensor(tgt_tokens).unsqueeze(0)
output = model(src_tensor, tgt_tensor)
next_token = output[0, -1].argmax().item()
if next_token == tgt_tokenizer.eos_token_id:
break
tgt_tokens.append(next_token)
# Convert tokens to text
translation = tgt_tokenizer.decode(tgt_tokens)
return translation
Respuestas a Preguntas
Al procesar preguntas, los mecanismos de atención emplean un enfoque sofisticado para el procesamiento de información. Estos mecanismos ayudan a los modelos a identificar y enfocarse en las partes específicas de un pasaje que contienen información relevante a través de un proceso de varios pasos:
Primero, el modelo analiza la pregunta para entender qué tipo de información necesita buscar. Luego, crea pesos de atención para cada palabra en el pasaje, asignando pesos más altos a las palabras y frases que tienen más probabilidades de contener la respuesta. Este enfoque selectivo permite al modelo extraer respuestas de manera eficiente mientras ignora contenido irrelevante.
Por ejemplo, al responder "¿Cuándo ocurrió el evento?", el modelo se enfocaría principalmente en expresiones temporales (como fechas, horarios y frases temporales como "ayer" o "la semana pasada") y en su contexto circundante en el pasaje. Los pesos de atención serían más altos para estos indicadores temporales y su contexto inmediato, permitiendo al modelo centrarse en la información más relevante. Este proceso es similar a cómo los humanos podrían escanear un texto en busca de palabras relacionadas con el tiempo al buscar cuándo ocurrió algo.
Ejemplo de Código: Respuestas a Preguntas con Auto-Atención
import torch
import torch.nn as nn
import torch.nn.functional as F
class QATransformer(nn.Module):
def __init__(self, vocab_size, d_model=512, nhead=8, num_layers=6):
super().__init__()
# Embedding layers
self.embedding = nn.Embedding(vocab_size, d_model)
self.pos_encoder = PositionalEncoding(d_model)
# Multi-head attention layers
self.question_encoder = nn.TransformerEncoder(
nn.TransformerEncoderLayer(d_model, nhead),
num_layers
)
self.context_encoder = nn.TransformerEncoder(
nn.TransformerEncoderLayer(d_model, nhead),
num_layers
)
# Cross-attention layer
self.cross_attention = nn.MultiheadAttention(d_model, nhead)
# Output layers for start and end position prediction
self.start_predictor = nn.Linear(d_model, 1)
self.end_predictor = nn.Linear(d_model, 1)
def forward(self, question, context):
# Embed inputs
q_embed = self.pos_encoder(self.embedding(question))
c_embed = self.pos_encoder(self.embedding(context))
# Encode question and context
q_encoded = self.question_encoder(q_embed)
c_encoded = self.context_encoder(c_embed)
# Cross-attention between question and context
attn_output, attention_weights = self.cross_attention(
q_encoded, c_encoded, c_encoded
)
# Predict answer span
start_logits = self.start_predictor(attn_output).squeeze(-1)
end_logits = self.end_predictor(attn_output).squeeze(-1)
return start_logits, end_logits, attention_weights
def train_qa_model(model, train_loader, optimizer, num_epochs=10):
model.train()
criterion = nn.CrossEntropyLoss()
for epoch in range(num_epochs):
for batch in train_loader:
question, context, start_pos, end_pos = batch
# Forward pass
start_logits, end_logits, _ = model(question, context)
# Calculate loss
start_loss = criterion(start_logits, start_pos)
end_loss = criterion(end_logits, end_pos)
loss = start_loss + end_loss
# Backward pass
optimizer.zero_grad()
loss.backward()
optimizer.step()
def predict_answer(model, tokenizer, question, context):
model.eval()
with torch.no_grad():
# Tokenize inputs
q_tokens = tokenizer.encode(question)
c_tokens = tokenizer.encode(context)
# Convert to tensors
q_tensor = torch.tensor(q_tokens).unsqueeze(0)
c_tensor = torch.tensor(c_tokens).unsqueeze(0)
# Get predictions
start_logits, end_logits, attention = model(q_tensor, c_tensor)
# Find most likely answer span
start_idx = torch.argmax(start_logits)
end_idx = torch.argmax(end_logits[start_idx:]) + start_idx
# Extract answer tokens
answer_tokens = c_tokens[start_idx:end_idx+1]
# Convert back to text
answer = tokenizer.decode(answer_tokens)
return answer, attention
Desglose del Código:
- Arquitectura del Modelo
- Implementa un modelo de preguntas y respuestas basado en Transformer con codificadores separados para preguntas y contexto
- Utiliza auto-atención multi-cabeza para el procesamiento tanto de preguntas como de contexto
- Incluye mecanismo de atención cruzada para relacionar preguntas con el contexto
- Cuenta con predicción de segmentos para la extracción de respuestas
- Componentes Principales
- Capa de Embedding: Convierte tokens de texto en vectores densos
- Codificación Posicional: Agrega información de posición a los embeddings
- Codificadores de Pregunta/Contexto: Procesan entradas usando auto-atención
- Atención Cruzada: Relaciona la pregunta con el contexto para encontrar respuestas
- Predictores de Segmento: Localizan los límites de la respuesta en el contexto
- Flujo de Procesamiento
- Incorpora y codifica la pregunta y el contexto por separado
- Aplica atención cruzada para encontrar regiones relevantes del contexto
- Predice las posiciones de inicio y fin del segmento de respuesta
- Devuelve el texto de la respuesta y los pesos de atención para análisis
Ejemplo de Uso:
# Initialize model and components
model = QATransformer(
vocab_size=30000,
d_model=512,
nhead=8,
num_layers=6
)
# Example usage
question = "When was the first computer invented?"
context = "The first general-purpose electronic computer, ENIAC, was completed in 1945."
# Get answer
answer, attention_weights = predict_answer(
model, tokenizer, question, context
)
print(f"Question: {question}")
print(f"Answer: {answer}")
3.3.5 Puntos Clave
- La auto-atención permite a los modelos calcular representaciones conscientes del contexto al atender a todos los tokens en una secuencia. Esto significa que cada palabra en una oración puede interactuar directamente con cualquier otra palabra, permitiendo al modelo entender relaciones y dependencias complejas. Por ejemplo, en la oración "El gato que persiguió al ratón era negro", la auto-atención ayuda al modelo a conectar "era negro" con "gato", aunque estén separados por varias palabras.
- La atención multi-cabeza mejora la auto-atención al capturar relaciones diversas simultáneamente. Mientras una sola cabeza de atención podría enfocarse en relaciones sintácticas, otra podría capturar similitudes semánticas y otra más podría rastrear relaciones temporales. Este enfoque multifacético permite al modelo procesar la información desde múltiples "perspectivas" al mismo tiempo, conduciendo a una comprensión más rica y matizada de la entrada.
- Juntas, estas mecánicas son la base de las arquitecturas Transformer, permitiendo paralelismo y modelado de dependencias a largo alcance. A diferencia de los modelos secuenciales tradicionales que procesan palabras una a la vez, los Transformers pueden procesar todas las palabras simultáneamente, mejorando drásticamente la eficiencia computacional. Además, dado que cada token puede atender directamente a cualquier otro token, los Transformers destacan en capturar relaciones entre palabras que están distantes en el texto, resolviendo el desafío histórico de modelar dependencias de largo alcance en el procesamiento del lenguaje natural.
3.3 Autoatención y Atención Multi-Cabeza
Sobre la base de los mecanismos de atención, la autoatención surgió como una innovación revolucionaria en el procesamiento del lenguaje natural (NLP). Este enfoque transformador cambió la forma en que los modelos procesan secuencias de entrada al introducir un mecanismo en el que cada elemento de una secuencia puede interactuar directamente con todos los demás. Esta interacción directa permite a los modelos procesar secuencias de entrada con una eficiencia y conciencia contextual sin precedentes, eliminando los cuellos de botella tradicionales del procesamiento secuencial.
La autoatención permite que cada token consulte y atienda simultáneamente a todos los demás tokens en la secuencia. Por ejemplo, al procesar la oración "The cat sat on the mat", cada palabra puede evaluar directamente su relación con todas las demás, ayudando al modelo a entender tanto relaciones locales (como "the cat") como dependencias a larga distancia (conectar "cat" con "sat").
Cuando se combina con la atención multi-cabeza, esta capacidad se vuelve aún más poderosa. La atención multi-cabeza permite al modelo mantener múltiples patrones de atención diferentes simultáneamente, cada uno enfocado en diferentes aspectos de las relaciones entre los tokens. Este enfoque multifacético es la base de los modelos Transformer, capacitando a los modelos para capturar relaciones complejas entre tokens en una secuencia desde múltiples perspectivas al mismo tiempo.
En esta sección exploraremos la autoatención y su extensión a la atención multi-cabeza, examinando cómo funcionan estos mecanismos y por qué son fundamentales en las arquitecturas Transformer. Analizaremos sus fundamentos matemáticos, sus implementaciones prácticas y demostraremos su efectividad con ejemplos concretos y código.
3.3.1 ¿Qué es la autoatención?
En la autoatención, cada token en una secuencia de entrada atiende a todos los demás tokens (incluyéndose a sí mismo) para calcular una nueva representación. Este mecanismo revolucionario crea conexiones dinámicas entre todos los elementos de una secuencia. Por ejemplo, al procesar una oración, cada palabra mantiene conciencia de todas las demás mediante pesos de atención que determinan cuánto influye cada palabra en la representación de la palabra actual. Estos pesos se aprenden durante el entrenamiento y se adaptan en función del contexto y la tarea.
Para ilustrar este concepto, considera la oración "The cat chased the mouse." Al procesar la palabra "chased", el mecanismo de autoatención considera simultáneamente todas las palabras en la oración:
- Presta mucha atención a "cat" como el sujeto que realiza la acción.
- Mantiene una atención fuerte en "mouse" como el objeto que recibe la acción.
- Puede prestar menos atención a artículos como "the", que aportan menos al significado semántico.
Este procesamiento paralelo permite al modelo construir una comprensión rica y contextual del papel de cada palabra en la oración.
A diferencia de los mecanismos de atención tradicionales, que típicamente trabajan con dos secuencias separadas (como en la traducción automática donde una palabra en inglés atiende a palabras en francés), la autoatención opera completamente dentro de una única secuencia. Este enfoque interno representa un avance significativo en el procesamiento del lenguaje natural.
La eficacia de este enfoque se hace particularmente evidente al manejar fenómenos lingüísticos complejos:
- Dependencias a larga distancia: (por ejemplo, "The cat, which had a brown collar, chased the mouse").
- Resolución de correferencias: (entender que "it" se refiere a "the cat").
- Etiquetado de roles semánticos: (identificar quién hizo qué a quién).
- Comprensión de estructuras sintácticas: (captar las relaciones gramaticales entre palabras).
Cómo funciona:
- Representación de entrada: Cada token (palabra o subpalabra) en la secuencia se convierte primero en un vector numérico mediante un proceso de embeddings. Estos vectores tienen cientos de dimensiones y capturan relaciones semánticas entre palabras. Por ejemplo, palabras similares como "cat" y "kitten" tendrán vectores cercanos en este espacio de alta dimensión.
- Creación de Query, Key y Value: El modelo transforma el vector inicial de cada token en tres vectores distintos mediante transformaciones lineales aprendidas:
- Query (Q): Representa lo que la palabra actual está buscando en la secuencia.
- Key (K): Funciona como un índice, ayudando a otros tokens a encontrar esta palabra cuando es relevante.
- Value (V): Contiene la información significativa que se usará en la representación final.
- Cálculo de puntajes de atención: El modelo calcula los puntajes de atención tomando el producto punto entre cada query y todas las keys. Esto crea una matriz de puntajes donde cada entrada (i, j) representa cuán relevante es el token j para el token i. Los puntajes se escalan dividiéndolos por la raíz cuadrada de la dimensión del key (\sqrt{d_k}) para evitar que los productos punto se vuelvan demasiado grandes, lo que ayuda a mantener gradientes estables durante el entrenamiento.
- Normalización de pesos: Los puntajes de atención se convierten en probabilidades usando la función softmax. Esto asegura que todos los pesos para un token dado sumen 1, creando una distribución de probabilidad adecuada. Por ejemplo, al procesar "ate" en "The hungry cat ate fish", el modelo podría asignar pesos más altos a palabras relevantes como "cat" (0.6) y "fish" (0.3), y pesos más bajos a palabras menos importantes como "the" (0.02).
- Cálculo de la salida: La representación final de cada token se calcula como una suma ponderada de todos los vectores de valor (V), utilizando los pesos de atención normalizados. Este proceso permite que cada token reúna información de todos los demás tokens en la secuencia, ponderada por su relevancia. Las representaciones resultantes son conscientes del contexto y pueden capturar tanto la estructura gramatical local como las dependencias a larga distancia, lo que permite al modelo entender relaciones entre palabras incluso cuando están muy separadas en el texto.
3.3.2 Matemáticas de la autoatención
Para una secuencia de tokens de entrada X = [x_1, x_2, \dots, x_n]:
- Calcular Q = XW_Q, K = XW_K y V = XW_V, donde W_Q, W_K, W_V son matrices de pesos aprendibles.
- Calcular los puntajes de atención:
\text{Scores} = \frac{Q \cdot K^\top}{\sqrt{d_k}}
Aquí, d_k es la dimensión de los vectores K.
- Normalizar los puntajes con softmax:
\text{Weights} = \text{softmax}\left(\text{Scores}\right)
- Calcular la salida:
\text{Output} = \text{Weights} \cdot V
Ejemplo: Implementación de autoatención
Implementemos autoatención para una secuencia sencilla.
Ejemplo de código: Autoatención en NumPy
import numpy as np
def self_attention(X, W_Q, W_K, W_V, mask=None):
"""
Compute self-attention for a sequence with optional masking.
Parameters:
-----------
X: np.ndarray
Input sequence of shape (n_tokens, d_model)
W_Q, W_K, W_V: np.ndarray
Weight matrices for Query, Key, Value transformations
mask: np.ndarray, optional
Attention mask of shape (n_tokens, n_tokens)
Returns:
--------
output: np.ndarray
Attended sequence of shape (n_tokens, d_model)
weights: np.ndarray
Attention weights of shape (n_tokens, n_tokens)
"""
# Linear transformations
Q = np.dot(X, W_Q) # Shape: (n_tokens, d_k)
K = np.dot(X, W_K) # Shape: (n_tokens, d_k)
V = np.dot(X, W_V) # Shape: (n_tokens, d_v)
# Calculate scaled dot-product attention
d_k = K.shape[1]
scores = np.dot(Q, K.T) / np.sqrt(d_k) # Shape: (n_tokens, n_tokens)
# Apply mask if provided
if mask is not None:
scores = scores * mask + -1e9 * (1 - mask)
# Softmax normalization
weights = np.exp(scores - np.max(scores, axis=-1, keepdims=True))
weights /= np.sum(weights, axis=-1, keepdims=True)
# Compute weighted sum
output = np.dot(weights, V) # Shape: (n_tokens, d_v)
return output, weights
# Example usage with a more complex sequence
def create_example():
# Create sample sequence
X = np.array([
[1, 0, 0], # First token
[0, 1, 0], # Second token
[0, 0, 1], # Third token
[1, 1, 0] # Fourth token
])
# Create weight matrices
d_model = 3 # Input dimension
d_k = 2 # Key/Query dimension
d_v = 4 # Value dimension
W_Q = np.random.randn(d_model, d_k) * 0.1
W_K = np.random.randn(d_model, d_k) * 0.1
W_V = np.random.randn(d_model, d_v) * 0.1
# Create attention mask (optional)
mask = np.array([
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 0], # Last token masked for third position
[1, 1, 1, 1]
])
return X, W_Q, W_K, W_V, mask
# Run example
X, W_Q, W_K, W_V, mask = create_example()
output, weights = self_attention(X, W_Q, W_K, W_V, mask)
print("Input Shape:", X.shape)
print("\nQuery Matrix Shape:", W_Q.shape)
print("Key Matrix Shape:", W_K.shape)
print("Value Matrix Shape:", W_V.shape)
print("\nAttention Weights:\n", weights)
print("\nOutput Shape:", output.shape)
print("Output:\n", output)
Explicación del desglose del código:
- Definición de la función y parámetros:
- La función toma como entrada la secuencia X y tres matrices de peso (W_Q, W_K, W_V).
- Incluye un parámetro opcional de enmascaramiento para un control más detallado de la atención.
- Proporciona una docstring completa con descripciones de los parámetros.
- Transformaciones lineales:
- Convierte los tokens de entrada en representaciones de Query (Q), Key (K) y Value (V).
- Utiliza multiplicación matricial (\text{np.dot}) para un cálculo eficiente.
- Mantiene transformaciones de forma adecuadas en todo el proceso.
- Cálculo de puntajes de atención:
- Implementa atención de producto punto escalado con el factor de escalado adecuado.
- Incluye funcionalidad de enmascaramiento para atención selectiva.
- Usa una implementación de softmax numéricamente estable.
- Implementación de ejemplo:
- Crea un ejemplo realista con 4 tokens y 3 características.
- Demuestra la inicialización adecuada de las matrices de peso.
- Muestra cómo usar el enmascaramiento opcional.
- Información sobre formas:
- Documenta claramente las formas de los tensores a lo largo del proceso.
- Ayuda a entender las transformaciones dimensionales.
- Facilita la depuración del código.
3.3.3 ¿Qué es la atención multi-cabeza?
La atención multi-cabeza es una mejora sofisticada del mecanismo de autoatención que ejecuta múltiples cálculos de atención en paralelo, llamados "cabezas." Cada cabeza opera de forma independiente y aprende a enfocarse en diferentes aspectos de las relaciones entre tokens en la secuencia. Por ejemplo, una cabeza puede aprender a centrarse en relaciones sintácticas (como la concordancia sujeto-verbo), otra en relaciones semánticas (como la relevancia temática) y otra en dependencias a largo plazo (como la resolución de correferencias).
Esta arquitectura de procesamiento paralelo ofrece varias ventajas clave:
- Permite al modelo analizar simultáneamente la secuencia de entrada desde múltiples perspectivas, como los humanos que procesan el lenguaje considerando varios aspectos a la vez.
- Tener múltiples mecanismos de atención especializados ayuda al modelo a capturar tanto patrones detallados como generales en los datos.
- Las representaciones diversas aprendidas por las diferentes cabezas se combinan para crear una comprensión más rica y matizada de la secuencia de entrada.
Las salidas de todas las cabezas se combinan finalmente a través de una operación de concatenación seguida de una transformación lineal. Esto permite al modelo sintetizar estas diferentes perspectivas en una representación cohesiva. Este enfoque multifacético mejora significativamente la capacidad del modelo para entender y procesar patrones lingüísticos complejos, haciéndolo particularmente eficaz para tareas que requieren una comprensión sofisticada del lenguaje.
Pasos en la atención multi-cabeza
- Dividir la entrada en múltiples cabezas:
- Divide la secuencia de entrada en subespacios separados.
- Cada cabeza recibe una porción de la dimensionalidad de la entrada.
- Esta división permite el procesamiento paralelo de diferentes aspectos de las características.
- Aplicar autoatención de forma independiente a cada cabeza:
- Cada cabeza calcula sus propias matrices de Query ($Q$), Key ($K$) y Value ($V$).
- Calcula los puntajes de atención usando atención de producto punto escalado.
- Procesa la información enfocándose en diferentes aspectos de la entrada.
- Concatenar las salidas de todas las cabezas:
- Combina los resultados de cada cabeza de atención.
- Preserva los patrones y relaciones únicos aprendidos por cada cabeza.
- Crea una representación integral de la secuencia de entrada.
- Aplicar una transformación lineal final:
- Proyecta las salidas concatenadas a la dimensión deseada.
- Integra la información de todas las cabezas en una representación cohesiva.
- Permite al modelo ponderar la importancia de las salidas de diferentes cabezas.
Beneficios de la Atención Multi-Cabeza
- Representaciones Diversas: Cada cabeza de atención se especializa en capturar diferentes tipos de relaciones dentro de los datos. Por ejemplo, una cabeza puede enfocarse en dependencias sintácticas (como la concordancia sujeto-verbo), otra en relaciones semánticas (como la relevancia temática), y otra en dependencias de largo alcance (como la resolución de correferencias). Esta diversidad permite al modelo construir una comprensión rica y multifacética de la entrada.
- Mayor Expresividad: El modelo puede enfocarse en múltiples aspectos de la entrada simultáneamente, similar a cómo los humanos procesan el lenguaje. Este procesamiento en paralelo permite al modelo:
- Capturar tanto el contexto local como el global.
- Procesar diferentes niveles semánticos (nivel de palabra, frase, oración).
- Aprender relaciones jerárquicas entre los tokens.
- Combinar diferentes perspectivas en una comprensión más completa.
- Capacidad de Aprendizaje Mejorada: Las múltiples cabezas permiten al modelo distribuir la atención a través de diferentes subespacios, incrementando efectivamente su poder representacional sin aumentar significativamente la complejidad computacional.
- Detección Robusta de Características: Al mantener múltiples mecanismos de atención independientes, el modelo se vuelve más robusto, ya que no depende de un único patrón de atención, reduciendo el impacto de ruido o patrones engañosos en los datos.
Ejemplo: Atención Multi-Cabeza
Implementemos una versión simplificada de la atención multi-cabeza.
Ejemplo de Código: Atención Multi-Cabeza en NumPy
import numpy as np
def multi_head_attention(X, W_Q, W_K, W_V, W_O, n_heads, mask=None):
"""
Compute multi-head attention with optional masking.
Parameters:
-----------
X: np.ndarray
Input sequence of shape (n_tokens, d_model)
W_Q, W_K, W_V: np.ndarray
Weight matrices for Query, Key, Value transformations
W_O: np.ndarray
Output projection matrix
n_heads: int
Number of attention heads
mask: np.ndarray, optional
Attention mask of shape (n_tokens, n_tokens)
Returns:
--------
final_output: np.ndarray
Transformed sequence of shape (n_tokens, d_model)
attention_weights: list
List of attention weights for each head
"""
d_model = X.shape[1]
head_dim = W_Q.shape[1] // n_heads
outputs = []
attention_weights = []
# Process each attention head
for i in range(n_heads):
# Split weights for current head
Q = np.dot(X, W_Q[:, i*head_dim:(i+1)*head_dim]) # (n_tokens, head_dim)
K = np.dot(X, W_K[:, i*head_dim:(i+1)*head_dim]) # (n_tokens, head_dim)
V = np.dot(X, W_V[:, i*head_dim:(i+1)*head_dim]) # (n_tokens, head_dim)
# Compute attention scores
scores = np.dot(Q, K.T) / np.sqrt(head_dim) # (n_tokens, n_tokens)
# Apply mask if provided
if mask is not None:
scores = scores * mask + -1e9 * (1 - mask)
# Apply softmax
weights = np.exp(scores - np.max(scores, axis=-1, keepdims=True))
weights = weights / np.sum(weights, axis=-1, keepdims=True)
# Compute weighted sum
output = np.dot(weights, V) # (n_tokens, head_dim)
outputs.append(output)
attention_weights.append(weights)
# Concatenate all heads
concatenated = np.concatenate(outputs, axis=-1) # (n_tokens, d_model)
# Final linear transformation
final_output = np.dot(concatenated, W_O) # (n_tokens, d_model)
return final_output, attention_weights
# Example usage with a more realistic sequence
def create_example_inputs(n_tokens=4, d_model=8, n_heads=2):
"""Create example inputs for multi-head attention."""
# Input sequence
X = np.random.randn(n_tokens, d_model)
# Weight matrices
head_dim = d_model // n_heads
W_Q = np.random.randn(d_model, d_model) * 0.1
W_K = np.random.randn(d_model, d_model) * 0.1
W_V = np.random.randn(d_model, d_model) * 0.1
W_O = np.random.randn(d_model, d_model) * 0.1
# Optional mask (causal attention)
mask = np.tril(np.ones((n_tokens, n_tokens)))
return X, W_Q, W_K, W_V, W_O, mask
# Run example
X, W_Q, W_K, W_V, W_O, mask = create_example_inputs()
output, weights = multi_head_attention(X, W_Q, W_K, W_V, W_O, n_heads=2, mask=mask)
print("Input shape:", X.shape)
print("Output shape:", output.shape)
print("\nAttention weights for first head:\n", weights[0])
print("\nAttention weights for second head:\n", weights[1])
Desglose del Código
- Arquitectura de la Función
- Implementa atención multi-cabeza con documentación detallada.
- Incluye una opción de enmascarado para atención causal.
- Devuelve tanto las salidas como los pesos de atención para análisis.
- Componentes Clave
- Cálculo de la Dimensión por Cabeza: Divide la dimensión de entrada entre las cabezas.
- Procesamiento por Cabeza: Calcula la atención separada para cada cabeza.
- Mecanismo de Atención: Implementa atención de producto punto escalada.
- Agregación de Salidas: Concatenación y proyección de las salidas de las cabezas.
- Características Mejoradas
- Estabilidad Numérica: Utiliza una implementación de softmax estable.
- Soporte de Enmascarado: Permite patrones de atención enmascarados.
- Escalado Correcto: Incluye un factor de escalado para la atención.
- Funciones Auxiliares
- create_example_inputs: Genera datos de prueba realistas.
- Incluye información de forma y lógica de inicialización.
- Demuestra patrones de uso adecuados.
- Análisis de Salida
- Imprime las formas de los datos para verificación.
- Muestra los pesos de atención para interpretación.
- Demuestra la naturaleza multi-cabeza de la atención.
3.3.4 Aplicaciones de la Auto-Atención y Atención Multi-Cabeza
Resumen de Textos
Los modelos aprovechan los mecanismos de atención de maneras sofisticadas para identificar y priorizar las partes más importantes de un documento. El mecanismo de atención funciona asignando diferentes pesos a diferentes partes del texto de entrada, esencialmente creando una jerarquía de importancia. Estos pesos se aprenden durante el entrenamiento y se ajustan dinámicamente según el contenido específico que se procesa.
Los pesos de atención actúan como un mecanismo de filtrado avanzado que ayuda a determinar qué oraciones contienen la información más crítica. Este proceso implica analizar diversas características lingüísticas, incluidas la relevancia semántica, la estructura sintáctica y las relaciones contextuales entre las diferentes partes del texto. El modelo puede así crear resúmenes concisos y significativos, preservando el mensaje principal y manteniendo la coherencia.
Por ejemplo, en el resumen de artículos de noticias, el modelo emplea un enfoque de atención en múltiples capas. Podría enfocarse fuertemente en eventos clave (como las principales acciones o desarrollos), citas significativas de figuras relevantes y datos estadísticos importantes que respaldan la narrativa principal. Mientras tanto, asigna pesos de atención más bajos a detalles complementarios, información de contexto o contenido redundante. Este proceso de atención selectiva refleja el comportamiento humano en la creación de resúmenes, donde naturalmente nos enfocamos en información crucial mientras pasamos por alto detalles menos importantes.
Ejemplo de Código: Resumen de Textos con Auto-Atención
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
class SelfAttentionSummarizer(nn.Module):
def __init__(self, vocab_size, embed_dim, num_heads, hidden_dim, max_length=512):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
self.position_encoding = nn.Parameter(
torch.zeros(max_length, embed_dim)
)
self.multihead_attention = nn.MultiheadAttention(
embed_dim, num_heads, batch_first=True
)
self.layer_norm1 = nn.LayerNorm(embed_dim)
self.feed_forward = nn.Sequential(
nn.Linear(embed_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, embed_dim)
)
self.layer_norm2 = nn.LayerNorm(embed_dim)
self.output_layer = nn.Linear(embed_dim, vocab_size)
def forward(self, x, src_mask=None):
# Add positional encoding to embeddings
seq_length = x.size(1)
x = self.embedding(x) + self.position_encoding[:seq_length]
# Self-attention block
attention_output, attention_weights = self.multihead_attention(
x, x, x,
key_padding_mask=src_mask,
need_weights=True
)
x = self.layer_norm1(x + attention_output)
# Feed-forward block
ff_output = self.feed_forward(x)
x = self.layer_norm2(x + ff_output)
# Generate output probabilities
output = self.output_layer(x)
return output, attention_weights
def generate_summary(model, input_ids, tokenizer, max_length=150):
model.eval()
with torch.no_grad():
output, attention_weights = model(input_ids)
# Get most attended words for summary
attention_scores = attention_weights.mean(dim=1)
top_scores = torch.topk(attention_scores.squeeze(), k=max_length)
# Extract and arrange summary tokens
summary_indices = top_scores.indices.sort().values
summary_tokens = input_ids[0, summary_indices]
# Convert to text
summary = tokenizer.decode(summary_tokens)
return summary, attention_weights
# Example usage
def summarize_text(text, model, tokenizer):
# Tokenize input text
inputs = tokenizer(text, return_tensors="pt", max_length=512, truncation=True)
# Generate summary
summary, attention = generate_summary(
model,
inputs["input_ids"],
tokenizer
)
return summary, attention
Desglose del Código
- Arquitectura del Modelo
- Implementa un resumidor basado en Transformer con auto-atención multi-cabeza.
- Incluye codificación posicional para reconocer la secuencia.
- Utiliza normalización por capas y conexiones residuales para un entrenamiento estable.
- Componentes Clave
- Capa de Embedding: Convierte tokens en vectores densos.
- Atención Multi-Cabeza: Procesa el texto desde múltiples perspectivas.
- Red Feed-Forward: Agrega no linealidad y transforma representaciones.
- Capa de Salida: Genera predicciones finales de tokens.
- Proceso de Resumen
- Analiza los pesos de atención para identificar los tokens importantes.
- Selecciona los tokens con mayor atención para generar el resumen.
- Mantiene el orden original de los tokens seleccionados para asegurar coherencia.
- Características Avanzadas
- Soporta entradas de longitud variable con enmascaramiento.
- Implementa un procesamiento eficiente por lotes.
- Devuelve pesos de atención para análisis y visualización.
Ejemplo de Uso:
# Example setup and usage
vocab_size = 30000
embed_dim = 512
num_heads = 8
hidden_dim = 2048
model = SelfAttentionSummarizer(
vocab_size=vocab_size,
embed_dim=embed_dim,
num_heads=num_heads,
hidden_dim=hidden_dim
)
# Example text
text = """
Climate change poses significant challenges to global ecosystems.
Rising temperatures affect wildlife habitats and agricultural productivity.
Scientists warn that immediate action is necessary to prevent irreversible damage.
"""
# Generate summary (assuming tokenizer is initialized)
summary, attention = summarize_text(text, model, tokenizer)
print("Summary:", summary)
Traducción Automática
Los mecanismos de atención han revolucionado la traducción automática al crear alineaciones dinámicas sofisticadas entre palabras y frases en diferentes idiomas. Este proceso funciona estableciendo conexiones ponderadas entre elementos de los idiomas de origen y destino, permitiendo al modelo comprender relaciones lingüísticas complejas. Por ejemplo, al traducir del inglés al japonés, el mecanismo de atención puede manejar las significativas diferencias en la estructura de las oraciones, donde el inglés sigue un orden Sujeto-Verbo-Objeto, mientras que el japonés generalmente utiliza Sujeto-Objeto-Verbo.
El mecanismo es especialmente poderoso al abordar tres desafíos clave de la traducción:
Primero, maneja variaciones complejas en el orden de las palabras entre idiomas. Por ejemplo, al traducir entre inglés y alemán, donde la posición del verbo puede variar significativamente, el mecanismo de atención puede mantener las relaciones semánticas adecuadas a pesar de las diferencias sintácticas.
Segundo, maneja eficazmente las correspondencias de palabras de muchos a uno y de uno a muchos. Por ejemplo, al traducir la palabra compuesta alemana "Schadenfreude" al inglés, el mecanismo puede mapearla a la frase "placer derivado del infortunio ajeno", manteniendo un significado preciso a pesar de la diferencia estructural.
Tercero, el modelo mantiene la conciencia contextual en oraciones extendidas gracias a su capacidad de referenciar y ponderar la importancia de diferentes partes de la secuencia de entrada. Esto garantiza que las oraciones largas conserven su significado y coherencia en la traducción, evitando problemas comunes como perder la relación sujeto-verbo o mal manejar cláusulas dependientes.
El mecanismo de atención logra esto actualizando continuamente su enfoque en función de la palabra actual que se está traduciendo y su relación con todas las demás palabras en la oración, asegurando que la traducción final preserve tanto el significado como el flujo natural del lenguaje.
Ejemplo de Código: Traducción Automática Neuronal con Auto-Atención
import torch
import torch.nn as nn
import torch.nn.functional as F
class TranslationTransformer(nn.Module):
def __init__(self, src_vocab_size, tgt_vocab_size, d_model=512, nhead=8,
num_encoder_layers=6, num_decoder_layers=6, dim_feedforward=2048):
super().__init__()
# Embedding layers
self.src_embedding = nn.Embedding(src_vocab_size, d_model)
self.tgt_embedding = nn.Embedding(tgt_vocab_size, d_model)
self.positional_encoding = PositionalEncoding(d_model)
# Transformer layers
self.transformer = nn.Transformer(
d_model=d_model,
nhead=nhead,
num_encoder_layers=num_encoder_layers,
num_decoder_layers=num_decoder_layers,
dim_feedforward=dim_feedforward
)
# Output projection
self.output_layer = nn.Linear(d_model, tgt_vocab_size)
def forward(self, src, tgt, src_mask=None, tgt_mask=None):
# Create source embedding
src_embedded = self.positional_encoding(self.src_embedding(src))
# Create target embedding
tgt_embedded = self.positional_encoding(self.tgt_embedding(tgt))
# Generate masks if not provided
if src_mask is None:
src_mask = self.generate_square_subsequent_mask(src.size(1))
if tgt_mask is None:
tgt_mask = self.generate_square_subsequent_mask(tgt.size(1))
# Pass through transformer
output = self.transformer(
src_embedded, tgt_embedded,
src_mask=src_mask,
tgt_mask=tgt_mask
)
# Project to vocabulary
return self.output_layer(output)
@staticmethod
def generate_square_subsequent_mask(sz):
mask = torch.triu(torch.ones(sz, sz), diagonal=1)
mask = mask.masked_fill(mask==1, float('-inf'))
return mask
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=5000):
super().__init__()
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2).float() *
(-math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0)
self.register_buffer('pe', pe)
def forward(self, x):
return x + self.pe[:, :x.size(1)]
# Training function
def train_translation_model(model, train_loader, optimizer, criterion, num_epochs=10):
model.train()
for epoch in range(num_epochs):
total_loss = 0
for batch_idx, (src, tgt) in enumerate(train_loader):
optimizer.zero_grad()
# Forward pass
output = model(src, tgt[:-1]) # exclude last target token
# Calculate loss
loss = criterion(
output.view(-1, output.size(-1)),
tgt[1:].reshape(-1) # exclude first target token (BOS)
)
# Backward pass
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(train_loader)
print(f'Epoch: {epoch+1}, Average Loss: {avg_loss:.4f}')
Desglose del Código
- Arquitectura del Modelo
- Implementa un modelo completo de traducción basado en Transformer.
- Utiliza un codificador y un decodificador con atención multi-cabeza.
- Incluye codificación posicional para reconocer el orden de la secuencia.
- Componentes Clave
- Embeddings de Origen y Destino: Convierte tokens en vectores.
- Codificación Posicional: Agrega información de posición a los embeddings.
- Bloque Transformer: Procesa secuencias utilizando auto-atención.
- Proyección de Salida: Mapea al vocabulario del idioma destino.
- Proceso de Entrenamiento
- Implementa teacher forcing durante el entrenamiento.
- Utiliza atención enmascarada para generación autorregresiva.
- Incluye pasos de cálculo de pérdida y optimización.
- Características Avanzadas
- Soporta secuencias de longitud variable.
- Implementa un procesamiento eficiente por lotes.
- Incluye generación de máscaras para atención causal.
Ejemplo de Uso:
# Initialize model and training components
model = TranslationTransformer(
src_vocab_size=10000,
tgt_vocab_size=10000,
d_model=512,
nhead=8
)
# Setup optimizer and criterion
optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)
criterion = nn.CrossEntropyLoss(ignore_index=pad_idx)
# Example translation
def translate(model, src_sentence, src_tokenizer, tgt_tokenizer, max_len=50):
model.eval()
with torch.no_grad():
# Tokenize source sentence
src_tokens = src_tokenizer.encode(src_sentence)
src_tensor = torch.LongTensor(src_tokens).unsqueeze(0)
# Initialize target with BOS token
tgt_tokens = [tgt_tokenizer.bos_token_id]
# Generate translation
for _ in range(max_len):
tgt_tensor = torch.LongTensor(tgt_tokens).unsqueeze(0)
output = model(src_tensor, tgt_tensor)
next_token = output[0, -1].argmax().item()
if next_token == tgt_tokenizer.eos_token_id:
break
tgt_tokens.append(next_token)
# Convert tokens to text
translation = tgt_tokenizer.decode(tgt_tokens)
return translation
Respuestas a Preguntas
Al procesar preguntas, los mecanismos de atención emplean un enfoque sofisticado para el procesamiento de información. Estos mecanismos ayudan a los modelos a identificar y enfocarse en las partes específicas de un pasaje que contienen información relevante a través de un proceso de varios pasos:
Primero, el modelo analiza la pregunta para entender qué tipo de información necesita buscar. Luego, crea pesos de atención para cada palabra en el pasaje, asignando pesos más altos a las palabras y frases que tienen más probabilidades de contener la respuesta. Este enfoque selectivo permite al modelo extraer respuestas de manera eficiente mientras ignora contenido irrelevante.
Por ejemplo, al responder "¿Cuándo ocurrió el evento?", el modelo se enfocaría principalmente en expresiones temporales (como fechas, horarios y frases temporales como "ayer" o "la semana pasada") y en su contexto circundante en el pasaje. Los pesos de atención serían más altos para estos indicadores temporales y su contexto inmediato, permitiendo al modelo centrarse en la información más relevante. Este proceso es similar a cómo los humanos podrían escanear un texto en busca de palabras relacionadas con el tiempo al buscar cuándo ocurrió algo.
Ejemplo de Código: Respuestas a Preguntas con Auto-Atención
import torch
import torch.nn as nn
import torch.nn.functional as F
class QATransformer(nn.Module):
def __init__(self, vocab_size, d_model=512, nhead=8, num_layers=6):
super().__init__()
# Embedding layers
self.embedding = nn.Embedding(vocab_size, d_model)
self.pos_encoder = PositionalEncoding(d_model)
# Multi-head attention layers
self.question_encoder = nn.TransformerEncoder(
nn.TransformerEncoderLayer(d_model, nhead),
num_layers
)
self.context_encoder = nn.TransformerEncoder(
nn.TransformerEncoderLayer(d_model, nhead),
num_layers
)
# Cross-attention layer
self.cross_attention = nn.MultiheadAttention(d_model, nhead)
# Output layers for start and end position prediction
self.start_predictor = nn.Linear(d_model, 1)
self.end_predictor = nn.Linear(d_model, 1)
def forward(self, question, context):
# Embed inputs
q_embed = self.pos_encoder(self.embedding(question))
c_embed = self.pos_encoder(self.embedding(context))
# Encode question and context
q_encoded = self.question_encoder(q_embed)
c_encoded = self.context_encoder(c_embed)
# Cross-attention between question and context
attn_output, attention_weights = self.cross_attention(
q_encoded, c_encoded, c_encoded
)
# Predict answer span
start_logits = self.start_predictor(attn_output).squeeze(-1)
end_logits = self.end_predictor(attn_output).squeeze(-1)
return start_logits, end_logits, attention_weights
def train_qa_model(model, train_loader, optimizer, num_epochs=10):
model.train()
criterion = nn.CrossEntropyLoss()
for epoch in range(num_epochs):
for batch in train_loader:
question, context, start_pos, end_pos = batch
# Forward pass
start_logits, end_logits, _ = model(question, context)
# Calculate loss
start_loss = criterion(start_logits, start_pos)
end_loss = criterion(end_logits, end_pos)
loss = start_loss + end_loss
# Backward pass
optimizer.zero_grad()
loss.backward()
optimizer.step()
def predict_answer(model, tokenizer, question, context):
model.eval()
with torch.no_grad():
# Tokenize inputs
q_tokens = tokenizer.encode(question)
c_tokens = tokenizer.encode(context)
# Convert to tensors
q_tensor = torch.tensor(q_tokens).unsqueeze(0)
c_tensor = torch.tensor(c_tokens).unsqueeze(0)
# Get predictions
start_logits, end_logits, attention = model(q_tensor, c_tensor)
# Find most likely answer span
start_idx = torch.argmax(start_logits)
end_idx = torch.argmax(end_logits[start_idx:]) + start_idx
# Extract answer tokens
answer_tokens = c_tokens[start_idx:end_idx+1]
# Convert back to text
answer = tokenizer.decode(answer_tokens)
return answer, attention
Desglose del Código:
- Arquitectura del Modelo
- Implementa un modelo de preguntas y respuestas basado en Transformer con codificadores separados para preguntas y contexto
- Utiliza auto-atención multi-cabeza para el procesamiento tanto de preguntas como de contexto
- Incluye mecanismo de atención cruzada para relacionar preguntas con el contexto
- Cuenta con predicción de segmentos para la extracción de respuestas
- Componentes Principales
- Capa de Embedding: Convierte tokens de texto en vectores densos
- Codificación Posicional: Agrega información de posición a los embeddings
- Codificadores de Pregunta/Contexto: Procesan entradas usando auto-atención
- Atención Cruzada: Relaciona la pregunta con el contexto para encontrar respuestas
- Predictores de Segmento: Localizan los límites de la respuesta en el contexto
- Flujo de Procesamiento
- Incorpora y codifica la pregunta y el contexto por separado
- Aplica atención cruzada para encontrar regiones relevantes del contexto
- Predice las posiciones de inicio y fin del segmento de respuesta
- Devuelve el texto de la respuesta y los pesos de atención para análisis
Ejemplo de Uso:
# Initialize model and components
model = QATransformer(
vocab_size=30000,
d_model=512,
nhead=8,
num_layers=6
)
# Example usage
question = "When was the first computer invented?"
context = "The first general-purpose electronic computer, ENIAC, was completed in 1945."
# Get answer
answer, attention_weights = predict_answer(
model, tokenizer, question, context
)
print(f"Question: {question}")
print(f"Answer: {answer}")
3.3.5 Puntos Clave
- La auto-atención permite a los modelos calcular representaciones conscientes del contexto al atender a todos los tokens en una secuencia. Esto significa que cada palabra en una oración puede interactuar directamente con cualquier otra palabra, permitiendo al modelo entender relaciones y dependencias complejas. Por ejemplo, en la oración "El gato que persiguió al ratón era negro", la auto-atención ayuda al modelo a conectar "era negro" con "gato", aunque estén separados por varias palabras.
- La atención multi-cabeza mejora la auto-atención al capturar relaciones diversas simultáneamente. Mientras una sola cabeza de atención podría enfocarse en relaciones sintácticas, otra podría capturar similitudes semánticas y otra más podría rastrear relaciones temporales. Este enfoque multifacético permite al modelo procesar la información desde múltiples "perspectivas" al mismo tiempo, conduciendo a una comprensión más rica y matizada de la entrada.
- Juntas, estas mecánicas son la base de las arquitecturas Transformer, permitiendo paralelismo y modelado de dependencias a largo alcance. A diferencia de los modelos secuenciales tradicionales que procesan palabras una a la vez, los Transformers pueden procesar todas las palabras simultáneamente, mejorando drásticamente la eficiencia computacional. Además, dado que cada token puede atender directamente a cualquier otro token, los Transformers destacan en capturar relaciones entre palabras que están distantes en el texto, resolviendo el desafío histórico de modelar dependencias de largo alcance en el procesamiento del lenguaje natural.
3.3 Autoatención y Atención Multi-Cabeza
Sobre la base de los mecanismos de atención, la autoatención surgió como una innovación revolucionaria en el procesamiento del lenguaje natural (NLP). Este enfoque transformador cambió la forma en que los modelos procesan secuencias de entrada al introducir un mecanismo en el que cada elemento de una secuencia puede interactuar directamente con todos los demás. Esta interacción directa permite a los modelos procesar secuencias de entrada con una eficiencia y conciencia contextual sin precedentes, eliminando los cuellos de botella tradicionales del procesamiento secuencial.
La autoatención permite que cada token consulte y atienda simultáneamente a todos los demás tokens en la secuencia. Por ejemplo, al procesar la oración "The cat sat on the mat", cada palabra puede evaluar directamente su relación con todas las demás, ayudando al modelo a entender tanto relaciones locales (como "the cat") como dependencias a larga distancia (conectar "cat" con "sat").
Cuando se combina con la atención multi-cabeza, esta capacidad se vuelve aún más poderosa. La atención multi-cabeza permite al modelo mantener múltiples patrones de atención diferentes simultáneamente, cada uno enfocado en diferentes aspectos de las relaciones entre los tokens. Este enfoque multifacético es la base de los modelos Transformer, capacitando a los modelos para capturar relaciones complejas entre tokens en una secuencia desde múltiples perspectivas al mismo tiempo.
En esta sección exploraremos la autoatención y su extensión a la atención multi-cabeza, examinando cómo funcionan estos mecanismos y por qué son fundamentales en las arquitecturas Transformer. Analizaremos sus fundamentos matemáticos, sus implementaciones prácticas y demostraremos su efectividad con ejemplos concretos y código.
3.3.1 ¿Qué es la autoatención?
En la autoatención, cada token en una secuencia de entrada atiende a todos los demás tokens (incluyéndose a sí mismo) para calcular una nueva representación. Este mecanismo revolucionario crea conexiones dinámicas entre todos los elementos de una secuencia. Por ejemplo, al procesar una oración, cada palabra mantiene conciencia de todas las demás mediante pesos de atención que determinan cuánto influye cada palabra en la representación de la palabra actual. Estos pesos se aprenden durante el entrenamiento y se adaptan en función del contexto y la tarea.
Para ilustrar este concepto, considera la oración "The cat chased the mouse." Al procesar la palabra "chased", el mecanismo de autoatención considera simultáneamente todas las palabras en la oración:
- Presta mucha atención a "cat" como el sujeto que realiza la acción.
- Mantiene una atención fuerte en "mouse" como el objeto que recibe la acción.
- Puede prestar menos atención a artículos como "the", que aportan menos al significado semántico.
Este procesamiento paralelo permite al modelo construir una comprensión rica y contextual del papel de cada palabra en la oración.
A diferencia de los mecanismos de atención tradicionales, que típicamente trabajan con dos secuencias separadas (como en la traducción automática donde una palabra en inglés atiende a palabras en francés), la autoatención opera completamente dentro de una única secuencia. Este enfoque interno representa un avance significativo en el procesamiento del lenguaje natural.
La eficacia de este enfoque se hace particularmente evidente al manejar fenómenos lingüísticos complejos:
- Dependencias a larga distancia: (por ejemplo, "The cat, which had a brown collar, chased the mouse").
- Resolución de correferencias: (entender que "it" se refiere a "the cat").
- Etiquetado de roles semánticos: (identificar quién hizo qué a quién).
- Comprensión de estructuras sintácticas: (captar las relaciones gramaticales entre palabras).
Cómo funciona:
- Representación de entrada: Cada token (palabra o subpalabra) en la secuencia se convierte primero en un vector numérico mediante un proceso de embeddings. Estos vectores tienen cientos de dimensiones y capturan relaciones semánticas entre palabras. Por ejemplo, palabras similares como "cat" y "kitten" tendrán vectores cercanos en este espacio de alta dimensión.
- Creación de Query, Key y Value: El modelo transforma el vector inicial de cada token en tres vectores distintos mediante transformaciones lineales aprendidas:
- Query (Q): Representa lo que la palabra actual está buscando en la secuencia.
- Key (K): Funciona como un índice, ayudando a otros tokens a encontrar esta palabra cuando es relevante.
- Value (V): Contiene la información significativa que se usará en la representación final.
- Cálculo de puntajes de atención: El modelo calcula los puntajes de atención tomando el producto punto entre cada query y todas las keys. Esto crea una matriz de puntajes donde cada entrada (i, j) representa cuán relevante es el token j para el token i. Los puntajes se escalan dividiéndolos por la raíz cuadrada de la dimensión del key (\sqrt{d_k}) para evitar que los productos punto se vuelvan demasiado grandes, lo que ayuda a mantener gradientes estables durante el entrenamiento.
- Normalización de pesos: Los puntajes de atención se convierten en probabilidades usando la función softmax. Esto asegura que todos los pesos para un token dado sumen 1, creando una distribución de probabilidad adecuada. Por ejemplo, al procesar "ate" en "The hungry cat ate fish", el modelo podría asignar pesos más altos a palabras relevantes como "cat" (0.6) y "fish" (0.3), y pesos más bajos a palabras menos importantes como "the" (0.02).
- Cálculo de la salida: La representación final de cada token se calcula como una suma ponderada de todos los vectores de valor (V), utilizando los pesos de atención normalizados. Este proceso permite que cada token reúna información de todos los demás tokens en la secuencia, ponderada por su relevancia. Las representaciones resultantes son conscientes del contexto y pueden capturar tanto la estructura gramatical local como las dependencias a larga distancia, lo que permite al modelo entender relaciones entre palabras incluso cuando están muy separadas en el texto.
3.3.2 Matemáticas de la autoatención
Para una secuencia de tokens de entrada X = [x_1, x_2, \dots, x_n]:
- Calcular Q = XW_Q, K = XW_K y V = XW_V, donde W_Q, W_K, W_V son matrices de pesos aprendibles.
- Calcular los puntajes de atención:
\text{Scores} = \frac{Q \cdot K^\top}{\sqrt{d_k}}
Aquí, d_k es la dimensión de los vectores K.
- Normalizar los puntajes con softmax:
\text{Weights} = \text{softmax}\left(\text{Scores}\right)
- Calcular la salida:
\text{Output} = \text{Weights} \cdot V
Ejemplo: Implementación de autoatención
Implementemos autoatención para una secuencia sencilla.
Ejemplo de código: Autoatención en NumPy
import numpy as np
def self_attention(X, W_Q, W_K, W_V, mask=None):
"""
Compute self-attention for a sequence with optional masking.
Parameters:
-----------
X: np.ndarray
Input sequence of shape (n_tokens, d_model)
W_Q, W_K, W_V: np.ndarray
Weight matrices for Query, Key, Value transformations
mask: np.ndarray, optional
Attention mask of shape (n_tokens, n_tokens)
Returns:
--------
output: np.ndarray
Attended sequence of shape (n_tokens, d_model)
weights: np.ndarray
Attention weights of shape (n_tokens, n_tokens)
"""
# Linear transformations
Q = np.dot(X, W_Q) # Shape: (n_tokens, d_k)
K = np.dot(X, W_K) # Shape: (n_tokens, d_k)
V = np.dot(X, W_V) # Shape: (n_tokens, d_v)
# Calculate scaled dot-product attention
d_k = K.shape[1]
scores = np.dot(Q, K.T) / np.sqrt(d_k) # Shape: (n_tokens, n_tokens)
# Apply mask if provided
if mask is not None:
scores = scores * mask + -1e9 * (1 - mask)
# Softmax normalization
weights = np.exp(scores - np.max(scores, axis=-1, keepdims=True))
weights /= np.sum(weights, axis=-1, keepdims=True)
# Compute weighted sum
output = np.dot(weights, V) # Shape: (n_tokens, d_v)
return output, weights
# Example usage with a more complex sequence
def create_example():
# Create sample sequence
X = np.array([
[1, 0, 0], # First token
[0, 1, 0], # Second token
[0, 0, 1], # Third token
[1, 1, 0] # Fourth token
])
# Create weight matrices
d_model = 3 # Input dimension
d_k = 2 # Key/Query dimension
d_v = 4 # Value dimension
W_Q = np.random.randn(d_model, d_k) * 0.1
W_K = np.random.randn(d_model, d_k) * 0.1
W_V = np.random.randn(d_model, d_v) * 0.1
# Create attention mask (optional)
mask = np.array([
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 0], # Last token masked for third position
[1, 1, 1, 1]
])
return X, W_Q, W_K, W_V, mask
# Run example
X, W_Q, W_K, W_V, mask = create_example()
output, weights = self_attention(X, W_Q, W_K, W_V, mask)
print("Input Shape:", X.shape)
print("\nQuery Matrix Shape:", W_Q.shape)
print("Key Matrix Shape:", W_K.shape)
print("Value Matrix Shape:", W_V.shape)
print("\nAttention Weights:\n", weights)
print("\nOutput Shape:", output.shape)
print("Output:\n", output)
Explicación del desglose del código:
- Definición de la función y parámetros:
- La función toma como entrada la secuencia X y tres matrices de peso (W_Q, W_K, W_V).
- Incluye un parámetro opcional de enmascaramiento para un control más detallado de la atención.
- Proporciona una docstring completa con descripciones de los parámetros.
- Transformaciones lineales:
- Convierte los tokens de entrada en representaciones de Query (Q), Key (K) y Value (V).
- Utiliza multiplicación matricial (\text{np.dot}) para un cálculo eficiente.
- Mantiene transformaciones de forma adecuadas en todo el proceso.
- Cálculo de puntajes de atención:
- Implementa atención de producto punto escalado con el factor de escalado adecuado.
- Incluye funcionalidad de enmascaramiento para atención selectiva.
- Usa una implementación de softmax numéricamente estable.
- Implementación de ejemplo:
- Crea un ejemplo realista con 4 tokens y 3 características.
- Demuestra la inicialización adecuada de las matrices de peso.
- Muestra cómo usar el enmascaramiento opcional.
- Información sobre formas:
- Documenta claramente las formas de los tensores a lo largo del proceso.
- Ayuda a entender las transformaciones dimensionales.
- Facilita la depuración del código.
3.3.3 ¿Qué es la atención multi-cabeza?
La atención multi-cabeza es una mejora sofisticada del mecanismo de autoatención que ejecuta múltiples cálculos de atención en paralelo, llamados "cabezas." Cada cabeza opera de forma independiente y aprende a enfocarse en diferentes aspectos de las relaciones entre tokens en la secuencia. Por ejemplo, una cabeza puede aprender a centrarse en relaciones sintácticas (como la concordancia sujeto-verbo), otra en relaciones semánticas (como la relevancia temática) y otra en dependencias a largo plazo (como la resolución de correferencias).
Esta arquitectura de procesamiento paralelo ofrece varias ventajas clave:
- Permite al modelo analizar simultáneamente la secuencia de entrada desde múltiples perspectivas, como los humanos que procesan el lenguaje considerando varios aspectos a la vez.
- Tener múltiples mecanismos de atención especializados ayuda al modelo a capturar tanto patrones detallados como generales en los datos.
- Las representaciones diversas aprendidas por las diferentes cabezas se combinan para crear una comprensión más rica y matizada de la secuencia de entrada.
Las salidas de todas las cabezas se combinan finalmente a través de una operación de concatenación seguida de una transformación lineal. Esto permite al modelo sintetizar estas diferentes perspectivas en una representación cohesiva. Este enfoque multifacético mejora significativamente la capacidad del modelo para entender y procesar patrones lingüísticos complejos, haciéndolo particularmente eficaz para tareas que requieren una comprensión sofisticada del lenguaje.
Pasos en la atención multi-cabeza
- Dividir la entrada en múltiples cabezas:
- Divide la secuencia de entrada en subespacios separados.
- Cada cabeza recibe una porción de la dimensionalidad de la entrada.
- Esta división permite el procesamiento paralelo de diferentes aspectos de las características.
- Aplicar autoatención de forma independiente a cada cabeza:
- Cada cabeza calcula sus propias matrices de Query ($Q$), Key ($K$) y Value ($V$).
- Calcula los puntajes de atención usando atención de producto punto escalado.
- Procesa la información enfocándose en diferentes aspectos de la entrada.
- Concatenar las salidas de todas las cabezas:
- Combina los resultados de cada cabeza de atención.
- Preserva los patrones y relaciones únicos aprendidos por cada cabeza.
- Crea una representación integral de la secuencia de entrada.
- Aplicar una transformación lineal final:
- Proyecta las salidas concatenadas a la dimensión deseada.
- Integra la información de todas las cabezas en una representación cohesiva.
- Permite al modelo ponderar la importancia de las salidas de diferentes cabezas.
Beneficios de la Atención Multi-Cabeza
- Representaciones Diversas: Cada cabeza de atención se especializa en capturar diferentes tipos de relaciones dentro de los datos. Por ejemplo, una cabeza puede enfocarse en dependencias sintácticas (como la concordancia sujeto-verbo), otra en relaciones semánticas (como la relevancia temática), y otra en dependencias de largo alcance (como la resolución de correferencias). Esta diversidad permite al modelo construir una comprensión rica y multifacética de la entrada.
- Mayor Expresividad: El modelo puede enfocarse en múltiples aspectos de la entrada simultáneamente, similar a cómo los humanos procesan el lenguaje. Este procesamiento en paralelo permite al modelo:
- Capturar tanto el contexto local como el global.
- Procesar diferentes niveles semánticos (nivel de palabra, frase, oración).
- Aprender relaciones jerárquicas entre los tokens.
- Combinar diferentes perspectivas en una comprensión más completa.
- Capacidad de Aprendizaje Mejorada: Las múltiples cabezas permiten al modelo distribuir la atención a través de diferentes subespacios, incrementando efectivamente su poder representacional sin aumentar significativamente la complejidad computacional.
- Detección Robusta de Características: Al mantener múltiples mecanismos de atención independientes, el modelo se vuelve más robusto, ya que no depende de un único patrón de atención, reduciendo el impacto de ruido o patrones engañosos en los datos.
Ejemplo: Atención Multi-Cabeza
Implementemos una versión simplificada de la atención multi-cabeza.
Ejemplo de Código: Atención Multi-Cabeza en NumPy
import numpy as np
def multi_head_attention(X, W_Q, W_K, W_V, W_O, n_heads, mask=None):
"""
Compute multi-head attention with optional masking.
Parameters:
-----------
X: np.ndarray
Input sequence of shape (n_tokens, d_model)
W_Q, W_K, W_V: np.ndarray
Weight matrices for Query, Key, Value transformations
W_O: np.ndarray
Output projection matrix
n_heads: int
Number of attention heads
mask: np.ndarray, optional
Attention mask of shape (n_tokens, n_tokens)
Returns:
--------
final_output: np.ndarray
Transformed sequence of shape (n_tokens, d_model)
attention_weights: list
List of attention weights for each head
"""
d_model = X.shape[1]
head_dim = W_Q.shape[1] // n_heads
outputs = []
attention_weights = []
# Process each attention head
for i in range(n_heads):
# Split weights for current head
Q = np.dot(X, W_Q[:, i*head_dim:(i+1)*head_dim]) # (n_tokens, head_dim)
K = np.dot(X, W_K[:, i*head_dim:(i+1)*head_dim]) # (n_tokens, head_dim)
V = np.dot(X, W_V[:, i*head_dim:(i+1)*head_dim]) # (n_tokens, head_dim)
# Compute attention scores
scores = np.dot(Q, K.T) / np.sqrt(head_dim) # (n_tokens, n_tokens)
# Apply mask if provided
if mask is not None:
scores = scores * mask + -1e9 * (1 - mask)
# Apply softmax
weights = np.exp(scores - np.max(scores, axis=-1, keepdims=True))
weights = weights / np.sum(weights, axis=-1, keepdims=True)
# Compute weighted sum
output = np.dot(weights, V) # (n_tokens, head_dim)
outputs.append(output)
attention_weights.append(weights)
# Concatenate all heads
concatenated = np.concatenate(outputs, axis=-1) # (n_tokens, d_model)
# Final linear transformation
final_output = np.dot(concatenated, W_O) # (n_tokens, d_model)
return final_output, attention_weights
# Example usage with a more realistic sequence
def create_example_inputs(n_tokens=4, d_model=8, n_heads=2):
"""Create example inputs for multi-head attention."""
# Input sequence
X = np.random.randn(n_tokens, d_model)
# Weight matrices
head_dim = d_model // n_heads
W_Q = np.random.randn(d_model, d_model) * 0.1
W_K = np.random.randn(d_model, d_model) * 0.1
W_V = np.random.randn(d_model, d_model) * 0.1
W_O = np.random.randn(d_model, d_model) * 0.1
# Optional mask (causal attention)
mask = np.tril(np.ones((n_tokens, n_tokens)))
return X, W_Q, W_K, W_V, W_O, mask
# Run example
X, W_Q, W_K, W_V, W_O, mask = create_example_inputs()
output, weights = multi_head_attention(X, W_Q, W_K, W_V, W_O, n_heads=2, mask=mask)
print("Input shape:", X.shape)
print("Output shape:", output.shape)
print("\nAttention weights for first head:\n", weights[0])
print("\nAttention weights for second head:\n", weights[1])
Desglose del Código
- Arquitectura de la Función
- Implementa atención multi-cabeza con documentación detallada.
- Incluye una opción de enmascarado para atención causal.
- Devuelve tanto las salidas como los pesos de atención para análisis.
- Componentes Clave
- Cálculo de la Dimensión por Cabeza: Divide la dimensión de entrada entre las cabezas.
- Procesamiento por Cabeza: Calcula la atención separada para cada cabeza.
- Mecanismo de Atención: Implementa atención de producto punto escalada.
- Agregación de Salidas: Concatenación y proyección de las salidas de las cabezas.
- Características Mejoradas
- Estabilidad Numérica: Utiliza una implementación de softmax estable.
- Soporte de Enmascarado: Permite patrones de atención enmascarados.
- Escalado Correcto: Incluye un factor de escalado para la atención.
- Funciones Auxiliares
- create_example_inputs: Genera datos de prueba realistas.
- Incluye información de forma y lógica de inicialización.
- Demuestra patrones de uso adecuados.
- Análisis de Salida
- Imprime las formas de los datos para verificación.
- Muestra los pesos de atención para interpretación.
- Demuestra la naturaleza multi-cabeza de la atención.
3.3.4 Aplicaciones de la Auto-Atención y Atención Multi-Cabeza
Resumen de Textos
Los modelos aprovechan los mecanismos de atención de maneras sofisticadas para identificar y priorizar las partes más importantes de un documento. El mecanismo de atención funciona asignando diferentes pesos a diferentes partes del texto de entrada, esencialmente creando una jerarquía de importancia. Estos pesos se aprenden durante el entrenamiento y se ajustan dinámicamente según el contenido específico que se procesa.
Los pesos de atención actúan como un mecanismo de filtrado avanzado que ayuda a determinar qué oraciones contienen la información más crítica. Este proceso implica analizar diversas características lingüísticas, incluidas la relevancia semántica, la estructura sintáctica y las relaciones contextuales entre las diferentes partes del texto. El modelo puede así crear resúmenes concisos y significativos, preservando el mensaje principal y manteniendo la coherencia.
Por ejemplo, en el resumen de artículos de noticias, el modelo emplea un enfoque de atención en múltiples capas. Podría enfocarse fuertemente en eventos clave (como las principales acciones o desarrollos), citas significativas de figuras relevantes y datos estadísticos importantes que respaldan la narrativa principal. Mientras tanto, asigna pesos de atención más bajos a detalles complementarios, información de contexto o contenido redundante. Este proceso de atención selectiva refleja el comportamiento humano en la creación de resúmenes, donde naturalmente nos enfocamos en información crucial mientras pasamos por alto detalles menos importantes.
Ejemplo de Código: Resumen de Textos con Auto-Atención
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
class SelfAttentionSummarizer(nn.Module):
def __init__(self, vocab_size, embed_dim, num_heads, hidden_dim, max_length=512):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
self.position_encoding = nn.Parameter(
torch.zeros(max_length, embed_dim)
)
self.multihead_attention = nn.MultiheadAttention(
embed_dim, num_heads, batch_first=True
)
self.layer_norm1 = nn.LayerNorm(embed_dim)
self.feed_forward = nn.Sequential(
nn.Linear(embed_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, embed_dim)
)
self.layer_norm2 = nn.LayerNorm(embed_dim)
self.output_layer = nn.Linear(embed_dim, vocab_size)
def forward(self, x, src_mask=None):
# Add positional encoding to embeddings
seq_length = x.size(1)
x = self.embedding(x) + self.position_encoding[:seq_length]
# Self-attention block
attention_output, attention_weights = self.multihead_attention(
x, x, x,
key_padding_mask=src_mask,
need_weights=True
)
x = self.layer_norm1(x + attention_output)
# Feed-forward block
ff_output = self.feed_forward(x)
x = self.layer_norm2(x + ff_output)
# Generate output probabilities
output = self.output_layer(x)
return output, attention_weights
def generate_summary(model, input_ids, tokenizer, max_length=150):
model.eval()
with torch.no_grad():
output, attention_weights = model(input_ids)
# Get most attended words for summary
attention_scores = attention_weights.mean(dim=1)
top_scores = torch.topk(attention_scores.squeeze(), k=max_length)
# Extract and arrange summary tokens
summary_indices = top_scores.indices.sort().values
summary_tokens = input_ids[0, summary_indices]
# Convert to text
summary = tokenizer.decode(summary_tokens)
return summary, attention_weights
# Example usage
def summarize_text(text, model, tokenizer):
# Tokenize input text
inputs = tokenizer(text, return_tensors="pt", max_length=512, truncation=True)
# Generate summary
summary, attention = generate_summary(
model,
inputs["input_ids"],
tokenizer
)
return summary, attention
Desglose del Código
- Arquitectura del Modelo
- Implementa un resumidor basado en Transformer con auto-atención multi-cabeza.
- Incluye codificación posicional para reconocer la secuencia.
- Utiliza normalización por capas y conexiones residuales para un entrenamiento estable.
- Componentes Clave
- Capa de Embedding: Convierte tokens en vectores densos.
- Atención Multi-Cabeza: Procesa el texto desde múltiples perspectivas.
- Red Feed-Forward: Agrega no linealidad y transforma representaciones.
- Capa de Salida: Genera predicciones finales de tokens.
- Proceso de Resumen
- Analiza los pesos de atención para identificar los tokens importantes.
- Selecciona los tokens con mayor atención para generar el resumen.
- Mantiene el orden original de los tokens seleccionados para asegurar coherencia.
- Características Avanzadas
- Soporta entradas de longitud variable con enmascaramiento.
- Implementa un procesamiento eficiente por lotes.
- Devuelve pesos de atención para análisis y visualización.
Ejemplo de Uso:
# Example setup and usage
vocab_size = 30000
embed_dim = 512
num_heads = 8
hidden_dim = 2048
model = SelfAttentionSummarizer(
vocab_size=vocab_size,
embed_dim=embed_dim,
num_heads=num_heads,
hidden_dim=hidden_dim
)
# Example text
text = """
Climate change poses significant challenges to global ecosystems.
Rising temperatures affect wildlife habitats and agricultural productivity.
Scientists warn that immediate action is necessary to prevent irreversible damage.
"""
# Generate summary (assuming tokenizer is initialized)
summary, attention = summarize_text(text, model, tokenizer)
print("Summary:", summary)
Traducción Automática
Los mecanismos de atención han revolucionado la traducción automática al crear alineaciones dinámicas sofisticadas entre palabras y frases en diferentes idiomas. Este proceso funciona estableciendo conexiones ponderadas entre elementos de los idiomas de origen y destino, permitiendo al modelo comprender relaciones lingüísticas complejas. Por ejemplo, al traducir del inglés al japonés, el mecanismo de atención puede manejar las significativas diferencias en la estructura de las oraciones, donde el inglés sigue un orden Sujeto-Verbo-Objeto, mientras que el japonés generalmente utiliza Sujeto-Objeto-Verbo.
El mecanismo es especialmente poderoso al abordar tres desafíos clave de la traducción:
Primero, maneja variaciones complejas en el orden de las palabras entre idiomas. Por ejemplo, al traducir entre inglés y alemán, donde la posición del verbo puede variar significativamente, el mecanismo de atención puede mantener las relaciones semánticas adecuadas a pesar de las diferencias sintácticas.
Segundo, maneja eficazmente las correspondencias de palabras de muchos a uno y de uno a muchos. Por ejemplo, al traducir la palabra compuesta alemana "Schadenfreude" al inglés, el mecanismo puede mapearla a la frase "placer derivado del infortunio ajeno", manteniendo un significado preciso a pesar de la diferencia estructural.
Tercero, el modelo mantiene la conciencia contextual en oraciones extendidas gracias a su capacidad de referenciar y ponderar la importancia de diferentes partes de la secuencia de entrada. Esto garantiza que las oraciones largas conserven su significado y coherencia en la traducción, evitando problemas comunes como perder la relación sujeto-verbo o mal manejar cláusulas dependientes.
El mecanismo de atención logra esto actualizando continuamente su enfoque en función de la palabra actual que se está traduciendo y su relación con todas las demás palabras en la oración, asegurando que la traducción final preserve tanto el significado como el flujo natural del lenguaje.
Ejemplo de Código: Traducción Automática Neuronal con Auto-Atención
import torch
import torch.nn as nn
import torch.nn.functional as F
class TranslationTransformer(nn.Module):
def __init__(self, src_vocab_size, tgt_vocab_size, d_model=512, nhead=8,
num_encoder_layers=6, num_decoder_layers=6, dim_feedforward=2048):
super().__init__()
# Embedding layers
self.src_embedding = nn.Embedding(src_vocab_size, d_model)
self.tgt_embedding = nn.Embedding(tgt_vocab_size, d_model)
self.positional_encoding = PositionalEncoding(d_model)
# Transformer layers
self.transformer = nn.Transformer(
d_model=d_model,
nhead=nhead,
num_encoder_layers=num_encoder_layers,
num_decoder_layers=num_decoder_layers,
dim_feedforward=dim_feedforward
)
# Output projection
self.output_layer = nn.Linear(d_model, tgt_vocab_size)
def forward(self, src, tgt, src_mask=None, tgt_mask=None):
# Create source embedding
src_embedded = self.positional_encoding(self.src_embedding(src))
# Create target embedding
tgt_embedded = self.positional_encoding(self.tgt_embedding(tgt))
# Generate masks if not provided
if src_mask is None:
src_mask = self.generate_square_subsequent_mask(src.size(1))
if tgt_mask is None:
tgt_mask = self.generate_square_subsequent_mask(tgt.size(1))
# Pass through transformer
output = self.transformer(
src_embedded, tgt_embedded,
src_mask=src_mask,
tgt_mask=tgt_mask
)
# Project to vocabulary
return self.output_layer(output)
@staticmethod
def generate_square_subsequent_mask(sz):
mask = torch.triu(torch.ones(sz, sz), diagonal=1)
mask = mask.masked_fill(mask==1, float('-inf'))
return mask
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=5000):
super().__init__()
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2).float() *
(-math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0)
self.register_buffer('pe', pe)
def forward(self, x):
return x + self.pe[:, :x.size(1)]
# Training function
def train_translation_model(model, train_loader, optimizer, criterion, num_epochs=10):
model.train()
for epoch in range(num_epochs):
total_loss = 0
for batch_idx, (src, tgt) in enumerate(train_loader):
optimizer.zero_grad()
# Forward pass
output = model(src, tgt[:-1]) # exclude last target token
# Calculate loss
loss = criterion(
output.view(-1, output.size(-1)),
tgt[1:].reshape(-1) # exclude first target token (BOS)
)
# Backward pass
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(train_loader)
print(f'Epoch: {epoch+1}, Average Loss: {avg_loss:.4f}')
Desglose del Código
- Arquitectura del Modelo
- Implementa un modelo completo de traducción basado en Transformer.
- Utiliza un codificador y un decodificador con atención multi-cabeza.
- Incluye codificación posicional para reconocer el orden de la secuencia.
- Componentes Clave
- Embeddings de Origen y Destino: Convierte tokens en vectores.
- Codificación Posicional: Agrega información de posición a los embeddings.
- Bloque Transformer: Procesa secuencias utilizando auto-atención.
- Proyección de Salida: Mapea al vocabulario del idioma destino.
- Proceso de Entrenamiento
- Implementa teacher forcing durante el entrenamiento.
- Utiliza atención enmascarada para generación autorregresiva.
- Incluye pasos de cálculo de pérdida y optimización.
- Características Avanzadas
- Soporta secuencias de longitud variable.
- Implementa un procesamiento eficiente por lotes.
- Incluye generación de máscaras para atención causal.
Ejemplo de Uso:
# Initialize model and training components
model = TranslationTransformer(
src_vocab_size=10000,
tgt_vocab_size=10000,
d_model=512,
nhead=8
)
# Setup optimizer and criterion
optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)
criterion = nn.CrossEntropyLoss(ignore_index=pad_idx)
# Example translation
def translate(model, src_sentence, src_tokenizer, tgt_tokenizer, max_len=50):
model.eval()
with torch.no_grad():
# Tokenize source sentence
src_tokens = src_tokenizer.encode(src_sentence)
src_tensor = torch.LongTensor(src_tokens).unsqueeze(0)
# Initialize target with BOS token
tgt_tokens = [tgt_tokenizer.bos_token_id]
# Generate translation
for _ in range(max_len):
tgt_tensor = torch.LongTensor(tgt_tokens).unsqueeze(0)
output = model(src_tensor, tgt_tensor)
next_token = output[0, -1].argmax().item()
if next_token == tgt_tokenizer.eos_token_id:
break
tgt_tokens.append(next_token)
# Convert tokens to text
translation = tgt_tokenizer.decode(tgt_tokens)
return translation
Respuestas a Preguntas
Al procesar preguntas, los mecanismos de atención emplean un enfoque sofisticado para el procesamiento de información. Estos mecanismos ayudan a los modelos a identificar y enfocarse en las partes específicas de un pasaje que contienen información relevante a través de un proceso de varios pasos:
Primero, el modelo analiza la pregunta para entender qué tipo de información necesita buscar. Luego, crea pesos de atención para cada palabra en el pasaje, asignando pesos más altos a las palabras y frases que tienen más probabilidades de contener la respuesta. Este enfoque selectivo permite al modelo extraer respuestas de manera eficiente mientras ignora contenido irrelevante.
Por ejemplo, al responder "¿Cuándo ocurrió el evento?", el modelo se enfocaría principalmente en expresiones temporales (como fechas, horarios y frases temporales como "ayer" o "la semana pasada") y en su contexto circundante en el pasaje. Los pesos de atención serían más altos para estos indicadores temporales y su contexto inmediato, permitiendo al modelo centrarse en la información más relevante. Este proceso es similar a cómo los humanos podrían escanear un texto en busca de palabras relacionadas con el tiempo al buscar cuándo ocurrió algo.
Ejemplo de Código: Respuestas a Preguntas con Auto-Atención
import torch
import torch.nn as nn
import torch.nn.functional as F
class QATransformer(nn.Module):
def __init__(self, vocab_size, d_model=512, nhead=8, num_layers=6):
super().__init__()
# Embedding layers
self.embedding = nn.Embedding(vocab_size, d_model)
self.pos_encoder = PositionalEncoding(d_model)
# Multi-head attention layers
self.question_encoder = nn.TransformerEncoder(
nn.TransformerEncoderLayer(d_model, nhead),
num_layers
)
self.context_encoder = nn.TransformerEncoder(
nn.TransformerEncoderLayer(d_model, nhead),
num_layers
)
# Cross-attention layer
self.cross_attention = nn.MultiheadAttention(d_model, nhead)
# Output layers for start and end position prediction
self.start_predictor = nn.Linear(d_model, 1)
self.end_predictor = nn.Linear(d_model, 1)
def forward(self, question, context):
# Embed inputs
q_embed = self.pos_encoder(self.embedding(question))
c_embed = self.pos_encoder(self.embedding(context))
# Encode question and context
q_encoded = self.question_encoder(q_embed)
c_encoded = self.context_encoder(c_embed)
# Cross-attention between question and context
attn_output, attention_weights = self.cross_attention(
q_encoded, c_encoded, c_encoded
)
# Predict answer span
start_logits = self.start_predictor(attn_output).squeeze(-1)
end_logits = self.end_predictor(attn_output).squeeze(-1)
return start_logits, end_logits, attention_weights
def train_qa_model(model, train_loader, optimizer, num_epochs=10):
model.train()
criterion = nn.CrossEntropyLoss()
for epoch in range(num_epochs):
for batch in train_loader:
question, context, start_pos, end_pos = batch
# Forward pass
start_logits, end_logits, _ = model(question, context)
# Calculate loss
start_loss = criterion(start_logits, start_pos)
end_loss = criterion(end_logits, end_pos)
loss = start_loss + end_loss
# Backward pass
optimizer.zero_grad()
loss.backward()
optimizer.step()
def predict_answer(model, tokenizer, question, context):
model.eval()
with torch.no_grad():
# Tokenize inputs
q_tokens = tokenizer.encode(question)
c_tokens = tokenizer.encode(context)
# Convert to tensors
q_tensor = torch.tensor(q_tokens).unsqueeze(0)
c_tensor = torch.tensor(c_tokens).unsqueeze(0)
# Get predictions
start_logits, end_logits, attention = model(q_tensor, c_tensor)
# Find most likely answer span
start_idx = torch.argmax(start_logits)
end_idx = torch.argmax(end_logits[start_idx:]) + start_idx
# Extract answer tokens
answer_tokens = c_tokens[start_idx:end_idx+1]
# Convert back to text
answer = tokenizer.decode(answer_tokens)
return answer, attention
Desglose del Código:
- Arquitectura del Modelo
- Implementa un modelo de preguntas y respuestas basado en Transformer con codificadores separados para preguntas y contexto
- Utiliza auto-atención multi-cabeza para el procesamiento tanto de preguntas como de contexto
- Incluye mecanismo de atención cruzada para relacionar preguntas con el contexto
- Cuenta con predicción de segmentos para la extracción de respuestas
- Componentes Principales
- Capa de Embedding: Convierte tokens de texto en vectores densos
- Codificación Posicional: Agrega información de posición a los embeddings
- Codificadores de Pregunta/Contexto: Procesan entradas usando auto-atención
- Atención Cruzada: Relaciona la pregunta con el contexto para encontrar respuestas
- Predictores de Segmento: Localizan los límites de la respuesta en el contexto
- Flujo de Procesamiento
- Incorpora y codifica la pregunta y el contexto por separado
- Aplica atención cruzada para encontrar regiones relevantes del contexto
- Predice las posiciones de inicio y fin del segmento de respuesta
- Devuelve el texto de la respuesta y los pesos de atención para análisis
Ejemplo de Uso:
# Initialize model and components
model = QATransformer(
vocab_size=30000,
d_model=512,
nhead=8,
num_layers=6
)
# Example usage
question = "When was the first computer invented?"
context = "The first general-purpose electronic computer, ENIAC, was completed in 1945."
# Get answer
answer, attention_weights = predict_answer(
model, tokenizer, question, context
)
print(f"Question: {question}")
print(f"Answer: {answer}")
3.3.5 Puntos Clave
- La auto-atención permite a los modelos calcular representaciones conscientes del contexto al atender a todos los tokens en una secuencia. Esto significa que cada palabra en una oración puede interactuar directamente con cualquier otra palabra, permitiendo al modelo entender relaciones y dependencias complejas. Por ejemplo, en la oración "El gato que persiguió al ratón era negro", la auto-atención ayuda al modelo a conectar "era negro" con "gato", aunque estén separados por varias palabras.
- La atención multi-cabeza mejora la auto-atención al capturar relaciones diversas simultáneamente. Mientras una sola cabeza de atención podría enfocarse en relaciones sintácticas, otra podría capturar similitudes semánticas y otra más podría rastrear relaciones temporales. Este enfoque multifacético permite al modelo procesar la información desde múltiples "perspectivas" al mismo tiempo, conduciendo a una comprensión más rica y matizada de la entrada.
- Juntas, estas mecánicas son la base de las arquitecturas Transformer, permitiendo paralelismo y modelado de dependencias a largo alcance. A diferencia de los modelos secuenciales tradicionales que procesan palabras una a la vez, los Transformers pueden procesar todas las palabras simultáneamente, mejorando drásticamente la eficiencia computacional. Además, dado que cada token puede atender directamente a cualquier otro token, los Transformers destacan en capturar relaciones entre palabras que están distantes en el texto, resolviendo el desafío histórico de modelar dependencias de largo alcance en el procesamiento del lenguaje natural.
3.3 Autoatención y Atención Multi-Cabeza
Sobre la base de los mecanismos de atención, la autoatención surgió como una innovación revolucionaria en el procesamiento del lenguaje natural (NLP). Este enfoque transformador cambió la forma en que los modelos procesan secuencias de entrada al introducir un mecanismo en el que cada elemento de una secuencia puede interactuar directamente con todos los demás. Esta interacción directa permite a los modelos procesar secuencias de entrada con una eficiencia y conciencia contextual sin precedentes, eliminando los cuellos de botella tradicionales del procesamiento secuencial.
La autoatención permite que cada token consulte y atienda simultáneamente a todos los demás tokens en la secuencia. Por ejemplo, al procesar la oración "The cat sat on the mat", cada palabra puede evaluar directamente su relación con todas las demás, ayudando al modelo a entender tanto relaciones locales (como "the cat") como dependencias a larga distancia (conectar "cat" con "sat").
Cuando se combina con la atención multi-cabeza, esta capacidad se vuelve aún más poderosa. La atención multi-cabeza permite al modelo mantener múltiples patrones de atención diferentes simultáneamente, cada uno enfocado en diferentes aspectos de las relaciones entre los tokens. Este enfoque multifacético es la base de los modelos Transformer, capacitando a los modelos para capturar relaciones complejas entre tokens en una secuencia desde múltiples perspectivas al mismo tiempo.
En esta sección exploraremos la autoatención y su extensión a la atención multi-cabeza, examinando cómo funcionan estos mecanismos y por qué son fundamentales en las arquitecturas Transformer. Analizaremos sus fundamentos matemáticos, sus implementaciones prácticas y demostraremos su efectividad con ejemplos concretos y código.
3.3.1 ¿Qué es la autoatención?
En la autoatención, cada token en una secuencia de entrada atiende a todos los demás tokens (incluyéndose a sí mismo) para calcular una nueva representación. Este mecanismo revolucionario crea conexiones dinámicas entre todos los elementos de una secuencia. Por ejemplo, al procesar una oración, cada palabra mantiene conciencia de todas las demás mediante pesos de atención que determinan cuánto influye cada palabra en la representación de la palabra actual. Estos pesos se aprenden durante el entrenamiento y se adaptan en función del contexto y la tarea.
Para ilustrar este concepto, considera la oración "The cat chased the mouse." Al procesar la palabra "chased", el mecanismo de autoatención considera simultáneamente todas las palabras en la oración:
- Presta mucha atención a "cat" como el sujeto que realiza la acción.
- Mantiene una atención fuerte en "mouse" como el objeto que recibe la acción.
- Puede prestar menos atención a artículos como "the", que aportan menos al significado semántico.
Este procesamiento paralelo permite al modelo construir una comprensión rica y contextual del papel de cada palabra en la oración.
A diferencia de los mecanismos de atención tradicionales, que típicamente trabajan con dos secuencias separadas (como en la traducción automática donde una palabra en inglés atiende a palabras en francés), la autoatención opera completamente dentro de una única secuencia. Este enfoque interno representa un avance significativo en el procesamiento del lenguaje natural.
La eficacia de este enfoque se hace particularmente evidente al manejar fenómenos lingüísticos complejos:
- Dependencias a larga distancia: (por ejemplo, "The cat, which had a brown collar, chased the mouse").
- Resolución de correferencias: (entender que "it" se refiere a "the cat").
- Etiquetado de roles semánticos: (identificar quién hizo qué a quién).
- Comprensión de estructuras sintácticas: (captar las relaciones gramaticales entre palabras).
Cómo funciona:
- Representación de entrada: Cada token (palabra o subpalabra) en la secuencia se convierte primero en un vector numérico mediante un proceso de embeddings. Estos vectores tienen cientos de dimensiones y capturan relaciones semánticas entre palabras. Por ejemplo, palabras similares como "cat" y "kitten" tendrán vectores cercanos en este espacio de alta dimensión.
- Creación de Query, Key y Value: El modelo transforma el vector inicial de cada token en tres vectores distintos mediante transformaciones lineales aprendidas:
- Query (Q): Representa lo que la palabra actual está buscando en la secuencia.
- Key (K): Funciona como un índice, ayudando a otros tokens a encontrar esta palabra cuando es relevante.
- Value (V): Contiene la información significativa que se usará en la representación final.
- Cálculo de puntajes de atención: El modelo calcula los puntajes de atención tomando el producto punto entre cada query y todas las keys. Esto crea una matriz de puntajes donde cada entrada (i, j) representa cuán relevante es el token j para el token i. Los puntajes se escalan dividiéndolos por la raíz cuadrada de la dimensión del key (\sqrt{d_k}) para evitar que los productos punto se vuelvan demasiado grandes, lo que ayuda a mantener gradientes estables durante el entrenamiento.
- Normalización de pesos: Los puntajes de atención se convierten en probabilidades usando la función softmax. Esto asegura que todos los pesos para un token dado sumen 1, creando una distribución de probabilidad adecuada. Por ejemplo, al procesar "ate" en "The hungry cat ate fish", el modelo podría asignar pesos más altos a palabras relevantes como "cat" (0.6) y "fish" (0.3), y pesos más bajos a palabras menos importantes como "the" (0.02).
- Cálculo de la salida: La representación final de cada token se calcula como una suma ponderada de todos los vectores de valor (V), utilizando los pesos de atención normalizados. Este proceso permite que cada token reúna información de todos los demás tokens en la secuencia, ponderada por su relevancia. Las representaciones resultantes son conscientes del contexto y pueden capturar tanto la estructura gramatical local como las dependencias a larga distancia, lo que permite al modelo entender relaciones entre palabras incluso cuando están muy separadas en el texto.
3.3.2 Matemáticas de la autoatención
Para una secuencia de tokens de entrada X = [x_1, x_2, \dots, x_n]:
- Calcular Q = XW_Q, K = XW_K y V = XW_V, donde W_Q, W_K, W_V son matrices de pesos aprendibles.
- Calcular los puntajes de atención:
\text{Scores} = \frac{Q \cdot K^\top}{\sqrt{d_k}}
Aquí, d_k es la dimensión de los vectores K.
- Normalizar los puntajes con softmax:
\text{Weights} = \text{softmax}\left(\text{Scores}\right)
- Calcular la salida:
\text{Output} = \text{Weights} \cdot V
Ejemplo: Implementación de autoatención
Implementemos autoatención para una secuencia sencilla.
Ejemplo de código: Autoatención en NumPy
import numpy as np
def self_attention(X, W_Q, W_K, W_V, mask=None):
"""
Compute self-attention for a sequence with optional masking.
Parameters:
-----------
X: np.ndarray
Input sequence of shape (n_tokens, d_model)
W_Q, W_K, W_V: np.ndarray
Weight matrices for Query, Key, Value transformations
mask: np.ndarray, optional
Attention mask of shape (n_tokens, n_tokens)
Returns:
--------
output: np.ndarray
Attended sequence of shape (n_tokens, d_model)
weights: np.ndarray
Attention weights of shape (n_tokens, n_tokens)
"""
# Linear transformations
Q = np.dot(X, W_Q) # Shape: (n_tokens, d_k)
K = np.dot(X, W_K) # Shape: (n_tokens, d_k)
V = np.dot(X, W_V) # Shape: (n_tokens, d_v)
# Calculate scaled dot-product attention
d_k = K.shape[1]
scores = np.dot(Q, K.T) / np.sqrt(d_k) # Shape: (n_tokens, n_tokens)
# Apply mask if provided
if mask is not None:
scores = scores * mask + -1e9 * (1 - mask)
# Softmax normalization
weights = np.exp(scores - np.max(scores, axis=-1, keepdims=True))
weights /= np.sum(weights, axis=-1, keepdims=True)
# Compute weighted sum
output = np.dot(weights, V) # Shape: (n_tokens, d_v)
return output, weights
# Example usage with a more complex sequence
def create_example():
# Create sample sequence
X = np.array([
[1, 0, 0], # First token
[0, 1, 0], # Second token
[0, 0, 1], # Third token
[1, 1, 0] # Fourth token
])
# Create weight matrices
d_model = 3 # Input dimension
d_k = 2 # Key/Query dimension
d_v = 4 # Value dimension
W_Q = np.random.randn(d_model, d_k) * 0.1
W_K = np.random.randn(d_model, d_k) * 0.1
W_V = np.random.randn(d_model, d_v) * 0.1
# Create attention mask (optional)
mask = np.array([
[1, 1, 1, 1],
[1, 1, 1, 1],
[1, 1, 1, 0], # Last token masked for third position
[1, 1, 1, 1]
])
return X, W_Q, W_K, W_V, mask
# Run example
X, W_Q, W_K, W_V, mask = create_example()
output, weights = self_attention(X, W_Q, W_K, W_V, mask)
print("Input Shape:", X.shape)
print("\nQuery Matrix Shape:", W_Q.shape)
print("Key Matrix Shape:", W_K.shape)
print("Value Matrix Shape:", W_V.shape)
print("\nAttention Weights:\n", weights)
print("\nOutput Shape:", output.shape)
print("Output:\n", output)
Explicación del desglose del código:
- Definición de la función y parámetros:
- La función toma como entrada la secuencia X y tres matrices de peso (W_Q, W_K, W_V).
- Incluye un parámetro opcional de enmascaramiento para un control más detallado de la atención.
- Proporciona una docstring completa con descripciones de los parámetros.
- Transformaciones lineales:
- Convierte los tokens de entrada en representaciones de Query (Q), Key (K) y Value (V).
- Utiliza multiplicación matricial (\text{np.dot}) para un cálculo eficiente.
- Mantiene transformaciones de forma adecuadas en todo el proceso.
- Cálculo de puntajes de atención:
- Implementa atención de producto punto escalado con el factor de escalado adecuado.
- Incluye funcionalidad de enmascaramiento para atención selectiva.
- Usa una implementación de softmax numéricamente estable.
- Implementación de ejemplo:
- Crea un ejemplo realista con 4 tokens y 3 características.
- Demuestra la inicialización adecuada de las matrices de peso.
- Muestra cómo usar el enmascaramiento opcional.
- Información sobre formas:
- Documenta claramente las formas de los tensores a lo largo del proceso.
- Ayuda a entender las transformaciones dimensionales.
- Facilita la depuración del código.
3.3.3 ¿Qué es la atención multi-cabeza?
La atención multi-cabeza es una mejora sofisticada del mecanismo de autoatención que ejecuta múltiples cálculos de atención en paralelo, llamados "cabezas." Cada cabeza opera de forma independiente y aprende a enfocarse en diferentes aspectos de las relaciones entre tokens en la secuencia. Por ejemplo, una cabeza puede aprender a centrarse en relaciones sintácticas (como la concordancia sujeto-verbo), otra en relaciones semánticas (como la relevancia temática) y otra en dependencias a largo plazo (como la resolución de correferencias).
Esta arquitectura de procesamiento paralelo ofrece varias ventajas clave:
- Permite al modelo analizar simultáneamente la secuencia de entrada desde múltiples perspectivas, como los humanos que procesan el lenguaje considerando varios aspectos a la vez.
- Tener múltiples mecanismos de atención especializados ayuda al modelo a capturar tanto patrones detallados como generales en los datos.
- Las representaciones diversas aprendidas por las diferentes cabezas se combinan para crear una comprensión más rica y matizada de la secuencia de entrada.
Las salidas de todas las cabezas se combinan finalmente a través de una operación de concatenación seguida de una transformación lineal. Esto permite al modelo sintetizar estas diferentes perspectivas en una representación cohesiva. Este enfoque multifacético mejora significativamente la capacidad del modelo para entender y procesar patrones lingüísticos complejos, haciéndolo particularmente eficaz para tareas que requieren una comprensión sofisticada del lenguaje.
Pasos en la atención multi-cabeza
- Dividir la entrada en múltiples cabezas:
- Divide la secuencia de entrada en subespacios separados.
- Cada cabeza recibe una porción de la dimensionalidad de la entrada.
- Esta división permite el procesamiento paralelo de diferentes aspectos de las características.
- Aplicar autoatención de forma independiente a cada cabeza:
- Cada cabeza calcula sus propias matrices de Query ($Q$), Key ($K$) y Value ($V$).
- Calcula los puntajes de atención usando atención de producto punto escalado.
- Procesa la información enfocándose en diferentes aspectos de la entrada.
- Concatenar las salidas de todas las cabezas:
- Combina los resultados de cada cabeza de atención.
- Preserva los patrones y relaciones únicos aprendidos por cada cabeza.
- Crea una representación integral de la secuencia de entrada.
- Aplicar una transformación lineal final:
- Proyecta las salidas concatenadas a la dimensión deseada.
- Integra la información de todas las cabezas en una representación cohesiva.
- Permite al modelo ponderar la importancia de las salidas de diferentes cabezas.
Beneficios de la Atención Multi-Cabeza
- Representaciones Diversas: Cada cabeza de atención se especializa en capturar diferentes tipos de relaciones dentro de los datos. Por ejemplo, una cabeza puede enfocarse en dependencias sintácticas (como la concordancia sujeto-verbo), otra en relaciones semánticas (como la relevancia temática), y otra en dependencias de largo alcance (como la resolución de correferencias). Esta diversidad permite al modelo construir una comprensión rica y multifacética de la entrada.
- Mayor Expresividad: El modelo puede enfocarse en múltiples aspectos de la entrada simultáneamente, similar a cómo los humanos procesan el lenguaje. Este procesamiento en paralelo permite al modelo:
- Capturar tanto el contexto local como el global.
- Procesar diferentes niveles semánticos (nivel de palabra, frase, oración).
- Aprender relaciones jerárquicas entre los tokens.
- Combinar diferentes perspectivas en una comprensión más completa.
- Capacidad de Aprendizaje Mejorada: Las múltiples cabezas permiten al modelo distribuir la atención a través de diferentes subespacios, incrementando efectivamente su poder representacional sin aumentar significativamente la complejidad computacional.
- Detección Robusta de Características: Al mantener múltiples mecanismos de atención independientes, el modelo se vuelve más robusto, ya que no depende de un único patrón de atención, reduciendo el impacto de ruido o patrones engañosos en los datos.
Ejemplo: Atención Multi-Cabeza
Implementemos una versión simplificada de la atención multi-cabeza.
Ejemplo de Código: Atención Multi-Cabeza en NumPy
import numpy as np
def multi_head_attention(X, W_Q, W_K, W_V, W_O, n_heads, mask=None):
"""
Compute multi-head attention with optional masking.
Parameters:
-----------
X: np.ndarray
Input sequence of shape (n_tokens, d_model)
W_Q, W_K, W_V: np.ndarray
Weight matrices for Query, Key, Value transformations
W_O: np.ndarray
Output projection matrix
n_heads: int
Number of attention heads
mask: np.ndarray, optional
Attention mask of shape (n_tokens, n_tokens)
Returns:
--------
final_output: np.ndarray
Transformed sequence of shape (n_tokens, d_model)
attention_weights: list
List of attention weights for each head
"""
d_model = X.shape[1]
head_dim = W_Q.shape[1] // n_heads
outputs = []
attention_weights = []
# Process each attention head
for i in range(n_heads):
# Split weights for current head
Q = np.dot(X, W_Q[:, i*head_dim:(i+1)*head_dim]) # (n_tokens, head_dim)
K = np.dot(X, W_K[:, i*head_dim:(i+1)*head_dim]) # (n_tokens, head_dim)
V = np.dot(X, W_V[:, i*head_dim:(i+1)*head_dim]) # (n_tokens, head_dim)
# Compute attention scores
scores = np.dot(Q, K.T) / np.sqrt(head_dim) # (n_tokens, n_tokens)
# Apply mask if provided
if mask is not None:
scores = scores * mask + -1e9 * (1 - mask)
# Apply softmax
weights = np.exp(scores - np.max(scores, axis=-1, keepdims=True))
weights = weights / np.sum(weights, axis=-1, keepdims=True)
# Compute weighted sum
output = np.dot(weights, V) # (n_tokens, head_dim)
outputs.append(output)
attention_weights.append(weights)
# Concatenate all heads
concatenated = np.concatenate(outputs, axis=-1) # (n_tokens, d_model)
# Final linear transformation
final_output = np.dot(concatenated, W_O) # (n_tokens, d_model)
return final_output, attention_weights
# Example usage with a more realistic sequence
def create_example_inputs(n_tokens=4, d_model=8, n_heads=2):
"""Create example inputs for multi-head attention."""
# Input sequence
X = np.random.randn(n_tokens, d_model)
# Weight matrices
head_dim = d_model // n_heads
W_Q = np.random.randn(d_model, d_model) * 0.1
W_K = np.random.randn(d_model, d_model) * 0.1
W_V = np.random.randn(d_model, d_model) * 0.1
W_O = np.random.randn(d_model, d_model) * 0.1
# Optional mask (causal attention)
mask = np.tril(np.ones((n_tokens, n_tokens)))
return X, W_Q, W_K, W_V, W_O, mask
# Run example
X, W_Q, W_K, W_V, W_O, mask = create_example_inputs()
output, weights = multi_head_attention(X, W_Q, W_K, W_V, W_O, n_heads=2, mask=mask)
print("Input shape:", X.shape)
print("Output shape:", output.shape)
print("\nAttention weights for first head:\n", weights[0])
print("\nAttention weights for second head:\n", weights[1])
Desglose del Código
- Arquitectura de la Función
- Implementa atención multi-cabeza con documentación detallada.
- Incluye una opción de enmascarado para atención causal.
- Devuelve tanto las salidas como los pesos de atención para análisis.
- Componentes Clave
- Cálculo de la Dimensión por Cabeza: Divide la dimensión de entrada entre las cabezas.
- Procesamiento por Cabeza: Calcula la atención separada para cada cabeza.
- Mecanismo de Atención: Implementa atención de producto punto escalada.
- Agregación de Salidas: Concatenación y proyección de las salidas de las cabezas.
- Características Mejoradas
- Estabilidad Numérica: Utiliza una implementación de softmax estable.
- Soporte de Enmascarado: Permite patrones de atención enmascarados.
- Escalado Correcto: Incluye un factor de escalado para la atención.
- Funciones Auxiliares
- create_example_inputs: Genera datos de prueba realistas.
- Incluye información de forma y lógica de inicialización.
- Demuestra patrones de uso adecuados.
- Análisis de Salida
- Imprime las formas de los datos para verificación.
- Muestra los pesos de atención para interpretación.
- Demuestra la naturaleza multi-cabeza de la atención.
3.3.4 Aplicaciones de la Auto-Atención y Atención Multi-Cabeza
Resumen de Textos
Los modelos aprovechan los mecanismos de atención de maneras sofisticadas para identificar y priorizar las partes más importantes de un documento. El mecanismo de atención funciona asignando diferentes pesos a diferentes partes del texto de entrada, esencialmente creando una jerarquía de importancia. Estos pesos se aprenden durante el entrenamiento y se ajustan dinámicamente según el contenido específico que se procesa.
Los pesos de atención actúan como un mecanismo de filtrado avanzado que ayuda a determinar qué oraciones contienen la información más crítica. Este proceso implica analizar diversas características lingüísticas, incluidas la relevancia semántica, la estructura sintáctica y las relaciones contextuales entre las diferentes partes del texto. El modelo puede así crear resúmenes concisos y significativos, preservando el mensaje principal y manteniendo la coherencia.
Por ejemplo, en el resumen de artículos de noticias, el modelo emplea un enfoque de atención en múltiples capas. Podría enfocarse fuertemente en eventos clave (como las principales acciones o desarrollos), citas significativas de figuras relevantes y datos estadísticos importantes que respaldan la narrativa principal. Mientras tanto, asigna pesos de atención más bajos a detalles complementarios, información de contexto o contenido redundante. Este proceso de atención selectiva refleja el comportamiento humano en la creación de resúmenes, donde naturalmente nos enfocamos en información crucial mientras pasamos por alto detalles menos importantes.
Ejemplo de Código: Resumen de Textos con Auto-Atención
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
class SelfAttentionSummarizer(nn.Module):
def __init__(self, vocab_size, embed_dim, num_heads, hidden_dim, max_length=512):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim)
self.position_encoding = nn.Parameter(
torch.zeros(max_length, embed_dim)
)
self.multihead_attention = nn.MultiheadAttention(
embed_dim, num_heads, batch_first=True
)
self.layer_norm1 = nn.LayerNorm(embed_dim)
self.feed_forward = nn.Sequential(
nn.Linear(embed_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, embed_dim)
)
self.layer_norm2 = nn.LayerNorm(embed_dim)
self.output_layer = nn.Linear(embed_dim, vocab_size)
def forward(self, x, src_mask=None):
# Add positional encoding to embeddings
seq_length = x.size(1)
x = self.embedding(x) + self.position_encoding[:seq_length]
# Self-attention block
attention_output, attention_weights = self.multihead_attention(
x, x, x,
key_padding_mask=src_mask,
need_weights=True
)
x = self.layer_norm1(x + attention_output)
# Feed-forward block
ff_output = self.feed_forward(x)
x = self.layer_norm2(x + ff_output)
# Generate output probabilities
output = self.output_layer(x)
return output, attention_weights
def generate_summary(model, input_ids, tokenizer, max_length=150):
model.eval()
with torch.no_grad():
output, attention_weights = model(input_ids)
# Get most attended words for summary
attention_scores = attention_weights.mean(dim=1)
top_scores = torch.topk(attention_scores.squeeze(), k=max_length)
# Extract and arrange summary tokens
summary_indices = top_scores.indices.sort().values
summary_tokens = input_ids[0, summary_indices]
# Convert to text
summary = tokenizer.decode(summary_tokens)
return summary, attention_weights
# Example usage
def summarize_text(text, model, tokenizer):
# Tokenize input text
inputs = tokenizer(text, return_tensors="pt", max_length=512, truncation=True)
# Generate summary
summary, attention = generate_summary(
model,
inputs["input_ids"],
tokenizer
)
return summary, attention
Desglose del Código
- Arquitectura del Modelo
- Implementa un resumidor basado en Transformer con auto-atención multi-cabeza.
- Incluye codificación posicional para reconocer la secuencia.
- Utiliza normalización por capas y conexiones residuales para un entrenamiento estable.
- Componentes Clave
- Capa de Embedding: Convierte tokens en vectores densos.
- Atención Multi-Cabeza: Procesa el texto desde múltiples perspectivas.
- Red Feed-Forward: Agrega no linealidad y transforma representaciones.
- Capa de Salida: Genera predicciones finales de tokens.
- Proceso de Resumen
- Analiza los pesos de atención para identificar los tokens importantes.
- Selecciona los tokens con mayor atención para generar el resumen.
- Mantiene el orden original de los tokens seleccionados para asegurar coherencia.
- Características Avanzadas
- Soporta entradas de longitud variable con enmascaramiento.
- Implementa un procesamiento eficiente por lotes.
- Devuelve pesos de atención para análisis y visualización.
Ejemplo de Uso:
# Example setup and usage
vocab_size = 30000
embed_dim = 512
num_heads = 8
hidden_dim = 2048
model = SelfAttentionSummarizer(
vocab_size=vocab_size,
embed_dim=embed_dim,
num_heads=num_heads,
hidden_dim=hidden_dim
)
# Example text
text = """
Climate change poses significant challenges to global ecosystems.
Rising temperatures affect wildlife habitats and agricultural productivity.
Scientists warn that immediate action is necessary to prevent irreversible damage.
"""
# Generate summary (assuming tokenizer is initialized)
summary, attention = summarize_text(text, model, tokenizer)
print("Summary:", summary)
Traducción Automática
Los mecanismos de atención han revolucionado la traducción automática al crear alineaciones dinámicas sofisticadas entre palabras y frases en diferentes idiomas. Este proceso funciona estableciendo conexiones ponderadas entre elementos de los idiomas de origen y destino, permitiendo al modelo comprender relaciones lingüísticas complejas. Por ejemplo, al traducir del inglés al japonés, el mecanismo de atención puede manejar las significativas diferencias en la estructura de las oraciones, donde el inglés sigue un orden Sujeto-Verbo-Objeto, mientras que el japonés generalmente utiliza Sujeto-Objeto-Verbo.
El mecanismo es especialmente poderoso al abordar tres desafíos clave de la traducción:
Primero, maneja variaciones complejas en el orden de las palabras entre idiomas. Por ejemplo, al traducir entre inglés y alemán, donde la posición del verbo puede variar significativamente, el mecanismo de atención puede mantener las relaciones semánticas adecuadas a pesar de las diferencias sintácticas.
Segundo, maneja eficazmente las correspondencias de palabras de muchos a uno y de uno a muchos. Por ejemplo, al traducir la palabra compuesta alemana "Schadenfreude" al inglés, el mecanismo puede mapearla a la frase "placer derivado del infortunio ajeno", manteniendo un significado preciso a pesar de la diferencia estructural.
Tercero, el modelo mantiene la conciencia contextual en oraciones extendidas gracias a su capacidad de referenciar y ponderar la importancia de diferentes partes de la secuencia de entrada. Esto garantiza que las oraciones largas conserven su significado y coherencia en la traducción, evitando problemas comunes como perder la relación sujeto-verbo o mal manejar cláusulas dependientes.
El mecanismo de atención logra esto actualizando continuamente su enfoque en función de la palabra actual que se está traduciendo y su relación con todas las demás palabras en la oración, asegurando que la traducción final preserve tanto el significado como el flujo natural del lenguaje.
Ejemplo de Código: Traducción Automática Neuronal con Auto-Atención
import torch
import torch.nn as nn
import torch.nn.functional as F
class TranslationTransformer(nn.Module):
def __init__(self, src_vocab_size, tgt_vocab_size, d_model=512, nhead=8,
num_encoder_layers=6, num_decoder_layers=6, dim_feedforward=2048):
super().__init__()
# Embedding layers
self.src_embedding = nn.Embedding(src_vocab_size, d_model)
self.tgt_embedding = nn.Embedding(tgt_vocab_size, d_model)
self.positional_encoding = PositionalEncoding(d_model)
# Transformer layers
self.transformer = nn.Transformer(
d_model=d_model,
nhead=nhead,
num_encoder_layers=num_encoder_layers,
num_decoder_layers=num_decoder_layers,
dim_feedforward=dim_feedforward
)
# Output projection
self.output_layer = nn.Linear(d_model, tgt_vocab_size)
def forward(self, src, tgt, src_mask=None, tgt_mask=None):
# Create source embedding
src_embedded = self.positional_encoding(self.src_embedding(src))
# Create target embedding
tgt_embedded = self.positional_encoding(self.tgt_embedding(tgt))
# Generate masks if not provided
if src_mask is None:
src_mask = self.generate_square_subsequent_mask(src.size(1))
if tgt_mask is None:
tgt_mask = self.generate_square_subsequent_mask(tgt.size(1))
# Pass through transformer
output = self.transformer(
src_embedded, tgt_embedded,
src_mask=src_mask,
tgt_mask=tgt_mask
)
# Project to vocabulary
return self.output_layer(output)
@staticmethod
def generate_square_subsequent_mask(sz):
mask = torch.triu(torch.ones(sz, sz), diagonal=1)
mask = mask.masked_fill(mask==1, float('-inf'))
return mask
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=5000):
super().__init__()
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2).float() *
(-math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0)
self.register_buffer('pe', pe)
def forward(self, x):
return x + self.pe[:, :x.size(1)]
# Training function
def train_translation_model(model, train_loader, optimizer, criterion, num_epochs=10):
model.train()
for epoch in range(num_epochs):
total_loss = 0
for batch_idx, (src, tgt) in enumerate(train_loader):
optimizer.zero_grad()
# Forward pass
output = model(src, tgt[:-1]) # exclude last target token
# Calculate loss
loss = criterion(
output.view(-1, output.size(-1)),
tgt[1:].reshape(-1) # exclude first target token (BOS)
)
# Backward pass
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(train_loader)
print(f'Epoch: {epoch+1}, Average Loss: {avg_loss:.4f}')
Desglose del Código
- Arquitectura del Modelo
- Implementa un modelo completo de traducción basado en Transformer.
- Utiliza un codificador y un decodificador con atención multi-cabeza.
- Incluye codificación posicional para reconocer el orden de la secuencia.
- Componentes Clave
- Embeddings de Origen y Destino: Convierte tokens en vectores.
- Codificación Posicional: Agrega información de posición a los embeddings.
- Bloque Transformer: Procesa secuencias utilizando auto-atención.
- Proyección de Salida: Mapea al vocabulario del idioma destino.
- Proceso de Entrenamiento
- Implementa teacher forcing durante el entrenamiento.
- Utiliza atención enmascarada para generación autorregresiva.
- Incluye pasos de cálculo de pérdida y optimización.
- Características Avanzadas
- Soporta secuencias de longitud variable.
- Implementa un procesamiento eficiente por lotes.
- Incluye generación de máscaras para atención causal.
Ejemplo de Uso:
# Initialize model and training components
model = TranslationTransformer(
src_vocab_size=10000,
tgt_vocab_size=10000,
d_model=512,
nhead=8
)
# Setup optimizer and criterion
optimizer = torch.optim.Adam(model.parameters(), lr=0.0001)
criterion = nn.CrossEntropyLoss(ignore_index=pad_idx)
# Example translation
def translate(model, src_sentence, src_tokenizer, tgt_tokenizer, max_len=50):
model.eval()
with torch.no_grad():
# Tokenize source sentence
src_tokens = src_tokenizer.encode(src_sentence)
src_tensor = torch.LongTensor(src_tokens).unsqueeze(0)
# Initialize target with BOS token
tgt_tokens = [tgt_tokenizer.bos_token_id]
# Generate translation
for _ in range(max_len):
tgt_tensor = torch.LongTensor(tgt_tokens).unsqueeze(0)
output = model(src_tensor, tgt_tensor)
next_token = output[0, -1].argmax().item()
if next_token == tgt_tokenizer.eos_token_id:
break
tgt_tokens.append(next_token)
# Convert tokens to text
translation = tgt_tokenizer.decode(tgt_tokens)
return translation
Respuestas a Preguntas
Al procesar preguntas, los mecanismos de atención emplean un enfoque sofisticado para el procesamiento de información. Estos mecanismos ayudan a los modelos a identificar y enfocarse en las partes específicas de un pasaje que contienen información relevante a través de un proceso de varios pasos:
Primero, el modelo analiza la pregunta para entender qué tipo de información necesita buscar. Luego, crea pesos de atención para cada palabra en el pasaje, asignando pesos más altos a las palabras y frases que tienen más probabilidades de contener la respuesta. Este enfoque selectivo permite al modelo extraer respuestas de manera eficiente mientras ignora contenido irrelevante.
Por ejemplo, al responder "¿Cuándo ocurrió el evento?", el modelo se enfocaría principalmente en expresiones temporales (como fechas, horarios y frases temporales como "ayer" o "la semana pasada") y en su contexto circundante en el pasaje. Los pesos de atención serían más altos para estos indicadores temporales y su contexto inmediato, permitiendo al modelo centrarse en la información más relevante. Este proceso es similar a cómo los humanos podrían escanear un texto en busca de palabras relacionadas con el tiempo al buscar cuándo ocurrió algo.
Ejemplo de Código: Respuestas a Preguntas con Auto-Atención
import torch
import torch.nn as nn
import torch.nn.functional as F
class QATransformer(nn.Module):
def __init__(self, vocab_size, d_model=512, nhead=8, num_layers=6):
super().__init__()
# Embedding layers
self.embedding = nn.Embedding(vocab_size, d_model)
self.pos_encoder = PositionalEncoding(d_model)
# Multi-head attention layers
self.question_encoder = nn.TransformerEncoder(
nn.TransformerEncoderLayer(d_model, nhead),
num_layers
)
self.context_encoder = nn.TransformerEncoder(
nn.TransformerEncoderLayer(d_model, nhead),
num_layers
)
# Cross-attention layer
self.cross_attention = nn.MultiheadAttention(d_model, nhead)
# Output layers for start and end position prediction
self.start_predictor = nn.Linear(d_model, 1)
self.end_predictor = nn.Linear(d_model, 1)
def forward(self, question, context):
# Embed inputs
q_embed = self.pos_encoder(self.embedding(question))
c_embed = self.pos_encoder(self.embedding(context))
# Encode question and context
q_encoded = self.question_encoder(q_embed)
c_encoded = self.context_encoder(c_embed)
# Cross-attention between question and context
attn_output, attention_weights = self.cross_attention(
q_encoded, c_encoded, c_encoded
)
# Predict answer span
start_logits = self.start_predictor(attn_output).squeeze(-1)
end_logits = self.end_predictor(attn_output).squeeze(-1)
return start_logits, end_logits, attention_weights
def train_qa_model(model, train_loader, optimizer, num_epochs=10):
model.train()
criterion = nn.CrossEntropyLoss()
for epoch in range(num_epochs):
for batch in train_loader:
question, context, start_pos, end_pos = batch
# Forward pass
start_logits, end_logits, _ = model(question, context)
# Calculate loss
start_loss = criterion(start_logits, start_pos)
end_loss = criterion(end_logits, end_pos)
loss = start_loss + end_loss
# Backward pass
optimizer.zero_grad()
loss.backward()
optimizer.step()
def predict_answer(model, tokenizer, question, context):
model.eval()
with torch.no_grad():
# Tokenize inputs
q_tokens = tokenizer.encode(question)
c_tokens = tokenizer.encode(context)
# Convert to tensors
q_tensor = torch.tensor(q_tokens).unsqueeze(0)
c_tensor = torch.tensor(c_tokens).unsqueeze(0)
# Get predictions
start_logits, end_logits, attention = model(q_tensor, c_tensor)
# Find most likely answer span
start_idx = torch.argmax(start_logits)
end_idx = torch.argmax(end_logits[start_idx:]) + start_idx
# Extract answer tokens
answer_tokens = c_tokens[start_idx:end_idx+1]
# Convert back to text
answer = tokenizer.decode(answer_tokens)
return answer, attention
Desglose del Código:
- Arquitectura del Modelo
- Implementa un modelo de preguntas y respuestas basado en Transformer con codificadores separados para preguntas y contexto
- Utiliza auto-atención multi-cabeza para el procesamiento tanto de preguntas como de contexto
- Incluye mecanismo de atención cruzada para relacionar preguntas con el contexto
- Cuenta con predicción de segmentos para la extracción de respuestas
- Componentes Principales
- Capa de Embedding: Convierte tokens de texto en vectores densos
- Codificación Posicional: Agrega información de posición a los embeddings
- Codificadores de Pregunta/Contexto: Procesan entradas usando auto-atención
- Atención Cruzada: Relaciona la pregunta con el contexto para encontrar respuestas
- Predictores de Segmento: Localizan los límites de la respuesta en el contexto
- Flujo de Procesamiento
- Incorpora y codifica la pregunta y el contexto por separado
- Aplica atención cruzada para encontrar regiones relevantes del contexto
- Predice las posiciones de inicio y fin del segmento de respuesta
- Devuelve el texto de la respuesta y los pesos de atención para análisis
Ejemplo de Uso:
# Initialize model and components
model = QATransformer(
vocab_size=30000,
d_model=512,
nhead=8,
num_layers=6
)
# Example usage
question = "When was the first computer invented?"
context = "The first general-purpose electronic computer, ENIAC, was completed in 1945."
# Get answer
answer, attention_weights = predict_answer(
model, tokenizer, question, context
)
print(f"Question: {question}")
print(f"Answer: {answer}")
3.3.5 Puntos Clave
- La auto-atención permite a los modelos calcular representaciones conscientes del contexto al atender a todos los tokens en una secuencia. Esto significa que cada palabra en una oración puede interactuar directamente con cualquier otra palabra, permitiendo al modelo entender relaciones y dependencias complejas. Por ejemplo, en la oración "El gato que persiguió al ratón era negro", la auto-atención ayuda al modelo a conectar "era negro" con "gato", aunque estén separados por varias palabras.
- La atención multi-cabeza mejora la auto-atención al capturar relaciones diversas simultáneamente. Mientras una sola cabeza de atención podría enfocarse en relaciones sintácticas, otra podría capturar similitudes semánticas y otra más podría rastrear relaciones temporales. Este enfoque multifacético permite al modelo procesar la información desde múltiples "perspectivas" al mismo tiempo, conduciendo a una comprensión más rica y matizada de la entrada.
- Juntas, estas mecánicas son la base de las arquitecturas Transformer, permitiendo paralelismo y modelado de dependencias a largo alcance. A diferencia de los modelos secuenciales tradicionales que procesan palabras una a la vez, los Transformers pueden procesar todas las palabras simultáneamente, mejorando drásticamente la eficiencia computacional. Además, dado que cada token puede atender directamente a cualquier otro token, los Transformers destacan en capturar relaciones entre palabras que están distantes en el texto, resolviendo el desafío histórico de modelar dependencias de largo alcance en el procesamiento del lenguaje natural.