Before you go, check out these stories!

0
Hackernoon logo'El transformador ilustrado' una traducción al español by@espejelomar

'El transformador ilustrado' una traducción al español

Author profile picture

@espejelomarOmar U. Espejel

Llevemos la IA a América Latina!! TSC.ai y Escuela Latinoamericana de IA (ELIA)

El siguiento texto es una traducción del texto "The Illustrated Transformer" de Jay Alammar. Vamos a democratizar el conocimiento de deep learning.

En la publicación anterior, analizamos la atención, un método omnipresente en los modelos modernos de aprendizaje profundo. La atención es un concepto que ayudó a mejorar el rendimiento de las aplicaciones de traducción automática neuronal. En esta publicación, veremos The Transformer, un modelo que usa la atención para aumentar la velocidad con la que se pueden entrenar estos modelos. Transformers supera al modelo de traducción automática neuronal de Google en tareas específicas. Sin embargo, el mayor beneficio proviene de cómo The Transformer se presta a la paralelización. De hecho, es la recomendación de Google Cloud usar The Transformer como modelo de referencia para usar su oferta de Cloud TPU. Así que intentemos dividir el modelo y veamos cómo funciona.

El transformador se propuso en el documento La atención es todo lo que necesita . Una implementación de TensorFlow está disponible como parte del paquete Tensor2Tensor . El grupo de PNL de Harvard creó una guía anotando el documento con la implementación de PyTorch . En esta publicación, intentaremos simplificar un poco las cosas e introducir los conceptos uno por uno para, con suerte, facilitar su comprensión a las personas sin un conocimiento profundo del tema.

Una mirada de alto nivel

Comencemos mirando el modelo como una única caja negra. En una aplicación de traducción automática, tomaría una oración en un idioma y generaría su traducción en otro.

Al abrir esa bondad de Optimus Prime, vemos un componente de codificación, un componente de decodificación y conexiones entre ellos.

El componente de codificación es una pila de codificadores (el papel apila seis de ellos uno encima del otro; no hay nada mágico en el número seis, definitivamente uno puede experimentar con otros arreglos). El componente de decodificación es una pila de decodificadores del mismo número.

Los codificadores son todos idénticos en estructura (sin embargo, no comparten pesos). Cada uno se divide en dos subcapas:

Las entradas del codificador primero fluyen a través de una capa de atención propia, una capa que ayuda al codificador a mirar otras palabras en la oración de entrada mientras codifica una palabra específica. Veremos más de cerca la atención personal más adelante en la publicación.

Las salidas de la capa de auto-atención se alimentan a una red neuronal de retroalimentación. La misma red de alimentación directa se aplica de forma independiente a cada posición.

El decodificador tiene ambas capas, pero entre ellas hay una capa de atención que ayuda al decodificador a enfocarse en las partes relevantes de la oración de entrada (similar a lo que hace la atención en los modelos seq2seq).

Llevando los tensores a la imagen

Ahora que hemos visto los componentes principales del modelo, comencemos a ver los diversos vectores / tensores y cómo fluyen entre estos componentes para convertir la entrada de un modelo entrenado en una salida.

Como es el caso de las aplicaciones de PNL en general, comenzamos por convertir cada palabra de entrada en un vector usando un algoritmo de incrustación .

Cada palabra está incrustada en un vector de tamaño 512. Representaremos esos vectores con estos simples cuadros.

La incrustación solo ocurre en el codificador más inferior. La abstracción que es común a todos los codificadores es que reciben una lista de vectores, cada uno del tamaño 512 - En el codificador inferior estaría la palabra incrustaciones, pero en otros codificadores, sería la salida del codificador que está directamente debajo . El tamaño de esta lista es un hiperparámetro que podemos establecer; básicamente, sería la longitud de la oración más larga en nuestro conjunto de datos de entrenamiento.

Después de incrustar las palabras en nuestra secuencia de entrada, cada una de ellas fluye a través de cada una de las dos capas del codificador.

Aquí comenzamos a ver una propiedad clave del Transformer, que es que la palabra en cada posición fluye a través de su propia ruta en el codificador. Hay dependencias entre estos caminos en la capa de auto-atención. Sin embargo, la capa de retroalimentación no tiene esas dependencias y, por lo tanto, las diversas rutas se pueden ejecutar en paralelo mientras fluyen a través de la capa de retroalimentación.

A continuación, cambiaremos el ejemplo a una oración más corta y veremos qué sucede en cada subcapa del codificador.

¡Ahora estamos codificando!

Como ya hemos mencionado, un codificador recibe una lista de vectores como entrada. Procesa esta lista pasando estos vectores a una capa de "atención propia", luego a una red neuronal de avance y luego envía la salida hacia arriba al siguiente codificador.

La palabra en cada posición pasa por un proceso de auto atención. Luego, cada uno pasa a través de una red neuronal de retroalimentación, la misma red exacta con cada vector que fluye a través de ella por separado.

Auto atención a un alto nivel

No se deje engañar por mí lanzando la palabra "atención a uno mismo" como si fuera un concepto con el que todos deberían estar familiarizados. Personalmente, nunca me había encontrado con el concepto hasta que leí el artículo La atención es todo lo que necesita. Destilemos cómo funciona.

Digamos que la siguiente oración es una oración de entrada que queremos traducir:

"The animal didn't cross the street because it was too tired"

¿A qué se refiere “eso” en esta oración? ¿Se refiere a la calle o al animal? Es una pregunta simple para un humano, pero no tan simple para un algoritmo.

Cuando el modelo está procesando la palabra "eso", la auto-atención le permite asociar "eso" con "animal".

A medida que el modelo procesa cada palabra (cada posición en la secuencia de entrada), la atención propia le permite buscar en otras posiciones en la secuencia de entrada pistas que puedan ayudar a conducir a una mejor codificación de esta palabra.

Si está familiarizado con los RNN, piense en cómo mantener un estado oculto permite que un RNN incorpore su representación de palabras / vectores anteriores que ha procesado con el actual que está procesando. La auto-atención es el método que usa el Transformer para convertir la "comprensión" de otras palabras relevantes en la que estamos procesando actualmente.

Mientras codificamos la palabra "eso" en el codificador n. ° 5 (el codificador superior de la pila), parte del mecanismo de atención se centró en "El animal" y horneó una parte de su representación en la codificación de "eso".

Asegúrese de consultar el cuaderno Tensor2Tensor donde puede cargar un modelo de Transformer y examinarlo con esta visualización interactiva.

Auto-atención en detalle

Primero veamos cómo calcular la atención personal usando vectores, luego procedamos a ver cómo se implementa realmente, usando matrices.

El primer paso para calcular la atención personal es crear tres vectores de cada uno de los vectores de entrada del codificador (en este caso, la incrustación de cada palabra). Entonces, para cada palabra, creamos un vector de consulta, un vector clave y un vector de valor. Estos vectores se crean multiplicando la incrustación por tres matrices que entrenamos durante el proceso de entrenamiento.

Observe que estos nuevos vectores son más pequeños en dimensión que el vector de incrustación. Su dimensionalidad es 64, mientras que los vectores de entrada / salida de incrustación y codificador tienen una dimensionalidad de 512. No TIENEN que ser más pequeños, esta es una elección de arquitectura para hacer que el cálculo de la atención de múltiples cabezas (en su mayoría) sea constante.

¿Qué son los vectores de "consulta", "clave" y "valor"?

Son abstracciones útiles para calcular y pensar en la atención. Una vez que continúe leyendo cómo se calcula la atención a continuación, sabrá prácticamente todo lo que necesita saber sobre el papel que juega cada uno de estos vectores.

El segundo paso para calcular la atención personal es calcular una puntuación. Supongamos que estamos calculando la atención propia para la primera palabra de este ejemplo, "Pensamiento". Necesitamos calificar cada palabra de la oración de entrada con esta palabra. La puntuación determina cuánto enfoque poner en otras partes de la oración de entrada mientras codificamos una palabra en una posición determinada.

La puntuación se calcula tomando el producto escalar del vector de consulta con el vector clave de la palabra respectiva que estamos puntuando. Entonces, si procesamos la atención propia de la palabra en la posición # 1 , la primera puntuación sería el producto escalar de q1 y k1 . La segunda puntuación sería el producto escalar de q1 y k2.

Los pasos tercero y cuarto son dividir las puntuaciones entre 8 (la raíz cuadrada de la dimensión de los vectores clave utilizados en el documento - 64. Esto conduce a tener gradientes más estables. Podría haber otros valores posibles aquí, pero este es el predeterminado), luego pase el resultado a través de una operación softmax. Softmax normaliza las puntuaciones para que todas sean positivas y sumen 1.

Esta puntuación softmax determina cuánto se expresará cada palabra en esta posición. Claramente, la palabra en esta posición tendrá la puntuación softmax más alta, pero a veces es útil prestar atención a otra palabra que sea relevante para la palabra actual.

El quinto paso es multiplicar cada vector de valor por la puntuación softmax (en preparación para sumarlos). La intuición aquí es mantener intactos los valores de las palabras en las que queremos centrarnos y ahogar las palabras irrelevantes (multiplicándolas por números diminutos como 0,001, por ejemplo).

El sexto paso es sumar los vectores de valor ponderado. Esto produce la salida de la capa de auto-atención en esta posición (para la primera palabra).

Con eso concluye el cálculo de la auto-atención. El vector resultante es uno que podemos enviar a la red neuronal de avance. Sin embargo, en la implementación real, este cálculo se realiza en forma de matriz para un procesamiento más rápido. Veamos eso ahora que hemos visto la intuición del cálculo a nivel de palabras.

Cálculo matricial de autoatención

El primer paso es calcular las matrices de consulta, clave y valor. Lo hacemos empaquetando nuestras incrustaciones en una matriz X y multiplicándolas por las matrices de peso que hemos entrenado ( WQ , WK , WV ).

Cada fila de la matriz X corresponde a una palabra en la oración de entrada. Nuevamente vemos la diferencia en el tamaño del vector de incrustación (512 o 4 cajas en la figura) y los vectores q / k / v (64 o 3 cajas en la figura)

Finalmente , dado que estamos tratando con matrices, podemos condensar los pasos del dos al seis en una fórmula para calcular los resultados de la capa de auto atención.

El cálculo de la auto-atención en forma de matriz

La bestia con muchas cabezas

El documento refinó aún más la capa de auto-atención al agregar un mecanismo llamado atención de “múltiples cabezas”. Esto mejora el rendimiento de la capa de atención de dos formas:

1. Amplía la capacidad del modelo para enfocarse en diferentes posiciones. Sí, en el ejemplo anterior, z1 contiene un poco de cualquier otra codificación, pero podría estar dominado por la propia palabra. Sería útil si traducimos una frase como “El animal no cruzó la calle porque estaba demasiado cansado”, querríamos saber a qué palabra se refiere “eso”.

2. Le da a la capa de atención múltiples “subespacios de representación”. Como veremos a continuación, con la atención de múltiples cabezas no solo tenemos uno, sino múltiples conjuntos de matrices de ponderación de consulta / clave / valor (el transformador usa ocho cabezas de atención, por lo que terminamos con ocho conjuntos para cada codificador / decodificador) . Cada uno de estos conjuntos se inicializa aleatoriamente. Luego, después del entrenamiento, cada conjunto se utiliza para proyectar las incorporaciones de entrada (o vectores de codificadores / decodificadores inferiores) en un subespacio de representación diferente.

Con la atención de múltiples cabezas, mantenemos matrices de peso Q / K / V separadas para cada cabezal, lo que da como resultado diferentes matrices Q / K / V. Como hicimos antes, multiplicamos X por las matrices WQ / WK / WV para producir matrices Q / K / V.

Si hacemos el mismo cálculo de atención propia que describimos anteriormente, solo ocho veces diferentes con diferentes matrices de peso, terminamos con ocho matrices Z diferentes.

Esto nos deja con un pequeño desafío. La capa de retroalimentación no espera ocho matrices, espera una sola matriz (un vector para cada palabra). Entonces necesitamos una forma de condensar estos ocho en una sola matriz.

¿Como hacemos eso? Concatizamos las matrices y luego las multiplicamos por una matriz de pesos adicional WO.

Eso es prácticamente todo lo que hay para la auto-atención de múltiples cabezas. Es un buen puñado de matrices, me doy cuenta. Permítanme intentar ponerlos todos en una sola imagen para que podamos verlos en un solo lugar

Ahora que hemos tocado las cabezas de atención, revisemos nuestro ejemplo anterior para ver dónde se enfocan las diferentes cabezas de atención cuando codificamos la palabra "eso" en nuestra oración de ejemplo:

Cuando codificamos la palabra "eso", una cabeza de atención se centra más en "el animal", mientras que otra se centra en "cansado"; en cierto sentido, la representación del modelo de la palabra "eso" se hornea en algunas de las representaciones. tanto de "animal" como de "cansado".

Sin embargo, si agregamos todas las cabezas de atención a la imagen, las cosas pueden ser más difíciles de interpretar:

Representar el orden de la secuencia mediante codificación posicional

Una cosa que falta en el modelo tal como lo hemos descrito hasta ahora es una forma de explicar el orden de las palabras en la secuencia de entrada.

Para solucionar esto, el transformador agrega un vector a cada inserción de entrada. Estos vectores siguen un patrón específico que aprende el modelo, lo que le ayuda a determinar la posición de cada palabra o la distancia entre diferentes palabras en la secuencia. La intuición aquí es que agregar estos valores a las incrustaciones proporciona distancias significativas entre los vectores de incrustación una vez que se proyectan en los vectores Q / K / V y durante la atención del producto punto.

Para darle al modelo un sentido del orden de las palabras, agregamos vectores de codificación posicional, cuyos valores siguen un patrón específico.

Si asumimos que la incrustación tiene una dimensionalidad de 4, las codificaciones posicionales reales se verían así:

Un ejemplo real de codificación posicional con un tamaño de incrustación de juguete de 4

¿Cómo se vería este patrón?

En la siguiente figura, cada fila corresponde a la codificación posicional de un vector. Entonces, la primera fila sería el vector que agregaríamos a la incrustación de la primera palabra en una secuencia de entrada. Cada fila contiene 512 valores, cada uno con un valor entre 1 y -1. Los hemos codificado por colores para que el patrón sea visible.

Un ejemplo real de codificación posicional para 20 palabras (filas) con un tamaño de incrustación de 512 (columnas). Puede ver que parece dividido por la mitad en el centro. Eso es porque los valores de la mitad izquierda son generados por una función (que usa seno), y la mitad derecha es generada por otra función (que usa coseno). Luego se concatenan para formar cada uno de los vectores de codificación posicional.

La fórmula para la codificación posicional se describe en el documento (sección 3.5). Puede ver el código para generar codificaciones posicionales en formato get_timing_signal_1d(). Este no es el único método posible para la codificación posicional. Sin embargo, ofrece la ventaja de poder escalar a longitudes invisibles de secuencias (por ejemplo, si a nuestro modelo entrenado se le pide que traduzca una oración más larga que cualquiera de las de nuestro conjunto de entrenamiento).

Actualización de julio de 2020: la codificación posicional que se muestra arriba es de la implementación Tranformer2Transformer del Transformer. El método que se muestra en el documento es ligeramente diferente en el sentido de que no concatena directamente, sino que entrelaza las dos señales. La siguiente figura muestra cómo se ve. Aquí está el código para generarlo :

Los residuos

Un detalle en la arquitectura del codificador que debemos mencionar antes de continuar, es que cada subcapa (atención propia, ffnn) en cada codificador tiene una conexión residual a su alrededor, y es seguida por un paso de normalización de capa .

Si vamos a visualizar los vectores y la operación de norma de capa asociada con la atención personal, se vería así:

Esto también se aplica a las subcapas del decodificador. Si pensamos en un transformador de 2 codificadores y decodificadores apilados, se vería así:

El lado del decodificador

Ahora que hemos cubierto la mayoría de los conceptos del lado del codificador, básicamente sabemos también cómo funcionan los componentes de los decodificadores. Pero echemos un vistazo a cómo funcionan juntos.

El codificador comienza procesando la secuencia de entrada. La salida del codificador superior se transforma luego en un conjunto de vectores de atención K y V. Estos deben ser utilizados por cada decodificador en su capa de "atención del codificador-decodificador" que ayuda al decodificador a enfocarse en los lugares apropiados en la secuencia de entrada:

Después de finalizar la fase de codificación, comenzamos la fase de decodificación. Cada paso de la fase de decodificación genera un elemento de la secuencia de salida (la oración de traducción al inglés en este caso).

Los siguientes pasos repiten el proceso hasta que un especial se alcanza el símbolo que indica que el decodificador del transformador ha completado su salida. La salida de cada paso se envía al decodificador inferior en el siguiente paso de tiempo, y los decodificadores aumentan sus resultados de decodificación como lo hicieron los codificadores. Y al igual que hicimos con las entradas del codificador, incorporamos y agregamos codificación posicional a esas entradas del decodificador para indicar la posición de cada palabra.

Las capas de auto atención en el decodificador operan de una manera ligeramente diferente a la del codificador:

En el decodificador, la capa de auto-atención solo puede atender posiciones anteriores en la secuencia de salida. Esto se hace enmascarando posiciones futuras (configurándolas en -inf) antes del paso softmax en el cálculo de auto atención.

La capa “Atención del codificador-decodificador” funciona como la auto atención de múltiples cabezas, excepto que crea su matriz de consultas a partir de la capa que está debajo y toma la matriz de claves y valores de la salida de la pila del codificador.

La capa final lineal y Softmax

La pila de decodificadores genera un vector de flotadores. ¿Cómo convertimos eso en una palabra? Ese es el trabajo de la capa lineal final que es seguida por una capa Softmax.

La capa lineal es una red neuronal simple completamente conectada que proyecta el vector producido por la pila de decodificadores en un vector mucho más grande llamado vector logits.

Supongamos que nuestro modelo conoce 10,000 palabras únicas en inglés (el "vocabulario de salida" de nuestro modelo) que ha aprendido de su conjunto de datos de entrenamiento. Esto haría que el vector logits tenga un ancho de 10,000 celdas, cada celda correspondiente a la puntuación de una palabra única. Así es como interpretamos la salida del modelo seguida de la capa Lineal.

La capa softmax luego convierte esos puntajes en probabilidades (todos positivos, todos suman 1.0). Se elige la celda con la probabilidad más alta y la palabra asociada con ella se genera como salida para este paso de tiempo.

Esta figura comienza desde abajo con el vector producido como salida de la pila de decodificadores. Luego se convierte en una palabra de salida.

Resumen de la formación

Ahora que hemos cubierto todo el proceso de avance a través de un transformador entrenado, sería útil echar un vistazo a la intuición de entrenar el modelo.

Durante el entrenamiento, un modelo no entrenado pasaría exactamente por el mismo pase hacia adelante. Pero como lo estamos entrenando en un conjunto de datos de entrenamiento etiquetado, podemos comparar su salida con la salida correcta real.

Para visualizar esto, supongamos que nuestro vocabulario de salida solo contiene seis palabras ("a", "am", "i", "gracias", "estudiante" y "<eos>" (abreviatura de 'final de oración')) .

El vocabulario de salida de nuestro modelo se crea en la fase de preprocesamiento antes incluso de comenzar a entrenar.

Una vez que definamos nuestro vocabulario de salida, podemos usar un vector del mismo ancho para indicar cada palabra en nuestro vocabulario. Esto también se conoce como codificación one-hot. Entonces, por ejemplo, podemos indicar la palabra "am" usando el siguiente vector:

Ejemplo: codificación one-hot de nuestro vocabulario de salida

Después de este resumen, analicemos la función de pérdida del modelo, la métrica que estamos optimizando durante la fase de entrenamiento para conducir a un modelo entrenado y, con suerte, increíblemente preciso.

La función de pérdida

Digamos que estamos entrenando nuestro modelo. Digamos que es nuestro primer paso en la fase de capacitación y lo estamos capacitando con un ejemplo simple: traducir "merci" en "gracias".

Lo que esto significa es que queremos que la salida sea una distribución de probabilidad que indique la palabra "gracias". Pero dado que este modelo aún no está entrenado, es poco probable que eso suceda todavía.

Dado que los parámetros del modelo (pesos) se inicializan todos al azar, el modelo (no entrenado) produce una distribución de probabilidad con valores arbitrarios para cada celda / palabra. Podemos compararlo con la salida real, luego ajustar todos los pesos del modelo usando retropropagación para acercar la salida a la salida deseada.

¿Cómo compara dos distribuciones de probabilidad? Simplemente restamos uno del otro. Para obtener más detalles, consulte la entropía cruzada y la divergencia de Kullback-Leibler .

Pero tenga en cuenta que este es un ejemplo muy simplificado. De manera más realista, usaremos una oración de más de una palabra. Por ejemplo, entrada: "je suis étudiant" y salida esperada: "soy un estudiante". Lo que esto realmente significa es que queremos que nuestro modelo genere distribuciones de probabilidad sucesivamente donde:

Cada distribución de probabilidad está representada por un vector de ancho vocab_size (6 en nuestro ejemplo de juguete, pero de manera más realista un número como 30.000 o 50.000)La primera distribución de probabilidad tiene la probabilidad más alta en la celda asociada con la palabra "i"La segunda distribución de probabilidad tiene la probabilidad más alta en la celda asociada con la palabra "am"Y así sucesivamente, hasta que la quinta distribución de salida indique el <end of sentence>símbolo " ", que también tiene una celda asociada del vocabulario de 10,000 elementos.

Las distribuciones de probabilidad objetivo con las que entrenaremos nuestro modelo en el ejemplo de entrenamiento para una oración de muestra.

Después de entrenar el modelo durante suficiente tiempo en un conjunto de datos lo suficientemente grande, esperamos que las distribuciones de probabilidad producidas se vean así:

Con suerte, después del entrenamiento, el modelo generaría la traducción correcta que esperamos. Por supuesto, no es una indicación real de si esta frase fue parte del conjunto de datos de entrenamiento (ver: validación cruzada ). Tenga en cuenta que cada posición tiene un poco de probabilidad, incluso si es poco probable que sea el resultado de ese paso de tiempo; esa es una propiedad muy útil de softmax que ayuda al proceso de entrenamiento.

Ahora, debido a que el modelo produce las salidas una a la vez, podemos asumir que el modelo está seleccionando la palabra con la mayor probabilidad de esa distribución de probabilidad y desechando el resto. Esa es una forma de hacerlo (llamada decodificación codiciosa). Otra forma de hacerlo sería aferrarse a, digamos, las dos palabras superiores (digamos, 'I' y 'a' por ejemplo), luego, en el siguiente paso, ejecutar el modelo dos veces: una vez asumiendo que la primera posición de salida fue la palabra 'I', y otra vez asumiendo que la primera posición de salida era la palabra 'a', y la versión que produjo menos error considerando que ambas posiciones # 1 y # 2 se mantiene. Repetimos esto para las posiciones # 2 y # 3… etc. Este método se llama "búsqueda de haz", donde en nuestro ejemplo, beam_size era dos (lo que significa que en todo momento, dos hipótesis parciales (traducciones sin terminar) se mantienen en la memoria), y top_beams también es dos (lo que significa que devolveremos dos traducciones). Ambos son hiperparámetros con los que puede experimentar.

Tags

Join Hacker Noon

Create your free account to unlock your custom reading experience.