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

Crea imágenes 360º con Stable Difussion

la forma de crear imágenes de 360° con SD es más simple de lo que parece, vamos a usar el siguiente LoRA: https://civitai.com/models/118025/360redmond-a-360-view-panorama-lora-for-sd-xl-10

Trabaja con SD XL. Una vez cargado el LoRA en nuestra aplicación para generar imágenes favorita hay que tener en cuenta varias cosas:

  • Lo recomendable es que la imagen tenga un formato 2:1, mejor aun si es 1600×800.
  • Un peso recomendable para ese LoRA es entre 1 y 1.2. No tiene que serlo obligatoriamente, pero por los ejemplos que he visto, y mis pruebas, son buenos valores para empezar
  • Para «activar» el LoRA es recomendable usar la expresión «360 view» en el prompt. A mi personalmente me ha funcionado muy bien empezar el prompt con «A 360 view of».
  • Este LoRA funciona muy bien generando imágenes realistas, no tan bien con otro tipo de imágenes. Sospecho que se debe al entrenamiento, que será mayoritariamente con fotos de paisajes, naturaleza, skylines, …
  • Para ver bien el resultado en un visor de imágenes de 360º lo recomendable seria escalar la imagen con alguna otra IA generada una al menos 3 o 4 veces su tamaño.

Podéis ver el proceso de crear una imagen en este vídeo de mi canal de Youtube:

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

Para ver las imágenes 360° generadas podéis usar: https://renderstuff.com/tools/360-panorama-web-viewer/

Little Alchemy infinito usando un modelo de lenguaje como motor del juego.

No se si habéis jugado al Little Alchemy, es un juego en el que partes con los 4 elementos (aire, agua, tierra, fuego) y tienes que ir combinándolos para obtener nuevas sustancias, materiales, cosas, … Algunas más predecibles que otras,  por ejemplo si mezclas agua y tierra y obtienes barro.

Si ya lo conocías, es posible que sepas lo adictivo que puede ser ir combinando items. Imaginaros lo que me pareció la idea de tener uno infinito (que luego no es tan infinito). Para ello usa un LLM para ir creando las diferentes combinaciones. Podéis probarlo en este link. Y en este otro encontrareis el articulo del autor sobre sobre el juego.

Nosotros vamos a probar la parte del uso de los modelos de lenguaje como motor del juego, para ello vamos a usar ChatGPT y Google Gemini.

Vamos a usar prompts diferentes ya que ChatGPT parece entender mejor los ejemplos que Gemini, al que hay que indicarle el

ChatGPT:

Vamos a jugar a un juego, yo te doy dos elementos y tu me dices un tercero producido por la suma de esos dos, por ejemplo:
(agua + fuego) = [vapor]
(tierra + tierra) = [roca]
(agua + tierra) = [barro]
(motor + rueda) = [coche]

Gemini:

Vamos a jugar a un juego, yo te doy dos elementos entre paréntesis y tu me dices un tercero producido por la suma de esos dos entre corchetes, por ejemplo:
(agua + fuego) = [vapor]
(tierra + tierra) = [roca]
(agua + tierra) = [barro]
(motor + rueda) = [coche]

En este vídeo de mi canal de Youtube se pueden ver las pruebas realizadas:

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

En resumen, ChatGPT resulta mas comedido en las repuestas lo cual en este caso como motor de juegos resulta mejor ya que facilita procesar las respuestas. Gemini da una gran cantidad de detalles lo cual se agradecería en el caso de ser una conversación pero no en el que se le ha pedido en el prompt.

El prompt no es suficiente. Diferentes aspectos a tener en cuenta cuando configuras un LLM

No se vosotros pero últimamente he estado viendo montón de anuncios, artículos y titulares que hablan del prompt engineering y parecen reducirlo a saber unos poco trucos. No digo que esos trucos no sean útiles para hacer buenos prompts, pero los prompts son solo una parte de todo lo que puedes configurar en un modelo del lenguaje para conseguir los resultados que deseas.

En la siguiente imagen se puede ver el proceso de funcionamiento de un modelo de lenguaje con cada una de las partes que se vamos a ver:

Elegir modelo y motor:

  • Elegir modelo: Para elegir el modelo necesitamos saber cuanta memoria VRAM tenemos (o podemos permitirnos). Como truco de calculo podemos aproximar que por cada parámetro el modelo ocupa 4 bytes (32 bits). Así que un modelo de 30B ocupa unos 120GB de memoria VRAM. Hay opciones como los modelos cuantizados, que bajan la precisión de cada parámetro. Por ejemplo si la bajas de 32 a 16 bits el modelo ocuparía la mitad. A cambio el modelo empeora. ¿Es mejor un modelo de 30B cuantizado a 4 bits, uno de 15B cuantizado a 8 bit, uno de 7B cuantizado a 16 bits o uno de 3,5B sin cuantizar? Todos ocupan casi lo mismo.
    Otra característica importante del modelo es para que está entrenado, básicamente puede estar entrenado para completar texto, seguir ordenes o chatear. Esto afecta a como va a interactuar con el usuario y como se van a escribir los prompts.
    Ademas es importante saber en que idiomas funciona el modelo y si ha sido entrenado o «finetuneado» con datos que necesitas, por ejemplo: un modelo entrenado para programar.
    No hay que olvidar la posibilidad de la multimodalidad (audio, voz, imágenes, vídeo, …)
  • Elegir motor: Una vez elegido el modelo habrá que elegir el motor para ejecutarlo. Dependerá básicamente de tres cosas: las características del modelo (familia y cuantización), de la API que necesitemos (servidor, librería, lenguaje de programación,…) y del hardware donde vaya a ejecutarse (GPU, CPU, RAM, VRAM)
  • Parámetros: Al ejecutar un modelo en el motor hay diferentes parámetros (dependen del motor y el modelo). Al configurarlos podemos obtener diversos resultados, un modelo más o menos original o coherente, variar las probabilidades de algunas palabras, sacrificar calidad por velocidad, …
  • Fine tuning: Si no se encuentra el modelo adecuado habrá que plantearse adaptar uno existente con fine tuning

Preprocesado

  • Preprocesado: Antes de enviar el texto al modelos habrá que tener en cuenta si es necesario procesarlo, cosas como: censurarlo, darle formato, traducirlo, …
  • Texto: Los datos en formato texto sobre los que vamos a trabajar
  • Multimedia: Si el modelo es multimodal no solo tendremos datos en forma de texto, también tendremos otro tipo de datos. Por supuesto habrá que preprocesar esos datos también.

Procesado:

  • RAG: Si queremos incluir documentos o información externa relacionada con el prompt podemos usar RAG. Para usar esta técnica tenemos que tomar varia decisiones: el algoritmo que se usa para elegir los documentos (puede ser necesario elegir otro modelo de lenguaje), el tamaño de los bloques que se van a elegir, como estos documentos se van a procesar para crear esos bloques, ….
  • Plantilla para el prompt: Hay que definir la plantilla que va a usar el modelo del lenguaje, vendrá definida por el entrenamiento y fine tuning del modelo de lenguaje.
  • Prompt de sistema: Aquí empieza ya la magia del prompt. Este es el prompt inicial que se le pasa al modelo describiendo su rol , comportamiento y funciones. Es importante definir lo correctamente
  • Restricciones: No tenia muy claro como llamar esta parte. Consiste en limitar la salida de texto a unas reglas definidas. Por ejemplo, usando expresiones regulares o gramáticas BNF. Sirve para obligar a que la respuesta tenga un formato concreto.
  • Function calling: Al modelo se le pueden pasar la descripción y firma de diversas funciones de código y, si esta entrenado para ello, puede invocarlas eligiendo los parámetros para las mismas.
  • Prompt: ¡Por fin! Aquí está el famoso prompt, el texto del prompt ha de integrar los demás datos y funcionalidades, describir correctamente lo que deseamos que el modelo de lenguaje haga.
    En muchos casos el prompt tiene varios pasos y es necesario pasar varias veces por el LLM para procesarlo.

Postprocesado:

  • Postprocesado: Una vez generado el texto será necesario procesar la respuesta.
  • Iteraciones extras: Hay flujos de trabajo que se dividen en varias iteraciones o pasos, sigues alguno de estos es posible que tengas que plantearte modelos para los siguientes pasos o
  • Function calling: Si se determina que hay que llamar a alguna función, este es el punto donde se le llama y se procesa la respuesta.

Es te flujo ha de considerarse un resumen simplificado. Hay más opciones y técnicas, pero aqui se recogen prácticamente las mínimas a usar hoy en día.

¿Y por qué te has centrado en modelos de lenguaje y no otras IA generativas que usan prompts como las que generan imágenes? Por que los modelos de lenguaje son sencillos. Por ejemplo, Stable Difussion tiene tantas formas de alterar su funcionamiento y resultados que no se si seria capaz de citar las más usadas.

Puedes ver un vídeo explicativo en mi canal de Youtube:

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

¿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

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

Comparativa entre LLaVA 1.5 y BakLLaVA 1

Vamos enfrentar, de manera muy informal, dos modelos multimodales código libre. LLaVA 1.5 contra BakLLaVA 1 y por extensión dos modelos de lenguaje: Vicuna 13B, usado por LLaVA y Mistral 7B, que es el que usa BakLLaVA.

Ambos modelos han sido cuantizados a 5 bits y se ejecutan usando le funcionalidad llava de llama.cpp. Los modelos los puedes encontrar en estos links: LLaVA y BakLLaVA

Puedes ver la explicación del combate en vídeo en mi canal de Youtube:

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

Imagen 1

Describe la imagen con detalle

Prompt: describe the image in detail

LLaVA – 1.5 (Vicuna 13B)

The image is a painting of a unicorn standing in a snowy field. The unicorn is positioned in the center of the scene, with its long horns prominently displayed. The field is covered in snow, giving the scene a wintry atmosphere.

In addition to the unicorn, there are several birds scattered throughout the scene, some flying in the sky and others perched on branches or the ground. The birds are of various sizes and are spread out across the field, adding to the overall beauty of the painting.

BakLLaVA – 1 (Mistral 7B)

The image features a painting of a unicorn standing in a snowy field. The unicorn appears to be walking through the snow, with its majestic figure covering a significant portion of the field.

In the background, there are a few trees scattered around the area, adding to the wintry atmosphere. The painting captures the beauty and grace of the unicorn in its natural habitat, creating a unique and captivating scene.

En mi opinión BakLLaVA escribe y describe mucho mejor, entiende claramente que se le pide y como se analiza una pintura. LLaVA en cambio se pone a hablar de unos pájaros que no hay por ningún lado

Imagen 2

Antes de empezar a ver las respuesta a las preguntas debo aclarar una cosa de esta imagen. Tras una ardua investigación consistente en ampliar la foto y buscar imágenes de «taxis USA» en Google puedo afirmar con bastante seguridad que ambos vehículos son taxis. Ya veréis el porqué aclaro esto.

Dibuja una caja alrededor del hombre

Prompt: draw a box around the man

LLaVA – 1.5 (Vicuna 13B)

[0.37, 0.11, 0.58, 0.36]

BakLLaVA – 1 (Mistral 7B)

[0.34, 0.1, 0.62, 0.43]

Los recortes son muy parecidos, centrándose en la parte superior del hombre, quizás porque la tabla y la ropa colgando los confunda.

¿Cuántas cosas amarillas hay en la foto?

Prompt: how many yellow things are in the photo?

LLaVA – 1.5 (Vicuna 13B)

There are two yellow things in the photo: a yellow taxi cab and a yellow SUV.

BakLLaVA – 1 (Mistral 7B)

There are two yellow things in the photo.

Ambas respuestas son casi correctas, olvidan el suéter del hombre. LLaVA da más información, aunque no es errónea es ambigua: el SUV es amarillo pero también es un taxi y se podría interpretar que no.

¿Qué cosas amarillas hay en la foto?

Prompt: what yellow things are in the photo?

LLaVA – 1.5 (Vicuna 13B)

In the photo, there are two yellow taxis.

BakLLaVA – 1 (Mistral 7B)

There are two yellow things in the photo: a van and a taxi.

En este caso paso lo contrario BakLLaVA da más información pero me temo que es incorrecta, no es una furgoneta.

¿Por qué la foto es graciosa?

Prompt: why the photo is funny?

LLaVA – 1.5 (Vicuna 13B)

The photo is funny because it shows a man ironing clothes while sitting in the back of a yellow car, which is an unusual and unconventional setting for ironing. Typically, ironing is done in a more controlled environment, such as a laundry room or a designated ironing area. The man’s choice to iron clothes in the back of a car on a busy street adds a humorous and unexpected element to the scene.

BakLLaVA – 1 (Mistral 7B)

The photo is funny because it shows a man hanging clothes, specifically a pair of jeans, on a clothesline outside a yellow SUV in the middle of a city street. This is an unusual and unexpected sight, as people typically do laundry or hang clothes in a home or a dedicated laundry facility, not while standing on a busy street. The man’s action contrasts with the typical urban scene, which involves vehicles, pedestrians, and city life, making it a humorous and unconventional sight that catches the viewer’s attention.

La explicación de ambos es muy parecida, la de BakLLaVA me parece mejor escrita pero comete un error: dice jeans pero es una camisa

Imagina una historia que explique la imagen

Prompt: imagine a story to explain the image

LLaVA – 1.5 (Vicuna 13B)

In the image, a man is standing in the back of a yellow SUV, which is driving down a busy city street. He is holding a blue shirt and appears to be ironing it while the car is in motion. This unusual scene captures the attention of onlookers, as it is not common to see someone ironing clothes in the back of a moving vehicle. The man’s actions might be a result of a time constraint or a unique way of multitasking, but it is essential to prioritize safety and avoid distractions while driving.

BakLLaVA – 1 (Mistral 7B)

The image shows a man hanging clothes, specifically a pair of jeans, on a clothesline attached to the back of a yellow SUV parked in a parking lot. This scene could be a part of a story where the man is a busy professional, possibly a real estate agent or a salesperson, who is on his way to a meeting or a presentation. He has found a way to utilize his time efficiently by combining his daily commute with tasks such as doing laundry or taking care of his personal errands. The clothesline on the back of the vehicle is a clever solution to save space and time while attending to his responsibilities.

En ambos casos la explicación es parecida (y poco imaginativa) de que tiene prisa y hace multitasking. Me da la sensación de que BakLLaVA escribe mejor, pero LLaVA es más preciso y acierta más, por ejemplo reconoce que esta planchando una camisa y no unos jeans.

Imagen 3

Lee el texto en la foto

Prompt: Read the text in the photo

LLaVA – 1.5 (Vicuna 13B)

The image displays a computer screen with a form open, asking for personal information. The form includes fields for name, surename, and acceptance of terms. There is also a send button at the bottom of the form, which indicates that the user can submit their information by clicking on it.

BakLLaVA – 1 (Mistral 7B)

In the photo, there is a pop-up window on a computer screen with the title » Fill in next form » above a form field. Below the form field, there is a button labeled » Send » along with some text. The rest of the form fields are not visible in the image.

En este caso LLaVA lee más campos correctamente (solo se deja el título), además queda claro que entiende mejor la funcionalidad de cada elemento de la imagen.

Escribe el código HTML para crear el formulario de la imagen

Prompt: Write HTML code to create the form in the image

LLaVA – 1.5 (Vicuna 13B)

To create the form in the image, you can use the following HTML code:

»’html

<form>
<label for=»name»>Name:</label>
<input type=»text» id=»name» name=»name» required>
<label for=»surname»>Surname:</label>
<input type=»text» id=»surname» name=»surname» required>
<label for=»email»>Email:</label>
<input type=»email» id=»email» name=»email» required>
<label for=»accept_terms»>Accept Terms:</label><input type=»checkbox» id=»accept_terms» name=»accept_terms» required>
<button type=»submit»>Send</button>
</form>

»’

This code creates a simple form with four input fields: name, surname, email, and accept terms. The «required» attribute is added to each input field to ensure that the user must fill in the information before submitting the form. The «type» attribute is set to «text» for the name and surname fields, and «email

BakLLaVA – 1 (Mistral 7B)

1. The top part of the form:

<form action=»/form_processor.php» method=»post» accept-terms=»yes» name=»form_name» id=»form_id» style=»display: flex; flex-direction: column; align-content: center; background-color: pink; border: 10px solid white; padding: 10px; gap: 10px; width: 500px; height: 700px; font-size: 16px; font-family: Arial, Helvetica, san-serif; text-align: center; overflow: hidden; resize: both; scroll: no;>

2. The label and input fields for the name and e-mail address:

<div class=»form-group» id=»form-group-1″ style=»gap: 10px; width: 500px;>

<label for=»surname» style=»font-size: 16px; font-family: Arial, Helvetica,

Aquí tenemos un problema habitual en estos modelos. Han reconocido que es un formulario pero han caído en una trampa. No es el formulario habitual y eso hace que se confundan. Ambos modelos añaden el email., BakLLaVA se olvida del surname. Debo decir que el código, incompleto, de BakLLaVA me gusta más.

Conclusiones

Creo que este combate lo gana LLaVA. Mientras que BakLLaVA genera textos mejor escritos y con más detalle, seguramente gracias a usar Mistral, su respuestas son peores. Su comprensión de la imagen es menor que con LLaVA y no hay que extrañarse. En lo de entrenar modelos de lenguaje la experiencia es un punto y LLaVA es la segunda versión. En BakLLaVA creo que son conscientes de esto y ya han anunciado que están preparando una nueva versión.

Combinar varios modelos de lenguaje para aumentar su funcionalidad

Vamos a ver como usar juntos varios pequeños modelos de lenguaje que aprendimos a crear en este otro post.

Básicamente hay tres arquitecturas:

LLM en linea

En este caso el prompt pasa por el primer modelo que lo trata y lo usa para generar el prompt que pasa al segundo modelo. Es un sistema muy habitual y ya lo hemos visto en otros post: Autotune para escritores, Integrar tus documentos en tus conversaciones con un chatbot Si queréis ver un ejemplo de funcionamiento podéis recurrir a ellos.

Esta arquitectura es útil para añadir las ventajas de varios modelos. Por ejemplo GPT4 lo usa para crear prompts para DALL-E 3. Si le pides que cree una imagen puedes ver como genera varios prompts para usarlos con DALL-E 3

Varios LLM en paralelo

Esta arquitectura se basa en tener varios modelos que pueden recibir tu prompt simultáneamente pero solo se tiene en cuenta la respuesta de uno de ellos. Hay dos formas de montar este sistema:

Selección de la mejor respuesta

En este caso podemos usar varios modelos de lenguaje o varias instancias del mismo modelo. Se parte de un único prompt que se pasa en paralelo a estos modelos. La respuestas son juzgadas ya sea por otra IA o por una algoritmo más convencional para quedarse con la mejor de todas.

En nuestro caso partimos del modelo que ya entrenamos en otro post. Este modelo esta pensado para controlar la domótica de una casa, se le pasa una petición del usuario en formato texto y la respuesta llega en forma de comando que indica que aparato de la casa hay que apagar o encender. El problema que tiene es que no siempre devuelve el comando correcto, por eso vamos a usar tres instancias el mismo y a quedarnos con el comando que devuelvan dos al menos dos de ellas (el típico sistema de votación).

Para ello usaremos el comando batched de llama.cpp que permite ejecutar en paralelo el mismo prompt sobre un único modelo de lenguaje, aunque con la pega de que divide el contexto entre cada una de las instancias. En este caso pasaremos el prompt a 3 instancias:

./batched ./ggml-onoff-256x8x16-f32.gguf \
    "<s> [ORDEN] apaga la luz del salon [COMANDO]" 3
...
sequence 0:

<s> 
  [ORDEN] apaga la luz del salon 
  [COMANDO] LUZ_SL OFF
 </s

sequence 1:

<s> 
  [ORDEN] apaga la luz del salon 
  [COMANDO] LUZ_SL ON
 </s>

sequence 2:

<s> 
  [ORDEN] apaga la luz del salon 
  [COMANDO] LUZ_SL ON
 </s>

Podemos ver que responde dos comandos LUZ_SL ON y uno LUZ_SL OFF por lo tanto elegiríamos LUZ_SL ON

Seleción del mejor modelo a partir del prompt

Es el mismo esquema que el modelo anterior pero «al revés» primero pasamos por el selector que elige a que modelo enviar el prompt.

En este ejemplo vamos a tener tres modelos. Uno de ellos es el que controla la domótica, otro que responde el tiempo que hace (este no existe realmente pero para el caso da igual) y un último que actúa como discriminador eligiendo a que modelo se le pasa cada prompt.

Vamos a ver como entrenamos el modelo discriminador. Para ellos crearemos un fichero de entrenamiento con un millón de prompts (medio millón de cada modelo). Con el entrenaremos un modelos al que le pensamos el prompt del usuario y nos dice que modelo es el más adecuado para ocuparse de él: LLM1 (domótica) o LLM2 (tiempo)

Un ejemplo del fichero de entrenamiento:

<s>
  [ORDEN] oye activa  led de la salon
  [COMANDO] LLM1
 </s>
<s>
  [ORDEN] hey como es el dia  en la calle
  [COMANDO] LLM2
 </s>

El comando usado para entrenar este modelo:

./train-text-from-scratch  \
    --vocab-model ./models/llama-2-13b.Q4_K_M.gguf  \
    --ctx 64 --embd 256 --head 8 --layer 16  \
    --checkpoint-in chk-select-256x16x32.gguf  \
    --checkpoint-out chk-select-256x16x32.gguf  \
    --model-out ggml-select-256x16x32-f32.gguf  \
    --train-data "sentencias2LLM.txt" \
    -t 6 -b 64 --seed 1 --adam-iter 1024 

Vamos ha probarlo

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


./main -m ggml-select-256x16x32-f32.gguf  -r "</s>" \
    -p "<s>\n [ORDEN] que tiempo hace\n [COMANDO]"
...
 <s>
  [ORDEN] que tiempo hace
  [COMANDO] LLM2
 </s>

Puede ver todo esto en funcionamiento en el siguiente vídeo de 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