Módulo 2: ¿Cómo funcionan Embeddings?
Transformer Encoders: La Arquitectura de Embeddings Modernos
Descripción de la cápsula
Los Transformers revolucionaron el procesamiento de lenguaje natural en 2017 y son la base de todos los modelos de embeddings modernos (BERT, GPT, OpenAI embeddings, Sentence-BERT).
En esta cápsula aprenderás la arquitectura encoder-only (BERT-style) que genera embeddings, cómo funciona self-attention para capturar contexto, y por qué Transformers superaron a RNNs/LSTMs. No implementarás un Transformer desde cero (eso es ML Engineering), pero entenderás conceptualmente cada componente para tomar decisiones informadas como AI Engineer.
También verás comparaciones concretas de embeddings pre-Transformer (Word2Vec) vs post-Transformer (BERT) y entenderás por qué el cambio fue tan disruptivo.
La revolución Transformer (2017)
Antes de Transformers (pre-2017):
Word2Vec, GloVe (2013-2015):
# Word embeddings estáticos:
embed("bank") = [0.5, 0.3, 0.8, ...] # SIEMPRE el mismo vector
# Problema: NO captura contexto
"Voy al banco del río" → "banco" = [0.5, 0.3, 0.8]
"Voy al banco a sacar dinero" → "banco" = [0.5, 0.3, 0.8] # ❌ Mismo vector
RNNs/LSTMs (2015-2017):
# Sí capturaban contexto, pero:
# ❌ Lentos (procesamiento secuencial, no paralelizable)
# ❌ Problemas con secuencias largas (vanishing gradients)
# ❌ Difíciles de entrenar
Después de Transformers (2017+):
BERT, GPT, OpenAI embeddings (2018+):
# Embeddings contextuales:
embed("Voy al banco del río") → "banco" = [0.2, 0.5, 0.1, ...] # Orilla
embed("Voy al banco a sacar dinero") → "banco" = [0.8, 0.3, 0.6, ...] # Financiero
# ✅ Contexto diferente → embedding diferente
# ✅ Rápido (procesamiento paralelo)
# ✅ Escalable (billones de parámetros)
Cambio fundamental: De embeddings estáticos a embeddings contextuales.
Arquitectura Transformer: Encoder-Decoder
Paper original (2017): "Attention Is All You Need"
Arquitectura completa:
┌─────────────────────────────────┐
│ INPUT TOKENS │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ ENCODER (N layers) │ ← Para embeddings usamos SOLO esto
│ - Self-Attention │
│ - Feed-Forward │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ DECODER (N layers) │ ← Para generación de texto (GPT)
│ - Self-Attention │
│ - Cross-Attention │
│ - Feed-Forward │
└─────────────────────────────────┘
↓
┌─────────────────────────────────┐
│ OUTPUT TOKENS │
└─────────────────────────────────┘
Para embeddings: Solo usamos ENCODER (encoder-only models).
Encoder-Only Models (BERT-style)
Arquitectura encoder-only:
Input: "Python es un lenguaje de programación"
↓
┌─────────────────────────────────────┐
│ Tokenización + Embedding Inicial │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Encoder Layer 1 │
│ - Multi-Head Self-Attention │
│ - Add & Norm │
│ - Feed-Forward Network │
│ - Add & Norm │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Encoder Layer 2 │
│ (misma estructura) │
└─────────────────────────────────────┘
↓
... (N layers)
↓
┌─────────────────────────────────────┐
│ Hidden States (output) │
│ Un vector por token │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Pooling │
│ (reduce a 1 vector) │
└─────────────────────────────────────┘
↓
Embedding Final
Cada encoder layer procesa todos los tokens en paralelo (no secuencialmente como RNN).
Self-Attention: El corazón del Transformer
Qué es self-attention:
Mecanismo que permite a cada token "prestar atención" a todos los otros tokens en la secuencia.
Ejemplo visual:
Input: "El banco del río"
Self-attention para "banco":
- "El" → 0.1 (poca atención)
- "banco" → 1.0 (atención a sí mismo, siempre alto)
- "del" → 0.3 (algo de atención)
- "río" → 0.8 (MUCHA atención) ✅
Resultado: "banco" entiende que está cerca de "río"
→ Embedding refleja "orilla" (no "financiero")
Visualización de attention weights:
# Conceptual (no ejecutable sin modelo cargado)
text = "El banco del río está lleno"
# Matriz de attention (simplificada):
# El banco del río está lleno
# El 1.0 0.2 0.1 0.0 0.1 0.0
# banco 0.2 1.0 0.3 0.8 0.1 0.0 ← "banco" presta atención a "río"
# del 0.1 0.3 1.0 0.6 0.0 0.0
# río 0.0 0.8 0.6 1.0 0.2 0.3 ← "río" presta atención a "banco"
# está 0.1 0.1 0.0 0.2 1.0 0.5
# lleno 0.0 0.0 0.0 0.3 0.5 1.0
Los valores altos indican relación semántica entre tokens.
Comparación con arquitecturas previas:
| Arquitectura | ¿Captura contexto? | ¿Paralelizable? | Complejidad |
|---|---|---|---|
| Word2Vec | ❌ No (estático) | ✅ Sí (entrenamiento) | O(1) per token |
| RNN/LSTM | ✅ Sí (secuencial) | ❌ No | O(n) secuencial |
| Transformer | ✅ Sí (self-attention) | ✅ Sí | O(n²) pero paralelo |
Trade-off: Transformers son O(n²) en memoria pero paralelizables (GPUs).
Componentes de un Encoder Layer
1. Multi-Head Self-Attention
Qué hace: Calcula attention weights entre todos los pares de tokens.
"Multi-Head": Múltiples attention mechanisms en paralelo (e.g., 12 heads en BERT-base).
┌─────────────────────────────────────┐
│ Multi-Head Self-Attention │
│ │
│ Head 1: Atención a relaciones │
│ sintácticas │
│ Head 2: Atención a relaciones │
│ semánticas │
│ Head 3: Atención a contexto │
│ local │
│ ... │
│ Head 12: Atención a contexto │
│ global │
└─────────────────────────────────────┘
Cada head aprende patrones diferentes automáticamente durante entrenamiento.
2. Add & Normalize (Residual Connection)
Qué hace: Suma input original + output de attention, luego normaliza.
# Pseudocódigo
output_attention = self_attention(input)
output = layer_norm(input + output_attention) # Residual connection
Por qué: Evita vanishing gradients en redes profundas (12-24 layers).
3. Feed-Forward Network
Qué hace: Transformación no-linear aplicada a cada token independientemente.
# Pseudocódigo
def feed_forward(x):
# Expansión
hidden = linear_1(x) # [hidden_size] → [4 * hidden_size]
hidden = relu(hidden) # Activación
# Contracción
output = linear_2(hidden) # [4 * hidden_size] → [hidden_size]
return output
# Aplicar a cada token
for token_vector in hidden_states:
token_vector = feed_forward(token_vector)
Intuición: Proyecta a espacio más grande (4x), aplica no-linearidad, proyecta de vuelta.
4. Add & Normalize (segunda vez)
# Pseudocódigo
output_ffn = feed_forward(input)
output = layer_norm(input + output_ffn) # Residual connection
Resultado: Output de un encoder layer completo.
Encoder Stack (N layers)
BERT-base: 12 encoder layers
Input tokens
↓
┌────────────┐
│ Layer 1 │ → Captura patrones locales (bigrams, trigrams)
├────────────┤
│ Layer 2 │
├────────────┤
│ Layer 3 │ → Captura relaciones sintácticas
├────────────┤
│ Layer 4-6 │ → Captura semántica básica
├────────────┤
│ Layer 7-9 │ → Captura semántica profunda
├────────────┤
│ Layer 10-12│ → Captura contexto global
└────────────┘
↓
Hidden states (contextualizados)
Más layers = más profundidad contextual.
Self-Attention: Matemáticas (simplificado)
Conceptos clave:
Para cada token, calculamos:
- Query (Q): "¿Qué busco?"
- Key (K): "¿Qué ofrezco?"
- Value (V): "Mi contenido"
Intuición: Token A hace query, compara con keys de todos los tokens, obtiene attention weights, y calcula weighted sum de values.
Fórmula de attention (simplificada):
Attention(Q, K, V) = softmax(Q · K^T / √d_k) · V
Donde:
- Q, K, V: Matrices de queries, keys, values
- Q · K^T: Dot product (similaridad)
- √d_k: Scaling factor (estabilidad numérica)
- softmax: Normaliza a probabilidades (suma = 1)
- · V: Weighted sum de values
No necesitas memorizar esto. Importante: self-attention calcula similaridad entre tokens.
Ejemplo conceptual:
# Token "banco" en "El banco del río"
# Query de "banco": [0.5, 0.3, 0.8]
# Keys de todos los tokens:
# "El": [0.1, 0.2, 0.1]
# "banco": [0.5, 0.3, 0.8] (sí mismo)
# "del": [0.2, 0.4, 0.3]
# "río": [0.6, 0.4, 0.9]
# Dot products (similaridad):
# "banco" · "El" = 0.19 (bajo)
# "banco" · "banco" = 0.98 (alto - sí mismo)
# "banco" · "del" = 0.46 (medio)
# "banco" · "río" = 1.02 (alto - relacionado) ✅
# Softmax (normalizar):
# "El": 0.1
# "banco": 0.3
# "del": 0.2
# "río": 0.4 ← Mayor atención
# Weighted sum de values → Nuevo vector de "banco" (contextualizado)
Resultado: "banco" actualizado con información de "río".
Positional Encoding
Problema:
Self-attention no tiene noción de orden (permutation-invariant).
# Sin positional encoding:
embed("El banco del río") ≈ embed("río del banco El") # ❌ Orden no importa
Solución: Agregar información de posición.
Positional Encoding (sinusoidal):
# Pseudocódigo (OpenAI/BERT usan learned embeddings)
def positional_encoding(position, d_model):
"""
Encoding único para cada posición
Args:
position: Posición del token (0, 1, 2, ...)
d_model: Dimensiones del embedding
"""
encoding = []
for i in range(d_model):
if i % 2 == 0:
encoding.append(sin(position / 10000^(i/d_model)))
else:
encoding.append(cos(position / 10000^(i/d_model)))
return encoding
# Agregar a token embeddings
token_embedding = [0.5, 0.3, 0.8, ...]
position_encoding = positional_encoding(position=2, d_model=768)
final_embedding = token_embedding + position_encoding
Ahora el modelo sabe que "banco" está en posición 2.
Comparación: Word2Vec vs Transformer Embeddings
Word2Vec (2013):
# Embeddings estáticos - NO contextuales
vocab = {
"banco": [0.5, 0.3, 0.8], # SIEMPRE este vector
"río": [0.2, 0.6, 0.1],
"dinero": [0.9, 0.1, 0.7]
}
# Problema:
text_1 = "banco del río"
text_2 = "banco de dinero"
# "banco" tiene el MISMO embedding en ambos contextos ❌
BERT / OpenAI Embeddings (2018+):
# Embeddings contextuales
def get_contextual_embedding(text, word):
# Tokenizar texto completo
tokens = tokenize(text)
# Procesar con Transformer
hidden_states = transformer_encoder(tokens)
# Embedding de "word" depende de contexto
word_idx = tokens.index(word)
return hidden_states[word_idx]
# Ejemplo:
emb_1 = get_contextual_embedding("banco del río", "banco")
# → [0.2, 0.5, 0.1, ...] (orilla)
emb_2 = get_contextual_embedding("banco de dinero", "banco")
# → [0.8, 0.3, 0.6, ...] (financiero)
# Embeddings DIFERENTES según contexto ✅
Modelos encoder-only populares
1. BERT (2018) - Google
Arquitectura: 12 layers, 768 hidden, 12 attention heads
Parámetros: 110M (base), 340M (large)
Embeddings: 768 dims (base), 1024 dims (large)
Training: Masked Language Modeling (MLM) + Next Sentence Prediction
Uso: Base para fine-tuning en tareas específicas.
2. Sentence-BERT (2019)
Arquitectura: BERT fine-tuned para sentence embeddings
Parámetros: 22M (MiniLM-L6), 110M (base)
Embeddings: 384 dims (MiniLM), 768 dims (base)
Training: Siamese network con pares similares
Pooling: Mean pooling (default)
Uso: Sentence embeddings out-of-the-box (mejor que BERT vanilla).
3. OpenAI text-embedding-3-small (2024)
Arquitectura: Transformer encoder (detalles no públicos)
Parámetros: No público
Embeddings: 1536 dims
Training: Contrastive learning a escala masiva
Pooling: No documentado (probablemente mean)
Uso: API, no local. Excelente calidad out-of-the-box.
4. BGE (BAAI General Embedding) - 2023
Arquitectura: BERT-large fine-tuned
Parámetros: 340M (large)
Embeddings: 1024 dims
Training: Contrastive learning + hard negatives
Pooling: CLS token
Uso: State-of-the-art open-source, local.
Ventajas de Transformers sobre arquitecturas previas
vs RNN/LSTM:
| Aspecto | RNN/LSTM | Transformer |
|---|---|---|
| Paralelización | ❌ No (secuencial) | ✅ Sí (todos los tokens en paralelo) |
| Secuencias largas | ❌ Vanishing gradients | ✅ Self-attention captura largo alcance |
| Velocidad | ❌ Lento (GPU subutilizada) | ✅ Rápido (GPU fully utilizada) |
| Escalabilidad | ❌ Difícil escalar | ✅ Escala a billones de parámetros |
vs Word2Vec/GloVe:
| Aspecto | Word2Vec/GloVe | Transformer |
|---|---|---|
| Contextual | ❌ No (estático) | ✅ Sí (contexto dinámico) |
| Polysemy | ❌ No (1 vector por palabra) | ✅ Sí (diferentes embeddings según contexto) |
| Calidad | ⚠️ Básico | ✅ State-of-the-art |
Limitaciones de Transformers
1. Complejidad cuadrática (O(n²)):
# Attention calcula similitud entre TODOS los pares de tokens
n_tokens = 1000
attention_computations = n_tokens ** 2 # 1,000,000 operaciones
# Para textos largos (10K tokens):
n_tokens = 10000
attention_computations = n_tokens ** 2 # 100,000,000 operaciones 😱
Límite: OpenAI embeddings = 8191 tokens max.
2. Memoria intensiva:
# Matriz de attention: [batch_size, n_heads, seq_len, seq_len]
batch_size = 32
n_heads = 12
seq_len = 512
attention_matrix_size = batch_size * n_heads * seq_len * seq_len * 4 # bytes (float32)
# = 32 * 12 * 512 * 512 * 4 ≈ 402 MB (solo attention!)
Requiere GPUs con mucha VRAM.
3. No entiende matemáticas/lógica perfectamente:
# Transformers son buenos en lenguaje natural, no en lógica exacta:
query = "¿Cuánto es 123456 × 789012?"
embedding = get_embedding(query)
# Embedding captura "pregunta de multiplicación" pero NO el resultado exacto
Para cálculos: usa herramientas (tool use), no embeddings.
Ejercicios
Ejercicio 1: Identificar arquitectura
¿Qué arquitectura es mejor para cada caso?
Caso A: Generar embeddings de frases Caso B: Generar texto (chatbot) Caso C: Traducción automática
Ver solución
-
Caso A: Encoder-only (BERT, Sentence-BERT, OpenAI embeddings)
- Solo necesitas representación vectorial, no generación
-
Caso B: Decoder-only (GPT, Claude)
- Generación de texto token por token
-
Caso C: Encoder-Decoder (T5, BART)
- Encoder procesa idioma origen, decoder genera idioma destino
Embeddings = Encoder-only.
Ejercicio 2: Predecir attention
Dado el texto "El gato persigue al ratón", ¿qué token debería recibir MÁS atención de "persigue"?
Opciones: A) "El" B) "gato" C) "al" D) "ratón"
Ver solución
Respuesta: B) "gato" y D) "ratón"
Razón:
- "persigue" es un verbo
- Verbo presta atención a sujeto ("gato") y objeto ("ratón")
- "El" y "al" son artículos (menos relevantes semánticamente)
Self-attention aprende relaciones sintácticas automáticamente.
Ejercicio 3: Comparar Word2Vec vs Transformer
¿Qué approach captura mejor la diferencia de "banco"?
text_1 = "Voy al banco del río a pescar"
text_2 = "Voy al banco a depositar dinero"
Opciones: A) Word2Vec (embeddings estáticos) B) Transformer (embeddings contextuales)
Ver solución
Respuesta: B) Transformer
Word2Vec:
- "banco" = [0.5, 0.3, 0.8] en AMBOS contextos ❌
- No diferencia entre "orilla" y "financiero"
Transformer:
- Text 1: "banco" con contexto de "río" + "pescar" → Embedding A (orilla)
- Text 2: "banco" con contexto de "depositar" + "dinero" → Embedding B (financiero)
- Embeddings A y B son DIFERENTES ✅
Transformers resuelven polysemy (múltiples significados).
Ejercicio 4: Calcular complejidad
¿Cuántas operaciones de attention para 512 tokens?
n_tokens = 512
# ¿Cuántas operaciones?
Ver solución
n_tokens = 512
attention_operations = n_tokens ** 2
print(f"Operaciones: {attention_operations}")
# → 262,144 operaciones
# Con 12 attention heads (BERT-base):
total_operations = attention_operations * 12
print(f"Total: {total_operations}")
# → 3,145,728 operaciones por layer
# × 12 layers = ~37M operaciones total
Por eso Transformers requieren GPUs potentes.
Troubleshooting común
Problema 1: "Token limit exceeded"
# Error: Texto > 8191 tokens (OpenAI)
long_text = "..." * 10000
embedding = get_embedding(long_text) # ❌ Error
Solución: Chunkear texto (Módulo 4).
Problema 2: Embeddings idénticos para contextos diferentes
# Esperabas diferentes, pero son casi idénticos:
emb_1 = embed("banco del río")
emb_2 = embed("banco de dinero")
similarity = cosine_similarity(emb_1, emb_2) # 0.95 (muy similar)
Causa: Modelo no es lo suficientemente contextual (e.g., Word2Vec legacy).
Solución: Usa modelo moderno (OpenAI, SBERT, BGE).
Resumen
Qué aprendiste:
- ✅ Transformers: Arquitectura encoder-only para embeddings
- ✅ Self-attention: Captura contexto entre todos los tokens
- ✅ Encoder layers: Stack de 12-24 layers procesa en paralelo
- ✅ Embeddings contextuales: "banco" tiene diferentes embeddings según contexto
- ✅ Ventajas: Paralelo, escalable, captura largo alcance
- ✅ Limitaciones: O(n²) complejidad, límite de tokens
Conceptos clave:
- Self-attention permite que cada token vea todos los otros tokens
- Encoder stack de N layers captura contexto progresivamente
- Embeddings contextuales > embeddings estáticos (Word2Vec)
Recursos adicionales
- Illustrated Transformer - Visualización excelente
- Attention Is All You Need - Paper original
- BERT Explained - Encoder-only architecture
- The Annotated Transformer - Implementación comentada
- Transformers from Scratch - Deep dive técnico
En la siguiente cápsula
Cápsula 03: Tokenización
Aprenderás:
- BPE (Byte Pair Encoding) algoritmo
- WordPiece tokenization (BERT)
- tiktoken código (OpenAI)
- Por qué sub-palabras > palabras completas
- Vocabulario y out-of-vocabulary handling
De arquitectura a primer paso del pipeline.
Módulo 2 - Embeddings Deep Dive Guide Entendiendo la arquitectura que revolucionó NLP