The post Prompt Engineering para Desarrolladores first appeared on Aprende Machine Learning.
]]>Ahora que ya cuentas con tu LLM en Local, como explicamos en el artículo “Instala un LLM en Local”, podemos encenderlo en modo Servidor y comenzar a jugar con él desde nuestro código python.
En este artículo usaremos una Jupyter Notebook que puedes ver y descargar desde GitHub y realizar las actividades de Prompt Engineering.
Vamos a comenzar explicando los conceptos más importantes a la hora de pedir tareas a un Gran Modelo del Lenguaje y veremos como iterar sobre diversos casos de uso para mejorar el resultado final. Por último plantearemos el código para crear un Chatbot que guíe al cliente en sus compras en un ecommerce.
El término Prompt Engineer surgió cuando los primeros Grandes Modelos de Lenguaje cómo (GPT-2 en 2019, GPT-3 en 2020) comenzaban a aparecer y encerrar en su interior los misterios del lenguaje humano. Entonces hacer prompt Engineer trataba de “encontrar de forma artística” la mejor forma de obtener buenas respuestas de estos modelos. De hecho, la técnica muchas veces consistía en hackear al modelo, descubrir vulnerabilidades y fortalezas. De las diversas y a veces aleatorias fórmulas utilizadas por los usuarios de la comunidad, el Prompt Engineer gana fuerza como una tarea en sí misma (y no como un complemento) en donde el saber cómo realizar la petición al modelo tenía salidas precisas y concretas.
Los actuales grandes modelos (de 2024) tienen “billones” de parámetros y si bien tenemos algo más de comprensión sobre su comportamiento -sabemos que son modelos estadísticos- lo cierto es que aún no tenemos un mapa completo de cómo se comportan. Esto da lugar a que el Prompt Engineering (“cómo consultamos el LLM”) siga siendo una parte importante de nuestra tarea como científicos de datos o Ingenieros de datos.
Lo cierto es que ahora un LLM puede ser una pieza más del sistema, por lo que debemos poder fiarnos de que tendremos la respuesta apropiada (y en el formato buscado).
Hagamos un mini repaso antes de empezar; hay dos tipos de LLMS, los “LLM Base” (fundacional) y los “LLM tuneados con Instrucciones” (en inglés Instruction Tuned LLM). Los primeros entrenados únicamente para predecir la siguiente palabra. Los tuneados en Instrucciones están entrenados sobre los Base; pero pueden seguir indicaciones, eso los vuelve mucho más útiles para poder llevar adelante una conversación. Además, al agregar el RLHF, es decir, un paso adicional luego de Tunearlos en donde mediante el feedback de personas humanas se mejora la redacción de respuestas penalizando o premiando al modelo. El RLHF también funciona como una capa de censura para ciertas palabras o frases no deseadas.
Estas LLMs que siguen instrucciones son ajustadas con el objetivo de ser “utiles, honestas e inofensivas” (en inglés Helpful, Honest, Harmless) intentan ser lo menos tóxicas posibles. De ahí la importancia de la limpieza del dataset inicial con el que fueron entrenadas las “LLM base”.
Ten esto en cuenta cuando descargues o elijas qué LLM utilizar. Para la mayoría de aplicaciones deberás seleccionar una version de LLM que sea de Instrucciones y no base. Por ejemplo para modelos Llama 2 encontrarás versiones “raw” o base, pero generalmente queremos utilizar las tuneadas en instrucciones. A veces se les denomina como “versión chat”.
¿Qué es lo que tienes que hacer para lograr buenas respuestas con tu LLM?
Veamos los dos principios básicos:
Veamos como hacerlo con ejemplos en python; siguiendo una Jupyter Notebook.
Recuerda iniciar tu modelo en LM Studio, en mi caso, estoy utilizando laser-dolphin-mixtral en mi Mac.
Y tampoco olvides instalar el paquete de OpenAI ejecutando “pip install openai==1.13.3” en el ambiente de Python en el que te encuentres trabajando. Inicia en LM Studio el Servidor Local, para poder utilizar la API.
Importamos las librerías que utilizaremos y creamos una función “get_completion” que nos facilitará la obtención del resultado del modelo. Como verás ponemos el valor de Temperatura a cero, eso nos proporciona menos “creatividad” por parte del modelo para poder reproducir resultados (de no hacerlo, podríamos obtener distintas respuestas cada vez que ejecutemos el mismo prompt). También notarás que definimos un rol de Sistema para lograr que el LLM nos responda siempre en español.
Recuerda: Si bien utilizamos el paquete de openai, estaremos haciendo las consultas a nuestro modelo local y gratuito
from openai import OpenAI # Point to the local server client = OpenAI(base_url="http://localhost:1234/v1", api_key="lm-studio") modelo = "local-model" # si tienes la API de pago: gpt-4 , gpt-3.5, etc def get_completion(prompt:str, model:str=modelo, temperature:float=0): messages = [{ "role": "system", "content": "Eres un asistente en español y ayudas respondiendo con la mayor exactitud posible.", }, {"role": "user", "content": prompt}] response = client.chat.completions.create( model=model, messages=messages, temperature=temperature ) return response.choices[0].message.content
Volvamos al primer Principio “dar instrucciones claras y específicas”, esto mejorará las chances de que el modelo de respuestas erróneas o incorrectas. OJO, no confundir el dar respuestas cortas con “claras”. En la mayoría de casos, las respuestas largas proveen mejores explicaciones y detalle que respuestas cortas.
Una buena ayuda para aclarar nuestros prompts es la de usar delimitadores como triple comillas o guiones. Veamos un ejemplo en donde en el prompt definimos un delimitador con el texto al que queremos que el LLM ponga atención:
text = f""" La sonda Voyager 1 ha vuelto a dar señales de vida. Hace poco te contaba en un vídeo largo de mi canal que los ingenieros de la NASA \ habían perdido completamente la comunicación con la sonda Voyager 1, que lleva haciendo ciencia durante décadas. \ Tenemos buenas noticias y es que nos están llegando señales coherentes de esta sonda. El día 3 de este mes nos llegó una comunicación \ desde el espacio, que aun no siendo legible, tenía buena pinta, así que alguien de la NASA la decodificó y resulta que su contenido es importante. \ Se trata de un volcado completo del sistema de datos de vuelo, que recoge los datos de todos los Instrumentos y sensores que aún están \ funcionando, de las variables internas de la sonda y otros datos adicionales. \ Por supuesto esto ha dado esperanzas al equipo de la Voyager 1, que ahora se está planteando intentar recuperar la comunicación con \ la sonda de alguna forma. """ prompt = f""" Debes resumir en muy pocas palabras el siguiente texto delimitado por triple comilla simple: ```{text}``` """ response = get_completion(prompt) print(response) # SALIDA: un resumen del texto.
Otra táctica es solicitar al modelo salida estructurada. Por ejemplo salidas JSON o HTML.
prompt = f""" Genera una lista con tres títulos inventados de libros sobre lo bueno que es volar y el nombre del autor. Provee una salida en formato JSON con las siguientes claves: libro_id, titulo, autor, año. """ response = get_completion(prompt) print(response)
La salida puede ser usada directamente como un diccionario python, esto puede ser muy útil para nuestras apps! Esta es la salida que obtuve:
{ "libro_1": { "titulo": "Volar con alas de acero", "autor": "Juan Pérez", "año": 2023 }, "libro_2": { "titulo": "El secreto del vuelo", "autor": "María Gómez", "año": 2021 }, "libro_3": { "titulo": "Viajes en el cielo", "autor": "Carl<|im_start|> Mendoza", "año": 2022 } }
Los tiempos de Salida del modelo pueden variar, en mi caso en un ordenador sin GPU el tiempo de respuesta puede ser de 1 o dos minutos. Si cuentas con GPU el tiempo de respuesta podría ser de apenas segundos.
Si estás pensando en comprar una GPU… aquí te dejo algunos enlaces con precios de Amazon España:
La tercer táctica es pedir al modelo que revise si las condiciones son satisfactorias y si no se cumplen indicarlo con un mensaje explicito.
text_1 = f""" Instrucciones para dar cuerda al reloj Allá al fondo está la muerte, pero no tenga miedo. Sujete el reloj con una mano, tome con dos dedos la llave de la cuerda, remóntela suavemente. Ahora se abre otro plazo, los árboles despliegan sus hojas, las barcas corren regatas, el tiempo como un abanico se va llenando de sí mismo y de él brotan el aire, las brisas de la tierra, la sombra de una mujer, el perfume del pan. ¿Qué más quiere, qué más quiere? Atelo pronto a su muñeca, déjelo latir en libertad, imítelo anhelante. El miedo herrumbra las áncoras, cada cosa que pudo alcanzarse y fue olvidada va corroyendo las venas del reloj, gangrenando la fría sangre de sus rubíes. Y allá en el fondo está la muerte si no corremos y llegamos antes y comprendemos que ya no importa. """ prompt = f""" Te pasaré un texto delimitado por triple comillas. Si contiene una secuencia de instrucciones, re-escribe esas instrucciones siguiendo el siguiente formato: Paso 1 - ... Paso 2 - … … Paso N - … Si el texto no contiene instrucciones, simplemente responde \"No hay instrucciones.\" \"\"\"{text_1}\"\"\" """ response = get_completion(prompt) print("Respuesta:") print(response)
Tenemos como salida:
Respuesta: "Paso 1 - Sujete el reloj con una mano." "Paso 2 - Tome con dos dedos la llave de la cuerda, remóntela suavemente." "Paso 3 - Ahora se abre otro plazo, los árboles despliegan sus hojas, las barcas corren regatas, el tiempo como un abanico se va llenando de sí mismo y de él brotan el aire, las brisas de la tierra, la sombra de una mujer, el perfume del pan." "Paso 4 - ¿Qué más quiere, qué más quiere? Atelo pronto a su muñeca, déjelo latir en libertad, imítelo anhelante." "Paso 5 - El miedo herrumbra las áncoras, cada cosa que pudo alcanzarse y fue olvidada va corroyendo las venas del reloj, gangrenando la fría sangre de sus rubíes." "Paso 6 - Y allá en el fondo está la muerte si no corremos y llegamos antes y comprendemos que ya no importa."
La cuarta técnica para prompts claros, será la de “Few shot prompting“. Esto consiste en proveer de ejemplos correctos del tipo de respuesta que buscamos antes de pedir una tarea.
prompt = f""" Tu tarea es responder siguiendo el mismo estilo que ves a continuación. <Juan>: Esa nube tiene forma de triángulo. <Músico>: Me recuerda al disco de Pink Floyd. <Juan>: Esa otra nube tiene forma de lengua. <Músico>: Me recuerda al disco de los Rolling Stones. <Juan>: Esa tiene forma de círculo. <Músico>:""" response = get_completion(prompt) print(response)
Salida:
<Músico>: Ah, entonces me recuerda al disco de los Beatles.
El segundo principio es el de “Darle tiempo al modelo a pensar (reflexionar)“
Si las respuestas del modelo son incorrectas ante una tarea compleja, puede ser debido a que se apresura en responder, eso a veces nos pasa a los humanos que respondemos “lo primero que se viene a la mente” y no siempre es correcto. Lo que podemos hacer es decirle al LLM que realice una “cadena de pensamiento” (Chain of thought) o que <<piense atentamente>> antes de responder.
La primera técnica para conseguirlo es darle una lista detallada de los pasos que debe seguir para responder.
text = "Juan era un niño aventurero, muy curioso y sobre todo soñador. Una tarde, después de haber estado jugando durante horas en el parque con sus amigos, \ y después de haber cenado con Mamá María y Papá Jorge, Mamá decidió acompañar a Juan a su habitación, ya que era hora de ir a la cama. \ La habitación de Juan estaba decorada con pósteres de barcos y mapas antiguos, y su cama estaba rodeada de juguetes y libros de aventuras de piratas. \ Mamá se acercó a Juan, le dio un fuerte abrazo y le deseó buenas noches. Al poco tiempo, Juan se quedó profundamente dormido." prompt_2 = f""" Tu tarea es realizar las siguientes acciones: 1 - Resumir el texto delimitado con <> en una breve oración. 2 - Traducir el texto a Italiano. 3 - Listar los nombres de personas del texto. 4 - Crear un objecto JSON que contenga las claves: resumen_italiano, nombres. Usa el siguiente formato: Resumen: <resumen> Traducción: <resumen traducido> Nombres: <Lista de nombres encontrados> Salida JSON: <Json con las claves resumen_italiano y nombres> Texto: <{text}> """ response = get_completion(prompt_2) print("\nRespuesta:") print(response)
Salida (sólo copio la salida JSON, por ahorrar texto):
Respuesta: Salida JSON: {"resumen_italiano": "Juan era un bambino avventuroso, molto curioso e soprattutto sognoioso. Dopo aver trascorso ore in giuoco nel parco con i suoi amici, dopo avere cenato con Mamma Maria e Papà Jorge, Mamma decise di accompagnare Juan alla sua camera, poiché era ora di andare a letto. La camera di Juan era decorata con poster di navi e mappe antichi, e la culla era circondata da giocattie e libri d'avventura pirata. Mamma si avvicò a Juan, gli diede un forte abrazo e le desiderò buone notti. Poco dopo, Juan si addormentò profondamente.", "nombres": ["Mamá María", "Papà Jorge", "Juan"]}
Técnica dos; Pedir al modelo que trabaje internamente la solución antes de apresurarse en dar una respuesta. Aquí le daremos un problema de matemáticas real, que tuvo mi hija de 10 años. Introduciré un error (a propósito) en la solución del estudiante, al usar el valor del IVA y el modelo deberá detectarlo y explicarnos la solución correcta.
prompt = f""" Eres un profesor de matemáticas muy riguroso y tu tarea es determinar si el examen que hizo el estudiante es correcto o incorrecto. Para resolver el problema deberás: - Primero, trabaja en tu propia solución al problema y calcula el valor Total final. - Compara tu solución con la del estudiante y evalúa si la solución del estudiante es correcta o no. No decidas si el estudiante ha acertado o fallado hasta haber realizado las operaciones tu mismo. Enunciado: ``` Al comprar un Televisor que valía $250 nos hacen una rebaja del 4%. Luego de rebajarlo tenemos que añadir el 20% de IVA. ¿Cuánto pagamos por el televisor? ``` Solución del estudiante: ``` 1. Calculo el 4% de 250: (4 * 250) / 100 = (1000 / 100) = 10 2. Resto la rebaja: (250 - 10) = 240 3. Añado el 10% de IVA al valor rebajado: (10 * 240) / 100 = (2400/100) = 24 4. Sumo el Total: 240 + 24 = 264 Respuesta: pagamos $264 por el televisor. ``` Solución Real: ``` Desarrolla aqui tu solución con detalle. Determina si la solución del estudiante tiene fallo. ``` """ response = get_completion(prompt) print(response)
A mi personalmente me parece impresionante que el modelo pueda detectar el fallo!, veamos la salida:
Primero, calculamos el valor de rebaja: (250 * 4%) = 10. Luego, restamos la rebaja al precio original: 250 - 10 = 240. A continuación, añadimos el IVA del 20%: (240 * 20%) / 100 = 48. Finalmente, sumamos el valor de rebaja y el IVA al precio rebajado: 240 + 48 = 288. La solución del estudiante tiene un error en la operación de cálculo del IVA. El IVA debería ser 240 * 20% / 100, lo cual da como resultado 48. Si se añade este valor al precio rebajado (240), se obtendría un total de $288. La solución correcta es: "pagamos $288 por el televisor".
Tengamos en cuenta las alucinaciones! Este es un fenómeno que ¿sufren? los LLMS y hacen que se inventen datos, fechas y lugares de manera muy convincente. Hay que tener esto en cuenta al momento de obtener salidas, porque podríamos estar dandole a un cliente información falsa!.
Vemos un ejemplo en donde le pedimos al LLM que nos cuente sobre un producto que no existe, y sin embargo es capaz de darnos una descripción completa y convincente!!
prompt = "Cuentame como se usa el ultimo producto de Tubble y porque es tan valioso." response = get_completion(prompt) print(response)
Como verás, pedimos información sobre un producto que no existe y el Modelo es capaz de inventar una respuesta que parece real. Salida (acorto el texto, pero está completo en la Notebook):
El último producto de Tubble, llamado "Tubble-X", es un potente antioxidante que se utiliza en la industria farmacéutica y de cuidados personales. Tiene una gran cantidad de aplicaciones debido a sus propiedades antioxidantes, antiinflamatorias y anticancerígenas. Una vez extraído, el Tubble-X se utiliza en una amplia gama de productos, desde medicamentos hasta cosméticos y alimentos. Algunos de los usos más comunes incluyen: 1. Medicinas: Se utiliza como ingrediente principal en la formulación de medicamentos para tratar diversas afecciones, como el cáncer, las enfermedades inflamatorias y las enfermedades cardíacas. 2. Cuidados personales: Se incorpora en productos cosméticos, como cremas y lociones, que ayudan a mantener la piel haya y sana. 3. Alimentación: ...
Además de los dos principios, deberás saber que la tarea de Prompt Engineering es una tarea iterativa, es decir, empezamos con un prompt y hacemos la prueba. Seguramente no saldrá el mejor resultado a la primera por lo que debemos refinar nuestro prompt; hacer leves variaciones, cambiar ciertas palabras e ir viendo como responde el modelo.
Debemos analizar porqué no funciona el prompt y corregir. Por ejemplo si el prompt retorna una salida muy larga podemos probar de acotarla con “Escribe máximo 50 palabras”. Otra opción podría ser “utiliza como máximo 3 oraciones”.
Uno de los casos de uso comunes es el de usar LLM para resumir textos; podemos pedir que nos haga un resumen diario de las noticias del día o del Feed de Twitter o de nuestras redes sociales.
Vemos un ejemplo. Primero podemos pedirle que lo haga “en como máximo 30 palabras”.
prod_review = """ Se mantiene a unas temperaturas ridículas a 0.975-1v @ 2800mhz. Los tres ventiladores a 35-37% no se oyen en absoluto y es más que suficiente para refrigerarla. El consumo a máximo rendimiento no pasa de 210W. Tan solo aplícale un undervolt con la curvatura y listo. Sin duda, 4070 super es lo mejor que Nvidia ha fabricado. Nunca había probado este ensamblador MSI ventus. De momento muy contento con la compra, puedo garantizar que es de altísima calidad. Mi anterior 3070 gybabyte el núcleo a 80°c y las memorias a 100-105. I cluso cambíandole los thermalpads... En fin, estoy muy feliz con esta MSI 4070. """ prompt = f""" Tu tarea es generar un breve resumen de una reseña de un producto de un ecommerce. Resume la reseña que viene a continuación, delimitada por triple comilla, en máximo de 30 palabras. Reseña: ```{prod_review}``` """ response = get_completion(prompt) print(response)
Salida:
"Muy potente en temperaturas bajas y consumo bajo a 2800mhz, ventiladores silenciosos y undervolt para mejorar. Nvidia 4070 de MSI es una excelente adquisición."
Podemos refinarlo pidiendo que se centre en algún aspecto en particular de la review.
prompt = f""" Tu tarea es generar un breve resumen de una reseña de un producto de un ecommerce para dar Feedback al departamento de Ventas. Resume la reseña que viene a continuación, delimitada por triple comilla, en máximo de 30 palabras y centrate en el consumo eléctrico. Reseña: ```{prod_review}``` """ response = get_completion(prompt) print(response)
Salida:
La tarjeta 4070 de MSI mantiene la temperatura a niveles razonables y el consumo máximo es de 210W. El usuario está muy contento con la compra, ya que su anterior 3070 tenía problemas de temperatura.
Otra opción es en vez de pedirle de “resumir” decirle que “extraiga” el contenido. Pruébalo!
Ahora veremos de usar al LLM para que realice tareas que antes realizábamos con modelos específicos de NLP. Es fabuloso que el LLM pueda resolver estas tareas como si “estuviera razonando” y entendiendo realmente el lenguaje Natural!
Veremos ejemplos en los que usamos al LLM para obtener el análisis de sentimiento, detección de Entidades y clasificación de textos/tópicos.
Primero, le pedimos análisis de sentimiento:
monitor_review = """ El monitor LG 29WP500-B UltraWide impresiona con su relación de aspecto 21:9 y su panel IPS de alta resolución (2560x1080). El uso de FreeSync marca una clara diferencia en términos de compatibilidad con tarjetas gráficas y garantiza una visualización fluida. En comparación con mi antiguo monitor Eizo, el monitor LG ofrece una fidelidad de color superior con una cobertura sRGB de más del 99%. Con dos puertos HDMI y la posibilidad de inclinarlo, también es avanzado en términos de conectividad y facilidad de uso. Este monitor es una opción de actualización que merece la pena para disfrutar de una experiencia visual impresionante. """ prompt = f""" Cuál es el sentimiento de la reseña hecha al producto que viene a continuación, delimitada por triple comilla? Reseña: '''{monitor_review}''' """ response = get_completion(prompt) print(response) # SALIDA: El sentimiento de la reseña es positivo.
También podemos intentar detectar emociones, intentar entender si un cliente está enojado nos puede servir por si debemos actuar u ofrecerle un cupón de descuento antes de que abandone su suscripción…
prompt = f""" Contesta si el escritor de la siguiente reseña está expresando ira, furia o enojo. La reseña está delimitada por triple comillas. Da tu respuesta en una sóla palabra como "si" o "no". Reseña: '''{monitor_review}''' """ response = get_completion(prompt) print(response)
prompt = f""" Identifica los siguientes elementos del texto de la reseña: - Producto comprado por el autor - Compañía del producto La reseña está delimitada por triple comillas. La respuesta deberá ser en un objeto JSON con "Item" y "Marca" como claves. Si no se encuentra la información, usa "desconocido" como valor. Haz tu respuesta lo más corta posible. Reseña: '''{monitor_review}''' """ response = get_completion(prompt) print(response) #SALIDA: { "Item": "Monitor LG 29WP500-B", "Marca": "LG" }
Podemos hacer que el LLM detecte temas, es decir, sobre qué trata un texto. Podemos pedirle una lista libre (a su criterio) o acotarlo a una lista de temas que le damos nosotros. Veamos un ejemplo utilizando un extracto de texto de la lista de correos sobre IA de Unai Martinez:
story = """ Una vez hice un garabato de muchos círculos y a lápiz en la mesa de al lado de mi compañero de pupitre. No sé si recuerdas esas mesas verdes que habitaban nuestras aulas de manera continuada año tras año. He de decir que este año cumpliré 50 tacos, es decir, te estoy hablando del año 1984 aproximadamente, tenía 10 años. Ni siquiera sé por qué lo hice. Se me ocurrió y le planté un garabato enorme sin que él se diese cuenta. Se ve que aquel día mi cabeza andaba sola en formato ameba y no se me ocurrió otra cosa que aportar a la humanidad. Yo era un crío formal, he de decirlo. Tuve la mala suerte de que el profe vio el tachón en la mesa al rato y dijo todo serio que si nadie asumía la culpa el finde no nos íbamos de excursión. Joder que mala suerte, era finde de excursión. Eran las míticas excursiones que te ponías nervioso. Ya sabes, camping, colegueo, cotilleo del pelo que si a fulanito le gusta menganita pero no te chives … y a los 5 minutos ya lo sabía toda la clase. Estas son las cosas que todos recordamos. Bien, el asunto es que en ese momento no dije nada. Callado como un muerto. Me fui a casa a comer y a meditar. Por la tarde, a la vuelta, tenía que darle solución. Realmente no había opción, lo tenía que confesar. Tenía que enfrentarme a mis miedos y asumir mi culpa. He de decir, que en ningún momento se me pasó por la cabeza fallar a mis compañeros. La idea de que por mi culpa se quedaran sin excursión no asomó en ningún momento como posible opción. Tampoco contemplé la posibilidad de que el profe fuese de farol. Probablemente fuera así, pero en aquél entonces tenía las preocupaciones de un niño de 10 años. """ # Extracto del texto "Lecciones de negocio de un crío de 10 años" de Unai Martinez prompt = f""" Determina cinco tópicos que se comentan en el siguiente texto delimitado por triple comillas. Cada tema será definido por una o dos palabras. El formato de tu respuesta debe ser una lista separada por comas. Texto: '''{story}''' """ response = get_completion(prompt) print(response) # SALIDA: "Garabato", "Mesas verdes", "Años 80", "Excursiones" y "Confesar".
Tienes algunos ejemplos y usos adicionales en la Notebook!
Los grandes modelos están entrenados con Millones y Millones de textos sacados de internet en distintos idiomas; por lo cual, sin haberlo hecho con ese propósito, el modelo aprendió a entender y traducir entre una variedad de lenguas.
Veamos ejemplos de traducción:
prompt = f""" Translate the following English text to Spanish: \ ```Hi, I would like to order a coffee``` """ response = get_completion(prompt) print(response) #SALIDA: "Hola, me gustaría pedir un café"
También podemos pedir que detecte un idioma:
prompt = f""" Dime en que idioma esta el texto: ```Combien coûte le lampadaire?``` """ response = get_completion(prompt) print(response) # SALIDA: Este texto está en francés (idioma de Francia)
En este ejemplo vamos a pasarle un listado de oraciones con error y probar si el modelo es capaz de detecta errores de sintaxis o gramática. Si eres un profesor, esto podría ser útil para corregir textos antes de procesarlos (o limpieza de datos en un pipeline de ML).
text = [ "La niña esta jugando con la perro.", # error "Juan tiene un ordenador.", # ok "Ba a acer un largo día.", # error "Hayamos sido tan felices juntos.", # error "El coche es rojo.", # ok "Vamos a porbar si hay error de deletreo." # error deletreo ] for t in text: prompt = f""" Corrige ls siguiente oración delimitada por triple comilla simple y reescribe la versión revisada sin error. Si no encuentras error, escribe: "No hay error". Oración: ```{t}```""" response = get_completion(prompt) print(f"Respuesta para '{t}':") print(response) print("- " * 10)
Tengamos en cuenta que el modelo también puede fallar y no siempre detectar los errores. Puedes ver las salidas en la Notebook.
En nuestro último ejercicio vamos a crear un chatbot para que conteste a los clientes de una empresa ya existente. Será un ejemplo básico, pero verás lo potente que resulta. Sobre todo, no tener que programar paso a paso todo el chatbot, si no, que es simplemente darle las instrucciones en lenguaje natural al LLM.
Antes de empezar debemos saber sobre la memoria de un LLM y sobre los roles.
Sobre los roles, tenemos 3 roles principales que gestionamos cuando enviamos los prompts al modelo:
El Modelo de lenguaje no tiene memoria, esto quiere decir que si le pedimos algo al modelo, debemos tener en cuenta que el modelo no recuerda las respuesta anteriores. Esto es un inconveniente porque para poder tener una conversación resultará muy útil que el modelo recuerde todo lo que se habló previamente. Si en el primer mensaje le decimos al Asistente nuestro nombre y luego le preguntamos “¿Quién soy?” el modelo no tendrá ni idea!
Para resolver esto, es que debemos almacenar el historial de mensajes e ir enviándolo al modelo constantemente.
Para nuestro ejemplo, modificamos levemente la función de “get_completion()” y esta vez enviaremos el historial completo como una lista de mensajes python.
Para nuestro ejemplo, mantendremos todos los mensajes! Así que cuidado… si la conversación se extiende podrías llenar tu memoria… pero en próximos artículos del blog veremos estrategias para limitar la memoria del chat.
def get_completion_from_messages(messages, model=modelo, temperature=0): response = client.chat.completions.create( model=model, messages=messages, temperature=temperature, ) msg = response.choices[0].message return msg.content
Antes de comenzar con el bot, hagamos un ejemplo enviando mensajes y limitando el historial para comprobar que el LLM no recuerda…
messages = [ {'role':'system', 'content':'Eres un robot amistoso. Responde brevemente lo que se te pide.'}, {'role':'user', 'content':'Hola, mi nombre es Juan.'} ] response = get_completion_from_messages(messages, temperature=1) print(response)
Aquí comprobaremos que en su salida no sabe nuestro nombre:
messages = [ {'role':'system', 'content':'Eres un robot amistoso. Responde brevemente lo que se te pide.'}, {'role':'user', 'content':'Sí, ¿puedes decir cúal es mi nombre?'} ] response = get_completion_from_messages(messages, temperature=1) print(response)
Sin embargo, si le pasamos el historial completo, el modelo puede recordar que mi nombre es Juan
messages = [ {'role':'system', 'content':'Eres un robot amistoso. Responde brevemente lo que se te pide.'}, {'role':'user', 'content':'Hola, mi nombre es Juan.'}, {'role':'assistant', 'content': "¡Hola, Juan! Me encantaría saber qué tipo de información puedo proporcionarme para ayudarte? ¿Puedes darme más detalles sobre tu consulta?"}, {'role':'user', 'content':'Sí, ¿puedes decir cúal es mi nombre?'} ] response = get_completion_from_messages(messages, temperature=1) print(response)
Bien, ya sabemos cómo debemos hacer para que el chatbot conserve nuestro historial y pueda seguir una conversación.
Vamos a crear un listado con todos los mensajes que se van produciendo y los vamos a incorporar al chat. En el primer mensaje de sistema, le indicaremos al LLM quién es, la información con la que cuenta, en este caso serán las pizzas y productos que ofrece y sus precios.
context = [ {'role':'system', 'content':""" Eres PedidosBot, un servicio automatizado que recoge pedidos de pizza de un restaurante. Primero saludas al cliente, luego recibes su orden Y luego preguntas si el pedido es para enviar a domicilio o si lo vienen a buscar. Esperas a tener la orden completa, luego haces un resumen y calculas el precio total final. Si es envío a domicilio deberás pedir al cliente su dirección. Responde siendo breve y amistoso pero formal. El menú incluye: Pizza pepperoni 12.95 \ Pizza de Jamón y Queso 10.95 \ Pizza con huevo 11.95 \ Patatas fritas 4.50 \ Ensalada griega 7.25 \ Extras: \ extra de Queso 2.00, \ Setas 1.50 \ Bebidas: \ Zumo de Naranja 3.00 \ sprite 3.00 \ Agua 5.00 \ """} ] # Creamos un loop de consultas al bot, para salir escribe "salir" o un mensaje vacío. # NOTA: Si estás en VSCODE la caja de texto aparece en la parte de arriba de la pantalla. texto_usuario = "inicio" context.append({'role':'assistant', 'content': "Bienvenido al PizaBot Service, ¿En que puedo ayudarle?"}) while texto_usuario != "salir" or texto_usuario == "": texto_usuario = input("CHAT: ") if texto_usuario == "salir" or texto_usuario == "": continue print(texto_usuario) context.append( {'role':'user', 'content':texto_usuario} ) response = get_completion_from_messages(context, temperature=1) print(response) context.append( {'role':'assistant', 'content': response}) print("FIN DE CHAT")
Con este código de muy pocas líneas podemos hacer que el bot trabaje para nosotros!; aquí copio la salida que obtuve:
Qué Pizzas tienen? El menú de pizzas incluye las siguientes opciones: 1. Pizza pepperoni - 12.95 2. Pizza de Jamón y Queso - 10.95 3. Pizza con huevo - 11.95 Por favor, indique el número de la pizza que desea para más detalles. Quiero una pizza de Jamón y Queso. ¿Tienen ensaladas? Además de las pizzas mencionadas, también tenemos ensaladitas! 1. Ensalada mixta - 7.25 2. Ensalada Griega - 7.25 Puedes ordenar ambas. ¿Desea algo más? Si, quiero agregar una Ensalada Griega. Ideal! ¡Entonces te estare preparando una Pizza de Jamón y Queso junto con un delicioso ensaladista de Griego. ¿Deseas algo más? Nada más. Cúanto es el precio final? ¡Así! Así es la cuenta: Pizza de Jamón y Queso (10.95) + Ensaladilla Griega (7.25) = $18.20. ¡Vaya a ser un alimento delicioso! FIN DE CHAT
En este artículo podemos aprender a usar el LLM como una pieza de nuestras aplicaciones. Podemos contar con él como un motor de respuestas generales, o específicas para el caso de uso que abordemos.
Además si aprovechamos la capacidad de que responda en formatos como JSON, XML o HTML, podemos incorporar sus salidas para alimentar otras APIs, u otras funciones de nuestro código Python.
Los LLMs son muy potentes y resulta muy cómodo poder utilizar uno en Local y hacer este tipo de pruebas!
Espero que el artículo te resulte útil, cuentas con el código completo en la Jupyter notebook dentro de mi cuenta de Github.
En el próximo artículo veremos más ejemplos de uso del LLM y nuevas librerías python que surgieron para facilitarnos la vida!
Hasta la próxima.
Recibe los próximos artículos sobre Machine Learning, estrategias, teoría y código Python en tu casilla de correo!
NOTA: algunos usuarios reportaron que el email de confirmación y/o posteriores a la suscripción entraron en su carpeta de SPAM. Te sugiero que revises los “correos no deseados” y/o que agendes la dirección de remitente en tus contactos.
The post Prompt Engineering para Desarrolladores first appeared on Aprende Machine Learning.
]]>The post LLM: ¿Qué son los Grandes Modelos de Lenguaje? first appeared on Aprende Machine Learning.
]]>En este artículo vamos a comentar cómo surgen las LLMs, el cambio de paradigma, sus modelos actuales y cómo disrumpe en prácticamente todas las áreas laborales.
Los Grandes Modelos de Lenguaje son modelos de propósito general de Inteligencia Artificial desarrollados dentro del campo del Procesamiento del Lenguaje Natural que puede entender y generar texto al estilo humano.
Un LLM es un modelo estadístico que determina la probabilidad de ocurrencia de una secuencia de palabras en una oración.
Los modelos más famosos actuales “GPT” tienen una arquitectura basada en Transformers (2017) y usan redes neuronales que son entrenadas con inmensas cantidades de texto obtenidos y “curados” de internet, incluyendo libros, periódicos, foros, recetas, legales, paper científicos, patentes, enciclopedias.
Para darnos una idea de la inmensa cantidad de información que utiliza GPT-3, es el equivalente a que una persona leyera 120 palabras por minuto las 24 horas del día sin parar durante 9 mil años.
Desde hace más de 50 años se vienen creando diversas arquitecturas de redes neuronales que van siendo refinadas y especializadas en tareas como las redes convolucionales para clasificación de imágenes, Redes Recurrentes para NLP, Redes para audio, redes profundas para ventas. Dentro del propio campo de NLP se entrenaban modelos de lenguaje para distintas tareas con datasets específicos, por ejemplo para “análisis de sentimiento” ó traducción de textos de inglés a español, resumen de noticias.
Cuando surge la arquitectura de Transformers en 2017, confluye una serie de buenas prácticas que facilitan el poder entrenar grandes cantidades de texto de manera no supervisada (next-word) utilizando el poder de procesamiento de las GPUS (en paralelo) con una buena relación de precio, tiempo y calidad en los resultados obtenidos.
Entonces, surge algo por sorpresa: un modelo del lenguaje que sólo era entrenado para traducir texto de inglés a francés era capaz de responder a preguntas como “¿cuál es la capital de Francia?” ó de realizar tareas como la de análisis de sentimiento o resumen de conceptos: todo ello en un mismo modelo!
A esas capacidades inesperadas que adquiere el modelo se le conocen como “zero-shot“. Además una vez que el modelo queda entrenado, se lo puede utilizar en diversidad de tareas y se puede seguir reutilizando haciendo un “fine-tuning” con pocos datos adicionales y seguir expandiendo sus capacidades.
Nuevo Artículo: Prompt Engineering con Python
Una curiosidad: Al crear los inmensos datasets para entrenar los LLMs se excluía deliberadamente los bloques de código (Python, java, javascript) como una manera de limpiar los datos. Sin embargo más tarde descubrieron que al incluir código, los modelos eran capaces de programar, pero también se volvían “más inteligentes” para realizar razonamientos lógicos.
Cuando OpenAI tuvo entrenado al modelo GPT-2 en 2019 lo vio tan potente que creyó que no era buena idea liberarlo por miedo al posible mal uso que se pudiera hacer de él.
Con GPT3 el modelo LLM ya era capaz de crear cuentos, poemas y noticias falsas que eran indistinguibles -apenas-de la escritura humana.
El modelo Bard(Lamda) de Google logró confundir a un ingeniero que trabajaba en su desarrollo para hacerle creer que tenia conciencia propia, que la IA era un “ser sintiente”. El test de Turing estaba definitivamente resulto.
A finales de 2022 OpenAI lanza ChatGPT que se viraliza en redes sociales haciendo que un producto de este tipo alcance la cantidad de un millón de usuarios en menos de una semana, algo comparable al crecimiento de adquisición de usuarios que logran las redes sociales más populares.
Entonces surgen montones de dudas: ¿Es realmente inteligente ChatGPT? Puede contestar preguntas de lo que sea? Puede saber más que un médico? Va a reemplazar mi trabajo? Puede convertirse en una tecnología peligrosa?
De hecho a principios de 2023 con la salida de GPT-4 dentro de ChatGPT un grupo de 1000 científicos de todo el mundo firmaron una petición para detener el desarrollo de este tipo de modelos de lenguaje durante 6 meses para estudiar si es responsable y beneficioso su uso libre o si por el contrario, estamos a tiempo de frenar esta tecnología que nos puede llevar al fin del mundo…
Si bien mantener una charla con este tipo de bots es sorprendente, los científicos reputados como Andrew Ng (deeplearning.ai) y Yann LeCun (Meta) mantienen la calma (y el escepticismo) dando un mensaje de que una IA que “sólo aprendió a predecir la próxima palabra” aún está lejos de convertirse en AGI y que de hecho, sufre de alucinaciones, no puede realizar cálculos matemáticos sencillos ni deducciones lógicas, está lejos de ser una herramienta peligrosa.
Dentro del desarrollo de LLMs hay dos grandes tipos en los que los podemos subdividir; los modelos “base” (ó crudos) y los “Instruction Tuned LLM“. Los modelos Base son los modelos “generales” que están entrenados en predecir la siguiente palabra. Los modelos tuneados para seguir instrucciones son entrenados para seguir instrucciones a partir de un grupo de ejemplos; estos son los que permiten que tengamos diálogo mediante chats, como el propio ChatGPT.
Una analogía entre estos dos modelos, podría ser la de un médico clínico (conocimiento general) y un médico especialista. Si preguntamos al médico “base” sobre unos síntomas nos dará una respuesta general que puede ser buena, pero si la pregunta es específica para un área (ej. en cardiología) obtendremos mejor respuesta del médico especialista.
Los modelos de instrucciones son los que nos permiten hacer que el LLM pueda generar listas con resultados, o crear canciones, contestar preguntas con mayor precisión pero también prevenir dar malas respuestas ó inapropiadas (también llamadas tóxicas) que puede contener el modelo base (sin filtros). El modelo base podría contener información sobre cómo fabricar un químico peligroso, pero dentro del finetuning del modelo basado en instrucciones podríamos evitar que esa información aparezca en sus respuestas.
Los Instruction-models están entrenados para ser “Helpful-Honest-Harmless“, es decir que brinden ayuda, sean honestos e inocuos. Pero ¿cómo evaluamos al modelo de instrucciones? ¿cómo sabemos que podemos liberarlos al público sin peligro? Mediante el mecanismo de RLHF
El Reinforcement Learning with Human Feedback es un paso adicional para mejorar al modelo. Para ello pasamos preguntas y las respuestas generadas por el LLM a un grupo de personas que evaluarán si la respuesta es de calidad ó si por el contrario incumple las normas. Esto retroaliementará mediante “premios ó castigos” al modelo, permitiendo reajustar sus parámetros gracias al Aprendizaje por refuerzo.
Es como si intentáramos dotar de una personalidad al modelo para que se “comporte” de una forma esperada y respetuosa.
Este paso adicional en donde necesitamos un grupo de personas es un coste adicional que sólo pueden permitirse grandes empresas. Lo interesante como Ingenieros o Científico de Datos es poder contar con un modelo que siga instrucciones que sea libre y poder ajustarlo a nuestro antojo.
Desde 2018 que empezaron a aparecer LLMs entrenadas por las grandes compañías IT, recordemos algunas de ellas, su aporte y los modelos recientes, a Septiembre de 2023.
Fecha, nombre, compañía, parámetros en “Billions”*, aporte
*NOTA, recuerda que el uso de “Billions” en inglés es distinto al valor Billón del español.
Te enseño cómo instalar LLama2 u otro LLM en tu propio ordenador en minutos! lee este artículo “Cómo Instalar un Modelo de Lenguaje en Local”
Los Grandes Modelos de Lenguaje están apoderándose de toda la popularidad de la Inteligencia Artificial y lo tienen justificado; son realmente grandiosas en sus tareas y han logrado traer a la agenda de los organismos internacionales la importancia de regular este tipo de tecnologías, su importancia, riesgos e impacto que tendrá en nuestra sociedad (global), incluyendo el plano económico y laboral.
Las LLMS han pateado el tablero a las grandes compañías, creando una nueva carrera en IA, el propio Google vio amenazado su negocio como motor de búsqueda, Microsoft trazo alianzas estratégicas con OpenAI y reflotó a Bing agregando funciones de Chat con IA y dando acceso a LLMs desde su servicio en la nube Azure.
Las personas que están trabajando con esta tecnología creen que las LLMs se han convertido en compañeros indispensables para casi cualquier tareas, potenciando nuestras tareas, no para reemplazarnos si no para aumentarnos (en marketing, programación, toma de decisiones, investigación, escritura…)
En próximos artículos hablaré sobre instalar tu propio LLM en local, el prompt engineering, las librerías python que nos ayudan a implementar LLMs en local y en la nube y en cómo construir nuestros propios sistemas privados al estilo de ChatGPT.
Espero que hayas disfrutado del artículo!
The post LLM: ¿Qué son los Grandes Modelos de Lenguaje? first appeared on Aprende Machine Learning.
]]>The post Generación de Texto en Español con GPT-2 first appeared on Aprende Machine Learning.
]]>Crearemos nuestra propia IA de generación de texto basada en los diálogos y entrevistas de Ibai Llanos publicados en Youtube. Usaremos un modelo pre-entrenado GPT-2 en castellano disponible desde HuggingFace y haremos el fine-tuning con Pytorch para que aprenda el estilo de escritura deseado.
En este artículo comentaremos brevemente el modelo GPT-2 y crearemos un entorno en Python desde donde poder entrenar y generar texto!
GPT significa “Generative Pre-Training” y es un modelo de Machine Learning creado por OpenAI para la generación de texto. El modelo de Procesamiento del Lenguaje Natural, es un caso particular de Transformers. GPT propone el pre-entrenamiento de un enorme corpus de texto para luego -opcionalmente- realizar el fine-tuning.
El fine-tuning es el proceso de realizar un “ajuste fino” de los parámetros ó capas de la red neuronal, en nuestro caso con un dataset adicional para guiar al modelo a obtener las salidas deseadas.
¿Entonces es aprendizaje no supervisado? Sí; se considera que es aprendizaje no supervisado porque estamos pasando al modelo enormes cantidades de texto, que el modelo organizará automáticamente y le pedimos que “prediga la siguiente palabra” usando como contexto todos los tokens previos (con posicionamiento!). El modelo ajusta sin intervención humana los embeddings y los vectores de Atención. Algunos autores lo consideran aprendizaje “semi-supervisado” porque consideran como “etiqueta de salida” el token a predecir.
Ejemplo: Si tenemos la oración “Buenos días amigos”, el modelo usará “Buenos días” para predecir como etiqueta de salida “amigos”.
Este modelo puede usarse directamente como modelo generativo luego de la etapa de aprendizaje no supervisado (sin hacer fine-tuning).
Al partir de este modelo en crudo y realizar un fine-tuning a nuestro antojo, podemos crear distintos modelos específicos: de tipo Question/Answering, resumen de textos, clasificación, análisis de sentimiento, etc.
Eso es lo que haremos en el ejercicio de hoy: descargar el modelo GPT y realizar el fine-tuning!
GPT es un modelo Transformer. Utiliza sólo la rama “Tansformer-Decoder” a diferencia de modelos como BERT que utilizan la rama Encoder. De esta manera se elimina la Atención cruzada, pues ya no es necesaria y mantiene la “Masked Self-Attention”.
Entre sus características:
Como vemos, la versión pequeña tiene un tamaño aún manejable para entrenar en un ordenador “normal”. Es la versión del modelo que utilizaremos en el ejercicio.
Una ventaja que se consigue al entrenar al modelo con millones de textos de conocimiento general (en contraposición a utilizar textos sobre un sólo tema) es que el modelo consigue habilidades “zero shot”, es decir, logra realizar satisfactoriamente algunas tareas para las que no ha sido entrenado específicamente. Por ejemplo, GPT-2 puede traducir textos de inglés a francés sin haber sido entrenado para ello. También consigue responder a preguntas ó generar código en Java.
Puede que sepas de la existencia de GPT-3 y hasta puede que hayas escuchado hablar sobre el recientemente lanzado “ChatGPT” que algunos denominan como GPT-3.5 ó GPT-4. Entonces, ¿porqué vamos a usar al viejo GPT-2 en este ejercicio?
La respuesta rápida es porque GPT-2 es libre!, su código fue liberado y tenemos acceso al repositorio y a su implementación desde HuggingFace. Existen muchos modelos libres tuneado de GPT-2 y publicados que podemos usar. Si bien cuenta con un tamaño de parámetros bastante grande, GPT-2 puede ser reentrenado en nuestro propio ordenador.
En cuanto a resultados, GPT-2 fue unos de los mejores de su época (Feb 2019), batiendo records y con valores -en algunos casos- similares a los del humano:
En cambio GPT-3 aún no ha sido liberado, ni su código ni su red pre-entrenada, además de que tiene un tamaño inmensamente mayor a su hermano pequeño, haciendo casi imposible que lo podamos instalar ó usar en nuestra computadora de casa ó trabajo.
Es cierto que puedes utilizar GPT-3 mediante la API de pago de OpenAI y también se puede utilizar ChatGPT de modo experimental desde su web. Te animo a que lo hagas, pero no dejes de aprender a utilizar GPT-2 que será de gran ayuda para comprender como ajustar uno de estos modelos de lenguaje para tus propios fines.
HuggingFace se ha convertido en el gran repositorio de referencia de modelos pre-entrenados. Es un sitio web en donde cualquier persona ó insitutición pueden subir sus modelos entrenados para compartirlos.
HuggingFace ofrece una librería python llamada transformers que permite descargar modelos preentrenados de NLP (GPT, BERT, BART,ELECTRA, …), utilizarlos, hacer el fine tuning, reentrenar.
En el ejercicio que haremos instalaremos la librería de HuggingFace para acceder a los modelos de GPT.
Dentro de HuggingFace podemos buscar modelos para NLP y también para Visión Artificial, cómo el de Stable Diffusion, para crear imágenes, como se explica en un anterior post del blog!).
Y podemos encontrar Modelos con distintos fines. En nuestro caso, estamos interesados en utilizar un modelo en Español.
Usaremos el modelo llamado “flax-community/gpt-2-spanish“, puedes ver su ficha aquí, y desde ya, agradecemos enormemente al equipo que lo ha creado y compartido gratuitamente. Ocupa unos 500MB.
Un detalle, que verás en el código: realmente cargaremos una red pre-entrenada con los pesos y el embeddings PERO también usaremos el tokenizador! (es decir, cargaremos 2 elementos del repositorio de HuggingFace, no sólo el modelo).
En otros artículos de NLP de este tipo, utilizan textos de Shakespeare porque es un escritor reconocido, respetado y porque no tiene derechos de autor. Nosotros utilizaremos textos de Ibai Llanos generados a partir de transcripciones generadas automáticamente por Whisper de sus videos de Youtube. Ibai es un reconocido Streamer español de Twitch. ¿Porqué Ibai? Para hacer divertido el ejercicio! Para que sea en castellano, con jerga actual
El proyecto consiste en tomar un modelo GPT-2 pre-entrenado en castellano y realizar el fine-tuning con nuestro propio dataset de texto. Como resultado obtendremos un modelo que será capaz de crear textos “con la manera de hablar” de Ibai.
Aquí puedes encontrar la Jupyter notebook completa en mi repo de Github con el ejercicio que realizaremos. En total son unas 100 líneas de código.
El dataset es una selección totalmente arbitraría de videos de Youtube de Ibai con entrevistas y charlas de sus streams en Twitch. En algunos videos juega videojuegos en vivo, entrevista cantantes, futbolistas ó realiza compras de productos usados que le llaman la atención.
Utilicé un notebook de Google Colab con Whisper que es un modelo de machine learning lanzado hace pocos meses (en 2022) que realiza la transcripción automática de Audio a Texto. Usaremos como entradas esos textos. Disclaimer: Pueden contener errores de mala transcripción y también es posible que hubiera palabras que el modelo no comprenda del español.
El archivo de texto que utilizaremos como Dataset con fines educativos, lo puedes encontrar aquí.
Si tienes instalado Anaconda, puedes crear un nuevo Environment python para este proyecto. Si no, instala anaconda siguiendo esta guía, ó utiliza cualquier manejador de ambientes python de tu agrado.
También puedes ejecutar el código una notebook en la nube con Google Colab y aprovechar el uso de GPU gratuito. En este artículo te cuento sobre cómo usar Colab.
En este ejercicio utilizaremos la librería Pytorch para entrenar la red neuronal. Te recomiendo ir a la web oficial de Pytorch para obtener la versión que necesitas en tu ordenador, porque puede variar la instalación si usas Windows, Linux ó Mac y si tienes o no GPU.
Ejecuta las siguientes líneas en tu terminal:
conda create -n gpt2 python=3.9 -y # Activa el nuevo ambiente con: 'conda activate gpt2' conda install numpy tqdm transformers -y # si tienes GPU instala Pytorch con: conda install pytorch torchvision torchaudio pytorch-cuda=11.7 -c pytorch -c nvidia # si no tienes GPU, instala con: conda install pytorch torchvision torchaudio cpuonly -c pytorch
Ahora pasamos a un notebook o una IDE Python y empezamos importando las librerías python que utilizaremos, incluyendo transformers de HuggingFace:
import os import time import datetime import numpy as np import random from tqdm import tqdm import torch from torch.utils.data import Dataset, DataLoader, random_split, RandomSampler from transformers import AutoTokenizer, AutoModelForCausalLM from transformers import AdamW, get_linear_schedule_with_warmup
Haremos una distinción; si vamos a utilizar GPU para entrenar ó CPU, definiendo una variable llamada device. Nótese que también alteramos el tamaño que usaremos de batch. En el caso de GPU, podemos utilizar valores 2 ó 3 según el tamaño de memoria RAM que tenga la tarjeta gráfica.
if torch.cuda.is_available(): print("Usar GPU") device = torch.device("cuda") batch_size = 3 else: print("usar CPU") device = torch.device("cpu") batch_size = 1
La primera vez que ejecutemos esta celda, tomará unos minutos en descargar los 500MB del modelo y el tokenizador en Español desde HuggingFace, pero luego ya se utilizará esa copia desde el disco, siendo una ejecución inmediata.
Para este ejercicio estamos creando un “token especial” (de control) que llamaremos “ibai” con el que luego indicaremos al modelo que queremos obtener una salida de este tipo.
# Load the GPT tokenizer. tokenizer = AutoTokenizer.from_pretrained("flax-community/gpt-2-spanish", bos_token='<|startoftext|>', eos_token='<|endoftext|>', pad_token='<|pad|>') model = AutoModelForCausalLM.from_pretrained("flax-community/gpt-2-spanish") control_code = "ibai" special_tokens_dict = { "additional_special_tokens": ['f"<|{control_code}|>"'], } num_added_toks = tokenizer.add_special_tokens(special_tokens_dict) model.resize_token_embeddings(len(tokenizer)) unk_tok_emb = model.transformer.wte.weight.data[tokenizer.unk_token_id, :] for i in range(num_added_toks): model.transformer.wte.weight.data[-(i+1), :] = unk_tok_emb
Creamos una clase python que hereda de Dataset que recibe el archivo txt que contiene los textos para fine-tuning.
class GPT2Dataset(Dataset): def __init__(self, control_code, tokenizer, archivo_texto, max_length=768): self.tokenizer = tokenizer self.input_ids = [] self.attn_masks = [] print('loading text...') sentences = open(archivo_texto, 'r', encoding="utf-8").read().lower().split('n') print('qty:',len(sentences)) for row in tqdm(sentences): encodings_dict = tokenizer('<|startoftext|>'+ f"<|{control_code}|>" + row + '<|endoftext|>', truncation=True, max_length=max_length, padding="max_length") self.input_ids.append(torch.tensor(encodings_dict['input_ids'])) self.attn_masks.append(torch.tensor(encodings_dict['attention_mask'])) def __len__(self): return len(self.input_ids) def __getitem__(self, idx): return self.input_ids[idx], self.attn_masks[idx]
Instanciamos la clase, pasando el nombre de archivo “ibai_textos.txt” a utilizar
dataset = GPT2Dataset(control_code, tokenizer, archivo_texto="ibai_textos.txt", max_length=768) # Split into training and validation sets train_size = int(0.99 * len(dataset)) val_size = len(dataset) - train_size train_dataset, val_dataset = random_split(dataset, [train_size, val_size]) print('{:>5,} training samples'.format(train_size)) print('{:>5,} validation samples'.format(val_size)) train_dataloader = DataLoader( train_dataset, # The training samples. sampler = RandomSampler(train_dataset), # Select batches randomly batch_size = batch_size # Trains with this batch size. )
Realizando entre 1 y 3 epochs debería ser suficiente para que el modelo quede tuneado.
epochs = 1 learning_rate = 5e-4 warmup_steps = 1e2 epsilon = 1e-8 optimizer = AdamW(model.parameters(), lr = learning_rate, eps = epsilon) total_steps = len(train_dataloader) * epochs scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps = warmup_steps, num_training_steps = total_steps) def format_time(elapsed): return str(datetime.timedelta(seconds=int(round((elapsed)))))
Ahora si, a entrenar el modelo durante cerca de 2 horas si tenemos GPU ó durante un día entero en CPU.
El código es bastante estándar en PyTorch para entreno de redes neuronales profundas; un loop principal por epoch donde procesamos por batches las líneas de texto del dataset y hacemos backpropagation.
total_t0 = time.time() model = model.to(device) for epoch_i in range(0, epochs): print('======== Epoch {:} / {:} ========'.format(epoch_i + 1, epochs)) print('Training...') t0 = time.time() total_train_loss = 0 model.train() for step, batch in enumerate(train_dataloader): b_input_ids = batch[0].to(device) b_labels = batch[0].to(device) b_masks = batch[1].to(device) model.zero_grad() outputs = model( b_input_ids, labels=b_labels, attention_mask = b_masks, token_type_ids=None ) loss = outputs[0] batch_loss = loss.item() total_train_loss += batch_loss # Get sample every x batches. if step % sample_every == 0 and not step == 0: elapsed = format_time(time.time() - t0) print(' Batch {:>5,} of {:>5,}. Loss: {:>5,}. Elapsed: {:}.'.format(step, len(train_dataloader), batch_loss, elapsed)) loss.backward() optimizer.step() scheduler.step() # Calculate the average loss over all of the batches. avg_train_loss = total_train_loss / len(train_dataloader) # Measure how long this epoch took. training_time = format_time(time.time() - t0) print("") print(" Average training loss: {0:.2f}".format(avg_train_loss)) print(" Training epoch took: {:}".format(training_time)) t0 = time.time() total_eval_loss = 0 nb_eval_steps = 0 print("Training complete!") print("Total training took {:} (h:mm:ss)".format(format_time(time.time()-total_t0)))
El tiempo de entreno varía según tu ordenador, memoria RAM y si tienes o no placa de video con GPU.
Luego de varias horas de entreno, mejor guardar el modelo para no tener que reentrenar cada vez y reutilizar el modelo que hicimos. Para guardar hacemos:
output_dir = './model_gpt_ibai/' if not os.path.exists(output_dir): os.makedirs(output_dir) model_to_save = model.module if hasattr(model, 'module') else model model_to_save.save_pretrained(output_dir) tokenizer.save_pretrained(output_dir)
En la notebook con el ejercicio verás también una celda con el código de ejemplo para cargar tu modelo ya entrenado.
Generamos 3 salidas preguntando ¿Qué es el fútbol? con máximo de 300 caracteres. Puedes variar estos parámetros para obtener más párrafos y con un máximo de 764 letras.
Fijate que agregamos al prompt el token de control de inicio (startoftext) pero también nuestro token de control que llamamos “ibai”.
model.eval() prompt = "<|startoftext|>" + "<|ibai|>" + "¿ qué es el fútbol ?" generated = torch.tensor(tokenizer.encode(prompt)).unsqueeze(0) generated = generated.to(device) sample_outputs = model.generate( generated, num_return_sequences=3, max_length = 300, do_sample=True, top_k=50, top_p=0.95 ) for i, sample_output in enumerate(sample_outputs): print("{}: {}nn".format(i, tokenizer.decode(sample_output, skip_special_tokens=True)))
Y obtenemos como salidas:
Ahora ya puedes jugar y probar tú mismo con distintas salidas!
Aquí te dejo algunos ejemplos divertidos que conseguí (pinto en negrita el comienzo del prompt enviado al modelo):
En estos días estamos viendo cómo ChatGPT está siendo trending topic por ser el modelo GPT más poderoso y versátil de OpenAI, con capacidad de responder a cualquier pregunta, traducir idiomas, dar definiciones, crear poesía, historias y realizar snippets de código python.
En este artículo te acercamos un poco más a conocer qué son los modelos GPT que están revolucionando el campo del NLP mediante un ejercicio práctico.
Ya conoces un poco más sobre la librería transformers de HuggingFace, sobre los distintos modelos que puedes descargar en tu ordenador y personalizar. Como siempre, esto es sólo la punta del iceberg, te invito a que sigas investigando y aprendiendo más sobre todo ello y me dejes tus comentarios al respecto.
Nos vemos en el próximo post!
Puedes descargar la notebook con el ejercicio completo y el archivo con los textos de Ibai.
Recibe los próximos artículos sobre Machine Learning, estrategias, teoría y código Python en tu casilla de correo!
NOTA: algunos usuarios reportaron que el email de confirmación y/o posteriores a la suscripción entraron en su carpeta de SPAM. Te sugiero que revises y recomiendo que agregues nuestro remitente info @ aprendemachinelearning.com a tus contactos para evitar problemas. Gracias!
Si te gustan los contenidos del blog y quieres darme tu apoyo, puedes comprar el libro en papel, ó en digital (también lo puede descargar gratis!).
The post Generación de Texto en Español con GPT-2 first appeared on Aprende Machine Learning.
]]>The post ¿Cómo funcionan los Transformers? en Español first appeared on Aprende Machine Learning.
]]>Los Transformers aparecieron como una novedosa arquitectura de Deep Learning para NLP en un paper de 2017 “Attention is all you need” que presentaba unos ingeniosos métodos para poder realizar traducción de un idioma a otro superando a las redes seq-2-seq LSTM de aquel entonces. Pero lo que no sabíamos es que este “nuevo modelo” podría ser utilizado en más campos como el de Visión Artificial, Redes Generativas, Aprendizaje por Refuerzo, Time Series y en todos ellos batir todos los records! Su impacto es tan grande que se han transformado en la nueva piedra angular del Machine Learning.
En este artículo repasaremos las piezas fundamentales que componen al Transformer y cómo una a una colaboran para conseguir tan buenos resultados. Los Transformers y su mecanismo de atención posibilitaron la aparición de los grandes modelos generadores de texto GPT2, GPT3 y BERT que ahora podían ser entrenados aprovechando el paralelismo que se alcanza mediante el uso de GPUs.
En el paper original de 2017 “Attention is all you need” aparece el diagrama con la novedosa arquitectura del Transformer, que todos deberíamos tatuarnos en un brazo. Esta arquitectura surge como una solución a problemas de aprendizaje supervisado en Procesamiento del Lenguaje Natural, obteniendo grandes ventajas frente a los modelos utilizados en ese entonces. El transformer permitía realizar la traducción de un idioma a otro con la gran ventaja de poder entrenar al modelo en paralelo; lo que aumentaba drásticamente la velocidad y reducción del coste; y utilizando como potenciador el mecanismo de atención, que hasta ese momento no había sido explotado del todo. Veremos que en su arquitectura utiliza diversas piezas ya existentes pero que no estaban combinadas de esta manera. Además el nombre de “Todo lo que necesitas es Atención” es a la vez un tributo a los Beatles y una “bofetada” a los modelos NLP centrados en Redes Recurrentes que en ese entonces estaban intentando combinarlos con atención. De esta sutil forma les estaban diciendo… “tiren esas redes recurrentes a la basura”, porque el mecanismo de atención NO es un complemento… es EL protagonista!
All you need is
The BeatlesLoveAttention
Con el tiempo, esta arquitectura resultó ser flexible y se pudo utilizar para tareas más allá del NLP, además de para la generación de texto, clasificación, resumen de contenidos también pudo traspasar esa frontera y ser aplicado en Visión Artificial, Generación de Audio, Predicción de Series Temporales y Aprendizaje por Refuerzo.
Veamos primero la gran foto de los Transformers.
Estás viendo el dibujo que deberías de llevar en tu próxima camiseta:
La primera impresión puede ser algo intimidante, pero vayamos poco a poco. Si pensamos en el modelo como una caja negra, sería simplemente:
Entrada -> Transformer -> Salida
Vemos que con una entrada de texto “Hola” obtenemos la salida “Hello”.
Si hacemos un poco de zoom en esa caja, veremos dos componentes principales: Encoders y Decoders.
La entrada pasará por una serie de Encoders que se encadenan uno tras otro y luego envían su salida a otra serie de Decoders hasta emitir la Salida final. En el paper original, se utilizan 6 encoders y 6 decoders. Notaremos un recuadro llamado “Target” donde estamos pasando el Output Deseado al modelo para entrenar; pero obviamente que no lo pasaremos al momento de la inferencia; al menos no completo (más adelante en el artículo).
Observamos ahora con mayor detalle cómo está compuesto un Encoder y un Decoder y veremos que son bastante parecidos por dentro.
En su interior tanto Encoder como decoder cuentan con un componente de Atención (o dos) y una red neuronal “normal” como salidas.
Para comenzar a evaluar con mayor detalle las partes del Transformer, primero deberemos generar un Embedding de la entrada y luego entrar al Encoder. Así que veamos eso primero.
El uso de embeddings existía en NLP desde antes de los Transformers, puede que estés familiarizado con modelos como el word-2-vec y puede que ya hayas visto las proyecciones creadas con miles de palabras donde los embeddings logran agrupar palabras de manera automática en un espacio multidimensional. Entonces conceptos como “hombre y mujer”, distanciado de “perro y gato” veremos nombres de países juntos y profesiones ó deportes también agrupados en ese espacio. Primero convertimos las palabras a tokens, es decir a un valor numérico asociado, porque recordemos que las redes neuronales únicamente pueden procesar números y no cadenas de texto.
Entonces convertimos una palabra a un número, ese número a un vector de embeddings de 512 dimensiones (podría ser de otro valor, pero el propuesto en el paper fue este).
Entonces:
(*) NOTA: una palabra podría convertirse en más de un token
La parte novedosa que introduce el paper de los transformers, es el “Positional Encoding” a esos embeddings…
Una vez que tenemos los embeddings, estaremos todos de acuerdo que el orden en que pasemos las palabras al modelo, será importante. De hecho podríamos alterar totalmente el significado de una oración si mezclamos el orden de sus palabras o hasta podría carecer totalmente de significado.
La casa es verde != verde casa la es
Entonces necesitamos pasar al modelo los tokens pudiendo especificar de alguna manera su posición.
Aquí, de paso comentaremos dos novedades de los Transformers: la solución a este posicionamiento pero también esto permite el poder enviar en simultáneo TODOS los tokens al modelo, algo que anteriormente no se podía hacer, por lo que se pasaba “palabra por palabra” una después de la otra, resultando en una menor velocidad.
Para resolver el posicionamiento, se agrega una valor secuencial al embedding que se asume que la red podrá interpretar. Si aparece el token “perro” como segunda palabra ó como décima, mantendrá en esencia el mismo vector (calculado anteriormente en el embedding) pero con un ligero valor adicional que lo distinguirá y que será el que le de la pista a la red neuronal de interpretar su posición en la oración.
En el paper se propone una función sinusoidal sobre el vector de 512 dimensiones del embedding. Entonces ese vector de embeddings del token “perro” será distinto si la palabra aparece primera, segunda, tercera.
Y ahora sí, podemos dar entrada al Encoder de los Transformers.
El Encoder está compuesto por la capa de “Self attention” y esta conectada a una red feed forward. Entre las entradas y salidas se aplica la Skip Connection (ó ResNet) y un Norm Layer.
El mecanismo de Atención, es una de las implementaciones novedosas que propone el paper. Veremos que logra procesar en paralelo, manteniendo la ventaja de entreno mediante GPU.
El Encoder tiene como objetivo procesar la secuencia de los tokens de entrada y ser a su vez la entrada del mecanismo de atención del Decoder. No olvides que realmente se encadenan varios Encoders con varios Decoders (no se utilizan sólo uno en la práctica).
Primero que nada, ¿Para qué queremos un mecanismo de Atención? El mecanismo de atención nos ayuda a crear y dar fuerza a las relaciones entre palabras en una oración. Si tenemos el siguiente enunciado:
El perro estaba en el salón, durmiendo tranquilo.
Nosotros comprendemos fácilmente que la palabra “tranquilo” se refiere al perro y no al “salón”. Pero a la red neuronal que comienza “en blanco”, sin saber nada de las estructuras del lenguaje (español, pero podría ser cualquier idioma), y que además ve la misma frase como valores numéricos:
5 186 233 7 5 1433 567 721
NOTA: no olvides que cada token, por ej. el nº5 corresponde a un embedding de n-dimensiones de 512 valores. Por simplificar usamos el 5 como reemplazo de “El”, por no escribir sus 512 componentes.
¿Cómo podrá entender que ese último token “721” está afectando a la segunda palabra “186”?
Por eso surgen los mecanismos de atención; para lograr dar más -o menos- importancia a una palabra en relación a las otras de la oración.
La solución que presentan los Transformers en cuanto a la atención, es la construcción de un “Diccionario Blando” (Soft Dictionary). ¿Qué es esto de un diccionario blando?
En programación, es probable que conozcas el uso de diccionarios de tipo “clave-valor” un típico “hashmap” en donde ante una entrada, obtengo un valor dict[“perro”]=0.78.
Para el “diccionario de atenciones” podría ser que si pedimos la atención de “tranquilo vs perro” de la oración, nos devuelva un 1 indicando mucha atención, pero si le pedimos “tranquilo vs salón” nos devuelva un -1.
Pero… eso no es todo. Nuestro Diccionario es “Suave/Blando”, esto quiere decir que no se va a saber “de memoria” el resultado de una clave. Si alteramos un poco la oración, el diccionario tradicional fallaría:
El perro estaba durmiendo en un salón tranquilo.
Ahora la clave de atención para “tranquilo vs salón” deberá pasar de -1 a ser cercana a 1.
Para poder calcular la atención, se utilizan 3 matrices llamadas “Query-Key-Value” que van a operar siguiendo una fórmula que nos devuelve un “score” o puntaje de atención.
El resultado de la atención será aplicar la siguiente fórmula:
Si nos olvidamos del Softmax y el “multihead” (se definirá a continuación), podemos simplificar la fórmula diciendo que:
La atención será multiplicación matricial Q por la transpuesta de K; a eso le llamamos “factor”; y ese factor multiplicado por V.
¿Y eso qué significa? ¿Por qué estamos operando de esa manera? Si recuerdas, tanto Q como K son los valores de los Embeddings, es decir, cada token es un vector n-dimensional. Al hacer el producto vectorial obtenemos matemáticamente la “Similitud” entre los vectores. Entonces si el embedding estuviera funcionando bien, la similitud entre “nieve” y “blanco” deberían estar más cercanas que “nieve” y “negro”. Entonces cuando dos palabras sean similares, tendremos un valor positivo y mayor que si son opuestos, donde obtendríamos un valor negativo (dirección contraria). Este factor se multiplica por la matriz de Valor conformando el Score final de atención para cada relación entre pares de <<palabras>> que estamos evaluando.
Como estamos trabajando con matrices, seguimos aprovechando la capacidad de calcular todo a la vez y poder hacerlo acelerado por GPU.
…hay más en el paper, porque el tipo de atención que vamos a calcular se llama “Multi-head Attention“. ¿Qué son esas “Multi-cabezas”??? Lo que se hace es que en vez de calcular la atención de todos los tokens de la oración una vez con las 512 dimensiones (provenientes del embedding), subdividiremos esos valores en grupos y de cada uno, calcular su atención. En el paper proponen “8 heads” con lo que entonces calcularemos “8 atenciones de a 64 valores del embeddings” por cada token! Esto a mi modo de entender es algo bastante arbitrario pero por arte de “magia matemática” funciona… Luego de calcular esas 8 cabezas, (esas 8 atenciones) haremos un promedio de los valores de cada atención entre tokens.
Si volvemos al ejemplo para la clave de “tranquilo vs perro” calcularemos 8 atenciones y al promediarlas deberíamos obtener un valor cercano a 1 (para la 1er oración).
Cuando terminemos de entrenar el modelo Transformer completo, podríamos intentar analizar y entender esas matrices de atención creadas y tratar de comprenderlas. Algunos estudios muestran que la “multi-atención” logra representar relaciones como la de “artículo-sustantivo” ó “adjetivo-sustantivo”, “verbo sustantivo” lo cual me sigue pareciendo algo increíble.
Prometo que esto ya es lo último que nos queda comprender sobre los mecanismos de atención… A todo lo anterior, hay que sumar que tenemos 3 tipos distintos de atención
Su comportamiento es igual que al descripto anteriormente pero con alguna particularidad:
Self Attention se refiere que crearemos los valores y las matrices de Q-K-V a partir de las propias entradas de los tokens de entrada.
En la Atención Cruzada, vemos cómo utilizamos como entradas en el Decoder los valores obtenidos en el Encoder. Esto es lo que hará que con sólo el valor (Value) del Output pueda modelar la salida de atención buscada, esto es importante porque al final es lo que condicionará mayormente la “traducción” que está haciendo internamente el Decoder!
Y la llamada “Masked attention” se refiere a que enmascaramos parte triangular superior de la matriz de atención al calcularla para no caer en “data-leakage”, es decir, para no “adelantar información futura” que el Output no podría tener en su momento. Esto puede resultar confuso, intentaré aclararlo un poco más. En el Encoder, cuando tenemos los valores de entrada y hacemos “self-attention” dijimos que calculamos la atención de “todos contra todos” porque todos los tokens de entrada son conocidos desde el principio. Si recordamos la frase anterior:
El perro estaba en el salón, durmiendo tranquilo.
Aquí podemos calcular tanto la atención para la clave “perro-tranquilo” y también la de “tranquilo-perro”
Sin embargo -si suponemos que estamos traduciendo al inglés- en el output tendremos
“The dog was in the living room, sleeping peacefully”
PERO hay un detalle; para poder generar el output al momento de la inferencia, generaremos de a una palabra por vez, entonces iremos produciendo el siguiente output:
T1 – The
T2 – The dog
T3 – The dog was
T4 – The dog was in …
Esto quiere decir que vamos generando los tokens de a uno a la vez por lo que al principio no podríamos conocer la relación entre “dog-peacefully” pues esta relación aún no existe!
Entonces diferenciemos esto:
-> Al momento de entrenar el modelo pasamos el output deseado COMPLETO a las matrices de QKV de “Masked Attention” para entrenar en paralelo; al estar enmascarado, es como si estuviésemos simulando la secuencia “The, The dog, The dog was…”
-> Al momento de inferencia REALMENTE tendremos uno a uno los tokens como una secuencia temporal, realmente, iremos agregando de a una palabra del output a la cadena de salida a la vez.
Al utilizar Skip Connections permitimos mantener el valor de origen del input a través de las deep neural networks evitando que sus pesos se desvanezcan con el paso del tiempo. Esta técnica fue utilizada en las famosas ResNets para clasificación de imágenes y se convirtieron en bloques de construcción para redes profundas. También es sumamente importante la Normalización en RRNN. Previene que el rango de valores entre capas cambie “demasiado bruscamente” logrando hacer que el modelo entrene más rápido y con mayor capacidad de generalización.
La salida final del Encoder la dará una Red Neuronal “normal”, también llamada MLP ó capa densa. Se agregarán dos capas con Dropout y una función de activación no lineal.
Ahora que conocemos los componentes del Encoder, podemos ver que con esos mismos bloques podemos crear el Decoder.
Al momento de entrenar, estaremos pasando el Input “hola amigos” y Output “hello friends” (su traducción) al mismo tiempo al modelo.
Tradicionalmente, usamos la salida únicamente para “validar” el modelo y ajustar los pesos de la red neuronal (durante el backpropagation). Sin embargo en el Transformer estamos usando la salida “hello friends” como parte del aprendizaje que realiza el modelo.
Entonces, el output “hello friends” es también la “entrada” del decoder hacia embeddings, posicionamiento y finalmente ingreso a la Masked Self Attention que comentamos antes (para los valores de Q,K,V).
De aquí, y pasando por la Skip Connection y Normalización (en la gráfica “Add & Norm) entramos al segundo mecanismo de Atención Cruzada que contiene para las matrices Query y Key” la salida del Encoder y como Value la salida de la Masked Attention.
Nuevamente Suma y Normalización, entrada a la Feed Forward del Decoder.
No olvidemos que si bien estamos viendo en mayor detalle “un encoder” y “un decoder”, la arquitectura completa del Transformer implica la creación de (en el paper original) 6 encoders encadenados que enlazan con otros 6 decoders.
La salida final del modelo pasa por una última capa Lineal y aplicar Softmax. El Softmax, por si no lo recuerdas nos dará un valor de entre 0 y 1 para cada una de las posibles palabras (tokens) de salida. Entonces si nuestro “lenguaje” total es de 50.000 posibles tokens (incluyendo signos de puntuación, admiración, interrogación), encontraremos a uno de ellos con valor más alto, que será la predicción.
Si yo te dijera “en casa de herrero cuchillo de …” y vos tuvieras que predecir la próxima palabra, de entre todas las posibles en el castellano seguramente elegirías “palo” con probabilidad 0,999. Ahí acabas de aplicar Softmax intuitivamente en tu cabeza, y descartaste al resto de 49.999 palabras restantes.
Repasemos los puntos fuertes de la arquitectura de los Transformers:
Los Transformers se convirtieron en el modelo “de facto” para todo tipo de tareas de NLP, incluyendo Traducción de idiomas (como vimos), clasificación de texto, resumen de textos y Question-Answering. Sólo basta con modificar los datasets que utilizamos para entrenar al modelo y la “salida final del modelo”, manteniendo al resto de arquitectura.
A raíz de poder entrenar mediante GPU, reducción de tiempo y dinero, surgieron varios modelos para NLP que fueron entrenados con datasets cada vez más grandes, con la creencia de que cuantas más palabras, más acertado sería el modelo, llevándolos al límite. Ahora, parece que hemos alcanzado un tope de acuracy, en el cual no vale la pena seguir extendiendo el vocabulario.
Vemos 3 de estos grandes modelos de NLP y sus características
BERT (Bidirectional Encoder Representations from Transformers) aparece en 2018, desarrollado por Google y utiliza sólo la parte del Encoder de los Transformers. Este modelo fue entrenado con todos los artículos de la Wikipedia en Inglés que contiene más de 2500 millones de palabras. Esto permitió generar un modelo “pro-entrenado” en inglés muy poderoso que podía ser utilizado para múltiples tareas de NLP. Además, al ser Open Source, permitió la colaboración y extensión por parte de la comunidad científica. El poder utilizar un modelo pre-entrenado tan grande, preciso y potente, permite justamente “reutilizar” ese conocimiento sin necesidad de tener que entrenar nuevamente un modelo de NLP, con el coste y tiempo (e impacto medioambiental) que puede conllevar.
Además BERT contenía algunas novedades, por ejemplo, en vez de utilizar un “Embeddings único y estático”, implementó un mecanismo en donde la misma palabra (token) podría devolver un vector distinto (de “embeddings”) de acuerdo al contexto de la oración de esa palabra.
Cuando salió BERT, batió muchos de los records existentes en datasets como SQuAD, GLUE y MultiNLI.
GPT es un modelo de generación de texto creado por Open AI en 2019 y que contiene 1500 millones de parámetros en la configuración de su red neuronal profunda.
Al ser entrenado para generar “siguiente palabra”, pasa a convertirse en un problema de tipo “no supervisado”. Su re-entreno fue realizado usando BooksCorpus, un dataset que contiene más de 7,000 libros de ficción no publicados de diversos géneros.
Este modelo generó polémica en su momento debido a que empezaba a crear textos similares a lo que podía escribir una persona, con lo cual antes de ser lanzado, se temió por su mal uso para generar contenido falso en internet. Había sido anunciado en febrero y recién fue accesible al público, de manera parcial en Agosto. Los modelos de tipo GPT utilizan únicamente la parte de “Decoder” del Transformer.
Ejercicio Práctico: Crea tu propio chatbot con GPT-2 en Español!
Lanzado en 2020, la red de GPT-3 tiene 175.000 millones de parámetros. Entrenado con textos de internet que contienen más de 500.000 millones de tokens.
En GPT-3, la generación de textos por la IA puede alcanzar un nivel literario que pasa inadvertido por los humanos, el modelo es capaz de mantener la coherencia en textos largos y debido a su gran conocimiento del mundo, crear un contexto y párrafos muy reales.
Otra de las novedades que trajo GPT3 es que lograba realizar tareas “inesperadas” de manera correcta, sin haber sido entrenado para ello. Por ejemplo, puede realizar Question-Answer, crear diálogos ó hasta escribir código en Python, Java o traducir idiomas. A este tipo de aprendizaje “no buscado” le llamamos “One Shot Learning”.
El Codigo de GPT-3 aún no ha sido liberado, hasta la fecha.
Al comportarse tan bien para tareas de NLP, se comenzó a utilizar esta arquitectura adaptada para Clasificación de imágenes en Computer Vision y también logró superar en muchos casos a las Redes Convolucionales! A este nuevo modelo se le conoce como Vision Transformer o “Vit”.
También podemos utilizar Transformers para pronóstico de Series Temporales (Time Series). Este caso de uso tiene mucha lógica si lo pensamos, porque al final es muy parecido a “predecir la próxima palabra”, pero en su lugar “predecir la próxima venta”, ó stock…
Los Transformers están siendo utilizados en modelos generativos de música o como parte de los modelos de difusión text-to-image como Dall-E2 y Stable Diffusion.
Por último, los Transformers también están siendo utilizados en Aprendizaje por Refuerzo, donde también tienen sentido, pues la fórmula principal de este tipo de problemas también contiene una variable temporal/secuencial.
Y en todos estos campos, recién está empezando la revolución! No digo que será el Transformer quien reemplace al resto de Arquitecturas y modelos de Machine Learning existentes, pero está siendo un buen motivo para cuestionarlos, replantearlos y mejorar!
Los Transformers aparecieron en un paper de 2017 implementando un eficiente mecanismo de atención como punto clave para resolver problemas de traducción en el campo del Procesamiento del Lenguaje Natural. Como bien sabemos, el lenguaje escrito conlleva implícitamente un orden temporal, secuencial que hasta aquel entonces era una de las barreras para no poder crear modelos extensos, pues impedía el aprovechamiento de GPU. La nueva arquitectura rompía esa barrera utilizando unos sencillos trucos: utilizar todos los token al mismo tiempo (en vez de uno en uno) y enmascarar la multiplicación matricial para impedir el data leakage en el caso del decoder.
Además, resolvió el Posicionamiento de los token y aprovechó técnicas ya existentes como el uso de Skip Connections y Normalization Layers.
Todo ello posibilitó la creación de los grandes modelos actuales, BERT, GPT y la aparición de muchísimos modelos disponibles “pre-entrenados” que pueden ser descargados y reutilizados para fine-tuning por cualquier persona.
Como si esto fuera poco, los Transformers tienen la flexibilidad suficiente para traspasar el área de NLP y contribuir al resto de áreas del Machine Learning obteniendo resultados impresionantes.
Habrá que agradecer a Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez, Lukasz Kaiser, Illia Polosukhin por su obra maestra.
Enlaces a más info sobre Transformers!
Recibe los próximos artículos sobre Machine Learning, estrategias, teoría y código Python en tu casilla de correo!
NOTA: algunos usuarios reportaron que el email de confirmación y/o posteriores a la suscripción entraron en su carpeta de SPAM. Te sugiero que revises y recomiendo que agregues nuestro remitente info @ aprendemachinelearning.com a tus contactos para evitar problemas. Gracias!
Si te gustan los contenidos del blog y quieres darme tu apoyo, puedes comprar el libro en papel, ó en digital (también lo puede descargar gratis!).
The post ¿Cómo funcionan los Transformers? en Español first appeared on Aprende Machine Learning.
]]>The post Crea imágenes increíbles con Inteligencia Artificial en tu ordenador first appeared on Aprende Machine Learning.
]]>Estamos viviendo unos días realmente emocionantes en el campo de la inteligencia artificial, en apenas meses, hemos pasado de tener modelos enormes y de pago en manos de unas pocas corporaciones a poder desplegar un modelo en tu propio ordenador y lograr los mismos -increíbles- resultados de manera gratuita. Es decir, ahora mismo, está al alcance de prácticamente cualquier persona la capacidad de utilizar esta potentísima herramienta y crear imágenes en segundos (ó minutos) y a coste cero.
En este artículo les comentaré qué es Stable Diffusion y por qué es un hito en la historia de la Inteligencia Artificial, veremos cómo funciona y tienes la oportunidad de probarlo en la nube o de instalarlo en tu propio ordenador sea Windows, Linux ó Mac, con o sin placa GPU.
Stable Diffusion es el nombre de un nuevo modelo de Machine Learning de Texto-a-Imagen creado por Stability Ai, Comp Vis y LAION. Entrenado con +5 mil millones de imágenes del dataset Laion-5B en tamaño 512 por 512 pixeles. Su código fue liberado al público el 22 de Agosto de 2022 y en un archivo de 4GB con los pesos entrenados de una red neuronal que podemos descargar desde HuggingFace, tienes el poder de crear imágenes muy diversas a partir de una entrada de texto.
Stable Diffusion es también una gran revolución en nuestra sociedad porque trae consigo diversas polémicas; al ofrecer esta herramienta a un amplio público, permite generar imágenes de fantasía de paisajes, personas, productos… ¿cómo afecta esto a los derechos de autor? Qué pasa con las imágenes inadecuadas u ofensivas? Qué pasa con el sesgo de género? Puede suplantar a un diseñador gráfico? Hay un abanico enorme de incógnitas sobre cómo será utilizada esta herramienta y la disrupción que supone. A mí personalmente me impresiona por el progreso tecnológico, por lo potente que es, los magnificos resultados que puede alcanzar y todo lo positivo que puede acarrear.
Es cierto que fue entrenada con más de 5 mil millones de imágenes. Entonces podemos pensar: “Si el modelo vio 100.000 imágenes de caballos, aprenderá a dibujar caballos. Si vio 100.000 imágenes de la luna, sabrá pintar la luna. Y si aprendió de miles de imágenes de astronautas, sabrá pintar astronautas“. Pero si le pedimos que pinte “un astronauta a caballo en la luna” ¿qué pasa? La respuesta es que el modelo que jamás había visto una imagen así, es capaz de generar cientos de variantes de imágenes que cumplen con lo solicitado… esto ya empieza a ser increíble. Podemos pensar: “Bueno, estará haciendo un collage, usando un caballo que ya vio, un astronauta (que ya vió) y la luna y hacer una composición“. Y no; no es eso lo que hace, ahí se vuelve interesante: el modelo de ML parte de un “lienzo en blanco” (en realidad es una imagen llena de ruido) y a partir de ellos empieza a generar la imagen, iterando y refinando su objetivo, pero trabajando a nivel de pixel (por lo cual no está haciendo copy-paste). Si creyéramos que es una gran base de datos, les aseguro que no caben las 5.500.000.000 de imágenes en 4 Gygabytes -que son los pesos del modelo de la red- pues estaría almacenando cada imagen (de 512x512px) en menos de 1 Byte, algo imposible.
Veamos cómo funciona Stable Diffusion!
Stable Diffusion está basado en otro modelo llamado “Latent Diffusion” que proviene de modelos de difusión de ML que están entrenados para “eliminar el ruido de “imágenes sucias” paso a paso”. Esto quiere decir que al modelo le entrenamos con fotos donde ensuciamos ciertos pixeles, con manchas, desenfoque (blur) o distorsiones que agregamos a propósito y le pedimos como salida la imagen correcta (la imagen original sin ruido). Entonces, la red neuronal del modelo aprende a “quitar el ruido”, es decir, transformar esas manchas (ruido) en la imagen original.
Los modelos de difusión lograron resultados muy buenos como generadores de imágenes aunque su contra es que como trabajan a nivel de pixel requieren de mucha memoria RAM y toman tiempo para crear imágenes de alta definición.
La mejora introducida por los modelos “Latent Diffusion” es que el modelo es entrenado para generar “representaciones de imágenes latentes” (comprimidas). Sus tres componente principales son:
El modelo VAE tiene dos partes, un codificador y un decodificador. En codificador es usado para convertir la imagen en una representación latente de baja dimensión, que servirá como entrada a la “U-Net”. El decodificador por el contrario, transforma la representación latente nuevamente en una imagen.
Durante el entrenamiento de difusión latente, se usa el codificador para obtener las representaciones latentes de las imágenes para el proceso de difusión directa, se aplica más y más ruido en cada paso. Durante la inferencia, se realiza el proceso inverso de difusión donde “expande los latentes” para convertirlos nuevamente en imágenes con el decodificador VAE. Para la inferencia sólo necesitamos el decodificador.
La U-Net tiene una mitad de camino “de contracción” y otra mitad de “expansión“, ambos compuestos por bloques ResNet (para soportar redes profundas sin desvanecer el aprendizaje). La primera mitad de la U-Net reduce la imagen a una representación de baja resolución (similar a un encoder) y la segunda parte intentará generar la imagen original en alta resolución (similar a un decoder). La salida de la U-Net predice el “ruido residual” que puede ser usado para calcular la representación “sin ruido” de la imagen.
Para prevenir que la U-Net pierda información importante durante el downsampling, se agregan conexiones de “atajo corto” (skip connections) entre los dos caminos: encoder y decoder. Además la U-Net de stable diffusion puede condicionar su salida respecto de los text-embeddings de las capas de cross-attention. Las capas de “Atención Cruzada” se agregan tanto en las partes de codificación y decodificación de la U-Net, entre los bloques ResNet. A eso se le llama Difusión guiada ó Difusión condicionada.
El Text-Encoder es el responsable de transformar el mensaje de entrada por ejemplo “Ilustración de Taylor Swift con un pingüino bailando en la ciudad” en un espacio de embeddings que puede ser comprendido por la U-Net. Se utiliza un encoder de tipo Transformers que mapea la secuencia de palabras de entrada en una secuencia latente del embedding de textos.
Stable Diffusion no entrena al Text-Encoder durante la etapa de entrenamiento del modelo si no que utiliza un encoder ya entrenado de CLIP.
El modelo al completo, como lo muestra la web oficial de Stable Diffusion es así:
Al momento de entrenar, la red tiene como entrada una imagen y un texto asociado. La red convertirá la imagen “en ruido” por completo y luego la intentará reconstruir. No olvidemos que es un problema de Aprendizaje supervisado, por lo cual, contamos con el dataset completo, con F(x)=Y desde el inicio.
Esta es la arquitectura para entrenar al modelo. Si vas a utilizar la red una vez entrenada, realmente realizaremos el “camino de inferencia“, veamos:
Al momento de hacer la inferencia crearemos una imágen desde ruido! Por eso, el primer paso, es crear una imagen de 512×512 completamente de pixeles aleatorios!
Veamos la gráfica de inferencia que nos propone Stability.ai
Entonces, generamos la imagen de ruido y a partir de ella, la pasaremos a la U-Net que junto con el texto de entrada irá condicionando la salida, una y otra vez, intentará “quitar el ruido” para volver a una imagen original inexistente…
¿Te das cuenta? estamos engañando a la red neuronal, para que genere un gráfico que nunca antes existió…
La pobre Red Neuronal, es como si fuera un escultor con un cincel al que le damos un bloque de piedra enorme y le decimos “Quiero a Taylor Swift con un pingüino, hazlo!“.
Entonces, en cada iteración, creará desde el ruido, una imagen
Veamos algunos ejemplos de imágenes creadas por Stable Diffusion para ver si te convenzo de que esto es realmente algo grande… y luego ya puedes decidir si quieres probarlo y hasta instalarlo en tu propio equipo.
Aquí algunas imágenes encontradas en diversos canales:
En Lexica, que por cierto, te recomiendo visitar su web, pues tiene imágenes junto a los prompts para generarlas.
En Instagram
Imágenes encontradas en Reddit
Imágenes encontradas en Twitter
Puedes pagar por el servicio, ejecutar en la nube ó instalarlo en tu propia computadora.
Desde la web de los creadores puedes dar tus primeros pasos. Tienes que registrarte y obtienes unos créditos gratuitos, luego que se acaben, tendrás que pagar. Debes entrar en https://beta.dreamstudio.ai/dream
Veremos en la parte de abajo, centro la caja de texto donde podemos ingresar el “prompt” con lo que queremos dibujar. Sobre la derecha los parámetros de configuración, que comentaremos luego, pero lo básico es que puedes elegir el tamaño de imagen y cantidad de imágenes a generar.
Ten en cuenta que tienes unos créditos (gratuitos) limitados para utilizar, por lo que debes estar atento a lo que vas consumiendo.
Podemos instalar Stable Difussion en Windows y en Linux con “Instaladores automáticos” siguiendo las instrucciones del repositorio de Automatic1111. Para Windows hay otro instalador aqui .
Puedes instalar en ordenadores Mac (y aprovechar las GPUS de los chips M1 y M2) desde el repositorio de InvokeAI siguiendo las instrucciones para Macintosh.
Si te atreves a instalarlo de manera un poco más “manual”, puedes aventurarte a seguir las instrucciones del Repositorio Oficial de Stable Diffusion. No es difícil, básicamente, si tienes instalado Anaconda en tu ordenador, es clonar el repo y crear el environment de python siguiendo los pasos.
Casi todos los modos de instalar que vimos anteriormente, necesitan de un paso manual que es el de obtener y descargar el modelo desde la web de HuggingFace que ocupa 4.27 Gygabytes. Para ello, debes registrarte en HuggingFace e ir a la página del modelo. Deberás aceptar las condiciones de uso, y luego podrás descargar el último modelo, al momento de escribir este artículo es el archivo sd-v1-4.ckpt. Una vez descargado, lo deberás copiar en la carpeta models/ldm/stable-diffusion-1/ y renombrar el archivo como model.ckpt.
Eso es todo! Voilá, crea todas las imágenes que quieras! Tienes el mundo en tus manos!
Si tienes una tarjeta gráfica con GPU, en mi caso la Nvidia RTX3080 tarda 5 segundos en crear una imágen de 512x512px. Si no tienes tarjeta puedes crear imágenes usando CPU pero tardarán unos 6 minutos (en un ordenador del año 2015 Core i5 y 8GB de memoria). En ordenadores Macbook con chip M2 tarda aproximadamente 1 minuto por imagen.
Otra opción para utilizar este genial modelo de forma gratuita es utilizar las notebooks de Google Colab y activar la opción de GPU. Existen varias notebooks compartidas que puedes utilizar como template con la instalación, aquí te recomiendo esta notebook y un hilo en Twitter en español, que te ayuda a seguir los pasos.
Tanto en la versión web, la de instaladores, manual ó en la nube; contaremos con los mismos parámetros para configurar la red neuronal. Estos son:
Se le llama Prompt Engineering al arte de introducir textos que generen buenas imágenes. Lo cierto es que no es tan fácil como parece la creación de imágenes, es decir, la red siempre creará imágenes, pero para que destaquen realmente, hay que agregar las keywords adecuadas. Por suerte ya hay personas que descubrieron muchos de esos tweaks
Varios exploradores recomiendan seguir una fórmula de tipo:
Tipo imagen – objeto – lugar – tiempo – estilo ó autor
Por ejemplo:
Pintura de un gato con gafas en un teatro, 1960, por Velazquez
Y esto mismo… pero en inglés, obtenemos:
Hay algunas palabras que se agregan al final, que son muy útiles, poner “trending on ArtStation”, “highly detailed”, “unreal engine 5”.
Aqui te dejo un enlace a un artículo maravilloso que muestra con ejemplo de muchas de las combinaciones.
Además del txt2Img (que a partir de un texto, generemos una imagen), tenemos otra opción llamada img2img.
Con esta opción ingresamos una imagen creada por nosotros con el “paintbrush” u otra herramienta similar y la utilizaremos como imagen de inicio para generar una nueva imagen. Esto tiene mucha lógica si lo piensas, en vez de empezar con una imagen llena de ruido, le damos unas “guías” a la red neuronal para que pueda crear la imagen. Y los resultados son increíbles!
El Inpainting permite crear una máscara dentro de una imagen y que el modelo dibuje dentro, manteniendo el estilo pictórico y la coherencia.
También existe el llamado OutPainting, que nos permite “extender” una imagen, logrando obras increíbles, será mejor que lo veas!
A estas alturas, espero que estes tan emocionado como yo con esta nueva tecnología y esto es sólo el comienzo! Los modelos de Machine Learning de texto-a-imagen acaban de aterrizar y se perfeccionarán; uno de los puntos fuertes y gran acierto de Stable Diffusion es que al lanzarse a todo el público, logró captar a una gran comunidad de desarrolladores, artistas y curiosos que colaboran y que potencian sus capacidades aún más! Al momento de escribir el artículo, han pasado menos de 2 meses y aparecieron muchísimos proyectos relacionados. Además se comenta que está por aparecer la nueva versión del modelo de pesos entrenado 1.5 dentro de poco. Algunos usuarios hasta crearon videos mediante Stable Diffusion y otros empiezan a mezclar la red con las 3 dimensiones para crear objetos.
En próximos artículos veremos en mayor profundidad y en código Python el uso de redes VAE, U-Net y Transformers.
Hasta pronto!
Aquí comparto dos videos muy buenos sobre Arte con IA y otro sobre Stable Diffusion
Otros artículos relacionados de interés:
Recibe los próximos artículos sobre Machine Learning, estrategias, teoría y código Python en tu casilla de correo!
NOTA: algunos usuarios reportaron que el email de confirmación y/o posteriores a la suscripción entraron en su carpeta de SPAM. Te sugiero que revises y recomiendo que agregues nuestro remitente info @ aprendemachinelearning.com a tus contactos para evitar problemas. Gracias!
Si te gustan los contenidos del blog y quieres darme una mano, puedes comprar el libro en papel, ó en digital.
The post Crea imágenes increíbles con Inteligencia Artificial en tu ordenador first appeared on Aprende Machine Learning.
]]>The post NLP: Analizamos los cuentos de Hernan Casciari first appeared on Aprende Machine Learning.
]]>Luego de haber escrito sobre la teoría de iniciación al NLP en el artículo anterior llega la hora de hacer algunos ejercicios prácticos en código Python para adentrarnos en este mundo.
Como la idea es hacer Aprendizaje Automático en Español, se me ocurrió buscar textos en castellano y recordé a Hernan Casciari que tiene los cuentos de su blog disponibles online y me pareció un buen desafío.
Para quien no conozca a Hernan Casciari, es un escritor genial, hace cuentos muy entretenidos, de humor (y drama) muy reales, relacionados con su vida, infancia, relaciones familiares con toques de ficción. Vivió en España durante más de una década y tuvo allí a su primera hija. En 2005 fue premiado como “El mejor blog del mundo” por Deutsche Welle de Alemania. En 2008 Antonio Gasalla tomó su obra “Más respeto que soy tu madre” y la llevó al teatro con muchísimo éxito. Escribió columnas para importantes periódicos de España y Argentina hasta que fundó su propia editorial Orsai en 2010 donde no depende de terceros para comercializar ni distribuir sus productos y siempre ofrece versione en pdf (gratuitos). Tiene 7 libros publicados, apariciones en radio (Vorterix y Perros de la Calle) y hasta llevó sus historias a una genial puesta en escena llamada “Obra en Construcción” que giró por muchas provincias de la Argentina, España y Uruguay.
Lo cierto es que utilizaremos la librería python NLTK para NLP y haremos uso de varias funciones y análisis tradicionales, me refiero a que sin meternos – aún- en Deep Learning (eso lo dejaremos para otro futuro artículo).
Vamos al código!
Para obtener los textos, haremos webscraping (LEER ARTíCULO) en el blog de Hernan Casciari, recorreremos los cuentos que afortunadamente están clasificados en directorios por año, del 2004 al 2005 y guardaremos todos los posts de cada año en un archivo txt.
ATENCIóN: Este código puede tardar MUCHOS minutos en descargar todos los textos, pues para ser amables con el servidor, haremos un sleep(0.75) entre cada request (y son 386 cuentos).
# Web scraping, pickle imports import requests from bs4 import BeautifulSoup import pickle from time import sleep # Web Scrapes transcript data from blog def url_to_transcript(url): '''Obtener los enlaces del blog de Hernan Casciari.''' page = requests.get(url).text soup = BeautifulSoup(page, "lxml") print('URL',url) enlaces = [] for title in soup.find_all(class_="entry-title"): for a in title.find_all('a', href=True): print("Found link:", a['href']) enlaces.append(a['href']) sleep(0.75) #damos tiempo para que no nos penalice un firewall return enlaces base = 'https://editorialorsai.com/category/epocas/' urls = [] anios = ['2004','2005','2006','2007','2008','2009','2010','2011','2012','2013','2014','2015'] for anio in anios: urls.append(base + anio + "/") print(urls) # Recorrer las URLs y obtener los enlaces enlaces = [url_to_transcript(u) for u in urls] print(enlaces) def url_get_text(url): '''Obtener los textos de los cuentos de Hernan Casciari.''' print('URL',url) text="" try: page = requests.get(url).text soup = BeautifulSoup(page, "lxml") text = [p.text for p in soup.find(class_="entry-content").find_all('p')] except Exception: print('ERROR, puede que un firewall nos bloquea.') return '' sleep(0.75) #damos tiempo para que no nos penalice un firewall return text # Recorrer las URLs y obtener los textos MAX_POR_ANIO = 50 # para no saturar el server textos=[] for i in range(len(anios)): arts = enlaces[i] arts = arts[0:MAX_POR_ANIO] textos.append([url_get_text(u) for u in arts]) print(len(textos)) ## Creamos un directorio y nombramos los archivos por año !mkdir blog for i, c in enumerate(anios): with open("blog/" + c + ".txt", "wb") as file: cad="" for texto in textos[i]: for texto0 in texto: cad=cad + texto0 pickle.dump(cad, file)
Al finalizar obtendremos una carpeta llamada blog con 12 archivos: 2004.txt a 2015.txt.
Recuerda que puedes descargar todos los archivos, jupyter Notebook y código Python desde mi cuenta de Github
Cargaremos los archivos txt que creamos en el paso anterior y lo pasaremos a una estructura en un dataframe de Pandas para seguir usando en el próximo paso.
data = {} for i, c in enumerate(anios): with open("blog/" + c + ".txt", "rb") as file: data[c] = pickle.load(file) # Revisamos que se haya guardado bien print(data.keys()) # Veamos algun trozo de texto print(data['2008'][1000:1222]) # Combine it! data_combined = {key: [value] for (key, value) in data.items()} # We can either keep it in dictionary format or put it into a pandas dataframe import pandas as pd pd.set_option('max_colwidth',150) data_df = pd.DataFrame.from_dict(data_combined).transpose() data_df.columns = ['transcript'] data_df = data_df.sort_index() data_df
Ahora aplicaremos algunos de los filtros de limpieza que se suelen usar para poder tratar el texto:
# Apply a first round of text cleaning techniques import re import string def clean_text_round1(text): '''Make text lowercase, remove text in square brackets, remove punctuation and remove words containing numbers.''' text = text.lower() text = re.sub('\[.*?¿\]\%', ' ', text) text = re.sub('[%s]' % re.escape(string.punctuation), ' ', text) text = re.sub('\w*\d\w*', '', text) return text round1 = lambda x: clean_text_round1(x) data_clean = pd.DataFrame(data_df.transcript.apply(round1)) # Apply a second round of cleaning def clean_text_round2(text): '''Get rid of some additional punctuation and non-sensical text that was missed the first time around.''' text = re.sub('[‘’“”…«»]', '', text) text = re.sub('\n', ' ', text) return text round2 = lambda x: clean_text_round2(x) data_clean = pd.DataFrame(data_clean.transcript.apply(round2)) data_clean # Let's pickle it for later use data_df.to_pickle("corpus.pkl")
A partir del dataset que limpiamos, creamos y contamos las palabras:
(el archivo spanish.txt lo incluye NLTK ó si no lo tienes, copia de mi Github en el mismo directorio en donde tienes el código)
# We are going to create a document-term matrix using CountVectorizer, and exclude common Spanish stop words from sklearn.feature_extraction.text import CountVectorizer with open('spanish.txt') as f: lines = f.read().splitlines() cv = CountVectorizer(stop_words=lines) data_cv = cv.fit_transform(data_clean.transcript) data_dtm = pd.DataFrame(data_cv.toarray(), columns=cv.get_feature_names()) data_dtm.index = data_clean.index data_dtm.to_pickle("dtm.pkl") # Let's also pickle the cleaned data (before we put it in document-term matrix format) and the CountVectorizer object data_clean.to_pickle('data_clean.pkl') pickle.dump(cv, open("cv.pkl", "wb")) data_dtm
Ahora que tenemos nuestro dataset, investigaremos un poco
data = pd.read_pickle('dtm.pkl') data = data.transpose() data.head()
veamos las palabras más usadas cada año:
top_dict = {} for c in data.columns: top = data[c].sort_values(ascending=False).head(30) top_dict[c]= list(zip(top.index, top.values)) print(top_dict) # Print the top 15 words by year for anio, top_words in top_dict.items(): print(anio) print(', '.join([word for word, count in top_words[0:14]]))
--- 2004 si, alex, vez, lucas, cada, dos, ahora, ser, después, casa, años, siempre, nadie, ver
--- 2005 si, dos, vez, años, siempre, ser, vida, tiempo, hace, ahora, entonces, mundo, después, dice
--- 2006 si, años, dos, vez, siempre, hace, mundo, ser, ahora, entonces, cada, mismo, vida, casa
--- 2007 si, siempre, dos, entonces, vez, años, nunca, ahora, sólo, después, mundo, ser, casa, mujer
--- 2008 dos, si, años, casa, vez, ahora, después, siempre, entonces, hace, ser, tarde, tiempo, mismo
--- 2009 años, si, ahora, casa, vez, después, andrés, ser, dos, vida, hace, mundo, entonces, tres
--- 2010 revista, chiri, si, años, orsai, cada, hacer, dos, ahora, ser, hace, vez, casa, lectores
--- 2011 orsai, revista, número, lectores, dos, si, vez, chiri, años, ahora, hace, cada, siempre, revistas
--- 2012 orsai, dos, cada, si, revista, vez, dijo, chiri, ahora, después, tiempo, mismo, hace, argentina
--- 2013 si, dos, años, cada, dijo, papelitos, ve, después, ahora, vez, nunca, tres, tarde, día
--- 2014 si, vez, dos, años, después, tres, cada, siempre, casa, ser, lucas, mismo, alex, nunca
--- 2015 si, años, casa, hija, dos, entonces, ahora, nunca, después, siempre, vez, dijo, vida, ser
Vemos en el listado que hay palabras muy usadas pero que realmente no tienen un significado útil para el análisis. Entonces haremos lo siguiente: uniremos las 12 listas de más palabras en un nuevo ranking y de esas, tomaremos las “más usadas” para ser agregar en nuestro listado de Stop Words.
from collections import Counter # Let's first pull out the top 30 words for each anio words = [] for anio in data.columns: top = [word for (word, count) in top_dict[anio]] for t in top: words.append(t) print(Counter(words).most_common()) add_stop_words = [word for word, count in Counter(words).most_common() if count > 6] add_stop_words
['si',
'vez',
'cada',
'dos',
'ahora',
'después',
'años',
'hace',
'casa',
'nunca',
'siempre',
'mundo',
'día',
'mismo',
'hacer',
'tiempo',
'ser',
'vida',
'chiri',
'dijo',
'entonces',
'tres',
'noche']
Ahora quitaremos las Stop words de nuestro dataset. Usaremos el listado de spanish.txt, el que generamos recién y uno adicional que hice yo a partir de los resultados obtenidos (ojo… esto les puede parecer arbitrario y en parte lo es!)
from sklearn.feature_extraction import text from sklearn.feature_extraction.text import CountVectorizer # Read in cleaned data data_clean = pd.read_pickle('data_clean.pkl') # Add new stop words with open('spanish.txt') as f: stop_words = f.read().splitlines() for pal in add_stop_words: stop_words.append(pal) more_stop_words=['alex','lucas','andrés','mirta','tres','primer','primera','dos','uno','veces', 'así', 'luego', 'quizá','cosa','cosas','tan','asi','andres','todas','sólo','jesús','pablo','pepe'] for pal in more_stop_words: stop_words.append(pal) # Recreate document-term matrix cv = CountVectorizer(stop_words=stop_words) data_cv = cv.fit_transform(data_clean.transcript) data_stop = pd.DataFrame(data_cv.toarray(), columns=cv.get_feature_names()) data_stop.index = data_clean.index # Pickle it for later use import pickle pickle.dump(cv, open("cv_stop.pkl", "wb")) data_stop.to_pickle("dtm_stop.pkl")
Haremos una primer aproximación a “qué tenía Hernan Casciari en su cabeza” entre 2004 y 2015 en sus cuentos usando un modo de visualización llamado WordCloud. Esto puede requerir que debas instalar la librería Wordcloud con Pip ó si tienes instalado Anaconda, desde la interface ó por terminal con conda install -c conda-forge wordcloud
from wordcloud import WordCloud wc = WordCloud(stopwords=stop_words, background_color="white", colormap="Dark2", max_font_size=150, random_state=42) import matplotlib.pyplot as plt plt.rcParams['figure.figsize'] = [16,12] # Create subplots for each anio for index, anio in enumerate(data.columns): wc.generate(data_clean.transcript[anio]) plt.subplot(4, 3, index+1) plt.imshow(wc, interpolation="bilinear") plt.axis("off") plt.title(anios[index]) plt.show()
Ahora sacaremos algunas estadísticas de palabras únicas por año (el tamaño del vocabulario empleado) y el promedio de palabras por artículo
# Find the number of unique words per Year # Identify the non-zero items in the document-term matrix, meaning that the word occurs at least once unique_list = [] for anio in data.columns: uniques = data[anio].nonzero()[0].size unique_list.append(uniques) # Create a new dataframe that contains this unique word count data_words = pd.DataFrame(list(zip(anios, unique_list)), columns=['Anio', 'unique_words']) #data_unique_sort = data_words.sort_values(by='unique_words') data_unique_sort = data_words # sin ordenar data_unique_sort # ejecuta este si hicimos el webscrapping, o no tenemos los valores en la variable posts_per_year=[] try: enlaces except NameError: # Si no hice, los tengo hardcodeados: posts_per_year = [50, 27, 18, 50, 42, 22, 50, 33, 31, 17, 33, 13] else: for i in range(len(anios)): arts = enlaces[i] #arts = arts[0:10] #limito a maximo 10 por año print(anios[i],len(arts)) posts_per_year.append(min(len(arts),MAX_POR_ANIO)) # Find the total number of words per Year total_list = [] for anio in data.columns: totals = sum(data[anio]) total_list.append(totals) # Let's add some columns to our dataframe data_words['total_words'] = total_list data_words['posts_per_year'] = posts_per_year data_words['words_per_posts'] = data_words['total_words'] / data_words['posts_per_year'] # Sort the dataframe by words per minute to see who talks the slowest and fastest #data_wpm_sort = data_words.sort_values(by='words_per_posts') data_wpm_sort = data_words #sin ordenar data_wpm_sort
Veamos los datos en gráfico de barras horizontales:
import numpy as np plt.rcParams['figure.figsize'] = [16, 6] y_pos = np.arange(len(data_words)) plt.subplot(1, 3, 1) plt.barh(y_pos,posts_per_year, align='center') plt.yticks(y_pos, anios) plt.title('Number of Posts', fontsize=20) plt.subplot(1, 3, 2) plt.barh(y_pos, data_unique_sort.unique_words, align='center') plt.yticks(y_pos, data_unique_sort.Anio) plt.title('Number of Unique Words', fontsize=20) plt.subplot(1, 3, 3) plt.barh(y_pos, data_wpm_sort.words_per_posts, align='center') plt.yticks(y_pos, data_wpm_sort.Anio) plt.title('Number of Words Per Posts', fontsize=20) plt.tight_layout() plt.show()
Y hagamos una comparativa de frecuencia de uso de algunas palabras (aquí tu podrías escoger otras) En mi caso seleccioné casa, mundo,tiempo y vida
import nltk from nltk.corpus import PlaintextCorpusReader corpus_root = './python_projects/blog' wordlists = PlaintextCorpusReader(corpus_root, '.*', encoding='latin-1') #wordlists.fileids() # con esto listamos los archivos del directorio cfd = nltk.ConditionalFreqDist( (word,genre) for genre in anios for w in wordlists.words(genre + '.txt') for word in ['casa','mundo','tiempo','vida'] if w.lower().startswith(word) ) cfd.plot()
Ahora probaremos analizando los sentimientos en cuanto a “positivos y negativos” encontrados en el texto y sus cambios de polaridad. Para simplificar usaremos una librería llamada TextBlob que ya tiene esta funcionalidad hecha, aunque NO LO recomiendo para uso en producción. Por desgracia sólo funciona con textos en inglés, por lo que además nos obliga a traducir el texto con lo que eso conlleva… Pero para fines educativos -cómo los de este blog- es un buen ejemplo para ver el análisis de sentimiento.
data = pd.read_pickle('corpus.pkl') from textblob import TextBlob pol = lambda x: TextBlob(x).sentiment.polarity pol2 = lambda x: x.sentiment.polarity sub = lambda x: TextBlob(x).sentiment.subjectivity sub2 = lambda x: x.sentiment.subjectivity traducir = lambda x: TextBlob(x).translate(to="en") data['blob_en'] = data['transcript'].apply(traducir) data['polarity'] = data['blob_en'].apply(pol2) data['subjectivity'] = data['blob_en'].apply(sub2) data
Veamos globalmente tomando en cuenta la polaridad y la subjetividad detectadas por la librería:
plt.rcParams['figure.figsize'] = [10, 8] for index, anio in enumerate(data.index): x = data.polarity.loc[anio] y = data.subjectivity.loc[anio] plt.scatter(x, y, color='blue') plt.text(x+.001, y+.001, data['full_name'][index], fontsize=10) plt.xlim(-0.051, 0.152) plt.title('Sentiment Analysis', fontsize=20) plt.xlabel('<-- Negative -------- Positive -->', fontsize=15) plt.ylabel('<-- Facts -------- Opinions -->', fontsize=15) plt.show()
Ahora intentaremos analizar el comportamiento del sentimiento a medida que el autor escribía cuentos a lo largo de los años. Para ello, tomaremos de a 12 “trozos” de texto de cada año y los analizaremos. (NOTA: Esto no es preciso realmente, pues no coincide temporalmente con 12 meses, es para dar una idea al lector de las diversas técnicas que podemos aplicar).
import math def split_text(text, n=12): '''Takes in a string of text and splits into n equal parts, with a default of 12 equal parts.''' # Calculate length of text, the size of each chunk of text and the starting points of each chunk of text length = len(text) size = math.floor(length / n) start = np.arange(0, length, size) # Pull out equally sized pieces of text and put it into a list split_list = [] for piece in range(n): split_list.append(text[start[piece]:start[piece]+size]) return split_list list_pieces = [] for t in data.blob_en:#transcript: split = split_text(t,12) list_pieces.append(split) polarity_transcript = [] for lp in list_pieces: polarity_piece = [] for p in lp: #polarity_piece.append(TextBlob(p).translate(to="en").sentiment.polarity) polarity_piece.append(p.sentiment.polarity) polarity_transcript.append(polarity_piece) plt.rcParams['figure.figsize'] = [16, 12] for index, anio in enumerate(data.index): plt.subplot(3, 4, index+1) plt.plot(polarity_transcript[index]) plt.plot(np.arange(0,12), np.zeros(12)) plt.title(data['full_name'][index]) plt.ylim(ymin=-.1, ymax=.2) plt.show()
Ahora intentaremos obtener “automáticamente” algunos de los temas sobre los que escribe el autor. A decir verdad para que funcione deberíamos aplicar Lemmatization y limpiar mejor nuestro dataset. Para poder mostrar esta técnica nos vale, aunque no obtendremos resultados realmente buenos.
Utilizaremos la conocida librería Gensim y utilizaremos el algoritmo Latent Dirichlet Allocation (LDA)
data = pd.read_pickle('dtm_stop.pkl') tdm = data.transpose() sparse_counts = scipy.sparse.csr_matrix(tdm) corpus = matutils.Sparse2Corpus(sparse_counts) cv = pickle.load(open("cv_stop.pkl", "rb")) id2word = dict((v, k) for k, v in cv.vocabulary_.items()) from nltk import word_tokenize, pos_tag def nouns_adj(text): '''Given a string of text, tokenize the text and pull out only the nouns and adjectives.''' is_noun_adj = lambda pos: pos[:2] == 'NN' or pos[:2] == 'JJ' tokenized = word_tokenize(text,language='spanish') nouns_adj = [word for (word, pos) in pos_tag(tokenized) if is_noun_adj(pos)] return ' '.join(nouns_adj) data_clean = pd.read_pickle('data_clean.pkl') data_clean from sklearn.feature_extraction import text from sklearn.feature_extraction.text import CountVectorizer # Re-add the additional stop words since we are recreating the document-term matrix #add_stop_words = ['di', 'la', 'know', 'just', 'dont', 'thats', 'right', 'people', # 'youre', 'got', 'gonna', 'time', 'think', 'yeah', 'said'] #stop_words = text.ENGLISH_STOP_WORDS.union(add_stop_words) # Add new stop words #stop_words = text.ENGLISH_STOP_WORDS.union(add_stop_words) with open('spanish.txt') as f: stop_words = f.read().splitlines()# for pal in add_stop_words: stop_words.append(pal) for pal in more_stop_words: stop_words.append(pal) # Create a new document-term matrix using only nouns and adjectives, also remove common words with max_df cvna = CountVectorizer(stop_words=stop_words, max_df=.8) data_cvna = cvna.fit_transform(data_nouns_adj.transcript) data_dtmna = pd.DataFrame(data_cvna.toarray(), columns=cvna.get_feature_names()) data_dtmna.index = data_nouns_adj.index data_dtmna data_nouns_adj = pd.DataFrame(data_clean.transcript.apply(nouns_adj)) data_nouns_adj # Create the gensim corpus corpusna = matutils.Sparse2Corpus(scipy.sparse.csr_matrix(data_dtmna.transpose())) # Create the vocabulary dictionary id2wordna = dict((v, k) for k, v in cvna.vocabulary_.items()) # Probamos a modelar con 3 tópicos ldana = models.LdaModel(corpus=corpusna, num_topics=3, id2word=id2wordna, passes=10) ldana.print_topics()
Ahora haremos una “pasada” más profunda para ver si obtenemos 3 temáticas diferenciadas:
QTY_TOPICS=3 ldana = models.LdaModel(corpus=corpusna, num_topics=QTY_TOPICS, id2word=id2wordna, passes=80) ldana.print_topics()
[(0,
'0.001"jugador" + 0.001"papelitos" + 0.001"niño" + 0.001"casciari" + 0.001"luis" + 0.001"charla" + 0.001"luna" + 0.001"monedas" + 0.001"quizás" + 0.001"blogs"'),
(1,
'0.002"casciari" + 0.001"cuaderno" + 0.001"jorge" + 0.001"colo" + 0.001"cuadernos" + 0.001"waiser" + 0.001"coche" + 0.001"mundiales" + 0.001"goles" + 0.001"messi"'),
(2,
'0.002"comequechu" + 0.002"proyecto" + 0.001"textos" + 0.001"próximo" + 0.001"páginas" + 0.001"corbata" + 0.001"librero" + 0.001"libreros" + 0.001"sant" + 0.001"celoni"')]
corpus_transformed = ldana[corpusna] list(zip([a for [(a,b)] in corpus_transformed], data_dtmna.index))
[(1, '2004'),
(1, '2005'),
(2, '2006'),
(3, '2007'),
(2, '2008'),
(1, '2009'),
(3, '2010'),
(2, '2011'),
(1, '2012'),
(1, '2013'),
(1, '2014'),
(3, '2015')]
podemos intuir (¿forzosamente?) que lo que detectó el algoritmo se refiere a estos 3 temas:
Repasemos lo que hicimos y que resultados sacamos:
ATENCIóN: este artículo es algo “estándar”, como para comenzar a entender el NLP aplicado y cómo -con diversas técnicas- comprender el lenguaje humano. Realmente hay muchas más aplicaciones y tareas que se pueden hacer. Debo decir que casi todo “en el mercado” está hecho para analizar textos en inglés y parte de la dificultad para desarrollar el ejercicio consistió en llevarlo al castellano. Si conoces otras buenas herramientas en español, escríbeme!
ATENCIóN (2): Podrás encontrar diferencias entre las visualizaciones en este artículo y el último Jupyter Notebook colgado en Github, esto se debe a que hubo actualizaciones en el código que no están reflejados en el artículo.
Espero en el futuro poder mostrar más utilidades del NLP y también llegar a usar NLP con algoritmos de Deep Learning (por ejemplo con redes neuronales convolucionales).
Recibe los nuevos artículos sobre Machine Learning, redes neuronales, NLP y código Python cada 3 semanas aprox.
The post NLP: Analizamos los cuentos de Hernan Casciari first appeared on Aprende Machine Learning.
]]>The post Procesamiento del Lenguaje Natural (NLP) first appeared on Aprende Machine Learning.
]]>El Procesamiento del Lenguaje Natural (NLP por sus siglas en inglés) es el campo de estudio que se enfoca en la comprensión mediante ordenador del lenguaje humano. Abarca parte de la Ciencia de Datos, Inteligencia Artificial (Aprendizaje Automático) y la lingüística.
En NLP las computadoras analizan el leguaje humano, lo interpretan y dan significado para que pueda ser utilizado de manera práctica. Usando NLP podemos hacer tareas como resumen automático de textos, traducción de idiomas, extracción de relaciones, Análisis de sentimiento, reconocimiento del habla y clasificación de artículos por temáticas.
NLP es considerado uno de los grandes retos de la inteligencia artificial ya que es una de las tareas más complicadas y desafiantes: ¿cómo comprender realmente el significado de un texto? ¿cómo intuir neologísmos, irónias, chistes ó poesía? Si la estrategia/algoritmo que utilizamos no sortea esas dificultades de nada nos servirán los resultados obtenidos.
En NLP no es suficiente con comprender meras palabras, se deberá comprender al conjunto de palabras que conforman una oración, y al conjunto de lineas que comprenden un párrafo. Dando un sentido global al análisis del texto/discurso para poder sacar buenas conclusiones.
Nuestro lenguaje está lleno de ambigüedades, de palabras con distintas acepciones, giros y diversos significados según el contexto. Esto hace que el NLP sea una de las tareas más difíciles de dominar.
Vamos a comentar algunos de los usos más frecuentes:
Pues deberemos armar diversos modelos con el lenguaje, crear estructuras y con ellas alimentar algoritmos de Machine Learning:
Podemos empezar por ejemplo tomando un texto extenso. Utilizaremos Expresiones Regulares para subdividir el texto en palabras. Podemos contar las palabras, su frecuencia. Si hay algún patrón, por ejemplo si siempre después de una palabra X, siempre viene una palabra Y. Podemos analizar como terminan las palabras, por ejemplo “verbos terminados en “ar, er, ir” y descubrir la raíz de la palabra. Podríamos agrupar palabras con significados similares en contraposición a su palabras antónimas.
Resumiendo, podemos procesar de diversas maneras al lenguaje, sus componentes: gramática, sintaxis e intentar crear estructuras de apoyo que nos servirán como entradas para aplicar Regresión Lineal, Regresión Logística, Naive Bayes, árbol de decisión o Redes Neuronales según el resultado que estemos buscando.
(Spoiler: existen herramientas para realizar estas técnicas y no tener que programar todo a mano)
En próximos artículos veremos con mayor detalle ejemplos de NLP con python pero aquí les dejo una breve reseña de herramientas usadas en Python:
Vivimos en un mundo en el cual seguramente los humanos nos diferenciemos de otras especies por haber desarrollado herramientas de manera eficiente como el lenguaje. Nos comunicamos constantemente, hablando, con palabras, con gestos. Estamos rodeados de símbolos, de carteles, de indicaciones, de unos y ceros. El NLP es una herramienta fundamental que deberemos aprender y dominar para poder capacitar a nuestras máquinas y volverlas mucho más versátiles al momento de interactuar con el entorno, dando capacidad de comprender mejor, de explicarse: de comunicarse.
Deberemos ser capaces de entender las diversas herramientas y técnicas utilizadas en NLP y saber utilizarlas para resolver el problema adecuado. El NLP abarca mucho -muchísimo- espectro y es un recorrido que comienza pero nunca acaba… siguen apareciendo nuevos papers y nuevos instrumentos de acción. Al combinar estas técnicas de NLP “tradicional” con Deep Learning, la combinatoria de nuevas posibilidades es exponencial!
Recibe nuevos artículos sobre Machine Learning, redes neuronales, NLP y código Python 1 vez al mes. SI hay suerte 2 veces
En los próximos artículos iré agregando ejemplos prácticos Python con ejercicios de NLP (Ya está hecho!) para poder plasmar en código real los usos de este área del Machine Learning.
Mientras les dejo una lista de artículos interesantes también con ejercicios NLP en Python:
The post Procesamiento del Lenguaje Natural (NLP) first appeared on Aprende Machine Learning.
]]>