Capítulo 3: Codificación Posicional y Embeddings
Introducción
A diferencia de las RNN, que procesan las secuencias token por token, los Transformers procesan todas las posiciones simultáneamente. Esto crea un desafío fundamental: ¿cómo sabe el modelo el orden de los tokens? Este capítulo explica la codificación posicional y el proceso más amplio de embedding de tokens.
El Problema de la Posición
Por Qué la Posición Importa
Considere estas oraciones:
- "El perro mordió al hombre"
- "El hombre mordió al perro"
Mismos tokens, significados completamente diferentes. La posición es crucial para entender el lenguaje.
Transformers y la Invariancia a la Permutación
Sin información posicional, la auto-atención es invariante a la permutación:
Donde es cualquier permutación. ¡El modelo trata "perro mordió hombre" y "hombre mordió perro" de manera idéntica!
# Demostración: los scores de atención son independientes de la posición
import torch
def attention_scores(Q, K):
return torch.softmax(Q @ K.T, dim=-1)
# Secuencia original
x1 = torch.tensor([[1.0, 0.0], [0.0, 1.0], [1.0, 1.0]])
# Secuencia permutada (intercambia posiciones 0 y 1)
x2 = torch.tensor([[0.0, 1.0], [1.0, 0.0], [1.0, 1.0]])
# ¡Mismo patrón de atención (solo permutado)!
print(attention_scores(x1, x1))
print(attention_scores(x2, x2))
Embeddings de Tokens
De Tokens a Vectores
Antes de agregar información posicional, primero convertimos los tokens en vectores densos:
Implementación
import torch
import torch.nn as nn
class TokenEmbedding(nn.Module):
"""
Convierte IDs de tokens en vectores densos.
"""
def __init__(self, vocab_size, d_model):
super().__init__()
self.embedding = nn.Embedding(vocab_size, d_model)
self.d_model = d_model
def forward(self, token_ids):
"""
token_ids: (batch, seq_len) de IDs de tokens enteros
Devuelve: embeddings (batch, seq_len, d_model)
"""
# Escala los embeddings (del paper original de Transformer)
return self.embedding(token_ids) * (self.d_model ** 0.5)
# Ejemplo
vocab_size = 50000
d_model = 512
embedding_layer = TokenEmbedding(vocab_size, d_model)
tokens = torch.tensor([[101, 7592, 2088, 102]]) # [CLS] Hello World [SEP]
embeddings = embedding_layer(tokens)
print(f"Shape: {embeddings.shape}") # (1, 4, 512)
Codificación Posicional Sinusoidal
El Enfoque Original
El paper original de Transformer utilizó funciones sinusoidales fijas:
Donde:
- = posición en la secuencia
- = índice de dimensión
- = dimensión del modelo
Intuición
Cada dimensión oscila a una frecuencia diferente:
- Dimensiones bajas: oscilación lenta (captura posiciones de largo alcance)
- Dimensiones altas: oscilación rápida (captura posiciones de detalle fino)
Implementación
import numpy as np
def get_sinusoidal_encoding(max_len, d_model):
"""
Genera codificaciones posicionales sinusoidales.
Args:
max_len: Longitud máxima de la secuencia
d_model: Dimensión del modelo
Returns:
Matriz de codificación posicional (max_len, d_model)
"""
position = np.arange(max_len)[:, np.newaxis]
div_term = np.exp(np.arange(0, d_model, 2) * -(np.log(10000.0) / d_model))
pe = np.zeros((max_len, d_model))
pe[:, 0::2] = np.sin(position * div_term) # Índices pares
pe[:, 1::2] = np.cos(position * div_term) # Índices impares
return pe
# Visualización
import matplotlib.pyplot as plt
pe = get_sinusoidal_encoding(100, 512)
plt.figure(figsize=(12, 4))
plt.imshow(pe.T, aspect='auto', cmap='RdBu')
plt.xlabel('Posición')
plt.ylabel('Dimensión')
plt.colorbar(label='Valor')
plt.title('Codificación Posicional Sinusoidal')
plt.show()
Propiedades de la Codificación Sinusoidal
- Única: Cada posición tiene una codificación única
- Acotada: Los valores siempre están en [-1, 1]
- Posiciones relativas: La relación lineal permite aprender posiciones relativas
- Extrapolación: Puede generalizar a secuencias más largas que las vistas en entrenamiento
Implementación en PyTorch
class SinusoidalPositionalEncoding(nn.Module):
"""
Codificación posicional sinusoidal de 'Attention Is All You Need'.
"""
def __init__(self, d_model, max_len=5000, dropout=0.1):
super().__init__()
self.dropout = nn.Dropout(p=dropout)
# Crear matriz de codificación posicional
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() * (-np.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) # Añade dimensión de batch
# Registrar como buffer (no es un parámetro, pero se guarda con el modelo)
self.register_buffer('pe', pe)
def forward(self, x):
"""
x: (batch, seq_len, d_model)
"""
x = x + self.pe[:, :x.size(1)]
return self.dropout(x)
Embeddings Posicionales Aprendidos
Un Enfoque Alternativo
Muchos LLM modernos usan embeddings posicionales aprendidos:
class LearnedPositionalEmbedding(nn.Module):
"""
Embeddings posicionales aprendidos (por ejemplo, usados en GPT-2).
"""
def __init__(self, max_len, d_model):
super().__init__()
self.position_embedding = nn.Embedding(max_len, d_model)
def forward(self, x):
"""
x: (batch, seq_len, d_model)
"""
positions = torch.arange(x.size(1), device=x.device)
return x + self.position_embedding(positions)
Comparación
| Aspecto | Sinusoidal | Aprendido |
|---|---|---|
| Parámetros | Ninguno | max_len × d_model |
| Extrapolación | Mejor | Limitado a max_len |
| Rendimiento | Comparable | Comparable |
| Flexibilidad | Fijo | Se adapta a los datos |
Rotary Position Embedding (RoPE)
Innovación Moderna
RoPE, utilizado en LLaMA, Mistral y muchos modelos modernos, codifica la posición mediante rotación:
Beneficios Clave
- Posiciones relativas: Captura naturalmente la información de posición relativa
- Extrapolación: Mejor manejo de secuencias largas
- Eficiencia: Se aplica directamente a Q, K sin parámetros adicionales
Implementación Simplificada
def rotary_embedding(x, seq_len, d_model):
"""
Implementación simplificada de RoPE.
x: (batch, seq_len, d_model)
"""
# Genera ángulos de rotación
position = torch.arange(seq_len).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2) * -(np.log(10000.0) / d_model))
angles = position * div_term
# Aplica rotación a pares de dimensiones
x_rotated = x.clone()
x_rotated[..., 0::2] = x[..., 0::2] * torch.cos(angles) - x[..., 1::2] * torch.sin(angles)
x_rotated[..., 1::2] = x[..., 0::2] * torch.sin(angles) + x[..., 1::2] * torch.cos(angles)
return x_rotated
ALiBi: Atención con Sesgos Lineales
Otro Enfoque
ALiBi (Attention with Linear Biases) añade una penalización lineal basada en la distancia:
Donde es una pendiente específica de cada cabeza.
Beneficios
- Sin parámetros adicionales
- Excelente extrapolación en longitud
- Implementación simple
Capa de Embedding Completa
Integrando Todo
class TransformerEmbedding(nn.Module):
"""
Capa de embedding completa: token + posición.
"""
def __init__(self, vocab_size, d_model, max_len=5000,
pos_encoding='sinusoidal', dropout=0.1):
super().__init__()
self.token_embedding = nn.Embedding(vocab_size, d_model)
self.d_model = d_model
self.dropout = nn.Dropout(dropout)
if pos_encoding == 'sinusoidal':
self.pos_encoding = SinusoidalPositionalEncoding(d_model, max_len)
elif pos_encoding == 'learned':
self.pos_encoding = LearnedPositionalEmbedding(max_len, d_model)
else:
raise ValueError(f"Unknown pos_encoding: {pos_encoding}")
def forward(self, token_ids):
"""
token_ids: (batch, seq_len)
Devuelve: (batch, seq_len, d_model)
"""
# Embeddings de tokens (escalados)
x = self.token_embedding(token_ids) * (self.d_model ** 0.5)
# Añade codificación posicional
x = self.pos_encoding(x)
return self.dropout(x)
# Uso
embedding = TransformerEmbedding(
vocab_size=50000,
d_model=512,
max_len=2048,
pos_encoding='sinusoidal'
)
tokens = torch.randint(0, 50000, (32, 100)) # Lote de 32, seq_len 100
embedded = embedding(tokens)
print(f"Output shape: {embedded.shape}") # (32, 100, 512)
El Panorama Completo
Así es como los tokens fluyen a través de los embeddings hasta la atención:
IDs de tokens: [101, 7592, 2088, 102]
↓
Embedding de tokens: búsqueda en la tabla de embeddings
↓
[0.23, -0.45, ...] [0.89, 0.12, ...] [0.45, -0.23, ...] [0.67, 0.34, ...]
↓
Agregar codificación posicional
↓
[0.23+PE_0, -0.45+PE_0, ...] [0.89+PE_1, 0.12+PE_1, ...] ...
↓
Atención multi-cabeza
↓
Salida con contexto sensible a la posición
Puntos Clave
- La información de posición es esencial—de lo contrario, los Transformers son invariantes a la permutación
- Los embeddings de tokens convierten tokens discretos en vectores continuos
- La codificación sinusoidal usa patrones de frecuencia fija (Transformer original)
- Los embeddings aprendidos se adaptan a los datos pero tienen longitud máxima fija
- RoPE codifica posiciones relativas mediante rotación (LLMs modernos)
- ALiBi usa sesgos de atención basados en la distancia
Mirando Hacia Adelante
Ahora que entendemos la arquitectura completa de Transformer—atención, atención multi-cabeza y codificación posicional—estamos listos para avanzar al Módulo 3, donde exploraremos cómo se entrenan estos modelos masivos.