Transformers
Desde la atención en RNNs hasta "Attention Is All You Need": la arquitectura que revolucionó el deep learning. Positional encoding, multi-head attention, encoder-decoder, entrenamiento con warmup y teacher forcing, código completo en PyTorch y TensorFlow, BERT vs GPT, T5, LLMs, Vision Transformers, Whisper, series temporales, DiT y más.
¿Qué es un Transformer?
Un Transformer es una arquitectura de red neuronal basada enteramente en mecanismos de atención, sin recurrencia ni convoluciones. Fue propuesta en 2017 por Vaswani et al. en el paper "Attention Is All You Need" y ha revolucionado prácticamente todos los campos del deep learning: procesamiento de lenguaje natural, visión por computador, generación de audio, series temporales, y más.
📐 Idea central
El Transformer se basa en un mecanismo llamado self-attention (autoatención): cada elemento de la secuencia puede «mirar» a todos los demás elementos y decidir cuánta atención prestar a cada uno. Matemáticamente:
donde \(Q\) (queries), \(K\) (keys) y \(V\) (values) son proyecciones lineales de la entrada, y \(d_k\) es la dimensión de las keys.
Pero para entender por qué el Transformer fue tan revolucionario, necesitamos entender lo que había antes: las RNNs, sus limitaciones, y cómo surgió la idea de la atención.
El camino hacia la atención: de las RNNs al Transformer
La historia del Transformer es la historia de cómo el deep learning aprendió a prestar atención. Antes de 2017, el paradigma dominante para procesar secuencias (texto, audio, series temporales) eran las Redes Neuronales Recurrentes (RNNs).
El problema de las RNNs
Las RNNs procesan la secuencia paso a paso, manteniendo un estado oculto \(h_t\) que se actualiza en cada timestep. Esto tiene dos problemas fundamentales:
El modelo seq2seq clásico (Sutskever et al., 2014; arXiv:1409.3215) tenía una limitación crítica: toda la información de la secuencia de entrada se comprimía en un único vector de contexto \(c\) — el último estado oculto del encoder. Para secuencias largas, esto provocaba una pérdida masiva de información.
Las LSTMs y GRUs mitigaron parcialmente el problema de vanishing gradients con gates, pero el cuello de botella y la falta de paralelismo seguían siendo fundamentales.
El nacimiento de la atención (Bahdanau, 2014)
La idea que cambió todo fue simple pero brillante: en lugar de comprimir toda la secuencia en un único vector, permitir que el decoder mire directamente a cada posición del encoder en cada paso de generación.
Bahdanau, Cho & Bengio (2014) propusieron un mecanismo de atención aditiva para modelos seq2seq de traducción automática. En cada paso del decoder \(t\), se calcula un score de atención \(e_{t,i}\) entre el estado del decoder \(s_t\) y cada estado del encoder \(h_i\):
Los pesos \(\alpha_{t,i}\) indican cuánta atención presta el decoder al token \(i\) cuando genera el token \(t\). El vector de contexto \(c_t\) es ahora diferente en cada paso: ya no hay cuello de botella.
Este paper fue un punto de inflexión. De repente, los modelos de traducción mejoraron drásticamente, y la idea de «atención» se convirtió en una herramienta fundamental en el toolkit del deep learning. La atención permitía resolver el problema del cuello de botella: el decoder ya no dependía de un único vector \(c\), sino que podía consultar directamente cada posición del encoder en cada paso de generación. Los pesos de atención \(\alpha_{t,i}\) además proporcionaban interpretabilidad: se podía visualizar exactamente a qué partes de la entrada prestaba atención el modelo.
Atención multiplicativa (Luong, 2015)
Poco después, Luong et al. (2015) propusieron una variante más simple y eficiente: la atención multiplicativa (dot-product attention), que calcula la similitud directamente con un producto escalar:
| Tipo | Fórmula del score | Complejidad | Ref |
|---|---|---|---|
| Aditiva (Bahdanau) | \(v^\top \tanh(W_s s + W_h h)\) | O(d) params extra | arXiv:1409.0473 |
| Multiplicativa (Luong) | \(s^\top W h\) | Más eficiente | arXiv:1508.04025 |
| Scaled Dot-Product | \(\frac{q^\top k}{\sqrt{d_k}}\) | La del Transformer | arXiv:1706.03762 |
La atención multiplicativa de Luong tiene la misma capacidad que la aditiva de Bahdanau, pero es más eficiente computacionalmente. La scaled dot-product attention del Transformer es una evolución directa de esta idea.
Self-Attention: el salto conceptual
Hasta 2016, la atención siempre era cross-attention: el decoder mira al encoder. Pero la pregunta clave que llevó al Transformer fue:
Esta idea — la self-attention o autoatención — fue la revolución. En lugar de procesar la secuencia paso a paso (RNN) o con filtros locales (CNN), cada token computa una representación que depende de toda la secuencia, ponderada por relevancia.
🔑 Self-attention vs Cross-attention
- Self-attention: Q, K, V vienen de la misma secuencia. Cada token de la frase mira a todos los demás tokens de la misma frase.
- Cross-attention: Q viene del decoder, K y V del encoder. El decoder mira a la secuencia de entrada.
El paper "Attention Is All You Need" demostró que con self-attention apilada en varias capas, más normalización y conexiones residuales, se podía superar a las RNNs en traducción automática, y entrenar órdenes de magnitud más rápido gracias a la paralelización.
Historia del Transformer
El paper: "Attention Is All You Need"
El paper de Vaswani et al. (2017) es uno de los más citados en la historia de la IA (>130.000 citas). Propone una arquitectura radicalmente simple que prescinde de toda recurrencia y convolución:
Visión general de la arquitectura
El Transformer original es un modelo encoder-decoder. El encoder procesa la secuencia de entrada completa y genera representaciones contextuales. El decoder genera la secuencia de salida token a token, usando tanto su propia secuencia parcial (self-attention con máscara) como la salida del encoder (cross-attention).
Input Embeddings
El primer paso es convertir cada token (palabra, subpalabra, o carácter) en un vector denso de dimensión \(d_{\text{model}}\). Esto se hace con una capa de embedding aprendida:
En el paper original, \(d_{\text{model}} = 512\). Los embeddings se multiplican por \(\sqrt{d_{\text{model}}}\) para escalar su magnitud relativa al positional encoding:
Positional Encoding: ¿dónde está cada token?
Como el Transformer procesa toda la secuencia en paralelo (sin recurrencia), no tiene noción inherente de orden. Para que sepa que «El gato» es diferente de «gato El», se añade un positional encoding a cada embedding.
El paper original usa funciones sinusoidales con frecuencias diferentes para cada dimensión:
donde \(pos\) es la posición del token en la secuencia e \(i\) es la dimensión. Las dimensiones bajas varían rápidamente (alta frecuencia), las altas varían lentamente (baja frecuencia), creando un patrón único para cada posición.
🤔 ¿Por qué seno y coseno?
- Cada posición tiene un vector único que la identifica.
- Las distancias relativas se capturan: \(PE_{pos+k}\) puede expresarse como una función lineal de \(PE_{pos}\) para cualquier offset \(k\).
- Se generaliza a longitudes no vistas: no requiere aprender un embedding para cada posición.
Una alternativa al PE sinusoidal es aprender los embeddings de posición como parámetros del modelo (como en GPT y BERT). Vaswani et al. encontraron que ambos enfoques producían resultados similares. En la práctica moderna:
| Tipo | Usado en | Ventaja | Limitación |
|---|---|---|---|
| Sinusoidal | Transformer original | Generaliza a cualquier longitud | Patrón fijo, no adaptativo |
| Aprendido (absoluto) | GPT, BERT | Se adapta a la tarea | Limitado a max_length visto |
| Relativo (Shaw et al.) | Transformer-XL, T5 | Captura relaciones relativas | Más complejo |
| RoPE (Rotary) | LLaMA, GPT-NeoX | Combina absoluto + relativo, escalable | Algo más costoso |
| ALiBi | BLOOM, MPT | Bias lineal, extrapolación a secuencias largas | Menos expresivo |
El Encoder: apilar autoatención
El encoder consiste en \(N\) capas idénticas apiladas (N=6 en el original). Cada capa tiene exactamente dos subcapas:
Cada subcapa tiene una conexión residual y layer normalization:
Batch Normalization normaliza a lo largo del batch (estadísticas sobre todos los ejemplos del mini-batch). Funciona bien en CNNs, pero tiene problemas con secuencias de longitud variable.
Layer Normalization normaliza a lo largo de las features dentro de cada ejemplo. Es independiente del batch y funciona perfectamente con secuencias de cualquier longitud:
donde \(\mu, \sigma\) se calculan sobre las dimensiones de cada vector individual, y \(\gamma, \beta\) son parámetros aprendidos.
El Decoder: generar token a token
El decoder también tiene \(N\) capas, pero cada capa tiene tres subcapas:
Capa de salida: Linear + Softmax
La salida del decoder pasa por una capa lineal que proyecta de \(d_{\text{model}}\) al tamaño del vocabulario \(V\), seguida de un softmax para obtener probabilidades:
🔄 Weight tying
Una técnica habitual (usada en el paper original) es compartir pesos entre la capa de embedding de entrada, la de salida del decoder, y la capa lineal final. Esto reduce el número de parámetros significativamente y mejora la generalización.
Hiperparámetros del Transformer original
| Parámetro | Símbolo | Valor (base) | Valor (big) | Descripción |
|---|---|---|---|---|
| Capas | \(N\) | 6 | 6 | Número de capas encoder/decoder |
| Dimensión del modelo | \(d_{\text{model}}\) | 512 | 1024 | Dimensión de embeddings y representaciones |
| Cabezas de atención | \(h\) | 8 | 16 | Número de heads en multi-head attention |
| Dimensión por head | \(d_k = d_v\) | 64 | 64 | \(d_{\text{model}} / h\) |
| FFN dimensión interna | \(d_{ff}\) | 2048 | 4096 | Hidden size del Feed-Forward Network |
| Dropout | \(p\) | 0.1 | 0.3 | Aplicado en residuals, attention, FFN |
| Parámetros totales | — | 65M | 213M | Pequeño para estándares actuales |
Comparado con los modelos actuales (GPT-4 tiene ~1.7T parámetros estimados, LLaMA 3 tiene hasta 405B), el Transformer original era sorprendentemente pequeño. La arquitectura ha escalado verticalmente, pero los principios fundamentales son exactamente los mismos.
Widget: explorador de Positional Encoding
Visualiza cómo varían las funciones sinusoidales del positional encoding según la posición y la dimensión. Observa cómo las frecuencias bajas (dimensiones altas) varían lentamente, creando patrones únicos para cada posición.
🧪 Positional Encoding Sinusoidal
Scaled Dot-Product Attention
El bloque fundamental del Transformer es la Scaled Dot-Product Attention. Cada token genera tres vectores: una query (\(Q\)), una key (\(K\)) y un value (\(V\)), obtenidos multiplicando la entrada por matrices de pesos aprendidas:
La atención se calcula como:
🔑 Intuición Q, K, V
Piensa en una búsqueda en una base de datos:
- Query (Q): «¿Qué estoy buscando?» — La pregunta que hace cada token.
- Key (K): «¿Qué tengo para ofrecer?» — La etiqueta de cada token.
- Value (V): «¿Qué información contiene?» — El contenido real de cada token.
La similitud entre Q y K (dot product) determina cuánta atención prestar a cada V. Los tokens con keys más similares a la query reciben más peso.
¿Por qué dividir entre \(\sqrt{d_k}\)?
Cuando \(d_k\) es grande, los dot products \(q \cdot k\) crecen en magnitud, lo que empuja al softmax a regiones de gradientes muy pequeños (saturación). Dividir entre \(\sqrt{d_k}\) normaliza la varianza de los scores a 1, manteniendo los gradientes saludables:
Si \(q_i, k_i \sim \mathcal{N}(0, 1)\) son independientes, el producto \(q_i k_i\) tiene media 0 y varianza 1. La suma de \(d_k\) de estos productos:
Con \(d_k = 64\), los scores no escalados tienen una desviación estándar de \(\sqrt{64} = 8\), lo que empujaría al softmax a dar probabilidad ~1 al máximo y ~0 a todo lo demás. Dividir entre \(\sqrt{d_k}\) restabiliza los gradientes.
Multi-Head Attention
En lugar de calcular una única función de atención, el Transformer usa múltiples cabezas (heads) que operan en paralelo. Cada head aprende a prestar atención a un tipo diferente de relación:
Con \(h = 8\) cabezas y \(d_{\text{model}} = 512\), cada head trabaja con vectores de dimensión \(d_k = d_v = 512 / 8 = 64\). Los resultados de las 8 heads se concatenan y se proyectan con \(W^O \in \mathbb{R}^{hd_v \times d_{\text{model}}}\).
🔍 ¿Qué aprende cada head?
Investigaciones empíricas (Clark et al., 2019; Voita et al., 2019) han mostrado que diferentes heads aprenden relaciones distintas. Esto es análogo a cómo filtros de una CNN aprenden features visuales diferentes:
📄 Clark et al. (2019): «What Does BERT Look At?» · Voita et al. (2019): «Analyzing Multi-Head Self-Attention»
Self-Attention: paso a paso con ejemplo
Veamos el cálculo completo de self-attention para la frase «El gato duerme» con \(d_k = 4\) (simplificado para visualización):
Cross-Attention: el puente encoder-decoder
En el decoder, además de la self-attention (masked), hay una capa de cross-attention donde:
- Queries (\(Q\)) vienen de la capa anterior del decoder.
- Keys (\(K\)) y Values (\(V\)) vienen de la salida del encoder.
Esto permite al decoder consultar la secuencia de entrada completa en cada paso de generación. Es el equivalente moderno del mecanismo de atención de Bahdanau, pero integrado en la arquitectura del Transformer sin RNNs.
La cross-attention es fundamental en cualquier tarea condicionada: traducción (decoder condicionado al texto fuente), generación de imágenes con texto (modelos de difusión como Stable Diffusion usan cross-attention para inyectar el prompt de texto), y modelos multimodales. En cambio, los modelos decoder-only (GPT, LLaMA) prescinden de cross-attention: solo usan masked self-attention, y toda la información de entrada se proporciona como parte del prompt concatenado.
🔄 Tres tipos de atención en un Transformer
| Tipo | Q de | K, V de | Dónde | Máscara |
|---|---|---|---|---|
| Self-Attention (encoder) | Encoder | Encoder | Encoder layers | Sin máscara |
| Masked Self-Attention | Decoder | Decoder | Decoder layers | Causal (triangular) |
| Cross-Attention | Decoder | Encoder output | Decoder layers | Sin máscara |
Masked Self-Attention: no mirar al futuro
En el decoder, la self-attention debe ser causal: el token en la posición \(t\) solo puede atender a las posiciones \(1, 2, \ldots, t\). Esto se implementa sumando una máscara al score antes del softmax:
donde \(M_{ij} = 0\) si \(j \leq i\), y \(M_{ij} = -\infty\) si \(j > i\). Tras el softmax, \(e^{-\infty} = 0\), así que los tokens futuros tienen peso cero.
Complejidad computacional
La self-attention tiene complejidad \(O(n^2 \cdot d)\) donde \(n\) es la longitud de la secuencia y \(d\) es la dimensión. Esto es cuadrático en la longitud, lo que se vuelve problemático para secuencias muy largas.
| Operación | Complejidad por capa | Longitud de secuencia | Paralelización |
|---|---|---|---|
| Self-Attention | \(O(n^2 \cdot d)\) | \(O(1)\) path máximo | \(O(1)\) — total ⚡ |
| RNN | \(O(n \cdot d^2)\) | \(O(n)\) path máximo | \(O(n)\) — secuencial 🐌 |
| CNN (kernel k) | \(O(k \cdot n \cdot d^2)\) | \(O(\log_k(n))\) dilatada | \(O(1)\) — paralela |
La tabla anterior revela una tensión fundamental: self-attention ofrece el camino más corto entre cualquier par de tokens (\(O(1)\) vs \(O(n)\) en RNNs), lo que facilita capturar dependencias a larga distancia. Pero su complejidad cuadrática en la longitud de secuencia limita su aplicación a secuencias largas. Esto ha motivado una línea de investigación muy activa en efficient attention, que busca mantener las ventajas de la atención reduciendo su coste computacional.
La complejidad \(O(n^2)\) ha motivado mucha investigación en «efficient attention»:
- Flash Attention (Dao et al., 2022): No cambia la complejidad computacional, pero optimiza el acceso a memoria GPU para ser 2-4× más rápido. Es el estándar en la práctica moderna.
- Linear Attention (Katharopoulos et al., 2020): Reemplaza softmax por un kernel para lograr \(O(n \cdot d^2)\).
- Sparse Attention (BigBird, Longformer): Solo atiende a un subconjunto de posiciones, logrando \(O(n \sqrt{n})\) o \(O(n)\).
- Mamba / SSMs (Gu & Dao, 2023): Modelos de espacio de estados selectivos que logran \(O(n)\) con selectividad. Una alternativa al Transformer.
Widget: explorador de Self-Attention
Escribe una frase y visualiza cómo la self-attention asigna pesos entre tokens. Ajusta la temperatura (escala del softmax) para ver su efecto: valores bajos → atención más uniforme, valores altos → atención más concentrada.
🧪 Self-Attention Explorer
Función de pérdida: Cross-Entropy
El Transformer para traducción/generación se entrena con cross-entropy loss sobre el vocabulario de salida. En cada posición del decoder, el modelo predice una distribución de probabilidad sobre \(V\) tokens y la comparamos con el token correcto:
donde \(y_t\) es el token objetivo en la posición \(t\), \(y_{
El paper original usa label smoothing con \(\epsilon = 0.1\):
en lugar de one-hot targets (1 para el token correcto, 0 para el resto), se usa:
Esto penaliza al modelo por ser «demasiado seguro» de sus predicciones y mejora
la generalización. El token correcto recibe probabilidad ~0.9 en lugar de 1.0,
y los demás ~\(0.1 / V\) en lugar de 0.
El Transformer usa Adam con un esquema de learning rate especial
que combina un warmup lineal con un decay proporcional a la raíz
cuadrada inversa del paso:
Con \(\text{warmup\_steps} = 4000\), el learning rate crece linealmente
durante los primeros 4000 pasos y luego decrece proporcionalmente a
\(1/\sqrt{\text{step}}\).
Durante el entrenamiento, el decoder recibe como entrada la secuencia objetivo
desplazada una posición a la derecha (shifted right), en lugar de sus propias
predicciones anteriores. Esto se llama teacher forcing:
Veamos una implementación limpia del Transformer en PyTorch y TensorFlow.
Incluye self-attention, multi-head attention, positional encoding, y el encoder completo.
En inferencia, el decoder genera un token a la vez. En cada paso,
toma toda la secuencia generada hasta el momento, pasa por el decoder, y predice
el siguiente token:
La combinación de KV-cache con Flash Attention
(Dao et al., 2022)
permite una inferencia eficiente incluso para secuencias con cientos de miles de tokens.
Flash Attention optimiza los accesos a memoria GPU sin cambiar la complejidad algorítmica,
logrando speedups de 2–4× en la práctica. Esto, junto con técnicas como
speculative decoding y quantization (INT8, INT4),
hace viable servir modelos con miles de millones de parámetros en tiempo real.
La arquitectura Transformer transformó (literalmente) el procesamiento de lenguaje natural.
La idea clave fue el pre-training a gran escala: entrenar un Transformer
enorme con cantidades masivas de texto, y luego fine-tunear para tareas
específicas. Esto dio lugar a dos paradigmas fundamentales:
Y existe un tercer paradigma que combina ambos:
BERT (Devlin et al., 2018) fue el primer modelo que demostró el poder
del pre-training bidireccional. Solo usa el encoder del Transformer.
BERT se pre-entrena con dos objetivos:
BERT estableció el paradigma de pre-training + fine-tuning que dominaría el NLP
durante años. La idea de enmascarar tokens y predecirlos (MLM) fuerza al modelo a construir
representaciones contextuales ricas — cada token codifica información sobre todos los demás.
Tras BERT, surgieron variantes mejoradas como
RoBERTa (Liu et al., 2019),
que eliminó el objetivo NSP y usó más datos y batch sizes mayores, y
DeBERTa (He et al., 2020),
que introdujo atención con codificación de posición relativa (disentangled attention).
GPT (Radford et al., 2018) toma el camino opuesto a BERT: usa solo
el decoder del Transformer con masked self-attention (causal), entrenado para
predecir el siguiente token.
La familia GPT demostró que el enfoque decoder-only con pre-training autoregresivo
escala extraordinariamente bien. Cada generación multiplicó los parámetros y los datos,
y con GPT-3 emergió la capacidad de in-context learning: resolver tareas
nuevas simplemente proporcionando ejemplos en el prompt, sin reentrenamiento (few-shot).
Esta propiedad emergente transformó la forma de usar modelos de lenguaje.
T5 (Raffel et al., 2019) unifica todas las tareas NLP bajo un mismo
formato: text-to-text. Usa el Transformer completo (encoder-decoder) y trata
cada tarea como una traducción entre textos:
Un Large Language Model (LLM) es un Transformer (generalmente decoder-only)
entrenado con cantidades masivas de texto. A partir de cierta escala (~10B+ parámetros),
emergen capacidades que no existían en modelos más pequeños:
Explora cómo funciona la cross-attention en un modelo de traducción.
La matriz muestra cuánta atención presta cada token de salida (decoder) a cada token
de entrada (encoder). Selecciona un par de frases y ajusta los parámetros.
Lo que empezó como una arquitectura para traducción de texto se ha convertido en la
columna vertebral de toda la IA moderna. La idea clave —procesar
secuencias con self-attention en paralelo— funciona igual de bien con píxeles,
ondas de audio, series temporales o combinaciones multimodales.
ViT (Dosovitskiy et al., 2020) fue la primera demostración contundente
de que los Transformers pueden competir con las CNNs en visión. La idea es radical
por su simplicidad:
La línea de investigación de Vision Transformers ha convergido con los modelos generativos:
los foundation models visuales como DINOv2 y SAM producen representaciones
que sirven para casi cualquier tarea visual (clasificación, segmentación, detección)
sin entrenamiento específico — el equivalente visual de BERT. También puedes ver cómo
se aplican los Transformers en el
submódulo de difusión,
donde DiT ha reemplazado a la U-Net como backbone de referencia.
Whisper (Radford et al., 2022, OpenAI) es un Transformer encoder-decoder
entrenado con 680.000 horas de audio (weakly supervised) que puede:
Es el mismo patrón encoder-decoder del Transformer original, pero con audio en lugar de texto en la entrada.
Las series temporales son secuencias — el dominio natural del Transformer. Varios
modelos adaptan self-attention para capturar dependencias temporales complejas:
No del todo. Modelos como Mamba (State Space Models, 2023) demuestran
que arquitecturas recurrentes con complejidad lineal pueden competir con Transformers.
La ventaja de Mamba: \(O(n)\) en lugar de \(O(n^2)\) de self-attention, con inferencia
más rápida y sin KV-cache. En la práctica, la comunidad investiga híbridos: combinar attention
con SSMs para obtener lo mejor de ambos mundos (Jamba, Zamba, etc.).
Las series temporales representan un campo donde la tensión entre la complejidad
cuadrática de self-attention y la longitud de las secuencias es especialmente crítica.
Modelos como PatchTST
(Nie et al., 2023)
mitigan esto agrupando timestamps en parches (como ViT con píxeles), reduciendo
la longitud de secuencia efectiva. iTransformer
(Liu et al., 2023)
va más allá: invierte la dimensión de atención, aplicando self-attention sobre las
variables (canales) en lugar de sobre los timestamps, lo que captura
mejor las correlaciones entre sensores o indicadores.
DiT (Peebles & Xie, 2023) reemplaza la U-Net de los modelos de
difusión por un Transformer. La idea: en lugar de aplicar convoluciones para predecir
el ruido, usar self-attention sobre parches del latent space.
La misma arquitectura puede procesar múltiples modalidades simultáneamente:
tokenizar cada modalidad, concatenar o intercalar, y dejar que self-attention
aprenda las relaciones cross-modal.
Optimizador: Adam con warmup
Teacher Forcing
Implementación: Transformer simplificado
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
# ─── Positional Encoding (sinusoidal) ───
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=5000, dropout=0.1):
super().__init__()
self.dropout = nn.Dropout(p=dropout)
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) # dimensiones pares
pe[:, 1::2] = torch.cos(position * div_term) # dimensiones impares
pe = pe.unsqueeze(0) # (1, max_len, d_model)
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)
# ─── Scaled Dot-Product Attention ───
def scaled_dot_product_attention(Q, K, V, mask=None):
"""
Q, K, V: (batch, heads, seq_len, d_k)
mask: (batch, 1, 1, seq_len) or (1, 1, seq_len, seq_len) for causal
"""
d_k = Q.size(-1)
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
attn_weights = F.softmax(scores, dim=-1)
output = torch.matmul(attn_weights, V)
return output, attn_weights
# ─── Multi-Head Attention ───
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super().__init__()
assert d_model % num_heads == 0
self.d_k = d_model // num_heads
self.num_heads = num_heads
self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)
self.W_o = nn.Linear(d_model, d_model)
def forward(self, Q, K, V, mask=None):
batch_size = Q.size(0)
# Linear projections + reshape to (batch, heads, seq, d_k)
Q = self.W_q(Q).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
K = self.W_k(K).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
V = self.W_v(V).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
# Attention
attn_output, attn_weights = scaled_dot_product_attention(Q, K, V, mask)
# Concat heads + linear
attn_output = attn_output.transpose(1, 2).contiguous().view(
batch_size, -1, self.num_heads * self.d_k
)
return self.W_o(attn_output), attn_weights
# ─── Feed-Forward Network ───
class FeedForward(nn.Module):
def __init__(self, d_model, d_ff, dropout=0.1):
super().__init__()
self.linear1 = nn.Linear(d_model, d_ff)
self.linear2 = nn.Linear(d_ff, d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
return self.linear2(self.dropout(F.relu(self.linear1(x))))
# ─── Encoder Layer ───
class EncoderLayer(nn.Module):
def __init__(self, d_model, num_heads, d_ff, dropout=0.1):
super().__init__()
self.self_attn = MultiHeadAttention(d_model, num_heads)
self.ffn = FeedForward(d_model, d_ff, dropout)
self.norm1 = nn.LayerNorm(d_model)
self.norm2 = nn.LayerNorm(d_model)
self.dropout1 = nn.Dropout(dropout)
self.dropout2 = nn.Dropout(dropout)
def forward(self, x, mask=None):
# Self-attention + residual + norm
attn_out, _ = self.self_attn(x, x, x, mask)
x = self.norm1(x + self.dropout1(attn_out))
# FFN + residual + norm
ffn_out = self.ffn(x)
x = self.norm2(x + self.dropout2(ffn_out))
return x
# ─── Encoder completo ───
class TransformerEncoder(nn.Module):
def __init__(self, vocab_size, d_model=512, num_heads=8,
d_ff=2048, num_layers=6, dropout=0.1, max_len=5000):
super().__init__()
self.embedding = nn.Embedding(vocab_size, d_model)
self.pos_encoding = PositionalEncoding(d_model, max_len, dropout)
self.layers = nn.ModuleList([
EncoderLayer(d_model, num_heads, d_ff, dropout)
for _ in range(num_layers)
])
self.d_model = d_model
def forward(self, x, mask=None):
# x: (batch, seq_len) of token ids
x = self.embedding(x) * math.sqrt(self.d_model)
x = self.pos_encoding(x)
for layer in self.layers:
x = layer(x, mask)
return x
# ─── Uso ───
encoder = TransformerEncoder(vocab_size=32000)
tokens = torch.randint(0, 32000, (2, 50)) # batch=2, seq=50
output = encoder(tokens)
print(output.shape) # torch.Size([2, 50, 512])import tensorflow as tf
import numpy as np
# ─── Positional Encoding ───
def positional_encoding(max_len, d_model):
positions = np.arange(max_len)[:, np.newaxis]
dims = np.arange(d_model)[np.newaxis, :]
angles = positions / np.power(10000, (2 * (dims // 2)) / d_model)
angles[:, 0::2] = np.sin(angles[:, 0::2])
angles[:, 1::2] = np.cos(angles[:, 1::2])
return tf.cast(angles[np.newaxis, :, :], dtype=tf.float32)
# ─── Scaled Dot-Product Attention ───
def scaled_dot_product_attention(Q, K, V, mask=None):
d_k = tf.cast(tf.shape(K)[-1], tf.float32)
scores = tf.matmul(Q, K, transpose_b=True) / tf.math.sqrt(d_k)
if mask is not None:
scores += (mask * -1e9)
weights = tf.nn.softmax(scores, axis=-1)
return tf.matmul(weights, V), weights
# ─── Multi-Head Attention ───
class MultiHeadAttention(tf.keras.layers.Layer):
def __init__(self, d_model, num_heads):
super().__init__()
self.num_heads = num_heads
self.d_k = d_model // num_heads
self.wq = tf.keras.layers.Dense(d_model)
self.wk = tf.keras.layers.Dense(d_model)
self.wv = tf.keras.layers.Dense(d_model)
self.wo = tf.keras.layers.Dense(d_model)
def split_heads(self, x, batch_size):
x = tf.reshape(x, (batch_size, -1, self.num_heads, self.d_k))
return tf.transpose(x, perm=[0, 2, 1, 3])
def call(self, Q, K, V, mask=None):
batch_size = tf.shape(Q)[0]
Q = self.split_heads(self.wq(Q), batch_size)
K = self.split_heads(self.wk(K), batch_size)
V = self.split_heads(self.wv(V), batch_size)
attn_out, attn_weights = scaled_dot_product_attention(Q, K, V, mask)
attn_out = tf.transpose(attn_out, perm=[0, 2, 1, 3])
attn_out = tf.reshape(attn_out, (batch_size, -1, self.num_heads * self.d_k))
return self.wo(attn_out), attn_weights
# ─── Encoder Layer ───
class EncoderLayer(tf.keras.layers.Layer):
def __init__(self, d_model, num_heads, d_ff, dropout_rate=0.1):
super().__init__()
self.mha = MultiHeadAttention(d_model, num_heads)
self.ffn = tf.keras.Sequential([
tf.keras.layers.Dense(d_ff, activation='relu'),
tf.keras.layers.Dense(d_model)
])
self.norm1 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
self.norm2 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
self.dropout1 = tf.keras.layers.Dropout(dropout_rate)
self.dropout2 = tf.keras.layers.Dropout(dropout_rate)
def call(self, x, mask=None, training=False):
attn_out, _ = self.mha(x, x, x, mask)
x = self.norm1(x + self.dropout1(attn_out, training=training))
ffn_out = self.ffn(x)
x = self.norm2(x + self.dropout2(ffn_out, training=training))
return x
# ─── Uso ───
encoder_layer = EncoderLayer(d_model=512, num_heads=8, d_ff=2048)
sample = tf.random.uniform((2, 50, 512))
output = encoder_layer(sample)
print(output.shape) # (2, 50, 512)Trucos de entrenamiento
Hiperparámetro
Paper original
Modelos modernos (GPT-3+)
Notas
Optimizer
Adam (β₁=0.9, β₂=0.98)
AdamW (weight decay)
AdamW separa weight decay de lr
LR Schedule
Warmup + inverse sqrt
Cosine annealing
Cosine suele funcionar mejor
Batch size
~25k tokens
~1-4M tokens
Batch size grande = más estable
Precisión
FP32
BF16 + loss scaling
Necesario para escalar
Paralelismo
8 GPUs (data parallel)
Tensor + Pipeline + Data parallel
Miles de GPUs para GPT-4
Inferencia: generación autoregresiva
Estrategia Descripción Pros Contras
Greedy
Elegir siempre el token más probable
Rápido, determinista
Repetitivo, no óptimo globalmente
Beam Search
Mantener los \(k\) mejores candidatos
Mejor calidad que greedy
Lento, poco diverso
Temperature Sampling
Muestrear de \(P^\tau\) (τ > 1 = más aleatorio)
Diverso, creativo
Puede generar basura con τ alto
Top-k
Solo considerar los \(k\) tokens más probables
Buen balance
\(k\) fijo no se adapta al contexto
Top-p (Nucleus)
Tokens cuya probabilidad acumulada ≤ \(p\)
Adaptativo, muy usado
Requiere ajustar \(p\)
El éxito del Transformer en NLP
Ideal para: clasificación, NER, QA, embedding de texto.
Ideal para: generación de texto, chatbots, code completion.
Ideal para: traducción, resumen, text-to-text en general.
BERT: Bidirectional Encoder Representations from Transformers
🎯 Pre-entrenamiento de BERT
Modelo Capas d_model Heads Params BERT-base 12 768 12 110M BERT-large 24 1024 16 340M GPT: Generative Pre-trained Transformer
Modelo Año Capas d_model Params Contexto GPT-1 2018 12 768 117M 512 GPT-2 2019 48 1600 1.5B 1024 GPT-3 2020 96 12288 175B 2048 GPT-4 (est.) 2023 ~120 ~? ~1.7T (MoE) 128k BERT vs GPT: dos filosofías
Aspecto
BERT (Encoder)
GPT (Decoder)
Dirección
Bidireccional
Unidireccional (izq→der)
Pre-training
MLM + NSP
Causal LM (next token)
Uso principal
Comprensión (clasificar, extraer)
Generación (escribir, chatear)
Fine-tuning
Añadir cabeza de clasificación
Prompt engineering / RLHF
Atención
Completa (ve todo)
Causal (solo pasado)
Escala moderna
~340M (limitado)
~1.7T (escala extrema)
Herederos
RoBERTa, ALBERT, DeBERTa, ModernBERT
GPT-4, LLaMA, Claude, Gemini
T5: Text-to-Text Transfer Transformer
translate English to German: The cat
Output: Die Katze
summarize: [texto largo]
Output: [resumen]
question: ... context: ...
Output: [respuesta]
sentiment: Great movie!
Output: positive
LLMs: Large Language Models
Widget: visualizador de atención en traducción
🧪 Visualizador de Atención en Traducción
Transformers más allá del NLP
Vision Transformers (ViT)
Modelo Año Innovación clave Resultado
ViT 2020
Parches → Transformer puro
Competitivo con CNNs (necesita mucho dato)
DeiT 2021
Data-efficient: distillation token
ViT entrenado solo con ImageNet
Swin 2021
Ventanas desplazadas (shifted windows)
Complejidad lineal; backbone universal
DINO / DINOv2 2021/23
Self-supervised ViT con self-distillation
Features visuales universales sin etiquetas
SAM 2023
ViT para segmentación universal (Meta)
Segmentar cualquier objeto con un click
📐 ViT vs CNN: ¿qué ventajas tiene?
Transformers para audio: Whisper
🔊 ¿Cómo procesa el audio?
Transformers para series temporales
Modelo Año Innovación
Informer 2020
ProbSparse attention: reduce complejidad a \(O(n \log n)\). Self-attention
solo calcula los queries más informativos.
Autoformer 2021
Auto-Correlation mechanism: reemplaza attention por correlación de series
+ descomposición estacional-tendencia.
PatchTST 2023
Parches temporales (como ViT): agrupa timestamps en parches y los trata
como tokens. Channel-independence para multivariable.
iTransformer 2023
Invierte la atención: aplica attention sobre las variables
(canales) en lugar de timestamps.
TimesFM 2024
Foundation model de Google: pre-entrenado en 100B+ timesteps.
Zero-shot forecasting.
⏰ ¿Y las RNNs? ¿Están muertas?
DiT: Diffusion Transformer
Transformers multimodales
Modelo Modalidades Método
CLIP (2021)
Imagen + Texto
Contrastive: alinea embeddings de ViT y Transformer de texto en un espacio
compartido.
Flamingo (2022)
Imagen + Texto
Perceiver Resampler + gated cross-attention para inyectar features visuales
en un LLM congelado.
GPT-4V / Gemini (2023)
Imagen + Texto + Audio
Tokenización nativa de imagen y texto en un único decoder.
ImageBind (2023)
6 modalidades
Alineación contrastiva de imagen, texto, audio, depth, thermal, IMU.
Papers y recursos fundamentales
Paper Año Contribución Link
Attention Is All You Need 2017
Transformer original
arXiv
BERT 2018
Pre-training bidireccional
arXiv
GPT-2 2019
Language models are unsupervised multitask learners
PDF
T5 2019
Text-to-text framework unificado
arXiv
ViT 2020
An Image is Worth 16×16 Words
arXiv
Whisper 2022
Robust speech recognition
arXiv
DiT 2023
Scalable Diffusion with Transformers
arXiv
Flash Attention 2 2023
Atención eficiente IO-aware
arXiv
📚 Recursos adicionales