Transformers
La arquitectura de los modelos de lenguaje: self-attention, multi-head, codificación posicional y bloques residuales, desde NumPy hasta un mini-GPT entrenado con el texto de este portal.
01Qué es
El Transformer es una arquitectura de red neuronal para secuencias presentada en 2017 en «Attention Is All You Need». Sustituye la recurrencia de las RNN por atención: cada elemento de la secuencia mira directamente a todos los demás y decide cuánto le importa cada uno. Así se procesa toda la secuencia en paralelo.
Es la base de BERT, GPT, Claude, Llama y de los modelos de visión y audio más recientes. Un LLM es, en esencia, una pila de bloques Transformer entrenada para predecir el siguiente token.
02Intuición
En la frase «el gato no cruzó la calle porque estaba cansado», para entender «cansado» hay que saber que se refiere al gato. La atención lo resuelve como una búsqueda: cada palabra lanza una pregunta (Query), cada palabra ofrece una etiqueta (Key) y un contenido (Value). Donde la pregunta encaja con la etiqueta, se toma más contenido.
Varias cabezas hacen búsquedas distintas en paralelo (una puede seguir la sintaxis, otra la posición, otra las referencias), y las capas se apilan para construir representaciones cada vez más abstractas.
Sigue las cuentas en el visualizador de atención y recorre las 13 etapas de un modelo completo en el Transformer Visualizer.
03Fundamento matemático
Atención de producto escalar escalado
M es la máscara causal (−∞ por encima de la diagonal) en los modelos que generan texto. Dividir por evita que los productos escalares crezcan con la dimensión y saturen el softmax.
Multi-head
Bloque (variante pre-norm, la de GPT-2 en adelante)
El coste de la atención es en la longitud T de la secuencia: duplicar el contexto cuadruplica el cálculo de la atención. Por eso el tamaño de contexto es un recurso caro.
04Algoritmo paso a paso
- Tokeniza el texto y convierte cada token en un vector (embedding).
- Suma a cada vector información de su posición.
- Pasa la secuencia por N bloques: atención multi-cabeza y red feed-forward, cada una con normalización y conexión residual.
- Proyecta el vector de cada posición sobre el vocabulario para obtener logits.
- Entrena con entropía cruzada para que cada posición prediga el token siguiente.
- Para generar: elige un token de la distribución, añádelo y repite.
05Pseudocódigo
función TRANSFORMER(ids):
X ← Embedding(ids) + Posición(0 … T−1)
para cada bloque:
H ← LayerNorm(X)
X ← X + Concat(ATENCIÓN(H·W_Qᵢ, H·W_Kᵢ, H·W_Vᵢ) para cada cabeza i)·W_O
X ← X + FFN(LayerNorm(X))
devolver LayerNorm(X)·W_vocab # logits
función ATENCIÓN(Q, K, V):
S ← Q·Kᵀ / √d_k ; S[j > i] ← −∞
devolver softmax(S)·V06Implementación desde cero en Python
import numpy as np
def self_attention(X, Wq, Wk, Wv):
Q, K, V = X @ Wq, X @ Wk, X @ Wv
S = Q @ K.T / np.sqrt(K.shape[1]) # puntuaciones escaladas
S = np.where(np.tril(np.ones_like(S)) == 1, S, -np.inf) # máscara causal
A = np.exp(S - S.max(1, keepdims=True))
A /= A.sum(1, keepdims=True) # softmax por filas
return A @ V, A
# Coincide con torch.nn.functional.scaled_dot_product_attention(..., is_causal=True)Con una secuencia de 4 elementos y d = 8, cada fila de la matriz de atención suma 1, la parte por encima de la diagonal es exactamente 0, y la salida coincide con scaled_dot_product_attention de PyTorch (comprobado con np.allclose).
07Implementación con PyTorch: un mini-GPT
import torch, torch.nn as nn
class Bloque(nn.Module):
def __init__(s):
super().__init__()
s.ln1, s.ln2 = nn.LayerNorm(D), nn.LayerNorm(D)
s.attn = nn.MultiheadAttention(D, H, dropout=0.2, batch_first=True)
s.ffn = nn.Sequential(nn.Linear(D, 4 * D), nn.GELU(), nn.Linear(4 * D, D), nn.Dropout(0.2))
s.register_buffer("mask", torch.triu(torch.ones(T, T, dtype=torch.bool), 1))
def forward(s, x):
t = x.shape[1]
h = s.ln1(x)
x = x + s.attn(h, h, h, attn_mask=s.mask[:t, :t], need_weights=False)[0] # residual
return x + s.ffn(s.ln2(x)) # residual
class MiniGPT(nn.Module):
def __init__(s):
super().__init__()
s.tok, s.pos = nn.Embedding(V, D), nn.Embedding(T, D) # tokens + posiciones aprendidas
s.bloques = nn.Sequential(*[Bloque() for _ in range(L)])
s.ln, s.head = nn.LayerNorm(D), nn.Linear(D, V, bias=False)
def forward(s, idx):
x = s.tok(idx) + s.pos(torch.arange(idx.shape[1]))
return s.head(s.ln(s.bloques(x))) # logits: [B, T, V]Es la misma estructura que GPT-2 a escala mínima: 2 bloques, dmodel = 96, 4 cabezas, contexto de 64 caracteres y 248.064 parámetros. Para modelos reales se usan las clases de Hugging Face transformers.
08Dataset de ejemplo
El modelo se entrena con el propio texto de este portal: las definiciones de los temas, las descripciones de laboratorios y proyectos y los párrafos de los artículos, un total de 19.612 caracteres en castellano con un vocabulario de 94 caracteres distintos. El 90 % se usa para entrenar y el 10 % final para validar. Es un corpus minúsculo (GPT-3 se entrenó con cientos de miles de millones de tokens), elegido a propósito para que se vea lo que ocurre cuando faltan datos.
09Entrenamiento
opt = torch.optim.AdamW(modelo.parameters(), lr=2e-3, weight_decay=0.1)
mejor = float("inf")
for paso in range(1, 1201):
x, y = lote(entrenamiento) # y es x desplazado una posición
perdida = F.cross_entropy(modelo(x).view(-1, V), y.view(-1))
opt.zero_grad(); perdida.backward(); opt.step()
if paso % 100 == 0:
v = perdida_validacion()
if v < mejor: # early stopping: guarda el mejor modelo
mejor, estado = v, copia_de(modelo.state_dict())Una primera prueba con un modelo mayor (3 bloques, d = 128, 632.960 parámetros, sin dropout) sobre un corpus sin limpiar de 26.490 caracteres lo mostró de forma extrema: la validación llegó a su mínimo (2,81) en el paso 250 y acabó en 5,13 al paso 2.000, con la pérdida de entrenamiento en 0,22. Demasiados parámetros para tan pocos datos.
10Evaluación
@torch.no_grad()
def generar(modelo, idx, n, temperatura=0.8):
for _ in range(n):
logits = modelo(idx[:, -T:])[:, -1] / temperatura
siguiente = torch.multinomial(F.softmax(logits, -1), 1)
idx = torch.cat([idx, siguiente], 1)
return idxTexto generado a partir de «El modelo » con temperatura 0,8, tal cual lo produjo el modelo:
El modelo con dista. / Elos pícioner y el de suprobusta de con error un con en allos emprendizar decer la conticación de contrones de la pantacionos proproductos sobre paci
Con 20.000 caracteres ha aprendido la ortografía del castellano, palabras frecuentes («con», «de», «error», «sobre»), terminaciones como «-ación» y la puntuación, pero no significado. Es el primer peldaño de lo que hace un LLM: la misma arquitectura, con miles de veces más parámetros y millones de veces más texto, produce frases coherentes.
11Métricas
Un modelo que no sabe nada reparte la probabilidad por igual entre los 94 caracteres: pérdida , perplejidad 94. Al empezar, el modelo mide 4,70, cerca de ese valor teórico. El mejor modelo llega a 2,114 en validación, una perplejidad de 8,3: en cada posición duda, en promedio, como si eligiera entre unos 8 caracteres en lugar de 94.
12Visualización
vocab, ctx, d, capas = 50257, 1024, 768, 12 # GPT-2 small
por_capa = (2*d # LayerNorm 1
+ d*3*d + 3*d # proyección Q, K, V
+ d*d + d # proyección de salida de la atención
+ 2*d # LayerNorm 2
+ d*4*d + 4*d # FFN: expansión
+ 4*d*d + d) # FFN: compresión
total = vocab*d + ctx*d + capas*por_capa + 2*d
print(por_capa, total) # 7087872 12443980813Ventajas
- Procesa toda la secuencia en paralelo: entrenamiento mucho más rápido que las RNN en GPU.
- Conecta directamente elementos lejanos de la secuencia.
- Escala de forma predecible: más datos, parámetros y cálculo dan mejores modelos.
- Sirve para texto, imágenes (ViT), audio, código, proteínas y combinaciones multimodales.
14Limitaciones
- Coste cuadrático de la atención con la longitud del contexto.
- Necesita enormes cantidades de datos y cálculo para dar lo mejor.
- Sin información de posición no distingue el orden: hay que añadirla.
- Los modelos grandes son caros de servir y difíciles de interpretar.
15Hiperparámetros
| Parámetro | Mini-GPT (este artículo) | GPT-2 small | Efecto |
|---|---|---|---|
d_model | 96 | 768 | Dimensión de los vectores de cada token. |
n_layers | 2 | 12 | Número de bloques apilados. |
n_heads | 4 | 12 | Cabezas de atención; d_model se reparte entre ellas. |
d_ff | 384 | 3.072 | Ancho de la red feed-forward (normalmente 4 · d_model). |
context | 64 caracteres | 1.024 tokens | Longitud máxima de la secuencia. |
vocab | 94 caracteres | 50.257 tokens BPE | Tamaño del vocabulario. |
dropout | 0.2 | 0.1 | Regularización durante el entrenamiento. |
lr / weight_decay | 2e-3 / 0.1 | — | Optimizador AdamW. |
16Casos de uso
- Modelos de lenguaje y asistentes conversacionales.
- Traducción automática y resumen.
- Búsqueda semántica y RAG con embeddings (BERT y derivados).
- Visión (ViT), voz (Whisper) y generación de código.
17Aplicaciones industriales
- Asistentes sobre documentación técnica con RAG: manuales, normativa, históricos de incidencias.
- Clasificación y extracción de información de tickets, correos y partes de trabajo.
- Series temporales: modelos fundacionales de previsión basados en Transformers.
- Agentes que consultan sistemas internos y ejecutan tareas.
18Ejercicio práctico
Varía el número de cabezas y elimina la codificación posicional para ver qué aporta cada pieza.
# Ejercicio: mide el efecto del número de cabezas y de la codificación posicional.
# 1) Entrena con H = 1, 2, 4 y 8 (D = 96 se reparte entre las cabezas) y compara la mejor pérdida de validación.
# 2) Elimina s.pos del forward (sin información de posición). ¿Qué pasa con la pérdida? ¿Y con el texto generado?19Código completo
La atención en NumPy, el mini-GPT completo con su entrenamiento y generación, y el recuento de GPT-2:
El entrenamiento tarda unos 2 minutos en CPU; en Colab con GPU, segundos.
20Referencias y papers
- A. Vaswani et al. (2017). «Attention Is All You Need». NeurIPS 2017. Ficha en la biblioteca.
- D. Bahdanau, K. Cho, Y. Bengio (2015). «Neural Machine Translation by Jointly Learning to Align and Translate». ICLR 2015.
- J. L. Ba, J. R. Kiros, G. E. Hinton (2016). «Layer Normalization». arXiv:1607.06450.
- A. Radford, J. Wu, R. Child, D. Luan, D. Amodei, I. Sutskever (2019). «Language Models are Unsupervised Multitask Learners». Informe técnico de OpenAI (GPT-2).
- J. Devlin, M.-W. Chang, K. Lee, K. Toutanova (2019). «BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding». NAACL 2019. Ficha en la biblioteca.
- A. Karpathy. nanoGPT (repositorio de código). github.com/karpathy/nanoGPT.