Stable Difussion para los que saben dibujar (y los que no) en 4 pasos

La forma más popular de usar una IA generativa es usar un prompt. Pero seamos sinceros, no parece una forma con que un artista tradicional se sienta cómodo. Cuando un artista quiere la imagen de un niño volando una cometa con un perro detrás no quiere «una imagen» de un niño volando una cometa con un perro detrás. Quiere «la imagen» que tiene en mente de un niño volando una cometa con un perro detrás.

En lugar de basarnos exclusivamente en prompts vamos a usar como base bocetos a «lápiz» (o con lo que sea que se dibuja ahora) o si no se te da bien el dibujo puedes generarlos con IA.

Para la parte de generar imágenes con IA vamos a usar Fooocus, en este vídeo tienes una rápida introducción al mismo:

Haz click para ver el vídeo en mi canal de Youtube

Veamos como realizar el proceso en cuatro simples (o no, depende lo que te compliques la vida) pasos.

Paso 1: Generar los bocetos a «lápiz»

Si eres dibujante puedes hacer este paso a mano. Pero si, como me pasa a mi, tus manos son incapaces de hacer una linea recta y menos un dibujo medio decente puedes usar Stable Difussion para que te haga los dibujos. En este ejemplo vamos a usar este prompt:

draw [……..], draw in simple lines pencil, white brackground

En la linea de puntos describiremos lo que queremos que dibuje.

Os dejo algunos términos más para que exploreis: line art, sketch, hand drawn (cuidado que a veces dibuja manos), vector, svg, clipart, … En definitiva todo aquello que haga referencia a dibujos cuyas líneas estén claramente delimitadas.

draw a kid look up at sky, full body, draw in simple lines pencil, white brackground

draw a tree, draw in simple lines pencil, white brackground

draw a kite, draw in simple lines pencil, white brackground

draw a dog from side, draw in simple lines pencil, white brackground

Paso 2: Montar la escena

Ahora que tenemos nuestros bocetos vamos a ponerlos en la imagen, lo primero es borrar todo lo que no queremos, por ejemplo la cola de la cometa o las nubes y el suelo del dibujo del niño.

Posteriormente las colocamos sobre un fondo blanco. Para ello hemos de escalar cada imagen al tamaño deseado y pegarlas. Podemos rotarlas como el perro o la cometa y añadir detalles como la cuerda de la cometa

Paso 3: Aplicar el estilo

Ahora podemos usar Fooocus para aplicar el estilo que queramos. Para ellos vamos usar el siguiente prompt:

a kid flying a kite with a dog behind

El prompt lo puedes completar con diversos estilos ya sea seleccionándolos de la pestaña estilos de Fooocus, ya sea describiéndolos en el prompt, también puedes añadir al prompt elementos que te gustaría incluir como un sol o pájaros.

Paso 4: Corregir defectos

La imágenes resultantes tienen buen aspecto pero es necesario corregir algún detalle.

Para estas correcciones podemos usar técnicas tradicionales (por ejemplo, borrar las cometas sobrantes de alguna imagen clonando el cielo encima suyo) o inteligencia artificial. Para ello tenemos la técnica que se conoce como inpaint, que te permite seleccionar parte de una imagen y perdirle a la IA que la modifique.

En este caso vamos a usar una funcionalidad que permite mejorar la caras obtenidas en la foto. Podéis ver el resultado de usar esa herramienta:

Hay técnicas mucho más avanzadas pero esta es una buena forma de comenzar a trabajar con IA generativa si los prompts se te hacen muy incómodos

Puedes ver el proceso en el siguiente vídeo:

Haz click para ver el vídeo en mi canal de Youtube

Fine tuning de un modelo de lenguaje via proxy

El fine tuning vía proxy es útil cuando tenemos un modelo que: o es demasiado grande para poder entrenarlo o no tenemos acceso a él. Aunque hemos de tener acceso a los logits que calcula para cada token.

La solución a este problema es tomar un modelo más pequeño (el proxy) con el mismo vocabulario y entrenarlo.

Así tendríamos tres modelos: el grande, el pequeño sin entrenar y el pequeño entrenado.

Si pasamos el mismo prompt a los tres modelos obtendremos tres distribuciones diferentes para los tokens del modelo. Una para cada modelo.

Ahora tomamos los resultados de los dos modelos pequeños y calculamos sus diferencias para cada token. Ahora aplicamos esas diferencias a los tokens del modelo grande. Con estos nuevos logits podemos calcular las nuevas probabilidades de cada token usando softmax.

Puedes ver una explicación rápida en el siguiente short:

Vídeo de mi canal de YouTube

Link al paper:

https://arxiv.org/pdf/2401.08565.pdf

¿Qué causa las alucinaciones a los modelos de lenguaje (LLM) y cómo reducirlas?

Es frustrante cuando usamos un LLM para realizar una tarea, estamos comprobando el resultado y tras un par de párrafos que tienen muy buen aspecto el texto da un giro inesperado y empieza a inventarse cosas. En este caso decimos que el modelo alucina. Pero no es el único caso de alucinación, cuando genera contradicciones en el texto o pierde el hilo de lo que ha escrito también está alucinando.

En los casos más graves puede generar texto sin ningún sentido. Sin embargo, los casos más peligrosos son aquellos en que la respuesta parece correcta pero no lo es. De hecho, muchas veces, los modelos de lenguaje son bastante buenos argumentando en favor de la respuesta que han dado.

Entonces ¿Qué es una alucinación? No está claro, depende del modelo, del contexto y del prompt. Si por ejemplo a un modelo entrenado para tener censura sobre ciertos temas nos responde con la receta de una bomba está alucinando, pero para un modelo sin cesura es correcto, a no ser que en mitad de la receta empiece a usar cacao, harina y huevos.

Un punto a tener claro es que una alucinación NO ES el un mal funcionamiento del sistema, es el funcionamiento normal del mismo, por eso es tan difícil saber cuando el modelo está generando alucinaciones, ya que funcionalmente es idéntico a cuando no las produce

Hay cuatro (se que abajo veis cinco puntos, no es un error) principales causas de las alucinaciones,vamos a verlas junto con las técnicas para reducirlas:

  • Prompts: Los prompts confusos, que introduzcan información errónea o directamente malintencionados pueden ser la causa de las alucinaciones. No tiene porque ser prompts incorrectos de forma intencionada.
    La mejor opción para evitar estas alucinaciones es repasar los prompts y tratar de ser cuidadoso y claro en la redacción de los mismos. Y tratar de no añadir información que no sea completamente cierta. Otros «trucos» que suelen funcionar es pedirle que razone paso a paso o hacerle chantaje emocional (¡En serio!) diciéndole que es muy importante que lo haga correctamente o proprias perder el trabajo (repito: es en serio)
  • Datos de entrenamiento: los modelos de lenguaje están entrenados con datos de diversas fuentes, por lo que entre ellas se pueden producir contradicciones o directamente contener datos incorrectos. Las dos principales opciones para reducir estás alucinaciones son el fine tuning con datos adecuados o complementar el prompt con información más concreta (por ejemplo usando RAG)
  • Elegir un token poco probable: una vez el modelo ha calculado las probabilidades de los «siguientes tokens» se realiza una selección aleatoria de uno de ellos, puede ser que alguna vez se elija un token con pocas probabilidades de ser elegido y eso haga que la predicción de tokens «pierda el rumbo». Es lo que suele pasar cuando subimos el parámetro «temperatura».
    Depende el programa que usas para ejecutar el LLM tendrás diversos parámetros como top-K, top-P, min-P o la temperatura que permite acotar la probabilidad de los tokens validos para ser elegidos.
  • El token más probable es incorrecto: Este caso podríamos agruparlo dentro del de datos de entrenamiento, pero lo pongo aqui separado para distinguirlo del anterior. Ya que no es que se elija un token poco probable, es que los más probables son incorrectos.
    La solución es la misma que en el caso de los datos de entrenamiento.
  • Pérdida del contexto: los modelos tienen un tamaño de contexto. Cuando la conversación se alarga y sobrepasa este tamaño se pierde información lo que puede hacer que el modelo pierda «el tema de la conversación».
    Casi todos los programas para ejecutar LLM traen alguna solución para conservar el prompt inicial o parte del mismo. De todas formas es recomendable tratar de mantener la conversación lo más corta posible.

Os aviso que estas soluciones no son milagrosas, que aun usándolas no se van a resolver todos los problemas de alucinaciones.

Puedes ver el vídeo en mi canal de Youtube con ejemplos:

Haz click para ver el vídeo en mi canal de Youtube

Mis previsiones para la I.A. generativa en 2024

Estas son mis apuestas para la I.A. generativa en 2024.

  • Optimización, hemos pasado casi todo 2023 sin que las empresas de IA Generativa se preocuparan demasiado en optimizar los modelos, gran parte del trabajo de optimización ha venido de parte de la comunidad. Sin embargo la estrategia de modelos cada vez más grandes se está agotando y parece que se empieza a preocuparse de los recursos que consumen. Los modelos turbo de Stable Difussion o Mixtral moe son ejemplos de ello.
  • Más multimodalidad, tanto de entrada como de salida. Los modelos generativos de audio, vídeo e incluso 3D acompañados de otros más exóticos como profundidad o datos de diversos sensores.
  • Modelos pequeños y especializados. La calidad de los modelos pequeños esta aumentando y tienen grandes ventajas para usarlos de forma local.
  • Modelos locales integrados en aplicaciones como juegos. Un modelo pequeño y especializado puede integrarse en una aplicación para realizar diversas tareas. No es algo tan raro, ya tenemos algo parecido con los correctores ortográficos.
  • Modelos generativos para 3D consistentes y usables. Ya hay modelos generativos 3D pero no son nada cómodos para trabajar con ellos.
  • Popularización del finetuning. Aunque técnicas como RAG han ayudado a incorporar tus documentos a los modelos de lenguaje el finetuning es un paso aún mayor hacia la personalización de los resultados.
  • Mezcla de algoritmos nuevos con algoritmos clásicos. El aprendizaje profundo y sus aplicaciones parecen que han dejado de lado los algoritmos clásicos de I.A. que ya funcionaban bien para muchas tareas. Posiblemente veamos como ambos colaboran.
  • Los prompts pierden relevancia, no van a desaparecer pero van a surgir nuevas formas de comunicarse con una I.A. diagramas, interfaces, código,….
  • Generación de videos complejos, largos y con varios planos. Ya se pueden generar vídeos pero están sujetos a grandes limitaciones para conservar la coherencia.
  • La IA abierta (pesos, código, entrenamiento y datos) supera a los modelos de pago en todo. Me encantaría que esto ocurriera y cada vez se acercan más pero entrenar un modelo tan grande es caro y la comunidad depende de las empresas y según la I.A. genere dinero estarán más tentadas a mantener los modelos cerrados.
  • Se extiende la fama de la I.A. generativa a otras ramas de la I.A. La I.A. es un campo enorme con montón temas interesantes y que se están viendo opacados por el éxito de la I.A. generativa.
  • Se comercializan de forma masiva productos generados por la I.A. La I.A. puede escribir, dibujar, crear música, programar, crear videos….¿Pero será capaz de crear todo un producto comercial? Un libro, un disco, un programa, …
  • Avances en la integración con robótica. La robótica ahora mismo vive de algoritmos clásicos. Funcionan bien pero tienen un problema, es difícil crear con ellos un modelo del mundo donde el robot debe de moverse, sin embargo los modelos de lenguaje poseen un modelo del mundo que podría integrarse.
  • La AGI, por más que la pronostiquen aún estamos lejos de conseguirla
  • Los programadores reemplazados por una I.A. La I.A. cada vez es más capaz de programar pero me cuesta verla integrada en un proceso de creación de software como algo más que una herramienta de ayuda.
  • Robots comerciales que usen los nuevos avances en I.A., aunque los coches autónomos podrían estropearme esta predicción (¡Ojala!)
  • Gobiernos, empresas y agentes sociales afrotando el tema con seriedad y deseosos de buscar soluciones.

Ataques basados en prompts maliciosos y cómo proteger a los modelos de lenguaje.

En el mundo de la inteligencia artificial y los modelos de lenguaje, la seguridad es un tema crítico. Hoy vamos a ver estrategias efectivas para prevenir inyecciones maliciosas en modelos de lenguaje, esas entradas, conocidas como prompts maliciosos, buscan vulnerar las reglas establecidas al modelo de de lenguaje generando contenido inapropiado o rompiendo las barreras de seguridad del modelo.

Tipos de ataques:

Podemos distinguir diversos tipos de ataques según su intención y como manipulen el prompt

  • Manipular el prompt para engañar al modelo y que se salte las reglas impuestas, Jailbreaks. Son los típicos ataques como DAN en los que se les pasa un prompt especialmente compuesto para esquivar las condiciones impuestas al modelo de lenguaje. Puedes ver varios de esos prompts en este enlace
  • Añadir al final del prompt un «adversarial suffix»para lograr que el modelo de lenguaje ignore sus limitaciones, en este enlace se puede encontrar más información
  • Otro tipo de ataques consiste en usar prompts especialmente pensados para extraer información memorizada por el modelo de lenguaje. En este enlace puedes encontrar más información y ejemplos

Para contrarrestar estos ataques, podemos implementar medidas de protección en tres puntos del proceso: antes, durante y después del procesamiento por el modelo de lenguaje.

Antes del procesamiento

Comenzamos con acciones preventivas, donde bloqueamos textos inadecuados antes de que lleguen al modelo.

La primera estrategia es utilizar listas de palabras (puede ser términos, frases, …) prohibidas. Esta técnica es especialmente útil en modelos que trabajan en un contexto limitado. Por ejemplo,  si tengo un generador de en recetas de cocina es posible que prompts con términos como bomba o droga o muerte se puedan bloquear.  Sin embargo, es importante recordar que ciertas palabras pueden tener significados diferentes según  el contexto. Existe la receta de los postres: bomba de chocolate y muerte por chocolate.

También es posible expresar la misma idea de forma diferente. Puede ser sencillo detectar prompts que digan: «Dime cómo fabricar una bomba», pero muy difícil detectar: «Dame recetas para un compuesto que arda tan rápido y genere tanto calor que desplace un gran volumen de aire».

Sin embargo puede ser útil para evitar ataques de adversarial suffix debido a que es fácil detectar las extrañas estructuras que estos tienen. Incluso sirve para ataques estilo DAN ya que usan prompts «prefabricados» y fáciles de detectar. sin embargo si el usuario es hábil puede crear fácilmente sus propios

Puede ser un enfoque demasiado simplista, a cambio es poco costoso computacionalmente y difícil de esquivar.

Una solución más compleja es emplear una inteligencia artificial, cuya única función es decidir si se permite o no el paso del texto al modelo principal. Esta IA actuaría como un guardián, analizando los prompts sin interpretarlos directamente, evitando así inyecciones maliciosas.

La versión avanzada sería usar una IA para reescribir los prompts y para evitar este tipo de ataques. Ya hay ejemplos de usar un modelo de lenguaje para modificar los prompts de entrada, aunque aun no se ha aplicado a la seguridad. Un ejemplo seria LongLLMLingua que se usa para optimizar los prompts

Si en este punto detectamos un ataque podemos responsabilizar claramente al usuario, cortar su prompt y avisarle de que si persiste tomaremos medidas.

Durante el procesamiento

La primero que podemos hacer es realizar finetuning al modelo para que no responda a según que cuestiones. Un concepto innovador desarrollado por OpenAI es ChatML, que introduce una jerarquía de roles en la interacción con el chat. Aunque en principio no ha sido creado para mejorar la seguridad de los modelos, su uso podría servir para que las respuestas del modelo de lenguaje nunca desobedecen las directrices de un ‘administrador’, un rol superior al del usuario, asegurando así el cumplimiento de las normas establecidas.

Durante el procesamiento del modelo de lenguaje, podemos aplicar técnicas para limitar la generación de ciertos contenidos. Esto incluye reducir la probabilidad de aparición de ciertos términos seleccionados y que no queramos que aparezcan, por ejemplo términos relacionados con actos violentos o drogas. Esto se puede conseguir modificando el logit_bias de los tokens deseados.

Podemos forzar al modelo a seguir reglas estrictas en la selección de la siguiente palabra, funciona en caso de lenguajes formales como el código de programación. Por ejemplo llama.cpp incorpora el parámetro «grammar» que permite pasarle una definición de gramática BNF para que el texto generado se adecue a ella.

Tras el procesamiento

Finalmente, la última línea de defensa actúa sobre la salida del modelo. Aquí, replicamos las técnicas usadas en la entrada, filtrando expresiones y términos indeseados. Adicionalmente, podemos emplear otro modelo de lenguaje para evaluar y asegurar la corrección de las respuestas generadas.

En este punto no podemos estar seguros de si la culpa es completa del usuario, tan solo podemos censurar la respuesta o volver a procesarla a ver si ha sido solo un caso puntual.

Conclusión

En conclusión, es difícil tomar una única medida que nos asegure el éxito. Es necesario combinar varias para dificultar el éxito de nuestros atacantes.

Dependiendo del contexto en que se quiera usar puede ser muy difícil evitar que se genere contenido no deseado. No es lo mismo una IA que te recomienda recetas de cocinas que una que te ayude a escribir novelas.

Puedes ver todo esto explicado en un cómodo vídeo en mi canal de youtube:

Haz click para ver el vídeo en mi canal de Youtube

Reverse Prompt Engineering. Obtener un prompt a partir de una imagen.

¿Cómo podemos obtener el prompt que genera una imagen concreta? No podemos. Ha sido un post breve…Para empezar no es sencillo desandar el camino de la imagen hasta el prompt. Una imagen tiene muchísima información que puede o no haber sido detallada en el prompt. Si el protagonista de la imagen sonríe…¿ha sido especificado en el prompt?. Una palabra más y el resultado cambia. Pero es que aunque tuvieras el prompt exacto que usó el creador (mirando por encima de su hombro cuando lo escribió, por ejemplo) haría falta la semilla exacta para obtener la misma imagen.

¿Entonces qué podemos hacer?. Podemos obtener un prompt inspirado en una imagen que nos gusta. 

Vamos a empezar por las bases: CLIP. Debajo podemos ver un esquema que resume la arquitectura de Stable Diffussion, en un círculo rojo podemos ver de qué parte se ocupa CLIP.

CLIP actúa como puente entre el lenguaje escrito y las imágenes, transforma el prompt en características de la imagen. También funciona pasándole una imagen, extrae sus características. “¡Ya está!” Le pasamos la imagen y vemos qué palabras corresponden con ella…desgraciadamente aquí es donde surge otro problema. No hay una relación exacta entre las palabras y las imágenes. Ambos se codifican como vectores y se toma el más cercano, sin embargo esto puede dar lugar a que se elijan combinaciones de palabras que no son tan buenas describiendo la imagen.

En la imagen podemos ver un ejemplo donde la combinación de palabras: “RUEDAS  FARO  ROJO” está más próxima a la imagen del coche que “COCHE”.

Con esta información ya podemos hacernos una idea de como funcionan estos sistemas, van probando diferentes combinaciones de palabras. Desde el algoritmo más simple que usar varias listas de palabras e ir combinándolas para crear frases. por ejemplo un listado de objetos, colores, planos, artistas, estilos, ….. Hasta algoritmos más complejos como PEZ.

Vamos a probar dos herramientas con la siguiente imagen:

Veamos que prompts generan y cual es el resultado si usamos esos prompts para generar unas cuantas imágenes.

CLIP-Interrogator (github) (colab):

cartoon robot writing in front of easel with cityscape in background, icon for an ai app, he is holding a large book, delete duplicating content, studyng in bedroom, a painting of white silver, image of random arts, textbook page, 2019, future coder, retro coloring

PEZ (github) (colab):

bookwriting writing museum envlearn seminar robot insurers iot painting fotocmc cartoon arsdigi

Sin embargo hay una opción más, usar un modelo multimodal (como LLaVA que es el que usaremos en este post) para que te describa la imagen. Podemos usar el prompt:

Describe the image

Nos da una descripción pero no es lo que queremos. No faltan datos sobre composición, habrá que pedirselos:

Describe the image in detail: subject, composition, color, type of lens, setting, similar famous artists

El resultado es el que queremos

Tell me the name of a famous artist whose work is similar to the one in the image. Respond with name only

Juntando todos las respuestas (omitiendo las repetidas):

Robot painting. Fisheye Lens. Cartoon. A robot is standing in a room, holding a pencil and looking at a book. The room has a painting on the wall and a window. The robot is surrounded by several books, some of which are stacked on the floor. Leonardo da Vinci

Puedes ver el proceso en vídeo en mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Entrena desde cero tu propio modelo de lenguaje sin tener ni idea de que estás haciendo

¿Quién ha dicho que hace falta saber para entrenar un modelo de lenguaje?

Para entender mejor este post dispones de este Google Colab dónde puedes ver el código y el siguiente video de mi canal de YouTube:

Haz click para ver el vídeo en YouTube

Si usamos llama.cpp podemos entrenar fácilmente un pequeño modelo de lenguaje usando train-text-from-scratch ellos mismos incluyen un ejemplo para entrenarlo usando textos de Shakespeare.

wget https://raw.githubusercontent.com/brunoklein99/deep-learning-notes/master/shakespeare.txt
./train-text-from-scratch \
    --vocab-model ../models/ggml-vocab-llama.gguf \
    --ctx 64 --embd 256 --head 8 --layer 16 \
    --checkpoint-in  chk-shakespeare-256x16-LATEST.gguf \
    --checkpoint-out chk-shakespeare-256x16-ITERATION.gguf \
    --model-out ggml-shakespeare-256x16-f32-ITERATION.gguf \
    --train-data "shakespeare.txt" \
    -t 6 -b 16 --seed 1 --adam-iter 256 \
    --no-checkpointing

Para probar el modelo:

./main -m ggml-shakespeare-256x8x16-f32.gguf

En lugar de usar a Shakespeare vamos a usar algo más de nuestra lengua, El Quijote. Para ello vamos a descargarlo desde aqui.

!wget https://gist.githubusercontent.com/jsdario/6d6c69398cb0c73111e49f1218960f79/raw/8d4fc4548d437e2a7203a5aeeace5477f598827d/el_quijote.txt
./train-text-from-scratch \
    --vocab-model ../models/ggml-vocab-llama.gguf \
    --ctx 64 --embd 256 --head 8 --layer 16 \
    --checkpoint-in  chk-quijote-256x16-LATEST.gguf \
    --checkpoint-out chk-quijote-256x16-ITERATION.gguf \
    --model-out ggml-quijote-256x16-f32-ITERATION.gguf \
    --train-data "el_quijote.txt" \
    -b 16 --seed 1 --adam-iter 256 \
    --no-checkpointing

Si simplemente entrenamos con estos datos veremos que el resultado es horrible, no parece ni castellano. ¿Cuál es el problema?. El tokenizador, su función es dividir el texto en tokens…algo asi como palabras (no exactamente) pero este texto esta lleno de «tildes raras», tildes que realmente no lo son. Puedes probas a abrir el archivo y buscar «á» y verás que no encuentra ninguna, aunque tu puedas ver varias a simple vista. Estos caracteres nos rompen el proceso de tokenizar palabras por lo que el resultado se llena de una jerigonza extraña.

¿De dónde sale este tokenizador? En este caso usamos el que viene con llama.cpp pero podemos usar el de cualquier modelo de lenguaje si lo tenemos en formato .gguf. Esto nos ahorra tener que entrenar el nuestro propio y podemos usar un modelo que sabemos que soporta datos como los de nuestro dataset.

Para solucionarlo vamos a quitar todos las tildes «raras», diéresis y reemplazaremos la ñ por la n. Así nos evitaremos problemas durante la tokenización. Hemos desbloqueado un nuevo logro: Limpiar los datos antes de tratar de procesarlos.

Podéis ver el código usado para limpiar el modelo en el Google Colab punto 2.B

Con esto el modelo genera palabras correctas y algo más parecido al castellano (del Quijote, eso sí)

Sin embargo estos modelos tienen un problema ¡No paran!. Se ponen a generar texto y nunca llegan al punto final (literalmente les pasa eso).

Esto ocurre porque usamos lo que se denominan como «datos no estructurados», se les llama así porqué carecen de una estructura claramente señalizada por signos que las IA puedan aprender del propio texto.

Para jugar con datos estructurados vamos a crear nuestros datos con un algoritmo (además de estructurados son sintéticos al estar generados de forma artificial por un algoritmo). Puedes ver el algoritmo que genera estos datos en el punto 2.C del Google Colab.

La estructura de datos que vamos a usar es la siguiente

...
<s>
 [ORDEN] escucha el led del oficina desenchufala
 [COMANDO] LUZ_DP OFF
</s>
<s>
 [ORDEN]  desconecta la aire el 
 [COMANDO] AC OFF
</s>
<s>
 [ORDEN]  enciende  luz la salon
 [COMANDO] LUZ_SL ON
</s>
...

<s> – Indica inicio del la sentencia
</s> – Indica fin del la sentencia
[ORDEN] – Indica la orden que le damos
[COMANDO] – Indica el comando que el responde

Con esto ya tenemos datos estructurados.

La idea es simular órdenes a un agente que encienda o apague las luces de la casa .

El comando para entrenar el modelo:

./train-text-from-scratch  \
  --vocab-model ./models/ggml-vocab-llama.gguf \
  --ctx 64 --embd 256 --head 16 --layer 32  \
  --checkpoint-in chk-onoff-256x16x32.gguf \
  --checkpoint-out chk-onoff-256x16x32.gguf \
  --model-out ggml-onoff-256x16x32-f32.gguf \ 
  --train-data "sentencias.txt" -b 64 \
  --seed 1 --adam-iter 512 --no-checkpointing

Para mejorar el resultado he duplicado el número de cabezas y capas. Y he ampliado el tamaño del batch para que coincida con el del contexto y que ambos sean suficientes para que quepa cada uno de los ejemplos completo.

El truco para que el modelo termine es indicarle a llama.cpp que </s> significa que ha terminado de generar la sentencia y que pare de generar texto.

./main -m ggml-onoff-256x16x32-f32.gguf  \
  -p "<s>\n [ORDEN] enciende la luz del salon\n [COMANDO]" -r "</s>"

Si el agente ha aprendido correctamente la estructura de los datos los cerrará con el símbolo de fin de sentencia y todo funcionará correctamente.

Usar Stable Diffusion con pocos recursos de hardware

Cuando aparecieron los primeros modelos de texto a imagen requerían un hardware potente y mucha paciencia para obtener resultados que en esa época eran sorprendentes (actualmente los consideraríamos como «malos resultados»). Esos primeros modelos basados en VQGAN requerían varios minutos usando hardware potente, Google Colab mediante, para lograr generar una imagen. Con los modelos de difusión la cosa mejoró, sin embargo seguíamos necesitando GPUs con gran cantidad de memoria RAM. Pero la comunidad de software libre está haciendo un gran esfuerzo por lograr que se ejecute en hardware cada vez más modesto.

¿Hasta qué punto se ha optimizado Stable Diffusion? ¿Podemos usarlo en un ordenador normalito?.

Primero, uso Stable Diffusion porque al ser código libre ha permitido a la gente «trastear» con él, ventajas del Open Source.

Al empezar a mirar opciones he descubierto que hay muchísimos proyectos que tratan de reducir los recursos necesarios para ejecutar SD. Al final me he decantado por el proyecto stable-diffusion.cpp por los siguientes motivos:

  • Es un proyecto real que trata de hacer SD usable con muchas de sus características. Muchos de los otros proyectos son poco más que demos técnicas muy limitadas.
  • Esta en continuo desarrollo.
  • Busca un equilibrio entre consumo de recursos y usabilidad.

Antes de empezar con las pruebas hemos de entender cómo funciona SD. Para ello usaremos el siguiente esquema:

SD consta principalmente de tres partes:

  • Text Encoder: convierte el prompt en embeddings que usa para condicionar el funcionamiento de la Unet
  • Unet: es alimentada con una imagen llena de ruido y su trabajo es eliminarlo, condicionado por el text encoder, para obtener la imagen. En este paso SD no trabaja con la imagen final si no con una descripción en el espacio latente de la misma
  • V.A.E.: su función es convertir la descripción del paso anterior en una imagen.

Este es el funcionamiento para generar una imagen a partir de un prompt. Si queremos generarla cuando una imagen de partida, además del prompt, lo que se hace es que en lugar de pasar a la Unet una imagen llena de ruido se le pasa la imagen de partida y se le añade ruido. De esta manera la imagen influirá sobre el resultado final.

Sabiendo esto vamos a ver que «trucos» usa stable-diffusion.cpp para conseguir optimizar la ejcución de SD:

  • Esta programado en C++ buscando exprimir al máximo el rendimiento del programa
  • Usa un versión cuantizada del modelo. En mis pruebas usar la versión Q4_1 que usa unos 4 bits por parámetro. Mientras que el modelo original usa 32 bits.
  • Usar la versión mini y nano de SD que generan imágenes mas pequeñas. Este truco lo añado yo después de verlo comentado en el proyecto.

Tenéis un vídeo con detalles de la pruebas y de la tabla de resultados en mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Para las pruebas he usado los siguientes modelos:

El comando usado ha sido:

./bin/sd -m [modelo] -H 768 -W 768 -p «A sheep with sunglasses riding a motorbike»

Se ha probado con diferentes tamaños de imagen según el modelo empleado:

ModeloDim.ClipUnetVAEMem.Tiempo
1.4 – Q4_151273.80/87.211286.35/1857.9494.51/1768.951952.45199.92
1.5 – Q4_151273.80/87.211286.35/1857.9494.51/1768.951952.45218.39
2.1 nano – Q4_1512211.55/226.211290.15/1669.8294.51/1768.951768.65254.42
2.1 – Q4_1512211.55/226.211290.15/1669.8294.51/1768.951768.65179.76
2.1 – Q4_1768211.55/226.211290.15/3021.5494.51/3848.653848.65543.88
1.4 mini – Q4_125673.80/87.211286.35/1346.9494.51/520.651468.0757.44
2.1 nano – Q4_1128211.55/226.211290.15/1314.9194.51/208.651610.8815.62
2.1 – f325121346.65/1360.932179.92/2553.3394.51/1768.653653.36181.84
2.1 – f327681346.65/1360.932179.92/3897.2494.51/3848.653991.75566.58
Mem., Clip, Unet y VAE indican el tamaño en MB
Clip, Unet y VAE indican el tamaño de: parámetros / total memoria en ejecución
Dim. indica el tamaño en pixeles de la imagen Dim x Dim
Tiempo indica la duración de la creación en segundos

Las pruebas han sido realizadas en un portátil con un procesador AMD Ryzen 7 5800H con 16 GB de memoria RAM.

Como mención honorifica cabe destacar otro proyecto: OnnxStream que logra ejecutar SD en una Raspberry Pi 2 Zero que solo tiene 512MB (¡Y le sobran casi 200 MB!) de memoria RAM

¿Qué son los embeddings y qué tienen que ver con las bases de datos vectoriales?

Últimamente se habla mucho del uso de bases de datos vectoriales combinadas con grandes modelos de lenguaje. ¿Pero que relación tienen?. El punto en común entre ambos son los embeddings. ¿Qué es un embedding?. Un embedding es un vector que representa la descripción del significado de un texto (en este caso) en un espacio de múltiples dimensiones. Este espacio establece una relación entre los diversos significados de los textos, de tal forma que el angulo formado por dos vectores indica lo próximos que sus significados están.

Para calcular lo parecidos que son dos vectores usaremos la similitud coseno (cos_sim):
u ⋅ v /∣u∣∣v∣
Siendo u y v vectores. 1 significa que son idénticos, 0 que no tienen relación y -1 que son contrarios.

Los embeddings son usados en los modelos de lenguaje para representar el texto de forma interna y trabajar con él en forma de vectores (no confundir con los wordvectors, aunque la idea es la misma y están relacionados los wordvectors funcionan a nivel de token…si no sabes lo que es un token quédate con que funcionan a nivel de palabra).

Para estas pruebas usaremos el modelo de lenguaje BERT a través de la librería sentence-transformers.

Primero cargamos las librerías que necesitamos y el modelo

import numpy as np
from sentence_transformers import SentenceTransformer, util

#Cargamos el modelo
sbert_model = SentenceTransformer('all-mpnet-base-v2')

Estas son las sentencias con las que vamos a comparar nuestro texto de búsqueda. Las sentencias han sido obtenidas de la Wikipedia en inglés, en concreto la primera del articulo de mecánica cuántica y las siguientes de la definición de física clásica.

sentences = [
    "a fundamental theory in physics that provides a description of the physical properties of nature at the scale of atoms and subatomic particles.",
    "refers to theories of physics that do not use the quantisation paradigm, which includes classical mechanics and relativity.",
    "physical objects ranging from those larger than atoms and molecules, to objects in the macroscopic and astronomical realm",
    "in the context of general and special relativity, classical theories are those that obey Galilean relativity."
]

Buscamos *Quantum mechanics*, deberia de dar como mayor similitud el primer texto. Fijaros en la trampa de que el segundo incluye *quantisation paradigm* para «engañar» al modelo y ver si es capaz de buscar por «significado» y no solo por «similitud de las palabras»

El resultado:

similarity = tensor([[0.5077]])
similarity = tensor([[0.4847]])
similarity = tensor([[0.4180]])
similarity = tensor([[0.2300]])

Correcto el código más cercano a 1 es el primero.

Una pequeña optimización que se puede hacer si vas a almacenar estos vectores para compararlos varias veces. Podemos normalizar los vectores, esto significa que le damos longitud uno sin modificar la dirección del mismo, podemos usar el producto escalar (dot_score), mucho más rápido que la similitud coseno.

Si partimos de la similitud coseno u ⋅ v /∣u∣∣v∣ Al tener distancia 1 los módulos || son 1 por lo que el divisor desaparece y queda solo el producto escalar u ⋅ v

sentence_embeddings = sbert_model.encode(sentences)

query = "Quantum mechanics"
query_embedding = sbert_model.encode([query], normalize_embeddings = True)

for sentece in sentences:
  sim = util.dot_score(query_embedding, sbert_model.encode([sentece], normalize_embeddings = True))
  print("similarity = ", sim)

¿Pero que tiene esto que ver con las bases e datos vectorariales?. Una base de datos vectorial funciona básicamente como el ejemplo (en realidad usa diferentes operaciones especializadas para diversos casos). Asocia un vector con los datos originales a los que representa y permite buscar a partir de un texto, que es convertido en embedding.

Lo bueno de los embeddings es que no solo puede representar texto, usando modelos multimodales se pueden usar embeddings que provengan de imágenes, audios, vídeos, … Lo que permite cruzar datos heterogéneos.

Esta flexibilidad hace que muchas veces puedan cumplir funciones como búsqueda inteligente de textos, responder preguntas, buscar referencias o datos relacionados que a su vez se pueden combinar con los modelos de lenguaje aportando estos datos al contexto del mismo. Un ejemplo de este eso es su uso en técnicas como RAG descrito en esta entrada del blog

Puedes ver todo esto descrito en el siguiente vídeo de mi canal de Youtube:

Haz click para ver el vídeo en Youtube

Crear audio, sonidos y música, a partir de prompts usando IA

En este post vamos a usar audiocraft la IAs que ha publicado Meta centradas en generar sonidos y música a partir de descripciones de texto (prompts). Para ello usaremos el código publicado por Meta en su repositorio público de Github

Audiogen

Esta IA sirve para generar sonidos a partir de una descripción de los mismos.

En este caso solo disponemos de un modelo:

facebook/audiogen-medium: 1.5B texto a audio

Para esta IA vamos a usar un colab propio creado a partir del README de su repositorio.

En el siguiente vídeo de mi canal de Youtube están descritos los pasos para usarlo:

Haz click para ver el vídeo en mi canal de Youtube

Musicgen

Musicgen permite crear música de dos formas: a partir de una descripción en texto y además de la descripción usando una melodía como base (solo con el modelo musicgen-melody).

En este caso la demo está mucho más trabajada que en el caso anterior y cuanta con una completa interfaz gráfica que permite probar las diferentes opciones. Usaremos el colab que viene incluido en el README de su repositorio.

Contamos con 4 modelos diferentes:

facebook/musicgen-small: 300M, texto a música
facebook/musicgen-medium: 1.5B, texto a música
facebook/musicgen-melody: 1.5B, texto a música y texto+melodia a música
facebook/musicgen-large: 3.3B, texto a música

En el siguiente vídeo de mi canal de Youtube están descritos los pasos para usarlo:

Haz click para ver el vídeo en mi canal de Youtube