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.

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

  1. Pingback: Combinar varios modelos de lenguaje para aumentar su funcionalidad | Construyendo a Chispas

Los comentarios están cerrados.