Let's build GPT: from scratch, in code, spelled out.

Let's build GPT: from scratch, in code, spelled out.

¿Qué es ChatGPT y cómo funciona?

Introducción a ChatGPT

  • ChatGPT ha revolucionado la comunidad de IA, permitiendo interacciones textuales con un modelo de lenguaje que puede realizar diversas tareas.
  • Se destaca su capacidad para generar respuestas variadas a un mismo prompt, mostrando su naturaleza probabilística.

Ejemplos de uso

  • Se mencionan ejemplos humorísticos de prompts, como explicar HTML a un perro o escribir notas sobre eventos actuales.
  • Un ejemplo dramático incluye la redacción de una noticia sobre una hoja cayendo de un árbol, ilustrando la creatividad del modelo.

Componentes técnicos detrás de ChatGPT

Arquitectura del modelo

  • El funcionamiento interno se basa en una red neuronal llamada Transformer, introducida en el artículo "Attention is All You Need" (2017).
  • GPT significa "Generative Pre-trained Transformer", lo que indica su diseño y propósito.

Impacto del Transformer

  • Aunque inicialmente se pensó para traducción automática, el Transformer ha transformado múltiples aplicaciones en IA en los años siguientes.
  • La arquitectura ha sido adaptada y utilizada ampliamente más allá de su contexto original.

Entrenamiento y desarrollo del modelo

Proceso educativo

  • Se propone crear un modelo basado en Transformer utilizando un conjunto de datos más pequeño llamado "Tiny Shakespeare".
  • Este conjunto contiene todas las obras de Shakespeare en un solo archivo, facilitando el entrenamiento del modelo.

Generación de texto

  • El objetivo es modelar cómo los caracteres siguen uno al otro para predecir secuencias similares a las escritas por Shakespeare.
  • Una vez entrenado, el sistema puede generar texto que imita el estilo shakespeariano.

Implementación práctica: Nano GPT

Repositorio GitHub

  • Se menciona la existencia del repositorio "Nano GPT" en GitHub, diseñado para entrenar Transformers con facilidad.

¿Cómo se entrena un modelo de lenguaje basado en Transformer?

Introducción al entrenamiento del modelo

  • Se entrena un modelo utilizando un conjunto de datos de texto abierto, reproduciendo el rendimiento de GPT-2, una versión temprana de OpenAI lanzada en 2017.
  • El objetivo es escribir un repositorio desde cero, comenzando con un archivo vacío y definiendo el Transformer pieza por pieza.

Preparación del conjunto de datos

  • Se utilizará el conjunto de datos "Tiny Shakespeare" para entrenar el modelo y generar texto similar a Shakespeare.
  • Se ha creado un cuaderno Jupyter en Google Colab para facilitar la compartición del código desarrollado durante la lección.

Análisis del conjunto de datos

  • Se descarga el conjunto de datos "Tiny Shakespeare", que tiene aproximadamente 1 MB, y se lee todo el texto como una cadena.
  • Al imprimir los primeros 1000 caracteres, se confirma que se está trabajando con aproximadamente 1 millón de caracteres.

Creación del vocabulario

  • Se utiliza Python para obtener todos los caracteres únicos presentes en el texto, creando así una lista ordenada que representa nuestro vocabulario.
  • El vocabulario contiene 65 elementos diferentes, incluyendo letras mayúsculas y minúsculas, espacios y caracteres especiales.

Tokenización del texto

  • La tokenización implica convertir el texto crudo en una secuencia de enteros según un vocabulario definido; aquí se construye un modelo a nivel de carácter.
  • Se desarrolla tanto un codificador como un decodificador para traducir cadenas arbitrarias a listas de enteros y viceversa.

Métodos alternativos de tokenización

  • Existen múltiples esquemas para la tokenización; por ejemplo, Google utiliza "SentencePiece", que codifica subunidades en lugar de palabras completas o caracteres individuales.
  • OpenAI emplea una biblioteca llamada "Tick Token" que utiliza codificación por pares (byte pair encoding), permitiendo trabajar con vocabularios más grandes (hasta 50.256 tokens).

Conclusiones sobre la tokenización simple

  • La elección entre tamaño del libro de códigos y longitud de las secuencias es crucial; se puede optar por secuencias largas con vocabularios pequeños o viceversa.

Introducción al Conjunto de Datos de Shakespeare

Preparación del Conjunto de Datos

  • Se utiliza la biblioteca PyTorch, específicamente torch.tensor, para codificar el texto de Shakespeare y convertirlo en un tensor de datos.
  • El tensor resultante representa una secuencia masiva de enteros que corresponde a los primeros 1000 caracteres del texto original, donde cada número puede representar caracteres como saltos de línea o espacios.

División del Conjunto de Datos

  • Se separa el conjunto de datos en un 90% para entrenamiento y un 10% para validación, lo cual es crucial para evaluar el sobreajuste del modelo.
  • La validación se mantiene oculta para evitar que el modelo memorice el texto exacto y en su lugar aprenda a generar texto similar al estilo de Shakespeare.

Entrenamiento del Modelo Transformer

Proceso de Entrenamiento

  • Se introducen las secuencias textuales (enteros) en el Transformer para que pueda aprender patrones sin procesar todo el texto a la vez, lo cual sería costoso computacionalmente.
  • Durante el entrenamiento, se utilizan fragmentos aleatorios del conjunto de datos, conocidos como "chunks", con un tamaño máximo definido como "block size".

Ejemplos dentro del Chunk

  • Un bloque inicial se define con un tamaño específico (8), permitiendo múltiples ejemplos dentro de una misma muestra.
  • Cada chunk permite hacer predicciones simultáneas en diferentes posiciones; por ejemplo, si hay nueve caracteres, se pueden extraer ocho ejemplos individuales.

Implementación Técnica

Código y Ejecución

  • Se presenta código que ilustra cómo los inputs (X) son los primeros caracteres y los targets (y) son los siguientes caracteres desplazados por uno.
  • Este enfoque no solo es eficiente desde una perspectiva computacional sino también necesario para entrenar al Transformer a manejar contextos variados durante la inferencia.

Dimensiones Adicionales

  • Además del tiempo, se introduce la dimensión batch; esto permite procesar múltiples chunks simultáneamente aprovechando la capacidad paralela de las GPUs.

Generación de Números Aleatorios y Procesamiento en Transformadores

Introducción a la Generación de Secuencias

  • Se establece una semilla en el generador de números aleatorios para asegurar que los números generados sean reproducibles.
  • El tamaño del bloque se refiere a la longitud máxima del contexto utilizada para hacer predicciones, con ejemplos como un tamaño de bloque de 4 o 8.

Preparación de Datos para Entrenamiento

  • Se generan desplazamientos aleatorios dentro del conjunto de datos para extraer fragmentos, asegurando que cada entrada sea independiente.
  • Los fragmentos se organizan en un tensor 4x8, donde cada fila representa un ejemplo independiente del conjunto de entrenamiento.

Estructura y Proceso del Transformador

  • La entrada al transformador es un tensor que contiene ejemplos independientes; las salidas son las respuestas correctas asociadas.
  • Cada fila del tensor representa ejemplos independientes, lo que permite al transformador procesar múltiples entradas simultáneamente.

Modelo de Lenguaje Bigram y su Implementación

Comienzo con Redes Neuronales Simples

  • Se introduce el modelo de lenguaje bigram como el más simple para modelar lenguaje, utilizando PyTorch para su implementación.
  • Se crea una tabla de embeddings donde cada índice corresponde a una fila específica en la tabla, facilitando la representación vectorial.

Predicción y Evaluación

  • Los logits se interpretan como puntuaciones para predecir el siguiente carácter basado solo en la identidad del token actual.

¿Cómo manejar la entrada de datos en PyTorch?

Problemas con la forma de los tensores

  • Se menciona que el código no se ejecutará correctamente y generará un mensaje de error, lo que lleva a la necesidad de entender cómo PyTorch espera las entradas.
  • PyTorch requiere que los tensores multi-dimensionales tengan las dimensiones organizadas como B x C x T, donde B es el tamaño del lote, C son los canales y T es el tiempo. Esto contrasta con la forma actual B x T x C.

Reorganización de tensores

  • Para cumplir con las expectativas de PyTorch, se sugiere renombrar las dimensiones y reorganizar los logits utilizando logits.view para convertirlos en un arreglo bidimensional.
  • La transformación busca preservar la dimensión del canal mientras se convierte a una estructura más adecuada para el procesamiento posterior.

Ajuste de objetivos

  • Los objetivos también deben ser reestructurados; actualmente tienen forma B x T y deben ser convertidos a una dimensión única B x T.
  • Se puede usar -1 en lugar de especificar explícitamente las dimensiones, ya que PyTorch puede inferir automáticamente la forma correcta.

Evaluación del modelo y generación de datos

Evaluación inicial del modelo

  • Se evalúa la pérdida inicial del modelo, obteniendo un valor de 4.87. Este valor indica que las predicciones iniciales no son muy difusas.
  • Se espera que la pérdida sea aproximadamente 4.1217 basándose en el logaritmo negativo de 1/65, sugiriendo que hay margen para mejorar en las predicciones.

Generación desde el modelo

  • Se introduce una función generate cuyo objetivo es extender un tensor B x T a B x (T + n), donde n representa nuevos tokens generados.
  • El proceso implica concatenar nuevas predicciones al índice anterior a lo largo de la dimensión temporal.

Proceso interno durante la generación

  • Durante la generación, se obtienen predicciones basadas solo en los últimos pasos temporales sin utilizar objetivos reales para calcular pérdidas.
  • Las probabilidades se obtienen mediante softmax sobre los logits extraídos y luego se muestrean usando torch.multinomial.

Manejo opcional de objetivos

  • Si no se proporcionan objetivos durante la generación, no habrá cálculo de pérdidas; esto permite flexibilidad al generar datos sin requerir etiquetas.

¿Cómo se inicia la generación en un modelo de lenguaje?

Introducción a la generación de texto

  • Se comienza la generación con el carácter cero, que representa un nuevo carácter de línea. Este es un punto razonable para iniciar una secuencia.
  • La función generate trabaja a nivel de lotes, lo que requiere indexar en la dimensión cero para obtener los pasos temporales como un arreglo unidimensional.

Proceso de decodificación

  • Los índices generados se convierten en texto mediante una función de decodificación. Sin embargo, el resultado inicial es aleatorio y poco útil debido a que el modelo no ha sido entrenado.
  • Aunque se alimenta al modelo con toda la secuencia, solo se utiliza la última parte para hacer predicciones, lo cual es ineficiente.

Entrenamiento del modelo

  • El enfoque actual es mantener la función flexible para futuros desarrollos donde el modelo pueda considerar más historia.
  • Se decide entrenar el modelo para mejorar su rendimiento y reducir su aleatoriedad.

¿Qué optimizador se utiliza y cómo afecta al entrenamiento?

Configuración del optimizador

  • Se crea un objeto de optimización utilizando Adam, preferido sobre SGD por ser más avanzado y efectivo en configuraciones típicas.
  • Para redes pequeñas, se pueden usar tasas de aprendizaje más altas; aquí se establece una tasa inicial alrededor de 3E negativo 4.

Ciclo típico de entrenamiento

  • En cada iteración del ciclo: muestreo de datos, evaluación de pérdida y actualización de parámetros usando gradientes.
  • La pérdida inicial era aproximadamente 4.7 y va disminuyendo progresivamente durante las iteraciones.

¿Cómo mejora el rendimiento del modelo tras varias iteraciones?

Resultados del entrenamiento

  • Después de diez mil iteraciones, la pérdida mejora significativamente hasta cerca de 2.5.
  • Aunque los resultados no son perfectos (no son comparables a Shakespeare), hay avances notables en comparación con las salidas iniciales aleatorias.

Interacción entre tokens

  • Es crucial que los tokens comiencen a interactuar entre sí para mejorar las predicciones basadas en contextos previos generados por el modelo.

¿Qué cambios se implementan al convertir código a script?

Simplificación del proceso

  • El código desarrollado en Jupyter Notebook se convierte en un script para simplificar el trabajo intermedio hacia un producto final más claro.

Nuevas funcionalidades añadidas

Cargar Datos y Modelos en Dispositivos

Transferencia de Datos a GPU

  • Es crucial mover los datos al dispositivo adecuado (GPU) al cargar, así como los parámetros del modelo. Esto permite que las operaciones se realicen más rápido.

Estimación de Pérdida en el Entrenamiento

  • Durante el bucle de entrenamiento, imprimir la pérdida actual puede ser ruidoso. Se sugiere utilizar una función de pérdida estimada que promedie la pérdida sobre múltiples lotes para obtener resultados más precisos.

Fase de Evaluación y Entrenamiento

  • Al cambiar entre fases de evaluación y entrenamiento, es importante considerar cómo se comporta el modelo. Aunque actualmente no hay capas que cambien su comportamiento, es buena práctica tener esto en cuenta.

Uso Eficiente de Memoria con torch.no_grad

Contexto sin Gradientes

  • Utilizar torch.no_grad indica a PyTorch que no se llamará a backward, lo cual mejora la eficiencia del uso de memoria al evitar almacenar variables intermedias innecesarias.

Resultados Iniciales del Script

Salida del Script

  • El script genera salidas en la terminal mostrando pérdidas de entrenamiento y validación, alcanzando un valor cercano a 2.5 con el modelo inicial.

Introducción al Bloque de Autoatención

Concepto Básico

  • Antes de implementar un bloque de autoatención, se introduce un truco matemático fundamental para entender su funcionamiento eficiente dentro del Transformer.

Interacción entre Tokens

Comunicación entre Tokens

  • En un lote con hasta ocho tokens, cada token debe comunicarse solo con sus predecesores inmediatos para evitar información futura durante la predicción.

Promedio como Método Inicial

Cálculo Promedio para Resumen

  • Para cada token en una secuencia, se calculará el promedio de los vectores informativos anteriores. Este método inicial es simple pero puede perder información sobre las disposiciones espaciales entre tokens.

Inicialización y Preparación para Procesar Tokens

Creación del Vector Promedio

¿Cómo optimizar el cálculo de promedios en matrices?

Iteración y Promedio Inicial

  • Se está utilizando un bucle for para iterar sobre todas las dimensiones del lote, lo que resulta ineficiente. La iteración se realiza de manera independiente sobre el tiempo y los tokens anteriores hasta el token actual.
  • Se calcula la media a lo largo de la dimensión cero, obteniendo un vector unidimensional que representa el promedio de los tokens previos en la secuencia actual.

Resultados del Promedio

  • El primer elemento del lote muestra que dos valores son iguales debido a que se está promediando solo un token. A medida que se avanza, los promedios incluyen más elementos.
  • El resultado final es un promedio vertical de todos los tokens, pero este método es muy ineficiente.

Multiplicación de Matrices como Solución

  • Se introduce la multiplicación de matrices como una solución matemática eficiente. Se presenta un ejemplo simple con matrices para ilustrar cómo se obtienen los resultados mediante productos punto.
  • Los elementos en la matriz resultante C se calculan sumando productos punto entre filas y columnas correspondientes.

Uso de Funciones Especializadas

  • Se menciona una función llamada trell, que permite obtener solo la parte triangular inferior de una matriz, ignorando ciertos elementos durante el cálculo.
  • Al aplicar esta función, algunos elementos son ignorados en el producto punto, permitiendo extraer filas específicas de B según corresponda.

Promedios Incrementales y Normalización

  • Dependiendo del número de unos y ceros en las matrices manipuladas, se pueden realizar sumas o promedios incrementales al normalizar las filas para que sumen uno.
  • Al dividir A por su suma total a lo largo de una dimensión específica, se logra que cada fila sume uno. Esto permite calcular promedios al multiplicar A por B.

Vectorización y Eficiencia Mejorada

  • Se propone crear un arreglo llamado "way" (pesos), donde cada fila indica cuánto contribuirá al promedio. Este arreglo debe sumar uno para asegurar cálculos correctos.

Multiplicación de Matrices en Lotes

Proceso de Multiplicación de Matrices

  • Se realiza una multiplicación de matrices en lotes, aplicando la operación a todos los elementos del lote en paralelo y de manera individual.
  • El resultado es una matriz con dimensiones B por T por C, donde se verifica que dos matrices (Expo y Expo 2) son idénticas utilizando torch.allclose.

Agregación y Sumas Ponderadas

  • La clave está en usar la multiplicación de matrices en lotes para realizar una agregación ponderada, donde los pesos están especificados en un arreglo T por T.
  • Las sumas ponderadas siguen una forma triangular, permitiendo que un token solo reciba información de los tokens precedentes.

Uso del Softmax

Implementación del Softmax

  • Se introduce el uso de softmax para normalizar las entradas; inicialmente, todos los elementos son cero.
  • Al aplicar masked fill, se convierten los ceros en negativos infinitos, lo que impide la comunicación entre ciertos tokens.

Normalización y Resultados

  • Al aplicar softmax a cada fila, se normalizan los valores resultantes. Esto produce un resultado donde algunos elementos son uno y otros son cero.
  • Este proceso permite obtener una máscara similar a la anterior mediante exponentiación y normalización.

Interacción entre Tokens

Fortalecimiento de Relaciones entre Tokens

  • Los pesos iniciales comienzan como ceros, representando la fuerza o afinidad entre tokens pasados.
  • Se establece que los tokens futuros no pueden comunicarse con el pasado al establecer sus valores como negativos infinitos.

Dependencia de Datos

  • Las afinidades entre tokens no serán constantes; dependerán del contenido específico. Algunos tokens encontrarán otros más interesantes según sus valores.

Agregaciones Ponderadas

Resumen sobre Autoatención

  • Se puede realizar agregaciones ponderadas utilizando multiplicaciones matriciales en forma triangular inferior.
  • Los elementos en esta parte triangular indican cuánto contribuye cada elemento a su posición correspondiente.

Preliminares para el Bloque de Autoatención

  • Se menciona que no es necesario pasar el tamaño del vocabulario al constructor ya que ya está definido globalmente.

Creación de Embeddings

Introducción a Embeddings Intermedios

  • Se propone crear un nivel intermedio antes de llegar a logits para facilitar futuras expansiones.
  • Se define un nuevo variable embed para representar dimensiones embebidas (por ejemplo, 32).

Conexión entre Embeddings y Logits

Implementación de Atención en Modelos de Lenguaje

Introducción a la Codificación de Tokens y Posiciones

  • Se discute cómo se codifican los tokens basándose en su identidad, utilizando una capa lineal para la interacción.
  • Se introduce una tabla de embeddings posicionales que asigna un vector de embedding único a cada posición desde 0 hasta el tamaño del bloque menos uno.
  • Los embeddings posicionales se combinan con los embeddings de tokens, creando una representación que incluye tanto la identidad como la posición de los tokens.

Importancia del Modelo de Atención

  • Aunque al principio la información posicional no parece útil, se vuelve relevante al implementar bloques de auto-atención.
  • Se presenta el concepto central de auto-atención, crucial para entender cómo funciona este mecanismo en modelos complejos.

Implementación del Mecanismo de Auto-Attention

  • Se trabaja con un ejemplo donde se cambia el número de canales a 32, manteniendo una disposición 4x8 para los tokens.
  • El código inicial realiza un promedio simple entre todos los tokens pasados y el token actual, utilizando una estructura triangular inferior para enmascarar la matriz de pesos.

Dependencia Basada en Datos

  • La uniformidad inicial no es deseable; diferentes tokens deben encontrar otros más interesantes según su contexto.
  • La auto-atención permite que cada token emita dos vectores: uno para consulta (query) y otro para clave (key), facilitando así la recolección selectiva de información.

Cálculo de Afinidades entre Tokens

  • Las afinidades entre los nodos o tokens se calculan mediante el producto punto entre las claves y las consultas.
  • Si las claves y consultas están alineadas, esto incrementa significativamente la interacción entre ellos, permitiendo aprender más sobre un token específico.

Configuración Final del Mecanismo

  • Se implementa un solo "head" (cabeza) dentro del mecanismo de atención; hay un parámetro hiper que define el tamaño del head.
  • Al inicializar módulos lineales sin sesgo, se producen vectores clave y consulta al aplicar multiplicaciones matriciales sobre x.

¿Cómo funciona la atención en redes neuronales?

Agregación ponderada y dependiente de datos

  • La agregación ponderada ahora se ejecuta de manera dependiente de los datos entre las claves y consultas de los nodos, lo que permite que cada elemento del lote tenga un peso diferente.
  • Los pesos ya no son uniformes; por ejemplo, el octavo token genera una consulta específica basada en su posición y contenido, buscando consonantes en posiciones anteriores.

Interacción entre tokens

  • Cada token emite claves que pueden coincidir con las consultas de otros tokens, creando afinidades altas cuando hay coincidencias relevantes.
  • Cuando hay alta afinidad entre un token y otro, se utiliza softmax para agregar información relevante al token consultante.

Proceso interno sin enmascaramiento

  • Sin el enmascaramiento ni softmax, los resultados brutos muestran interacciones negativas y positivas entre nodos.
  • Se aplica un enmascaramiento triangular superior para evitar que ciertos nodos se comuniquen entre sí.

Normalización y cálculo de valores

  • Se normalizan las interacciones para obtener una distribución adecuada que indique cuánto información agregar desde otros tokens.
  • Además de la consulta (query), se introduce un valor (value), que es el vector agregado durante la operación.

Mecanismo de auto-atención

  • El mecanismo de auto-atención produce una salida dimensional correspondiente al tamaño del cabezal; X representa información privada del token mientras V es lo que se comunica a otros nodos.
  • La auto-atención actúa como un mecanismo de comunicación donde cada nodo agrega información mediante sumas ponderadas basadas en vectores almacenados.

Estructura del grafo dirigido

  • En este contexto, el grafo tiene ocho nodos debido al tamaño del bloque; cada nodo apunta a sí mismo o a nodos anteriores.
  • A diferencia de otras estructuras gráficas, aquí no hay noción espacial inherente; esto requiere codificación posicional para proporcionar contexto sobre la ubicación relativa.

Comparación con convoluciones

  • A diferencia de las operaciones convolucionales que dependen fuertemente del espacio físico, la atención opera sobre conjuntos abstractos de vectores sin referencia espacial directa.

¿Cómo se estructura la atención en modelos de lenguaje?

Conceptos básicos sobre el espacio y la independencia de ejemplos

  • Se necesita agregar específicamente una noción de espacio al calcular las codificaciones de posición, lo que se ha hecho al añadir esta información a los vectores.
  • Los elementos a través de la dimensión del lote son ejemplos independientes que no interactúan entre sí; se procesan de manera independiente mediante multiplicación matricial en paralelo.
  • En el modelado del lenguaje, existe una estructura específica donde los tokens futuros no se comunican con los tokens pasados, aunque esto puede variar según el caso.

Estructura del modelo y bloques de atención

  • En análisis de sentimientos, todos los nodos pueden comunicarse entre sí para predecir aspectos como el sentimiento de una oración; esto requiere un bloque encoder con auto-atención.
  • Un bloque decoder permite que los nodos futuros no hablen con los pasados mediante una matriz triangular, asegurando que no se revele la respuesta anticipada.

Diferencias entre auto-atención y atención cruzada

  • La auto-atención implica que las claves, consultas y valores provienen de la misma fuente. Sin embargo, en atención cruzada, las claves y valores pueden provenir de fuentes externas diferentes.
  • La atención cruzada es útil cuando hay un conjunto separado de nodos del cual queremos extraer información adicional.

Implementación técnica y normalización

  • En el artículo "Attention is All You Need", ya se ha implementado la atención utilizando consultas, claves y valores; falta dividir por la raíz cuadrada del tamaño del cabezal para normalizar adecuadamente.
  • Esta normalización es crucial porque evita que las salidas sean demasiado extremas durante la inicialización; si son muy positivas o negativas, softmax convergerá hacia vectores one-hot.

Importancia del escalado en softmax

  • El escalado controla la varianza durante la inicialización para evitar resultados extremos en softmax. Si los pesos son demasiado altos o bajos, afectarán negativamente cómo se agregan las informaciones entre nodos.

Implementación de Atención Escalada en Redes Neuronales

Creación de Variables y Proyecciones Lineales

  • Se introducen proyecciones lineales que se aplican a todos los nodos. Se crea una variable llamada "Trill", que no es un parámetro del módulo, sino un buffer que debe ser registrado.

Cálculo de Claves y Consultas

  • Al recibir la entrada X, se calculan las claves y consultas utilizando atención escalada. Se asegura que una característica no se comunique con el pasado, lo que convierte este bloque en un bloque decodificador.

Encabezado de Autoatención

  • En el constructor, se crea un encabezado de autoatención llamado "self attention head". Se mantiene el tamaño del encabezado igual al tamaño del embedding para facilitar la codificación.

Ajustes en el Tamaño del Bloque

  • Es necesario asegurarse de que el índice alimentado al modelo no exceda el tamaño del bloque, ya que esto podría causar problemas con la tabla de embeddings posicionales.

Entrenamiento Inicial y Resultados

  • Se disminuye la tasa de aprendizaje debido a la sensibilidad de la autoatención a tasas altas. A pesar de una ligera mejora en la pérdida (de 2.5 a 2.4), los resultados generados aún no son satisfactorios.

Atención Multi-Cabeza: ¿Qué es y Cómo Funciona?

Implementación Sencilla

  • La atención multi-cabeza implica aplicar múltiples atenciones en paralelo y concatenar los resultados. Esto permite tener varios canales independientes para mejorar la comunicación entre tokens.

Estructura Paralela en PyTorch

  • En PyTorch, se pueden crear múltiples cabezas según sea necesario, ejecutándolas en paralelo y concatenando las salidas sobre la dimensión del canal.

Comparativa con Convoluciones

  • La atención multi-cabeza puede compararse con convoluciones grupales; cada canal tiene vectores dimensionales más pequeños pero juntos forman un vector original más grande.

Mejora en Pérdida Validada

  • Tras implementar atención multi-cabeza, se observa una reducción adicional en la pérdida (de 2.4 a aproximadamente 2.28). Aunque los resultados generados siguen sin ser óptimos, hay mejoras notables en la validación.

Componentes Adicionales: Feed Forward y Repetición

Componentes Implementados

  • Se han implementado componentes como codificaciones posicionales y codificaciones tokenizadas junto con atención multi-cabeza.

Red Neuronal Feed Forward

  • El componente feed forward consiste en una red neuronal multicapa simple (MLP). Este elemento es crucial para añadir computación adicional al nivel por nodo dentro de la red.

Reflexiones sobre Comunicación entre Tokens

Implementación de una Red Neuronal Profunda

Estructura de la Capa Feed Forward

  • Se implementa una capa feed forward simple que consiste en una transformación lineal seguida de una no linealidad relativa. Esta capa se aplica secuencialmente después de la auto-atención.

Proceso de Auto-atención y Cálculo Independiente

  • La capa feed forward opera a nivel de cada token, permitiendo que cada uno procese la información independientemente tras la comunicación inicial proporcionada por la auto-atención.

Mejora del Pérdida de Validación

  • Durante el entrenamiento, se observa que la pérdida de validación disminuye a 2.24 desde 2.28, aunque los resultados aún son insatisfactorios. Se planea intercalar comunicación con cálculo, como lo hace el Transformer.

Bloques en el Transformer

  • Se describe un bloque que combina comunicación (auto-atención multi-cabeza) y cálculo (redes feed forward), aplicándose a todos los tokens independientemente para mejorar el rendimiento.

Optimización y Conexiones Residuales

  • Para abordar problemas de optimización en redes neuronales profundas, se introducen conexiones residuales, que permiten transformar datos mientras mantienen un camino directo para las características anteriores.

Visualización del Camino Residual

  • Las conexiones residuales permiten bifurcarse durante el cálculo y regresar al camino residual mediante adición, facilitando así la propagación eficiente del gradiente hacia las entradas sin obstáculos.

Implementación de Conexiones Residuales

Optimización de Redes Neuronales Profundas

Cambios en la Dimensionalidad

  • Se observa que la dimensionalidad de entrada y salida es 512, mientras que la capa interna de la red feedforward tiene una dimensionalidad de 2048, lo que implica un multiplicador de cuatro.
  • Al aumentar el tamaño del canal en la capa interna, se logra una pérdida de validación de 2.08, aunque comienza a notarse un sobreajuste debido a que la pérdida de entrenamiento supera a la pérdida de validación.

Implementación de Layer Norm

  • Se introduce el concepto de Layer Norm, similar a Batch Normalization, para optimizar redes neuronales profundas. Esto normaliza cada neurona individualmente en función del lote.
  • La implementación inicial se basa en Batch Normalization, asegurando que cada columna tenga una distribución gaussiana con media cero y desviación estándar uno.

Ajustes en Layer Norm

  • Se modifica el enfoque para normalizar las filas en lugar de las columnas, permitiendo así eliminar los buffers innecesarios durante el entrenamiento y prueba.
  • La nueva formulación pre-norm aplica Layer Norm antes de las transformaciones dentro del Transformer, lo cual es un cambio respecto al enfoque original.

Estructura del Transformer

  • Se implementan dos capas Layer Norm: una antes y otra después del proceso principal. Esto asegura que las características sean normalizadas adecuadamente durante el entrenamiento.
  • A pesar de que los parámetros gamma y beta son entrenables, eventualmente se ajustarán para no mantener siempre una distribución gaussiana unitaria.

Resultados y Mejoras

  • Tras incorporar Layer Norm, se observa una mejora ligera en la pérdida (de 2.08 a 2.06), sugiriendo beneficios adicionales con redes más grandes y profundas.

¿Cómo se implementa el Dropout en redes neuronales?

Concepto de Dropout

  • El Dropout se aplica justo antes de la conexión residual, permitiendo que algunas neuronas no se comuniquen aleatoriamente durante el entrenamiento.
  • Esta técnica, introducida en un artículo de 2014, apaga aleatoriamente un subconjunto de neuronas en cada pasada hacia adelante y hacia atrás, lo que ayuda a prevenir el sobreajuste.

Efectos del Dropout

  • Al cambiar la máscara de neuronas apagadas en cada iteración, se entrena efectivamente un conjunto de subredes. Al final, todas estas subredes se combinan en un solo modelo.
  • Se añadió esta técnica para escalar el modelo y mitigar preocupaciones sobre el sobreajuste.

Ajustes del Modelo

  • Se modificaron varios hiperparámetros: tamaño del lote aumentado a 64, tamaño del bloque a 256 caracteres y tasa de aprendizaje reducida debido al aumento del tamaño del modelo.
  • La dimensión de embedding ahora es 384 con seis cabezas (64 dimensiones por cabeza), y una tasa de Dropout establecida en 0.2.

Resultados del Entrenamiento

  • Después del entrenamiento, se obtuvo una pérdida de validación mejorada a 1.48 desde 2.07, mostrando la efectividad del ajuste y uso de Dropout.
  • El modelo fue entrenado durante aproximadamente 15 minutos en una GPU A100; no es recomendable ejecutarlo en CPU debido a su complejidad.

¿Qué tipo de arquitectura tiene este Transformer?

Características Generales

  • El Transformer implementado es solo un decodificador sin componente encoder ni bloques de atención cruzada; solo incluye auto-atención y red neuronal feedforward.

Propósito del Decodificador

  • Este diseño permite generar texto sin condiciones externas; utiliza una máscara triangular para mantener propiedades autorregresivas durante la generación.

Comparación con Arquitectura Original

  • La arquitectura original incluía tanto encoder como decodificador para tareas como traducción automática, donde los tokens deben ser condicionados por información adicional (ej., frases en francés).

Proceso Generativo

¿Cómo funciona un modelo Transformer en el procesamiento de lenguaje?

Proceso de codificación y decodificación

  • El encoder procesa la parte del texto en francés, creando tokens sin una máscara triangular, permitiendo que todos los tokens se comuniquen entre sí durante la codificación.
  • En el decodificador, se establece una conexión adicional con las salidas del encoder a través de una atención cruzada, donde las consultas provienen de X y las claves y valores del encoder.
  • La atención cruzada condiciona la decodificación no solo en el pasado sino también en el contenido completo del prompt francés previamente codificado.
  • Se utiliza un modelo encoder-decoder debido a esta estructura dual de Transformers; sin embargo, para ciertos casos como GPT, solo se usa un decodificador.

Implementación de Nano GPT

  • Nano GPT consiste principalmente en dos archivos: train.py y model.py, donde train.py contiene el código base para entrenar la red neuronal.
  • El archivo train.py es más complejo ya que incluye opciones para guardar y cargar puntos de control, ajustar tasas de aprendizaje y realizar entrenamiento distribuido.
  • El bloque de auto-atención causal es similar al discutido anteriormente; se generan consultas, claves y valores mediante productos punto y aplicando softmax.
  • Se ha separado la atención multi-cabeza en cabezas individuales concatenadas; esto permite manejar tensores con cuatro dimensiones para mayor eficiencia computacional.

Comparación con ChatGPT

  • Al considerar cómo entrenar ChatGPT, hay dos etapas principales: pre-entrenamiento (entrenar sobre grandes volúmenes de datos) y ajuste fino (fine-tuning).
  • Durante el pre-entrenamiento, se entrena un Transformer solo decodificador utilizando grandes cantidades de texto disponible en internet.
  • Se menciona que el modelo creado tiene aproximadamente 10 millones de parámetros con un conjunto de datos alrededor de 1 millón de caracteres o 300 mil tokens según vocabulario utilizado por OpenAI.

¿Cómo se entrena un modelo de lenguaje grande?

Etapas del entrenamiento

  • Se está entrenando un modelo significativamente más grande utilizando una buena parte de internet, lo que representa un desafío masivo en infraestructura. Esto implica el uso de miles de GPUs que deben comunicarse entre sí para entrenar modelos de este tamaño.
  • Al finalizar la etapa de pre-entrenamiento, el modelo no responde preguntas directamente; en cambio, actúa como un completador de documentos, generando artículos y textos arbitrarios sin coherencia específica.
  • El comportamiento del modelo puede ser indefinido: podría responder con más preguntas o ignorar la pregunta original, ya que su objetivo es completar secuencias basadas en los datos con los que fue entrenado.

Ajuste fino del modelo

  • La segunda etapa consiste en alinear el modelo para que funcione como asistente. Esto se logra recolectando datos específicos donde las preguntas están seguidas por respuestas adecuadas.
  • Se utilizan miles de ejemplos para afinar el modelo, enfocándose solo en documentos con formato pregunta-respuesta. Este proceso ayuda a preparar al modelo para esperar preguntas y generar respuestas coherentes.

Proceso adicional de ajuste fino

  • Después del primer ajuste fino, se permite que el modelo responda y diferentes evaluadores clasifican estas respuestas según sus preferencias. Esta información se utiliza para crear un modelo de recompensa.
  • Con el modelo de recompensa establecido, se aplica PPO (Proximal Policy Optimization), una técnica de aprendizaje por refuerzo, para optimizar la política de muestreo y mejorar las respuestas generadas por GPT.

Limitaciones y consideraciones finales

  • La fase final lleva al modelo desde ser un simple completador a convertirse en un sistema capaz de responder preguntas. Sin embargo, muchos datos utilizados son internos a OpenAI y no están disponibles públicamente.
  • Aunque Nano GPT se centra principalmente en la etapa de pre-entrenamiento, hay muchas etapas adicionales necesarias si se desea realizar tareas específicas o detectar sentimientos más allá del modelado básico del lenguaje.

Resultados y recursos compartidos

  • Se ha desarrollado un Transformer solo decodificador basado en el famoso artículo "Attention is All You Need" (2017), logrando resultados sensatos tras ser entrenado con textos pequeños como Shakespeare.
Video description

We build a Generatively Pretrained Transformer (GPT), following the paper "Attention is All You Need" and OpenAI's GPT-2 / GPT-3. We talk about connections to ChatGPT, which has taken the world by storm. We watch GitHub Copilot, itself a GPT, help us write a GPT (meta :D!) . I recommend people watch the earlier makemore videos to get comfortable with the autoregressive language modeling framework and basics of tensors and PyTorch nn, which we take for granted in this video. Links: - Google colab for the video: https://colab.research.google.com/drive/1JMLa53HDuA-i7ZBmqV7ZnA3c_fvtXnx-?usp=sharing - GitHub repo for the video: https://github.com/karpathy/ng-video-lecture - Playlist of the whole Zero to Hero series so far: https://www.youtube.com/watch?v=VMj-3S1tku0&list=PLAqhIrjkxbuWI23v9cThsA9GvCAUhRvKZ - nanoGPT repo: https://github.com/karpathy/nanoGPT - my website: https://karpathy.ai - my twitter: https://twitter.com/karpathy - our Discord channel: https://discord.gg/3zy8kqD9Cp Supplementary links: - Attention is All You Need paper: https://arxiv.org/abs/1706.03762 - OpenAI GPT-3 paper: https://arxiv.org/abs/2005.14165 - OpenAI ChatGPT blog post: https://openai.com/blog/chatgpt/ - The GPU I'm training the model on is from Lambda GPU Cloud, I think the best and easiest way to spin up an on-demand GPU instance in the cloud that you can ssh to: https://lambdalabs.com . If you prefer to work in notebooks, I think the easiest path today is Google Colab. Suggested exercises: - EX1: The n-dimensional tensor mastery challenge: Combine the `Head` and `MultiHeadAttention` into one class that processes all the heads in parallel, treating the heads as another batch dimension (answer is in nanoGPT). - EX2: Train the GPT on your own dataset of choice! What other data could be fun to blabber on about? (A fun advanced suggestion if you like: train a GPT to do addition of two numbers, i.e. a+b=c. You may find it helpful to predict the digits of c in reverse order, as the typical addition algorithm (that you're hoping it learns) would proceed right to left too. You may want to modify the data loader to simply serve random problems and skip the generation of train.bin, val.bin. You may want to mask out the loss at the input positions of a+b that just specify the problem using y=-1 in the targets (see CrossEntropyLoss ignore_index). Does your Transformer learn to add? Once you have this, swole doge project: build a calculator clone in GPT, for all of +-*/. Not an easy problem. You may need Chain of Thought traces.) - EX3: Find a dataset that is very large, so large that you can't see a gap between train and val loss. Pretrain the transformer on this data, then initialize with that model and finetune it on tiny shakespeare with a smaller number of steps and lower learning rate. Can you obtain a lower validation loss by the use of pretraining? - EX4: Read some transformer papers and implement one additional feature or change that people seem to use. Does it improve the performance of your GPT? Chapters: 00:00:00 intro: ChatGPT, Transformers, nanoGPT, Shakespeare baseline language modeling, code setup 00:07:52 reading and exploring the data 00:09:28 tokenization, train/val split 00:14:27 data loader: batches of chunks of data 00:22:11 simplest baseline: bigram language model, loss, generation 00:34:53 training the bigram model 00:38:00 port our code to a script Building the "self-attention" 00:42:13 version 1: averaging past context with for loops, the weakest form of aggregation 00:47:11 the trick in self-attention: matrix multiply as weighted aggregation 00:51:54 version 2: using matrix multiply 00:54:42 version 3: adding softmax 00:58:26 minor code cleanup 01:00:18 positional encoding 01:02:00 THE CRUX OF THE VIDEO: version 4: self-attention 01:11:38 note 1: attention as communication 01:12:46 note 2: attention has no notion of space, operates over sets 01:13:40 note 3: there is no communication across batch dimension 01:14:14 note 4: encoder blocks vs. decoder blocks 01:15:39 note 5: attention vs. self-attention vs. cross-attention 01:16:56 note 6: "scaled" self-attention. why divide by sqrt(head_size) Building the Transformer 01:19:11 inserting a single self-attention block to our network 01:21:59 multi-headed self-attention 01:24:25 feedforward layers of transformer block 01:26:48 residual connections 01:32:51 layernorm (and its relationship to our previous batchnorm) 01:37:49 scaling up the model! creating a few variables. adding dropout Notes on Transformer 01:42:39 encoder vs. decoder vs. both (?) Transformers 01:46:22 super quick walkthrough of nanoGPT, batched multi-headed self-attention 01:48:53 back to ChatGPT, GPT-3, pretraining vs. finetuning, RLHF 01:54:32 conclusions Corrections: 00:57:00 Oops "tokens from the _future_ cannot communicate", not "past". Sorry! :) 01:20:05 Oops I should be using the head_size for the normalization, not C