<jupyter_start><jupyter_text>Modelos de clasificación de texto basados en mecanismos de atención> Creando modelos de clasificación de texto usando FastAI y BLURR.- toc: true - badges: true- comments: true- categories: [practica]- image: images/chart-preview.png En este notebook se muestra cómo crear un modelo de clasificación de texto usando modelos basados en mecanismos de atención mediante la librería [HuggingFace](https://huggingface.co/), y en concreto mediante la librería [blurr](https://github.com/ohmeow/blurr) que integra HuggingFace con FastAI.Para esta práctica es necesario el uso de GPU, así que recuerda activar esta opción en Colab.   LibreríasComenzamos actualizando la librería FastAI, e instalando las librerías de HuggingFace (llamadas `transformers` y `datasets`) y la librería blurr. Al finalizar la instalación deberás reiniciar el kernel (menú Entorno de ejecución -> Reiniciar Entorno de ejecución).<jupyter_code>!pip install fastai -Uqq
!pip install datasets -Uqq
!pip install transformers[sentencepiece] -Uqq
!pip install git+https://github.com/ohmeow/blurr.git@dev-2.0.0 -Uqq<jupyter_output>[K     |████████████████████████████████| 189 kB 4.2 MB/s 
[K     |████████████████████████████████| 55 kB 3.6 MB/s 
[K     |████████████████████████████████| 325 kB 3.5 MB/s 
[K     |████████████████████████████████| 1.1 MB 38.3 MB/s 
[K     |████████████████████████████████| 134 kB 31.0 MB/s 
[K     |████████████████████████████████| 212 kB 35.2 MB/s 
[K     |████████████████████████████████| 67 kB 4.7 MB/s 
[K     |████████████████████████████████| 127 kB 40.2 MB/s 
[K     |████████████████████████████████| 94 kB 1.6 MB/s 
[K     |████████████████████████████████| 271 kB 34.0 MB/s 
[K     |████████████████████████████████| 144 kB 49.3 MB/s 
[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
datascience 0.10.6 requires folium==0.2.1, but you have folium 0.8.3 which is incompatible.[0m
[K     |████████████████████████████████| 3.8 MB 4.0 MB/s 
[...]<jupyter_text>Cargamos a continuación las librerías que necesitaremos en esta práctica.<jupyter_code>from fastai.data.all import *
from fastai.learner import *
from fastai.losses import CrossEntropyLossFlat
from fastai.optimizer import Adam, OptimWrapper, params
from fastai.metrics import accuracy, F1Score
from fastai.torch_core import *
from fastai.torch_imports import *
from transformers import AutoModelForSequenceClassification

from blurr.data.core import *
from blurr.modeling.core import *
from blurr.utils import BLURR
from datasets import load_dataset,concatenate_datasets<jupyter_output><empty_output><jupyter_text>DatasetPara este ejemplo vamos a usar el dataset [Gutenberg Poem Dataset](https://huggingface.co/datasets/poem_sentiment), un dataset para detectar sentimientos en poemas (negativos, positivos, sin impacto, mezcla de positivo y negativo).Descarga el dataset usando el siguiente comando.<jupyter_code>poem_sentiment_dataset = load_dataset("poem_sentiment")<jupyter_output>Using custom data configuration default
Reusing dataset poem_sentiment (/root/.cache/huggingface/datasets/poem_sentiment/default/1.0.0/4e44428256d42cdde0be6b3db1baa587195e91847adabf976e4f9454f6a82099)<jupyter_text>Vamos a añadir a nuestro dataset una columna que nos indique si estamos trabajando con el conjunto de entrenamiento o el de validación. Para lo cuál debemos definir la siguiente función.<jupyter_code>def add_is_valid_batch_friendly(examples, is_valid=False):
  return {"is_valid": [is_valid for txt in examples["verse_text"]]}<jupyter_output><empty_output><jupyter_text>Y ahora añadimos esa información.<jupyter_code>poem_sentiment_dataset["train"] = poem_sentiment_dataset["train"].map(partial(add_is_valid_batch_friendly,is_valid=False),batched=True)
poem_sentiment_dataset["validation"] = poem_sentiment_dataset["validation"].map(partial(add_is_valid_batch_friendly,is_valid=True),batched=True)

print(poem_sentiment_dataset)
print(poem_sentiment_dataset["train"][0])<jupyter_output>Loading cached processed dataset at /root/.cache/huggingface/datasets/poem_sentiment/default/1.0.0/4e44428256d42cdde0be6b3db1baa587195e91847adabf976e4f9454f6a82099/cache-a5043da3f45ff884.arrow
Loading cached processed dataset at /root/.cache/huggingface/datasets/poem_sentiment/default/1.0.0/4e44428256d42cdde0be6b3db1baa587195e91847adabf976e4f9454f6a82099/cache-078c48838227da06.arrow<jupyter_text>Antes de empezar a entrenar nuestro modelo combinamos los conjuntos de entrenamiento y test.<jupyter_code>proc_ds = concatenate_datasets([poem_sentiment_dataset["train"],poem_sentiment_dataset["validation"]])
proc_ds<jupyter_output><empty_output><jupyter_text>Entrenando un modelo de clasificación El proceso a seguir para hacer fine-tuning sobre el modelo de lenguaje de FastAI es análogo al visto en prácticas anteriores. Comenzamos creando un `DataBlock` a partir de nuestro dataframe. Sin embargo, para llevar a cabo esta tarea tenemos que definirlo a partir de los constructores de la librería Blurr. En primer lugar definimos la tarea con la que vamos a trabajar que es la clasificación de secuencias de texto.<jupyter_code>model_cls = AutoModelForSequenceClassification<jupyter_output><empty_output><jupyter_text>A continuación debemos indicar el modelo que vamos a utilizar, ya que la creación de los `datablocks` es dependiente de esta elección. En nuestro caso usaremo el modelo [Bert](https://huggingface.co/bert-base-uncased).<jupyter_code>pretrained_model_name = "bert-base-uncased"<jupyter_output><empty_output><jupyter_text>Seguidamente debemos crear la configuración de nuesto `datablock`, para ello vamos a usar la configuración por defecto del modelo que hemos seleccionado y cambiaremos el número de posibles clases de nuestro problema (en este caso 4).<jupyter_code>labels = proc_ds.features["label"].names
labels
n_labels = len(labels)<jupyter_output><empty_output><jupyter_text>Por último generamos varias componentes que son necesarias para entrenar nuestro modelo y que son dependientes del modelo que elijamos:    - La arquitectura del modelo.    - La configuración del modelo.    - El tokenizer.    - El modelo de huggingface.<jupyter_code>hf_arch, hf_config, hf_tokenizer,hf_model = BLURR.get_hf_objects(
    pretrained_model_name,model_cls= model_cls, config_kwargs={"num_labels": n_labels}
)

hf_arch, type(hf_config), type(hf_tokenizer),type(hf_model)<jupyter_output><empty_output><jupyter_text>Ahora ya podemos construir nuestro `datablock` para lo cual primero debemos preprocesarlo con el modelo que vamos a usar. Notar que debemos indicar el atributo donde se encuentran las frases de nuestro dataset (en este caso ``verse_text``).<jupyter_code>preprocessor = ClassificationPreprocessor(hf_tokenizer,label_mapping=labels,text_attr='verse_text')
ds = preprocessor.process_hf_dataset(proc_ds)
ds<jupyter_output><empty_output><jupyter_text>También debemos identificar los índices de nuestro dataset que pertenecen al conjunto de validación.<jupyter_code>val_idxs = [idx for idx,el in enumerate(ds) if el["is_valid"]==True]
min(val_idxs),max(val_idxs)<jupyter_output><empty_output><jupyter_text>Y por fin construimos nuestro ``datablock``.<jupyter_code>blocks = (
    TextBlock(
        hf_arch,
        hf_config,
        hf_tokenizer,
        hf_model,
        is_pretokenized=True,
        before_batch_kwargs={"labels":labels},
        tok_kwargs={"add_special_tokens":False}
    ),
    CategoryBlock
)

dblock = DataBlock(
    blocks=blocks,
    get_x=ItemGetter('verse_text'), 
    get_y=ItemGetter('label'), 
    splitter=IndexSplitter(val_idxs)
)<jupyter_output><empty_output><jupyter_text>Creamos ahora nuestro `dataloader`.<jupyter_code>dls = dblock.dataloaders(ds,bs=16)<jupyter_output><empty_output><jupyter_text>Podemos ahora mostrar un batch de este `dataloader`.<jupyter_code>dls.show_batch(dataloaders=dls, max_n=2)<jupyter_output><empty_output><jupyter_text>Creamos ahora nuestro `Learner`.<jupyter_code>model = BaseModelWrapper(hf_model)

learn = Learner(dls, 
                model,
                metrics=[accuracy],
                cbs=[BaseModelCallback],
                splitter=blurr_splitter
                )<jupyter_output><empty_output><jupyter_text>Y por último entrenamos el modelo.<jupyter_code>learn.fine_tune(10,1e-3)<jupyter_output><empty_output><jupyter_text>Una vez entrenado el modelo podemos guardarlo para el futuro.<jupyter_code>export_fname = 'seq_class_learn_export'
learn.export(fname=f'{export_fname}.pkl')<jupyter_output><empty_output><jupyter_text>Para realizar predicciones debemos usar el método `blurr_predict`.<jupyter_code>learn.blurr_predict('with pale blue berries. in these peaceful shades--.')<jupyter_output><empty_output><jupyter_text>Por último, validamos en nuestro conjunto de test.<jupyter_code>poem_sentiment_dataset["test"] = poem_sentiment_dataset["test"].map(partial(add_is_valid_batch_friendly,is_valid=True),batched=True)
proc_ds_test = concatenate_datasets([poem_sentiment_dataset["train"],poem_sentiment_dataset["test"]])
ds_test = preprocessor.process_hf_dataset(proc_ds_test)

test_idxs = [idx for idx,el in enumerate(ds_test) if el["is_valid"]==True]

dblock_test = DataBlock(
    blocks=blocks,
    get_x=ItemGetter('verse_text'), 
    get_y=ItemGetter('label'), 
    splitter=IndexSplitter(test_idxs)
)

dls_test = dblock_test.dataloaders(ds_test,bs=16)
learn.dls = dls_test
learn.validate()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12583752093802344, 'fraction_numerical': 0.02523031825795645, 'mean_word_length': 6.510220125786163, 'pattern_counts': {'":': 4, '<': 78, '<?xml version=': 0, '>': 80, 'https://': 5, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '20', 'dir': 'jupyter-structured-clean-dedup', 'id': '659181', 'license': 'Apache-2.0', 'n_tokens_mistral': 3778, 'n_tokens_neox': 3406, 'n_words': 751, 'path': '_notebooks/2022-03-22-practica8-clasificacion-texto-blurr.ipynb', 'repo_name': 'AP2122/aprendizajeprofundo2122', 'size': '9552'}