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:
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.
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}}}\).
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.
🔄 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 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 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:
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.
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:
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.).
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