Generación de Texto

Cómo afinar un modelo GPT-2 en español para generar letras de RAP.

Manuel Romero

Open In Colab

Este notebook es una adaptación delColabde Sylvain Gugger.

Comenzamos con una definición:

  • CLM: el modelo tiene que predecir el siguiente token en la oración (por lo que las etiquetas son las mismas que las entradas desplazadas hacia la derecha). Para asegurarse de que el modelo no haga trampa, obtiene una máscara de atención que le impedirá acceder a los tokens después del token i cuando intente predecir el token i + 1 en la oración.

rap_cml_widget.png

Probar el modelo

Comprobamos la GPU asignada

! nvidia-smi

Autenticación en HF (para compartir modelo)

Para poder compartir nuestro modelo con la comunidad y generar resultados como el que se muestra en la imagen de arriba a través de la API de inferencia, hay algunos pasos más a seguir.

Primero debe almacenar su token de autenticación del sitio web HuggingFace (regístrateaquísi aún no lo ha hecho), luego ejecute la siguiente celda e ingrese su nombre de usuario y contraseña:

from huggingface_hub import notebook_login

notebook_login()

Luego, debemos instalarGit-LFSy configurar Git si aún no lo hemos hecho. Descomenta las siguientes instrucciones e introduzca su nombre y correo electrónico:

# !apt install git-lfs
# !git config --global user.email "you@example.com"
# !git config --global user.name "Your Name"

Instalamos las dependencias requeridas

! pip install -q transformers datasets

Elegimos un modelo (checkpoint)

model_checkpoint = "datificate/gpt2-small-spanish"

Elegimos y pre-procesamos el dataset

'''
%%bash
# Descarga desde Kaggle
# Subir archivo `kaggle.json`
pip install -q kaggle
mkdir ~/.kaggle
cp kaggle.json ~/.kaggle/
chmod 600 ~/.kaggle/kaggle.json
kaggle datasets download -d smunoz3801/9325-letras-de-rap-en-espaol
'''
! wget --no-check-certificate 'https://docs.google.com/uc?export=download&id=1-3xPU8ZofDuyHrsgQvNgUikQXLazocYl' -O rap_corpus.csv
from datasets import load_dataset
dataset = load_dataset('csv', data_files=['/content/rap_corpus.csv'], split='train')
datasets = dataset.train_test_split(test_size=0.10)
datasets
datasets["train"][10]
from datasets import ClassLabel
import random
import pandas as pd
from IPython.display import display, HTML

def show_random_elements(dataset, num_examples=3):
    assert num_examples <= len(dataset), "El número de filas a mostrar no puede ser mayor que el número de filas en el dataset."
    picks = []
    for _ in range(num_examples):
        pick = random.randint(0, len(dataset)-1)
        while pick in picks:
            pick = random.randint(0, len(dataset)-1)
        picks.append(pick)
    
    df = pd.DataFrame(dataset[picks])
    for column, typ in dataset.features.items():
        if isinstance(typ, ClassLabel):
            df[column] = df[column].transform(lambda i: typ.names[i])
    display(HTML(df.to_html()))
show_random_elements(datasets["train"])

Tokenización del dataset

from transformers import AutoTokenizer
    
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, use_fast=True)
def tokenize_fn(examples):
    return tokenizer(examples["letra"])
tokenized_datasets = datasets.map(tokenize_fn, batched=True, remove_columns=["id", "artista", "album", "cancion", "anyo", "visitas", "letra"])
tokenized_datasets.column_names
tokenized_datasets["train"][1]
len(tokenized_datasets["train"][5]['input_ids'])

Formatear el dataset

Ahora, para la parte más difícil: necesitamos concatenar todos nuestros textos juntos y luego dividir el resultado en pequeños trozos de un ciertoblock_size. Para hacer esto, usaremos el métodomapnuevamente, con la opciónbatched = True. Esta opción en realidad nos permite cambiar la cantidad de ejemplos en los conjuntos de datos al devolver una cantidad diferente de ejemplos a la que obtuvimos. De esta manera, podemos crear nuestras nuevas muestras a partir de un lote de ejemplos.

Primero, tomamos la longitud máxima con la que se entrenó previamente nuestro modelo. Esto podría ser demasiado grande para caber en la RAM de nuestra GPU, por lo que aquí tomamos un poco menos de solo 128.

#block_size = tokenizer.model_max_length
block_size = 128
def group_texts(examples):
    # Concatenate all texts.
    concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()}
    total_length = len(concatenated_examples[list(examples.keys())[0]])
    # We drop the small remainder, we could add padding if the model supported it instead of this drop, you can
        # customize this part to your needs.
    total_length = (total_length // block_size) * block_size
    # Split by chunks of max_len.
    result = {
        k: [t[i : i + block_size] for i in range(0, total_length, block_size)]
        for k, t in concatenated_examples.items()
    }
    result["labels"] = result["input_ids"].copy()
    return result
lm_datasets = tokenized_datasets.map(
    group_texts,
    batched=True,
    batch_size=1000)

Primero tenga en cuenta que duplicamos las entradas para nuestras etiquetas. Esto se debe a que el modelo de la biblioteca 🤗 Transformers aplica el desplazamiento a la derecha, por lo que no es necesario que lo hagamos manualmente.

También tenga en cuenta que, por defecto, el métodomapenviará un lote de 1000 ejemplos para ser tratados por la función de preprocesamiento. Entonces, aquí, soltaremos el resto para hacer que los textos tokenizados concatenados sean un múltiplo deblock_sizecada 1,000 ejemplos. Puede ajustar este comportamiento pasando un tamaño de lote mayor (que también se procesará más lentamente).

Entrenar el modelo

from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained(model_checkpoint)
from transformers import Trainer, TrainingArguments
model_name = model_checkpoint.split("/")[-1]
training_args = TrainingArguments(
    "rap-clm",
    #per_device_train_batch_size = 2,
    #per_device_eval_batch_size = 2,
    evaluation_strategy = "epoch",
    learning_rate=2e-5,
    weight_decay=0.01,
    #push_to_hub=True,
    #push_to_hub_model_id=f"{model_name}-finetuned-rap",
)
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=lm_datasets["train"],
    eval_dataset=lm_datasets["test"],
)
trainer.evaluate()
trainer.train()
import math
eval_results = trainer.evaluate()
print(f"Perplexity: {math.exp(eval_results['eval_loss']):.2f}")

Este notebook forma parte del curso de NLP de 0 a 100. Puedes encontrar el curso completoaquí.


Manuel Romero

Ingeniero de NLP/NLG @Narrativa.AI