Clase 11: Transformers
Ventajas
Desventajas
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
Objetivo
Tomar información de entrada y generar una salida fijándose sólo en tokens pasados.
Forward Pass en el Decoder
Supongamos que tenemos la siguiente frase:
Me gusta la pizza de Pepperoni
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.
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}}\).
Cada token está representado por un Embedding de \(d_{model}\) dimensiones.
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á”.
Corresponden a una manera en la que se pueden generar un vector único que representa el orden en el que aparece cada token.
\[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)\]
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\).
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.
\[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.
Self-Attention
dan pie al Multihead Attention
.Self-Attention
tiene la capacidad de acceder a toda la secuencia, por ende modelar relaciones a larga distancia.Causal Self-Attention
, una variante que se utiliza en el Decoder sólo puede ver la relación con tokens pasados.Multihead Attention
es paralelizable y no secuencial como las RNN.\[ \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.
Dimensiones de Q,K y V
:::
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.
\[\frac{Q \cdot K^T}{\sqrt{d_k}}\]
Similaridad
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.
Attention Weights
\[Attention(Q,K,V) = Softmax\left(\frac{Q \cdot K^T}{\sqrt{d_k}}\right) V\]
OJO
¿Y, estamos seguros que las atenciones/relaciones obtenidas por este algoritmos son (las) únicas/más correctas?
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.
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.
Dimensiones de Q, K y V
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} \]
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} \]
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).
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\]
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\).
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
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 Layers
👀 Ojito
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.
\[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.
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
\[Attention(Q_{decoder},K_{encoder},V_{encoder}) = Softmax\left(\frac{Q_{decoder} \cdot K_{encoder}^T + Mask}{\sqrt{d_k}}\right) V_{encoder}\]
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.