De-codificado Transformers explicados en un lenguaje sencillo

Explicando los Transformers de manera sencilla y comprensible

Sin código, matemáticas o mención de Claves, Consultas y Valores

Desde su introducción en 2017, los transformers se han convertido en una fuerza prominente en el campo del Aprendizaje Automático, revolucionando las capacidades de los servicios de traducción principal y autocompletar.

Recientemente, la popularidad de los transformers ha aumentado aún más con la llegada de modelos de lenguaje grandes como el ChatGPT de OpenAI, GPT-4 y el LLama de Meta. Estos modelos, que han recibido una atención y emoción inmensas, están todos construidos sobre la base de la arquitectura del transformer. Al aprovechar el poder de los transformers, estos modelos han logrado avances notables en la comprensión y generación de lenguaje natural; exponiéndolos al público en general.

A pesar de muchos buenos recursos que explican cómo funcionan los transformers, me encontré en una posición en la que entendía cómo funcionaban mecánicamente matemáticamente pero encontraba difícil explicar cómo funciona intuitivamente un transformer. Después de realizar muchas entrevistas, hablar con mis colegas y dar una charla relámpago sobre el tema, ¡parece que muchas personas comparten este problema!

En esta publicación del blog, trataré de proporcionar una explicación de alto nivel de cómo funcionan los transformers sin depender de código o matemáticas. Mi objetivo es evitar confundir con jerga técnica y comparaciones con arquitecturas anteriores. Si bien trataré de mantener las cosas lo más simples posible, esto no será fácil ya que los transformers son bastante complejos, pero espero que proporcione una mejor intuición de lo que hacen y cómo lo hacen.

¿Qué es un Transformer?

Un transformer es un tipo de arquitectura de red neuronal que es adecuada para tareas que implican el procesamiento de secuencias como entradas. Quizás el ejemplo más común de una secuencia en este contexto es una oración, que podemos pensar como un conjunto ordenado de palabras.

El objetivo de estos modelos es crear una representación numérica para cada elemento dentro de una secuencia; encapsulando información esencial sobre el elemento y su contexto vecino. Las representaciones numéricas resultantes luego se pueden pasar a redes de nivel inferior, que pueden aprovechar esta información para realizar varias tareas, incluyendo generación y clasificación.

Al crear estas representaciones ricas, estos modelos permiten que las redes de nivel inferior comprendan mejor los patrones y relaciones subyacentes dentro de la secuencia de entrada, lo que mejora su capacidad para generar salidas coherentes y contextualmente relevantes.

La ventaja clave de los transformers radica en su capacidad para manejar dependencias de largo alcance dentro de las secuencias, así como su alta eficiencia; capaz de procesar secuencias en paralelo. Esto es particularmente útil para tareas como la traducción automática, el análisis de sentimientos y la generación de texto.

Imagen generada por el modelo DALL-E del Servicio Azure OpenAI con la siguiente indicación: «El código verde y negro de Matrix en forma de Optimus Prime»

¿Qué se incluye en el Transformer?

Para alimentar una entrada en un transformer, primero debemos convertirla en una secuencia de tokens; un conjunto de enteros que representa nuestra entrada.

Como los transformers se aplicaron primero en el dominio del Procesamiento del Lenguaje Natural (NLP), consideremos este escenario primero. La forma más sencilla de convertir una oración en una serie de tokens es definir un vocabulario que actúe como una tabla de búsqueda, mapeando palabras a enteros; podemos reservar un número específico para representar cualquier palabra que no esté contenida en este vocabulario, para poder asignar siempre un valor entero.

En la práctica, esta es una forma ingenua de codificar texto, ya que palabras como “gato” y “gatos” son tratadas como tokens completamente diferentes, ¡a pesar de ser descripciones singulares y plurales del mismo animal! Para superar esto, se han ideado diferentes estrategias de tokenización, como codificación de pares de bytes, que descomponen las palabras en fragmentos más pequeños antes de indexarlos. Además, suele ser útil agregar tokens especiales para representar características como el inicio y el final de una oración, para proporcionar contexto adicional al modelo.

Consideremos el siguiente ejemplo para comprender mejor el proceso de tokenización.

“Hola, ¿no está agradable el clima hoy en Drosval?”

Drosval es un nombre generado por GPT-4 utilizando el siguiente concepto: “¿Puedes crear un nombre de un lugar ficticio que suene como si pudiera pertenecer al universo de Drenai de David Gemmell?”, elegido de manera deliberada para no aparecer en el vocabulario de ningún modelo entrenado.

Utilizando el tokenizador bert-base-uncased de la librería transformers, esto se convierte en la siguiente secuencia de tokens:

Los números que representan cada palabra cambiarán dependiendo del entrenamiento específico del modelo y de la estrategia de tokenización. Al decodificar esto, podemos ver la palabra que representa cada token:

Curiosamente, podemos ver que esto no es lo mismo que nuestra entrada. Se han agregado tokens especiales, nuestra abreviatura se ha dividido en varios tokens y nuestro nombre de lugar ficticio está representado por diferentes “fragmentos”. Como usamos el modelo “uncased”, también hemos perdido todo el contexto de capitalización.

Sin embargo, aunque usamos una oración como ejemplo, los transformers no se limitan a entradas de texto; esta arquitectura también ha demostrado buenos resultados en tareas de visión. Para convertir una imagen en una secuencia, los autores de ViT dividieron la imagen en fragmentos de píxeles de 16×16 que no se superponen y los concatenaron en un vector largo antes de pasarlo al modelo. Si estuviéramos utilizando un transformer en un sistema de recomendaciones, un enfoque podría ser utilizar los identificadores de los últimos n elementos navegados por un usuario como entrada a nuestra red. Si podemos crear una representación significativa de tokens de entrada para nuestro dominio, podemos alimentar esto a una red transformer.

Incrustando nuestros tokens

Una vez que tenemos una secuencia de enteros que representa nuestra entrada, podemos convertirlos en incrustaciones. Las incrustaciones son una forma de representar información que puede ser fácilmente procesada por algoritmos de aprendizaje automático; tienen como objetivo capturar el significado del token codificado en un formato comprimido, representando la información como una secuencia de números. Inicialmente, las incrustaciones se inicializan como secuencias de números aleatorios y se aprenden representaciones significativas durante el entrenamiento. Sin embargo, estas incrustaciones tienen una limitación inherente: no tienen en cuenta el contexto en el que aparece el token. Hay dos aspectos de esto.

Dependiendo de la tarea, cuando incrustamos nuestros tokens, también podemos desear preservar el orden de nuestros tokens. Esto es especialmente importante en dominios como el PLN, de lo contrario, terminamos con un enfoque de bolsa de palabras. Para superar esto, aplicamos codificación posicional a nuestras incrustaciones. Si bien hay múltiples formas de crear incrustaciones posicionales, la idea principal es que tenemos otro conjunto de incrustaciones que representan la posición de cada token en la secuencia de entrada, que se combinan con nuestras incrustaciones de tokens.

El otro problema es que los tokens pueden tener diferentes significados dependiendo de los tokens que los rodean. Considera las siguientes oraciones:

¡Está oscuro, quién apagó la luz?

¡Guau, este paquete está muy ligero!

Aquí, la palabra “ligero” se utiliza en dos contextos diferentes, ¡donde tiene significados completamente diferentes! Sin embargo, es probable que, dependiendo de la estrategia de tokenización, la inserción sea la misma. En un transformador, esto se maneja mediante su mecanismo de atención.

Conceptualmente, ¿qué es la atención?

Tal vez el mecanismo más importante utilizado por la arquitectura del transformador sea conocido como atención, que permite que la red comprenda qué partes de la secuencia de entrada son las más relevantes para la tarea en cuestión. Para cada token en la secuencia, el mecanismo de atención identifica qué otros tokens son importantes para comprender el token actual en el contexto dado. Antes de explorar cómo se implementa esto en un transformador, vamos a comenzar de manera simple y tratar de entender qué se busca lograr conceptualmente con el mecanismo de atención para construir nuestra intuición.

Una forma de entender la atención es pensar en ella como un método que reemplaza cada inserción de token con una inserción que incluye información sobre sus tokens vecinos; en lugar de usar la misma inserción para cada token, independientemente de su contexto. Si supiéramos qué tokens son relevantes para el token actual, una forma de capturar este contexto sería crear un promedio ponderado – o, más generalmente, una combinación lineal – de estas inserciones.

Consideremos un ejemplo simple de cómo podría verse esto para una de las oraciones que vimos anteriormente. Antes de aplicar la atención, las inserciones en la secuencia no tienen contexto de sus vecinos. Por lo tanto, podemos visualizar la inserción para la palabra “ligero” como la siguiente combinación lineal.

Aquí, podemos ver que nuestros pesos son simplemente la matriz identidad. Después de aplicar nuestro mecanismo de atención, nos gustaría aprender una matriz de pesos de manera tal que pudiéramos expresar nuestra inserción “ligero” de manera similar a lo siguiente.

En esta ocasión, se otorgan pesos mayores a las inserciones que corresponden a las partes más relevantes de la secuencia para nuestro token elegido; lo que debería garantizar que se capture el contexto más importante en el nuevo vector de inserción.

A veces, a las inserciones que contienen información sobre su contexto actual se les conoce como inserciones contextualizadas, y esto es en última instancia lo que estamos tratando de crear.

Ahora que tenemos una comprensión a alto nivel de lo que se busca lograr con la atención, exploremos cómo se implementa esto en la siguiente sección.

¿Cómo se calcula la atención?

Existen múltiples tipos de atención, y las principales diferencias radican en la forma en que se calculan los pesos utilizados para realizar la combinación lineal. Aquí, consideraremos atención producto puntual escalada, como se introdujo en el artículo original, ya que este es el enfoque más común. En esta sección, supongamos que todas nuestras inserciones han sido codificadas posicionalmente.

Recordando que nuestro objetivo es crear inserciones contextualizadas utilizando combinaciones lineales de nuestras inserciones originales, comencemos de manera simple y asumamos que podemos codificar toda la información necesaria en nuestros vectores de inserción aprendidos, y todo lo que necesitamos calcular son los pesos.

Para calcular los pesos, primero debemos determinar qué tokens son relevantes entre sí. Para lograr esto, necesitamos establecer una noción de similitud entre dos inserciones. Una forma de representar esta similitud es mediante el producto punto, donde nos gustaría aprender inserciones de manera tal que puntajes más altos indiquen que dos palabras son más similares.

Dado que, para cada token, debemos calcular su relevancia con cada otro token en la secuencia, podemos generalizar esto a una multiplicación de matrices, que nos proporciona nuestra matriz de pesos; que a menudo se denomina puntajes de atención. Para asegurarnos de que nuestros pesos sumen uno, también aplicamos la función SoftMax. Sin embargo, como las multiplicaciones de matrices pueden producir números arbitrariamente grandes, esto podría resultar en que la función SoftMax devuelva gradientes muy pequeños para puntajes de atención grandes, lo que podría llevar al problema del gradiente desvaneciente durante el entrenamiento. Para contrarrestar esto, los puntajes de atención se multiplican por un factor de escala antes de aplicar la función SoftMax.

Ahora, para obtener nuestra matriz de incrustación contextualizada, podemos multiplicar nuestros puntajes de atención con nuestra matriz de incrustación original; lo que equivale a tomar combinaciones lineales de nuestras incrustaciones.

Cálculo simplificado de atención: suponiendo que las incrustaciones están codificadas de manera posicional

Aunque es posible que un modelo pueda aprender incrustaciones lo suficientemente complejas como para generar puntajes de atención y posteriormente incrustaciones contextualizadas, estamos tratando de condensar mucha información en la dimensión de incrustación, que suele ser bastante pequeña.

Por lo tanto, para facilitar un poco la tarea de aprendizaje del modelo, ¡introduzcamos algunos parámetros más que se puedan aprender! En lugar de usar nuestra matriz de incrustación directamente, pasemos esto a través de tres capas lineales independientes (multiplicaciones de matrices); esto debería permitir que el modelo se “enfoque” en diferentes partes de las incrustaciones. Esto se muestra en la siguiente imagen:

Atención propia basada en producto punto escalado: suponiendo que las incrustaciones están codificadas de manera posicional

A partir de la imagen, podemos ver que las proyecciones lineales se etiquetan como Q, K y V. En el artículo original, estas proyecciones se denominaron Consulta, Clave y Valor, supuestamente inspiradas en la recuperación de información. Personalmente, nunca encontré que esta analogía ayudara a mi comprensión, así que tiendo a no centrarme en esto; he seguido la terminología aquí por consistencia con la literatura y para dejar en claro que estas capas lineales son distintas.

Ahora que entendemos cómo funciona este proceso, podemos pensar en el cálculo de atención como un bloque único con tres entradas, que se pasarán a Q, K y V.

Cuando pasamos la misma matriz de incrustación a Q, K y V, esto se conoce como autoatención.

¿Qué es la atención multi-head?

En la práctica, a menudo utilizamos varios bloques de autoatención en paralelo para permitir que el transformador atienda diferentes partes de la secuencia de entrada simultáneamente; esto se conoce como atención multi-head.

La idea detrás de la atención multi-head es bastante simple, las salidas de varios bloques de autoatención independientes se concatenan y luego se pasan por una capa lineal. Esta capa lineal permite que el modelo aprenda a combinar la información contextual de cada cabeza de atención.

En la práctica, el tamaño de la dimensión oculta utilizado en cada bloque de autoatención suele elegirse como el tamaño de la incrustación original dividido por el número de cabezas de atención; para preservar la forma de la matriz de incrustación.

¿Qué más forma parte de un Transformador?

Aunque el artículo que introdujo el transformador se llamaba (ahora famosamente) Attention is all you need, esto es un poco confuso, ya que ¡un transformador tiene más componentes además de la atención!

Un bloque de transformador también contiene lo siguiente:

  • Red Neuronal de Avance (FFN): una red neuronal de dos capas que se aplica a cada incrustación de token en el grupo y secuencia de manera independiente. El propósito del bloque FFN es introducir parámetros que se puedan aprender adicionales en el transformador, que son responsables de garantizar que las incrustaciones contextuales sean distintas y se distribuyan. El artículo original utilizó una función de activación GeLU, pero los componentes del FFN pueden variar según la arquitectura.
  • Normalización de Capas: ayuda a estabilizar el entrenamiento de redes neuronales profundas, incluyendo transformadores. Normaliza las activaciones para cada secuencia, evitando que se vuelvan demasiado grandes o demasiado pequeñas durante el entrenamiento; lo que puede llevar a problemas relacionados con el gradiente como gradientes que desaparecen o explotan. Esta estabilidad es crucial para entrenar de manera efectiva modelos de transformador muy profundos.
  • Conexiones de salto: Al igual que en arquitecturas ResNet, se utilizan conexiones residuales para mitigar el problema del gradiente que desaparece y mejorar la estabilidad del entrenamiento.

Mientras que la arquitectura del transformador se ha mantenido bastante constante desde su introducción, la colocación de los bloques de normalización de capas puede variar dependiendo de la arquitectura del transformador. La arquitectura original, ahora conocida como post-layer norm, se presenta a continuación:

La ubicación más común en arquitecturas recientes, como se puede ver en la figura a continuación, es pre-layer norm, que coloca los bloques de normalización antes de los bloques de autoatención y FFN, dentro de las conexiones de omisión.

¿Cuáles son los diferentes tipos de transformadores?

Aunque ahora existen muchas arquitecturas de transformadores diferentes, la mayoría se pueden clasificar en tres tipos principales.

Arquitecturas del codificador

Los modelos del codificador tienen como objetivo producir incrustaciones contextuales que se pueden utilizar para tareas posteriores como clasificación o reconocimiento de entidades nombradas, ya que el mecanismo de atención puede atender a toda la secuencia de entrada; este es el tipo de arquitectura que se ha explorado en este artículo hasta ahora. La familia más popular de transformadores de solo codificador son BERT y sus variantes.

Después de pasar nuestros datos a través de uno o más bloques de transformadores, tenemos una matriz de incrustaciones contextualizadas complejas, que representa una incrustación para cada token en la secuencia. Sin embargo, para usar esto para una tarea posterior como la clasificación, solo necesitamos hacer una predicción. Tradicionalmente se toma el primer token y se pasa a través de una cabeza de clasificación; que generalmente contiene capas de Dropout y Lineales. La salida de estas capas se puede pasar a través de una función SoftMax para convertirlas en probabilidades de clase. Un ejemplo de cómo podría ser esto se muestra a continuación.

Arquitecturas del decodificador

Casi idénticas a las arquitecturas del codificador, la diferencia clave es que las arquitecturas del decodificador utilizan una capa de autoatención enmascarada (o causal), de modo que el mecanismo de atención solo puede atender a los elementos actuales y anteriores de la secuencia de entrada; esto significa que la incrustación contextual producida solo considera el contexto anterior. Modelos populares de solo decodificador incluyen la familia GPT.

Esto se logra usualmente enmascarando las puntuaciones de atención con una matriz triangular inferior binaria y reemplazando los elementos no enmascarados con menos infinito; al pasar a través de la siguiente operación SoftMax, esto asegurará que las puntuaciones de atención para estas posiciones sean iguales a cero. Podemos actualizar nuestra figura de autoatención anterior para incluir esto de la siguiente manera.

Cálculo de autoatención enmascarado: asumiendo incrustaciones codificadas posicionalmente

Dado que solo pueden atender desde la posición actual hacia atrás, las arquitecturas del decodificador se utilizan habitualmente para tareas autoregresivas como generación de secuencias. Sin embargo, al usar incrustaciones contextuales para generar secuencias, hay algunas consideraciones adicionales en comparación con el uso de un codificador. Un ejemplo de esto se muestra a continuación.

Podemos observar que, aunque el decodificador produce una incrustación contextual para cada token en la secuencia de entrada, generalmente usamos la incrustación correspondiente al último token como entrada para capas posteriores al generar secuencias.

Además, después de aplicar la función SoftMax a los logits, si no se aplica ningún filtrado, obtendríamos una distribución de probabilidades sobre cada token en el vocabulario del modelo; ¡esto puede ser increíblemente grande! A menudo, deseamos reducir el número de opciones potenciales usando diversas estrategias de filtrado, algunos de los métodos más comunes son:

  • Ajuste de temperatura: la temperatura es un parámetro que se aplica dentro de la operación SoftMax y que influye en la aleatoriedad del texto generado. Determina lo creativa o enfocada que es la salida del modelo al alterar la distribución de probabilidad de las palabras en la salida. Una temperatura más alta aplana la distribución, lo que hace que las salidas sean más diversas.
  • Muestreo Top-P: este enfoque filtra el número de posibles candidatos para el siguiente token en función de un umbral de probabilidad dado y redistribuye la distribución de probabilidad en función de los candidatos que superan este umbral.
  • Muestreo Top-K: este enfoque restringe el número de posibles candidatos a los K tokens más probables en función de su puntaje de logit o probabilidad (dependiendo de la implementación).

Más detalles sobre estos métodos se pueden encontrar aquí.

Una vez que hemos modificado o reducido nuestra distribución de probabilidad sobre los posibles candidatos para el siguiente token, podemos muestrear de ella para obtener nuestra predicción, esto es simplemente muestreo de una distribución multinomial. El token predicho se agrega a la secuencia de entrada y se alimenta nuevamente al modelo, hasta que se hayan generado el número deseado de tokens o el modelo produzca un token de parada; un token especial que indica el final de una secuencia.

Arquitecturas de codificador-decodificador

Originalmente, el transformer fue presentado como una arquitectura para la traducción automática y utilizaba tanto un codificador como un decodificador para lograr este objetivo; utilizando el codificador para crear una representación intermedia, antes de utilizar el decodificador para traducir al formato de salida deseado. Si bien los transformers de codificador-decodificador han ido siendo menos comunes, arquitecturas como T5 demuestran cómo tareas como responder preguntas, resumir y clasificar pueden enmarcarse como problemas de secuencia a secuencia y resolverse utilizando este enfoque.

La diferencia clave con las arquitecturas de codificador-decodificador es que el decodificador utiliza una atención de codificador-decodificador, que utiliza tanto las salidas del codificador (como K y V) como las entradas del bloque del decodificador (como Q) durante su cálculo de atención. Esto contrasta con la auto-atención, donde se utiliza la misma matriz de incrustación de entrada para todas las entradas. Aparte de esto, el proceso de generación en general es muy similar al uso de una arquitectura solo con decodificador.

Podemos visualizar una arquitectura de codificador-decodificador como se muestra en la figura a continuación. Aquí, para simplificar la figura, elegí representar la variante de post-capas de norma del transformer como se ve en el artículo original; donde las capas de norma se sitúan después de los bloques de atención.

Conclusión

Espero que esto haya proporcionado una intuición sobre cómo funcionan los transformers, haya ayudado a desglosar algunos de los detalles de una manera más comprensible y haya servido como un buen punto de partida para desmitificar las arquitecturas modernas de los transformers.

Chris Hughes está en LinkedIn

A menos que se indique lo contrario, todas las imágenes fueron creadas por el autor.

Referencias

We will continue to update Zepes; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

Inteligencia Artificial

Cómo implementar la IA adaptativa en tu negocio.

La inteligencia artificial ha surgido como una tecnología poderosa que puede impulsar transformaciones sustanciales e...

Inteligencia Artificial

Conoce snnTorch Un paquete de Python de código abierto para realizar aprendizaje basado en gradientes con redes neuronales de disparo.

En inteligencia artificial, la eficiencia y el impacto ambiental se han convertido en preocupaciones primordiales. Ab...

Inteligencia Artificial

Investigadores cultivan matrices precisas de nanoLEDs

Una nueva técnica produce nanocristales de perovskita justo donde se necesitan, para que los materiales extremadament...

Inteligencia Artificial

GPT-Engineer Tu nuevo asistente de programación de IA

GPT-Engineer es un constructor de aplicaciones impulsado por IA que genera bases de código a partir de descripciones ...

Inteligencia Artificial

Top 50 Herramientas de Escritura de IA para Probar (Agosto 2023)

Grammarly Grammarly es una gran herramienta para mejorar la escritura. Revisa la gramática, ortografía, puntuación y ...