Desde su introducción en 2017los transformadores han surgido como una fuerza destacada en el campo del aprendizaje automático, revolucionando las capacidades de traducción importante y autocompletar servicios.
Recientemente, la popularidad de los transformadores se ha disparado aún más con la llegada de grandes modelos de lenguaje como OpenAI. ChatGPT, GPT-4y Meta Llama. Estos modelos, que han atraído una inmensa atención y entusiasmo, están construidos sobre la base de la arquitectura del transformador. Al aprovechar el poder de los transformadores, estos modelos han logrado avances notables en la comprensión y generación del lenguaje natural; exponerlos al público en general.
A pesar de muchos buenos recursos que analizan cómo funcionan los transformadores, me encontré en una posición en la que entendía cómo funcionaba la mecánica matemáticamente, pero me resultaba difícil explicar cómo funciona un transformador de forma intuitiva. Después de realizar muchas entrevistas, hablar con mis compañeros y dar una charla relámpago sobre el tema, ¡parece que mucha gente comparte este problema!
En esta publicación de blog, intentaré brindar una explicación de alto nivel de cómo funcionan los transformadores sin depender del código o las matemáticas. Mi objetivo es evitar jergas técnicas confusas y comparaciones con arquitecturas anteriores. Si bien intentaré mantener las cosas lo más simples posible, esto no será fácil ya que los transformadores son bastante complejos, pero espero que proporcione una mejor intuición de lo que hacen y cómo lo hacen.
Un transformador es un tipo de arquitectura de red neuronal que es muy adecuada para tareas que implican procesar secuencias como entradas. Quizás el ejemplo más común de secuencia en este contexto sea una oración, que podemos considerar 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 pueden luego transmitirse a redes posteriores, que pueden aprovechar esta información para realizar diversas tareas, incluidas la generación y clasificación.
Al crear representaciones tan ricas, estos modelos permiten que las redes posteriores comprendan mejor los patrones y relaciones subyacentes dentro de la secuencia de entrada, lo que mejora su capacidad para generar resultados coherentes y contextualmente relevantes.
La ventaja clave de los transformadores radica en su capacidad para manejar dependencias de largo alcance dentro de secuencias, además de ser altamente eficientes; capaz de procesar secuencias en paralelo. Esto es particularmente útil para tareas como traducción automática, análisis de opiniones y generación de texto.
Para alimentar una entrada a un transformador, primero debemos convertirla en una secuencia de tokens; un conjunto de números enteros que representan nuestra entrada.
Como los transformadores se aplicaron por primera vez en el dominio de la PNL, 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úa como una tabla de búsqueda, asignando palabras a números enteros; Podemos reservar un número específico para representar cualquier palabra que no esté contenida en este vocabulario, de modo que siempre podamos asignar un valor entero.
En la práctica, esta es una forma ingenua de codificar texto, ya que palabras como gato y gatos se tratan como fichas completamente diferentes, ¡a pesar de que son descripciones singulares y plurales del mismo animal! Para superar esto, diferentes estrategias de tokenización, como codificación de par de bytes – Se han ideado sistemas que dividen las palabras en fragmentos más pequeños antes de indexarlas. 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 hace buen tiempo hoy en Drosval?”
Drosval es un nombre generado por GPT-4 usando el siguiente mensaje: “¿Puedes crear un nombre de lugar ficticio que parezca pertenecer a ¿El universo Drenai de David Gemmell?“; elegido deliberadamente ya que no debería aparecer en el vocabulario de ningún modelo entrenado.
Utilizando el bert-base-uncased tokenizador de la biblioteca de transformadoresesto se convierte en la siguiente secuencia de tokens:
Los números enteros que representan cada palabra cambiarán según la estrategia de tokenización y entrenamiento del modelo específico. Decodificando 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 múltiples tokens y el nombre de nuestro lugar ficticio está representado por diferentes “fragmentos”. Como utilizamos el modelo “sin caja”, también perdimos todo el contexto de capitalización.
Sin embargo, aunque usamos una oración para nuestro ejemplo, los transformadores no se limitan a entradas de texto; Esta arquitectura también tiene demostró buenos resultados en tareas de visión. Para convertir una imagen en una secuencia, los autores de ViT cortaron la imagen en parches de 16×16 píxeles que no se superponen y los concatenaron en un vector largo antes de pasarlo al modelo. Si estuviéramos usando un transformador en un sistema de recomendación, un enfoque podría ser usar los identificadores de elementos del último norte elementos navegados por un usuario como entrada a nuestra red. Si podemos crear una representación significativa de los tokens de entrada para nuestro dominio, podemos introducirla en una red transformadora.
Incrustando nuestros tokens
Una vez que tenemos una secuencia de números enteros que representa nuestra entrada, podemos convertirlos en incrustaciones. Las incrustaciones son una forma de representar información que puede procesarse fácilmente mediante algoritmos de aprendizaje automático; Su objetivo es capturar el significado del token que se codifica 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 incorporaciones 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, es posible que también deseemos preservar el orden de nuestros tokens; Esto es especialmente importante en dominios como la PNL, o esencialmente terminaremos con una bolsa de palabras acercarse. Para superar esto, aplicamos codificación posicional a nuestras incrustaciones. Mientras haya múltiples formas de crear incrustaciones posicionalesla 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. Considere las siguientes oraciones:
Está oscuro, ¿quién apagó la luz?
¡Vaya, este paquete es realmente liviano!
Aquí, la palabra luz se utiliza en dos contextos diferentes, donde tiene significados completamente diferentes. Sin embargo, es probable que, dependiendo de la estrategia de tokenización, la integración sea la misma. En un transformador, esto lo maneja su atención mecanismo.
Quizás el mecanismo más importante utilizado por la arquitectura del transformador se conoce como atención, que permite a la red comprender qué partes de la secuencia de entrada son las más relevantes para la tarea determinada. Para cada token de 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 dentro de un transformador, comencemos de manera simple e intentemos comprender qué intenta lograr conceptualmente el mecanismo de atención para desarrollar nuestra intuición.
Una forma de entender la atención es pensar en ella como un método que reemplaza cada incrustación de token con una incrustación que incluye información sobre sus tokens vecinos; en lugar de utilizar la misma incrustació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 incorporaciones.
Consideremos un ejemplo simple de cómo podría verse esto para una de las oraciones que vimos anteriormente. Antes de que se aplique la atención, las incrustaciones en la secuencia no tienen contexto de sus vecinos. Por lo tanto, podemos visualizar la incrustación de la palabra. luz como la siguiente combinación lineal.
Aquí podemos ver que nuestros pesos son solo la matriz identidad. Después de aplicar nuestro mecanismo de atención, nos gustaría aprender una matriz de peso tal que podamos expresar nuestra luz incrustando de una manera similar a la siguiente.
Esta vez, se dan pesos mayores a las incrustaciones que corresponden a las partes más relevantes de la secuencia de nuestro token elegido; lo que debería garantizar que el contexto más importante se capture en el nuevo vector de incrustación.
Las incrustaciones que contienen información sobre su contexto actual a veces se conocen como incrustaciones contextualizadas, y esto es, en última instancia, lo que estamos tratando de crear.
Ahora que tenemos un alto nivel de comprensión de lo que la atención intenta lograr, exploremos cómo se implementa realmente en la siguiente secció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 de producto escaladotal como se introdujo en el papel original, ya que este es el enfoque más común. En esta sección, supongamos que todas nuestras incrustaciones han sido codificadas posicionalmente.
Recordando que nuestro objetivo es crear incrustaciones contextualizadas utilizando combinaciones lineales de nuestras incrustaciones originales, comencemos de manera simple y supongamos que podemos codificar toda la información necesaria en nuestros vectores de incrustació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 incrustaciones. Una forma de representar esta similitud es mediante el uso del producto escalar, donde nos gustaría aprender incrustaciones de modo que puntuaciones más altas indiquen que dos palabras son más similares.
Como, para cada token, necesitamos calcular su relevancia con todos los demás tokens de la secuencia, podemos generalizar esto a una multiplicación de matrices, que nos proporciona nuestra matriz de peso; que a menudo se denominan puntuaciones 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 dar como resultado que la función SoftMax devuelva gradientes muy pequeños para puntuaciones de atención grandes; lo que puede conducir a la problema de gradiente de fuga durante el entrenamiento. Para contrarrestar esto, las puntuaciones de atención se multiplican por un factor de escala antes de aplicar SoftMax.
Ahora, para obtener nuestra matriz de incrustación contextualizada, podemos multiplicar nuestras puntuaciones de atención con nuestra matriz de incrustación original; que es el equivalente a tomar combinaciones lineales de nuestras incrustaciones.