21

Atención

¿Cómo decide una palabra a cuáles otras mirar?

12 min

Atención

Con voz. Puedes pausar y saltar a cualquier paso.

Lección completa

Subtítulos

Lee esta frase: «Me senté en el banco de mi jardín». Aquí, un banco es un asiento.

Voz

Sin voz: la lección avanza sola con subtítulos.

La voz de esta lección está en preparación: por ahora avanza sola con subtítulos.

0:00 / 0:00

Qué lograrás

  • Explicar por qué un token necesita mirar a los demás para tener sentido, y cómo la atención decide a quién mirar y cuánto.
  • Calcular a mano, con vectores de dos números, un producto punto, los pesos de atención con softmax y la mezcla de valores.
  • Reconocer qué resume la fórmula softmax(QKᵀ/√d)V, para qué sirven varias cabezas y qué ganan y cuestan todos los tokens a la vez.

Ideas clave

  1. Una palabra puede significar cosas distintas («banco»), pero entra al modelo con un solo vector. Para entenderla hay que mirar las demás: eso hace la atención.
  2. Cada token produce una consulta (lo que busca), una clave (cómo se deja encontrar) y un valor (lo que entrega). El producto punto dice qué tanto coinciden una consulta y una clave.
  3. Softmax convierte las puntuaciones en pesos de atención que suman cien por ciento, y el nuevo vector de cada token es la mezcla de los valores con esos pesos.
  4. Todo se resume en softmax(QKᵀ/√d)·V. Se divide entre √d para que, con vectores largos, softmax no le dé todo el peso a un solo token.
  5. Varias cabezas miran cosas distintas a la vez, y todos los tokens se calculan al mismo tiempo: rápido en una GPU, pero el costo crece con el cuadrado de la longitud del texto.

Lo que mucha gente cree

Creencia común Un mapa de atención muestra por qué el modelo respondió lo que respondió: lo que «miró» es su razón.

Lo correcto Muestra cómo reparte sus pesos una cabeza, en una capa. Es una pista de por dónde fluye la información, pero la respuesta depende de muchas cabezas y capas, y pesos muy distintos pueden dar la misma respuesta. Si eso cuenta como explicación sigue en debate.

Compruébalo

Cuatro preguntas. Sin nota ni cuenta: sirven para ver si la idea quedó.

ConceptoEn la atención, ¿qué papel cumple la clave de un token?
CálculoLa consulta de un token es (2, 1) y la clave de otro es (1, 3). ¿Cuál es su producto punto?
Concepto¿Para qué se divide entre √d antes del softmax?
AplicaciónUn equipo pasa de enviar textos de 4 000 a 8 000 tokens sin cambiar nada más. ¿Qué esperar del número de comparaciones de atención?

Por dentroPara quien quiere las fórmulas y el código. No hace falta para seguir el curso.

La fórmula

Para nn tokens, se apilan sus consultas y claves (de dkd_k números) y sus valores (de dvd_v) en matrices Q,K∈Rn×dkQ,K\in\mathbb{R}^{n\times d_k} y V∈Rn×dvV\in\mathbb{R}^{n\times d_v}:

Atencioˊn(Q,K,V)=softmax ⁣(QK⊤dk)V\text{Atención}(Q,K,V)=\text{softmax}\!\left(\frac{QK^{\top}}{\sqrt{d_k}}\right)V

Fila por fila: la puntuación del token ii sobre el jj es sij=qi⋅kj/dks_{ij}=q_i\cdot k_j/\sqrt{d_k}; el peso es αij=esij/∑lesil\alpha_{ij}=e^{s_{ij}}/\sum_l e^{s_{il}} (cada fila suma 1) y la salida es oi=∑jαij vjo_i=\sum_j\alpha_{ij}\,v_j. Es la «atención de producto punto escalado» de Vaswani et al. (2017), que la describen como una función que lleva una consulta y un conjunto de pares clave-valor a una salida: una suma ponderada de los valores.

En la escena, la consulta de «banco» es q=(1,1)q=(1,1) y la clave de «jardín» k=(1,0.8)k=(1,0.8): q⋅k=1⋅1+1⋅0.8=1.8q\cdot k=1\cdot1+1\cdot0.8=1.8. Las puntuaciones 1.8, 0.7, 0, −1.61.8,\,0.7,\,0,\,-1.6 dan e1.8≈6.0e^{1.8}\approx6.0, e0.7≈2.0e^{0.7}\approx2.0, e0=1e^{0}=1 y e−1.6≈0.2e^{-1.6}\approx0.2; la suma es ≈10\approx10, y los pesos, 60 %60\,\%, 20 %20\,\%, 10 %10\,\% y 2 %2\,\%. La escena no divide entre 2\sqrt{2} para que las cuentas se sigan de cabeza; con la división, los pesos serían 46 %46\,\%, 21 %21\,\%, 13 %13\,\% y 4 %4\,\%, y el vector nuevo, (0.63, 0.06)(0.63,\,0.06) en vez de (0.75, 0.05)(0.75,\,0.05): el mismo lado, menos marcado. Todos los números del ejemplo están escritos a mano (ver data.ts).

De dónde salen Q, K y V

Si XX es la matriz de vectores de entrada (embeddings; en un transformer, más la posición), Q=XWQQ=XW^Q, K=XWKK=XW^K y V=XWVV=XW^V. Esas tres matrices son los parámetros que se aprenden; los pesos αij\alpha_{ij} no: se calculan de nuevo para cada frase. En la autoatención, QQ, KK y VV salen de la misma XX. En la atención de Bahdanau et al. (2014), para traducir, las consultas venían del decodificador y las claves y valores del codificador, y la puntuación era una red pequeña, eij=a(si−1,hj)e_{ij}=a(s_{i-1},h_j), en lugar de un producto punto; los pesos eran el mismo softmax y el resultado, la misma suma ponderada ci=∑jαijhjc_i=\sum_j\alpha_{ij}h_j. Los autores lo dicen así: el decodificador decide a qué partes de la frase de origen prestar atención.

Por qué dk\sqrt{d_k}

Si las componentes de qq y kk son independientes, con media 0 y varianza 1, el producto q⋅k=∑i=1dkqikiq\cdot k=\sum_{i=1}^{d_k}q_ik_i tiene media 0 y varianza dkd_k (nota 4 de Vaswani et al., 2017): su tamaño típico es dk\sqrt{d_k}, ocho con dk=64d_k=64. Dividir entre dk\sqrt{d_k} devuelve la varianza a 1. Sin ello, el softmax se satura: si α=softmax(s)\alpha=\text{softmax}(s), entonces ∂αi/∂sj=αi(δij−αj)\partial\alpha_i/\partial s_j=\alpha_i(\delta_{ij}-\alpha_j), que tiende a 0 cuando un peso se acerca a 1 y los demás a 0. Es la «pendiente casi nula» de la narración. Con ocho claves al azar y dk=64d_k=64, el peso mayor promedia 0.890.89 sin dividir y 0.360.36 dividiendo (100 sorteos; el sorteo de la escena da 0.980.98 y 0.350.35).

Varias cabezas

MultiHead(Q,K,V)=Concat(cabeza1,…,cabezah) WO,cabezai=Atencioˊn(XWiQ, XWiK, XWiV)\text{MultiHead}(Q,K,V)=\text{Concat}(\text{cabeza}_1,\dots,\text{cabeza}_h)\,W^O,\qquad \text{cabeza}_i=\text{Atención}(XW_i^Q,\,XW_i^K,\,XW_i^V)

Según Vaswani et al. (2017), varias cabezas permiten atender a la vez a información de distintos «subespacios» de representación; con una sola, el promedio lo impide. En su modelo base, h=8h=8 y dk=dv=512/8=64d_k=d_v=512/8=64, así que ocho cabezas cuestan casi lo mismo que una grande. BERT base tiene 12 capas de 12 cabezas (144) con 768 dimensiones, es decir, 768/12=64768/12=64 por cabeza (Devlin et al., 2018).

Los mapas reales de la lección

Se calcularon con real-attention.py (en la carpeta de la lección; corre en CPU): bert-base-uncased para el inglés y bert-base-multilingual-cased para el español, ambos con licencia Apache-2.0 y revisión fijada. Son los pesos de una sola cabeza tras softmax, sin promediar; las piezas de una palabra se suman. La cabeza de «banco» se eligió así: entre las cabezas en las que el mayor peso de «banco» cae en la palabra que decide su sentido en las dos frases (jardín y dinero), la de mayor suma de esos dos pesos. Solo 4 de 144 cumplen esa condición en español (capa 1, cabeza 4: 44 % y 65 %) y 6 de 144 en inglés (capa 3, cabeza 11: 93 % y 60 %). En cambio, el mayor peso de «banco» cae en una marca ([CLS], [SEP] o el punto) en 58 de 144 cabezas en español y en 113 de 144 en inglés. Las otras tres son las de mayor peso promedio en la palabra anterior, en la siguiente y en las marcas. Clark et al. (2019) describen estos mismos tipos de cabeza en BERT: las que miran a los delimitadores, las que miran a una posición fija y las que miran a toda la frase.

Costo

La matriz de pesos es n×nn\times n: el cómputo por capa crece como O(n2⋅d)O(n^2\cdot d). Una capa recurrente cuesta O(n⋅d2)O(n\cdot d^2), pero necesita O(n)O(n) pasos uno tras otro, y la información entre dos posiciones recorre un camino de longitud O(n)O(n); en la autoatención, ambos son O(1)O(1) (tabla 1 de Vaswani et al., 2017). Por eso la atención aprovecha bien una GPU, y por eso los contextos largos son caros.

En código

import numpy as np

def softmax(x):
    e = np.exp(x - x.max(axis=-1, keepdims=True))
    return e / e.sum(axis=-1, keepdims=True)

def attention(Q, K, V, scale=True):
    d = K.shape[-1]
    scores = Q @ K.T                     # productos punto: qué tanto coincide cada consulta con cada clave
    if scale:
        scores = scores / np.sqrt(d)     # la pieza que la escena deja para el final
    weights = softmax(scores)            # cada fila suma 1
    return weights @ V, weights          # mezcla ponderada de los valores

# «Me senté en el banco de mi jardín»: los números de la escena (d = 2)
K = np.array([[-1.6, 0], [-0.3, 1], [-0.8, -0.8], [-0.8, -0.8],
              [0.5, -0.5], [-0.8, -0.8], [-0.8, -0.8], [1, 0.8]])
V = np.array([[0, 0], [0.5, 0], [0, 0], [0, 0],
              [0.5, 0.5], [0, 0], [0, 0], [1, 0]])     # (asiento, dinero)
q = np.array([[1.0, 1.0]])                          # la consulta de «banco»

out, w = attention(q, K, V, scale=False)
print(np.round(w, 2), np.round(out, 2))
out, w = attention(q, K, V)                          # con la división entre √2
print(np.round(w, 2), np.round(out, 2))
# [[0.02 0.2  0.02 0.02 0.1  0.02 0.02 0.6 ]] [[0.75 0.05]]
# [[0.04 0.21 0.04 0.04 0.13 0.04 0.04 0.46]] [[0.63 0.06]]

Y la comprobación de d\sqrt{d}: la desviación típica de q⋅kq\cdot k crece como d\sqrt{d}; al dividir, queda en 1.

rng = np.random.default_rng(0)
for d in (1, 2, 64, 512):
    q = rng.standard_normal((20000, d))
    k = rng.standard_normal((20000, d))
    s = (q * k).sum(axis=1)                         # 20 000 productos punto q·k
    print(d, round(s.std(), 1), round((s / np.sqrt(d)).std(), 2))
# 1 1.0 1.0
# 2 1.4 1.0
# 64 8.0 1.0
# 512 22.6 1.0

Lo que viene

Falta lo que la atención sola no hace: no sabe el orden de las palabras (si se barajan, cada token reparte lo mismo entre los mismos tokens), y al escribir texto cada token solo puede mirar hacia atrás (antes del softmax se pone −∞-\infty en las puntuaciones que miran al futuro). Con una codificación de posición, esa máscara, una red por token, conexiones residuales y normalización, se apilan bloques: el transformer, en la lección 22.

Lente de decisión

Qué significa esta idea si tu trabajo es decidir, no programar.

  • Cada token se compara con todos los demás: el costo de la atención crece con el cuadrado del contexto. Duplicar el texto cuesta unas cuatro veces más comparaciones; es una de las razones por las que los contextos largos son caros (lecciones 26 y 30).
  • Un mapa de atención no es una explicación. Si un proveedor muestra «a qué miró el modelo» como justificación de una respuesta, pide además pruebas con casos tuyos y medidas de calidad (lecciones 33 y 34).
  • Todo lo que está en el contexto puede influir en todo lo demás: la arquitectura no distingue por sí sola entre una instrucción tuya y un documento ajeno. De ahí el riesgo de inyección de instrucciones: separa y valida lo que entra (lección 35).
  • Más contexto no garantiza más comprensión: con más tokens, la atención se reparte entre más candidatos. Prueba con tus documentos largos y no supongas que el modelo usa todo por igual (lección 30).

Términos de la lección

Transcripción completa
ComentariosConversación pública de esta lección: dudas, datos por revisar, ideas.

Los comentarios viven en GitHub Discussions, en el repositorio público del curso; para escribir se necesita una cuenta de GitHub. Se cargan desde giscus.app al abrir esta sección. Las personas que revisan el curso los leen, y si un cambio se acepta, se aplica en los dos idiomas. Abrir el hilo en GitHub

Fuentes