TICS-579-Deep Learning

Clase 11: Transformers

Alfonso Tobar-Arancibia

Transformers (Attention is all you need, 2017)

Transformers
Corresponden a la arquitectura más moderna diseñada al día de hoy. Está basado en mecanismos de atención y posee hasta 4 tipos de atención distintos.

Ventajas

  • No tiene problemas de “memoria” para modelar dependencias de largo plazo.
  • Permite procesamiento en paralelo.
  • Su bajísimo inductive bias le permite adaptarse a distintos dominios.
  • No es necesario utilizar el transformer completo para un problema en específico.

Desventajas

  • Apto sólo para datos secuenciales.
  • Las secuencias deben de ser del mismo largo.
  • Alta demanda de recursos computacionales GPU y/o TPUs para entrenamiento distribuido.
  • Data hungry.
  • Limitaciones de secuencias muy largas por restricciones de memoria computacional.

Encoder

Objetivo

Codificar y comprimir información en Logits que puedan ser usados para clasificar o para ser utilizados por un Decoder.

Forward Pass en el Encoder

  • El embedding asociado a una secuencia se bifurca en 4 ramas:
    • Residual Connection
    • Query
    • Key
    • Value
  • Query, Key y Value ingresan al Multihead Attention.
  • La salida del Multihead Attention + el Residual Connection pasan por un LayerNorm.
  • Nueva bifurcación.
    • Una parte entra a un MLP
    • Otra va como skip connection.
  • La salida del MLP + la Residual Connection pasan por un segundo LayerNorm para generar la salida del Encoder.

Decoder

Objetivo

Tomar información de entrada y generar una salida fijándose sólo en tokens pasados.

Forward Pass en el Decoder

  • El embedding asociado a una secuencia se bifurca en 4 ramas:
    • Residual Connection
    • Query
    • Key
    • Value
  • Query, Key y Value ingresan al Masked (Causal) Multihead Attention.
  • La salida del Masked Multihead Attention + el Residual Connection pasan por LayerNorm.
  • Se pasa por un Cross Attention (esto podría ser opcional).
    • Key y Value provienen del Encoder como contexto.
    • La salida del Causal Multihead Attention se utiliza como Query.
  • Nueva bifurcación.
    • Una parte entra a un MLP
    • Otra va como skip connection.
  • La salida del MLP + la Residual Connection pasan por LayerNorm para generar el Output.

Ejemplo

Supongamos que tenemos la siguiente frase:

Me gusta la pizza de Pepperoni

Tokenización

Aplicaremos un proceso de Tokenización simple, donde cada palabra es un Token.

Ya sabemos que esto no tiene por qué ser así. De hecho cada modelo tiene su propio tipo de tokenización, e incluso se pueden entrenar Tokenizaciones nuevas. Más información al respecto pueden encontrarla acá.

La documentación de Tokenizers de HuggingFace la traduje yo, así que si encuentran algo me dicen para corregir.

Luego la secuencia tokenizada de largo \(L=6\) será:

[105,6587,5475,301,708,358]

Recordar que dependiendo del modelo se pueden agregar al inicio o al final tokens especiales. Hablaremos de eso más adelante.

Embedding

Embedding
No es más que una Lookup Table. Es una tabla de parámetros entrenables que permitirá transformar índices enteros en vectores de una dimensión determinada.
nn.Embedding(num_embeddings, embedding_dim)

Esta clase permite el ingreso de tensores de cualquier tamaño \((*)\) y devuelve tensores de tamaño \((*,H)\). Donde \(H\) es el embedding_dim.

La sección 3.1 del paper se refiere al tamaño del embedding como \(d_{model}=512\).

En la sección 3.4 del paper se menciona que los parámetros de los embeddings son multiplicados por \(\sqrt{d_{model}}\).

Embedding

Cada token está representado por un Embedding de \(d_{model}\) dimensiones.

Positional Encoder

Un potencial problema que puede tener un transformer es reconocer el orden de las frases.

No es lo mismo decir “El perro del papá mordió al niño” que “El perro del niño mordió al papá”. Las palabras usadas en ambas frases son exactamente las mismas, pero en un orden distinto implican desenlaces distintos. ¿Cómo podemos entender el concepto de orden si no tenemos recurrencia?

Incluso algunos órdenes no tienen tanto sentido lógico: “El niño del perro mordió al papá”.

Positional Encoder

Corresponden a una manera en la que se pueden generar un vector único que representa el orden en el que aparece cada token.

Positional Encoder

\[PE_{(pos,2i)} = sin\left(\frac{pos}{10000^{2i/d_{model}}}\right)\] \[PE_{(pos,2i+1)} = cos\left(\frac{pos}{10000^{2i/d_{model}}}\right)\]

  • \(pos\) corresponde a la posición del Token en la secuencia, y \(2i\) y \(2i+1\) corresponden a las posiciones pares e impares respectivamente del embedding dimension de cada token, en este caso llamado \(d_{model}\) (\(i\) comienza en 0).

Una forma más clara de ver esto es que la posición está definida por sinusoidales de periodo \(2\pi \cdot 10000^{i/d_{model}/2}\).

El positional encoder debe tener el mismo tamaño que el Embedding para que se puedan sumar.

Estabilidad numérica

Por temas de estabilidad el argumento del \(sin(\cdot)\) y \(cos(\cdot)\) se suele implementar como: \[pos \cdot exp\left[-\frac{2i}{d_{model}} log(10000)\right]\]

Regularización

La sección 5.4 menciona que se aplica Dropout posterior a sumar los Embeddings con el Positional Encoding. Se utilizo un \(P_{drop}=0.1\).

Positional Encoder: Ejemplo

Implementación Eficiente

El Positional Encoding se implementa como una matriz de tamaño \((L,d_{model})\) en la que cada fila es un embedding de \(d_{model}\) dimensiones asociado a cada token.

Importante

La suma del Embedding con el Positional Encoder codifica la información del token y su posición relativa dentro de la secuencia.

Encoder: Self-Attention

\[Attention(Q,K,V) = Softmax\left(\frac{Q \cdot K^T}{\sqrt{d_k}}\right) V\]

Ejemplo

“La sopa se cocinó en la olla y estaba rica”. Rica podría estar refiriéndose a olla o a sopa. Sabemos que se refiere a la sopa.

El Scaled Dot-Product, más conocido como Self-Attention, es el mecanismo clave en las redes neuronales modernas. Permite determinar la atención/relación que existe entre palabras de una misma secuencia.

  • Está compuesto por 3 proyecciones lineales las cuales reciben los nombres de Query (Q), Key (K) y Value (V).
  • Estás 3 proyecciones se combinan para poder determinar la atención/relación que cada Token tiene con los otros tokens de una misma secuencia.
  • Varios procesos de Self-Attention dan pie al Multihead Attention.
  • El Self-Attention tiene la capacidad de acceder a toda la secuencia, por ende modelar relaciones a larga distancia.
  • El Causal Self-Attention, una variante que se utiliza en el Decoder sólo puede ver la relación con tokens pasados.
  • Su característica más importante es que el Multihead Attention es paralelizable y no secuencial como las RNN.
  • Tiene capacidad de escalabilidad para secuencias largas.

Encoder: Self-Attention

  • Supongamos que tenemos la secuencia “Me gusta la Pizza de Pepperoni”.
  • Utilizaremos \(d_{model} = 512\) y \(h=8\).
  • El paper utiliza \(d_k=d_v=d_{model}/h=64\) para el cálculo de los Attention Weights.

\[ \begin{array}{c c} X = \left[ \begin{array}{c c c} [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ \end{array} \right] \begin{array}{c c c}Me\\gusta\\la\\pizza\\de\\pepperoni \end{array} & \end{array} \]

👀 Ojito

Esto se debe aplicar a cada secuencia. Por lo tanto se debe agregar una dimensión (como unsqueeze(0)) que contabilice el número de secuencias para \(Q\), \(K\), y \(V\).

Matrices de Proyección

Definiremos 3 matrices de Proyección. Una matriz de proyección permite llevar transportar un vector \(X\) a otro espacio (es decir, son entrenables). En este caso crearemos matrices que puedan multiplicarse con \(X\). Por lo tanto irán desde \(d_{model}\) hasta \(d_q=d_k\) y \(d_v\) respectivamente.

  • \(W_q = (d_{model}, d_k)\)
  • \(W_k = (d_{model}, d_k)\)
  • \(W_v = (d_{model}, d_v)\)

Dimensiones de Q,K y V

  • \(Q = (L, d_k)\)
  • \(K = (L, d_k)\)
  • \(V = (L, d_v)\)
  • \(L\) corresponde a largo de la secuencia (es decir, el número de Tokens)

:::

Encoder: Self-Attention

Siguiendo nuestro ejemplo: \(d_k = d_v = 64\)

Query (6,64)

\[ \begin{array}{c c} Q = \left[ \begin{array}{c c c} [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ \end{array} \right] \end{array} \]

Key (6,64)

\[ \begin{array}{c c} K = \left[ \begin{array}{c c c} [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ \end{array} \right] \end{array} \]

Value (6,64)

\[ \begin{array}{c c} V = \left[ \begin{array}{c c c} [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ \end{array} \right] \end{array} \]

👀 Ojito

Como esto se aplica a una sola secuencia, la dimensión real de estos tensores debería ser \((1,6,64)\). Ese 1 cambiará si tenemos más secuencias. Pero, todas las secuencias deben ser del mismo largo.

Encoder: Self-Attention (Scale Dot Product)

\[\frac{Q \cdot K^T}{\sqrt{d_k}}\]

Similaridad

  • \(Q \cdot K^T\) representa el producto punto entre \(Q\) (un token de referencia que está consultando la atención contra otros tokens) y \(K\) (otro token que se compara contra la “query”).

Control de Gradientes

\(\sqrt{d_k}\) es un factor que reduce la escala de los valores para el control de los gradientes. Recordar que esta matriz es de parámetros entrenables.

Attention

Dado que el rango de estos valores van de \(-\infty\) a \(\infty\), es más común aplicar una softmax para poder garantizar que la suma de las atenciones para cada palabra “query” sume 1.

Encoder: Self-Attention (Scale Dot Product)

Attention Weights

  • Esta matriz indica cuánta atención (en términos porcentuales) entrega cada palabra “query” a cada palabra “key”.
  • Esta matriz permitirá crear embeddings contextualizados, que incluyen la información de la palabra y su contexto de atención.

Encoder: Self-Attention (Scale Dot Product)

\[Attention(Q,K,V) = Softmax\left(\frac{Q \cdot K^T}{\sqrt{d_k}}\right) V\]

OJO

  • A modo de ejemplo, el elemento en Rojo representa una suma ponderada de la primera dimensión de cada proyección de tokens.
  • El resultado de cada dimensión es una combinación lineal de las dimensiones de cada token.
  • Cada fila corresponde a un embedding contextualizado que tiene información sobre el token y su contexto combinado.

¿Y, estamos seguros que las atenciones/relaciones obtenidas por este algoritmos son (las) únicas/más correctas?

Encoder: Multihead Attention

Multihead Attention

Es una extensión del Self-Attention. En lugar de calcular sólo “una atención” sobre el input, genera distintas “atenciones” en múltiples “cabezas” independientes. Cada Attention Head se encarga de aprender relaciones diferentes, lo que mejora la capacidad del modelo de captar patrones cada vez más complejos.

Normalmente se calculan entre \(h=8\) y \(h=12\) attention heads, las cuales se concatenan para luego pasar por una proyección lineal.

Encoder: Multihead Attention

Si queremos calcular \(h=8\) attention heads. Necesitamos 8 \(Q\), 8 \(K\) y 8 \(V\). Por lo tanto, necesitamos 8 matrices de proyección. ¿Cómo lo paralelizamos?

Implementación en paralelo

Podemos definir en realidad todas las matrices de manera análoga rescribiendo las matrices de proyección para \(Q\), \(K\) y \(V\) como una subdivisión de cada embedding en \(h\) cabezas.

  • Por lo tanto si \(d_k=d_v=64\) y \(h=8\) tendríamos una dimensión total de 512.
  • \(dim(Q) = dim(K) = (N,h \cdot d_k) = (6, 512)\)
  • \(dim(V) = (N, h \cdot d_v) = (6, 512)\)
  • \(dim(W_q) = (d_{model}, h, d_k)\)
  • \(dim(W_k) = (d_{model}, h, d_k)\)
  • \(dim(W_v) = (d_{model}, h, d_v)\)

Dimensiones de Q, K y V

  • \(Q = X \cdot W_q = (6,512) \cdot (512,\overbrace{8,64}^{512}) = (6,8,512)\)
  • \(K = X \cdot W_k = (6,512) \cdot (512,\overbrace{8,64}^{512}) = (6,8,512)\)
  • \(V = X \cdot W_v = (6,512) \cdot (512,\overbrace{8,64}^{512}) = (6,8,512)\)

Encoder: Multihead Attention (Independencia)

Independent Heads

Es importante mencionar que cada cabeza debe ser independiente una de otra para que se pueda paralelizar. Para ello basta con transponer las dos primeras dimensiones.

Query/Key/Value (6, 8, 64) (Previo a Transponer)

\[ \begin{array}{c c} Q/K/V = \left[ \begin{array}{c c c} \overbrace{[1,...,64]}^{Head 1}, \overbrace{[65,...,128]}^{Head 2}, ..., \overbrace{[449,...,512]}^{Head 8}\\ [1,...,64],[65,..., 128],...[449,...,512]\\ [1,...,64],[65,..., 128],...[449,...,512]\\ [1,...,64],[65,..., 128],...[449,...,512]\\ [1,...,64],[65,..., 128],...[449,...,512]\\ [1,...,64],[65,..., 128],...[449,...,512]\\ \end{array} \right] \end{array} \]

OJO

Esto permite calcular cada Head en paralelo. Este procedimiento se aplica a cada secuencia. Por lo tanto, un Multihead Attention recibe Tensores de dimensión \((N,L,h \cdot d_i)\) con \(i=k,v\).

Query/Key/Value (8,6, 64) (Luego de Transponer)

\[ \begin{array}{c c} \left[ \begin{array}{c c c} \text{Head1}\left\{ \begin{array}{c c c} \begin{bmatrix} [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ [1,...,64]\\ \end{bmatrix} \end{array} \right. \\ \vdots \\ \vdots \\ \text{Head8}\left\{ \begin{array}{c c c} \begin{bmatrix} [449,...,512]\\ [449,...,512]\\ [449,...,512]\\ [449,...,512]\\ [449,...,512]\\ [449,...,512]\\ \end{bmatrix} \end{array} \right. \\ \end{array} \right] \end{array} \]

Encoder: Multihead Attention (Concatenación)

Self-Attentions (aka Multihead Attention) (6,8,64)

\[ \begin{array}{c c} \left[ \begin{array}{c c c} \text{Head1}\left\{ \begin{array}{c c c} \begin{bmatrix} [SA_1,...,SA_{64}]\\ [SA_1,...,SA_{64}]\\ [SA_1,...,SA_{64}]\\ [SA_1,...,SA_{64}]\\ [SA_1,...,SA_{64}]\\ [SA_1,...,SA_{64}]\\ \end{bmatrix} \end{array} \right. \\ \vdots \\ \vdots \\ \text{Head8}\left\{ \begin{array}{c c c} \begin{bmatrix} [SA_{449},...,SA_{512}]\\ [SA_{449},...,SA_{512}]\\ [SA_{449},...,SA_{512}]\\ [SA_{449},...,SA_{512}]\\ [SA_{449},...,SA_{512}]\\ [SA_{449},...,SA_{512}]\\ \end{bmatrix} \end{array} \right. \\ \end{array} \right] \end{array} \]

Self-Attention Transpuesto (6,8,64)

\[ \begin{array}{c c} \left[ \begin{array}{c c c} \overbrace{[SA_1,...,SA_{64}]}^{Head 1}, \overbrace{[SA_{65},...,SA_{128}}^{Head 2}, ..., \overbrace{[SA_{449},...,SA_{512}]}^{Head 8}\\ [SA_1,...,SA_{128}],[SA_{65},..., SA_{128}],...[SA_{449},...,SA_{512}]\\ [SA_1,...,SA_{128}],[SA_{65},..., SA_{128}],...[SA_{449},...,SA_{512}]\\ [SA_1,...,SA_{128}],[SA_{65},..., SA_{128}],...[SA_{449},...,SA_{512}]\\ [SA_1,...,SA_{128}],[SA_{65},..., SA_{128}],...[SA_{449},...,SA_{512}]\\ [SA_1,...,SA_{128}],[SA_{65},..., SA_{128}],...[SA_{449},...,SA_{512}]\\ \end{array} \right] \end{array} \]

Self-Attention Concatenado (6,512)

\[ \begin{array}{c c} \left[ \begin{array}{c c c} [SA_1,.....,SA_{512}]\\ [SA_1,.....,SA_{512}]\\ [SA_1,.....,SA_{512}]\\ [SA_1,.....,SA_{512}]\\ [SA_1,.....,SA_{512}]\\ [SA_1,.....,SA_{512}]\\ \end{array} \right] \end{array} \]

Encoder: Multihead Attention (Output)

Head Mixing

Los outputs de cada cabeza ahora están uno al lado del otro. Por lo tanto, si aplicamos una capa lineal \(W^O \in \mathbb{R}^{d_v \cdot h \times d_{model}}\), estos parámetros entrenables se encargarán de aprender una combinación lineal que mezcla la información aprendida por cada Attention Head de manera óptima.

Multihead Attention Output (6,512)

\[ Multihead(Q,K,V) = \begin{array}{c c} \left[ \begin{array}{c c c} [SA_1,.....,SA_{512}]\\ [SA_1,.....,SA_{512}]\\ [SA_1,.....,SA_{512}]\\ [SA_1,.....,SA_{512}]\\ [SA_1,.....,SA_{512}]\\ [SA_1,.....,SA_{512}]\\ \end{array} \right] \end{array} \cdot W^O \]

\(W^O\) se encarga de retornar a la dimensión del Input original para poder realizar la Residual Connection (similar al downsample de la Resnet).

Encoder: Add + LayerNorm

Residual Connection (Add&Norm)

Corresponde a una conexión residual. Combina la información de entrada al Multihead y su salida para luego aplicar LayerNorm.

\[Add\&Norm = LayerNorm(X + Multihead(Q,K,V))\]

LayerNorm

El LayerNorm calcula el promedio y la varianza por token normalizando las dimensiones del embedding de cada token.

\[X_{norm} = \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}}\cdot \gamma + \beta\]

  • \(\gamma\) y \(\beta\) son parámetros entrenables.

Regularización

La sección 5.4 menciona que se aplica Dropout posterior a cada sublayer del Encoder (y el Decoder) con \(P_{drop}=0.1\).

Encoder: Feed Forward (MLP)

La sección 3.3 del paper define el bloque Feed Forward de la siguiente manera:

\[FFN(x) = max(0,x \cdot W_1+b_1)W_2 + b_2\]

Donde \(W_1 \in \mathbb{R}^{d_{model} \times {d_{ff}}}\) y \(W_2 \in \mathbb{R}^{d_{ff} \times {d_{model}}}\).

Arquitectura

  • 2 capas Feed Forward con bias.
  • Una RelU como activación intermedia.
  • De acuerdo a la sección 5.4, a la salida incluiría un Dropout con \(P_{drop}=0.1\).

Residual Connection (Add&Norm)

Al igual que en el Multihead Attention, se la salida de esta capa se une con una conexión residual y se pasa por un LayerNorm.

Encoder: Output Final

Encoder Layers

  • La combinación de todos los pasos anteriores constituyen un (1) Encoder. En el caso del paper el Transformer está compuesto de \(N=6\) Encoder Layers uno después del otro.

👀 Ojito

  • Sólo antes de la primera Encoder Layer se aplica el Input Embedding y el Positional Encoding.

Arquitectura Encoder-Decoder

En el caso de estas arquitecturas, entonces el output del Encoder sirve como Keys y Values para el proceso de Cross Attention.

Decoder: Causal Self-Attention

\[Attention(Q,K,V) = Softmax\left(\frac{Q \cdot K^T + Mask}{\sqrt{d_k}}\right) V\]


Corresponde a una variante del Self-Attention en el cuál sólo se presta atención a Tokens pasados, esto para preservar las propiedades auto-regresivas.

Decoder: Cross Attention

Opcionalmente podría utilizar una Máscara en caso de querer evitar el Look Ahead.

Cross Attention

Este mecanismo permite generar relaciones/atenciones entre dos secuencias de datos distintos. En este caso se relaciona una secuencia “query” con elementos “key” y “values” de otra secuencia. Además limita la generación del Decoder.

Dimensiones de Q, K y V

  • \(Q = X_{decoder} \cdot W_q = (F,512) \cdot (512,\overbrace{8,64}^{512}) = (6,8,512)\)
  • \(K = X_{encoder} \cdot W_k = (6,512) \cdot (512,\overbrace{8,64}^{512}) = (6,8,512)\)
  • \(V = X_{encoder} \cdot W_v = (6,512) \cdot (512,\overbrace{8,64}^{512}) = (6,8,512)\)

\[Attention(Q_{decoder},K_{encoder},V_{encoder}) = Softmax\left(\frac{Q_{decoder} \cdot K_{encoder}^T + Mask}{\sqrt{d_k}}\right) V_{encoder}\]

Prediction Head

Arquitectura

Corresponde a una capa Feed Forward que proyecta desde \(d_{model}\) hasta \(vocab\_size\) seguida de una Softmax.

¿Por qué es necesaria?

Por que la salida del Decoder tiene dimensiones \((N, L, d_{model})\). Es decir, tenemos \(N\) secuencias de largo \(L\), donde cada token está representado como un embedding de \(d_{model}\) dimensiones, lo cuál no es interpretable por humanos.

Esta capa tiene el objetivo de estimar la probabilidad de que ocurra el siguiente token, de este modo predecir de manera autoregresiva.

Gracias Totales