Desde que Watson me dejó con la boca abierta hace años, he estado intentando seguir de cerca el Procesamiento de Lenguaje Natural (NLP). Últimamente, el estándar era usar Redes Neuronales Recurrentes (RNN) o LSTMs. El problema es que procesan el texto como los humanos: palabra por palabra, de izquierda a derecha. Si tienes una frase larga, la red "olvida" el principio cuando llega al final, y lo peor: no puedes paralelizar el cálculo en las GPUs. Tienes que esperar a procesar la palabra 1 para procesar la 2.

A principios de verano, un grupo de investigadores de Google Brain publicó un paper con un título chulesco: "Attention Is All You Need". Lo imprimí, lo he estado leyendo a ratos durante un mes, y creo que por fin he entendido la genialidad matemática que propone: la arquitectura Transformer.

La magia del Self-Attention

Los autores proponen tirar a la basura las redes recurrentes. En su lugar, el Transformer inyecta toda la frase a la vez en la red neuronal. Para mantener el contexto, utilizan un mecanismo llamado "Autoatención" (Self-Attention).

La idea es que cada palabra de la frase mira simultáneamente al resto de palabras y calcula matemáticamente qué tanta "atención" debe prestarles para entender su propio significado en ese contexto específico. En la frase "El banco del parque está roto", la palabra "banco" prestará mucha atención a "parque" y "roto", descartando el significado financiero.

Técnicamente, esto se resuelve con pura multiplicación de matrices algebraicas. A cada palabra se le asignan tres vectores: Query (Q), Key (K) y Value (V).

La fórmula del paper, que ya me sé de memoria, es: $$ Attention(Q, K, V) = softmax(\frac{QK^T}{\sqrt{d_k}})V $$

En pseudocódigo de Python (usando Numpy), la base conceptual de una capa de atención se vería así:

import numpy as np

def self_attention(Q, K, V, d_k):
    # 1. Multiplicamos Queries por Keys transpuestas para ver la "afinidad"
    scores = np.matmul(Q, K.transpose())

    # 2. Escalamos para evitar que los gradientes exploten
    scores = scores / np.sqrt(d_k)

    # 3. Aplicamos Softmax para convertir los scores en probabilidades (pesos de 0 a 1)
    pesos = softmax(scores)

    # 4. Multiplicamos por los Values para obtener el vector contextualizado final
    return np.matmul(pesos, V)

Reflexión: El fin de las secuencias

Esta simple multiplicación matricial lo cambia todo. Como no hay que procesar palabra por palabra, podemos meter matrices gigantescas en esas GPUs que usábamos para visión artificial y entrenar modelos masivos en una fracción del tiempo.

El paper lo usa para traducción automática, pero las implicaciones dan vértigo. Si podemos paralelizar el entrenamiento del lenguaje, los laboratorios con mucho presupuesto van a empezar a entrenar modelos con miles de millones de parámetros chupándose medio internet. Acabamos de romper el cuello de botella del NLP.