Abrir en Google Colab Descargar notebook

BERT en un problema de clasificación

Introducción

Los modelos basados en transformers nos pueden ayudar a resolver varios tipos de problemas. Desde problemas de clasificación y regresión hasta tareas más complejas como resumen de textos o generación de leguaje condicionado. Veamos como resolver el problema de clasificación de tweets sobre el que hemos estado trabajando anteriormente pero ahora utilizando el modelo BERT.

Para ejecutar este notebook

Para ejecutar este notebook, instale las siguientes librerias:

[ ]:
!wget https://raw.githubusercontent.com/santiagxf/M72109/master/NLP/Datasets/mascorpus/tweets_marketing.csv \
    --quiet --no-clobber --directory-prefix ./Datasets/mascorpus/

!wget https://raw.githubusercontent.com/santiagxf/M72109/master/docs/nlp/neural/BERT.txt \
    --quiet --no-clobber
!pip install -r BERT.txt --quiet
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 7.2/7.2 MB 50.4 MB/s eta 0:00:00
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 486.2/486.2 kB 42.6 MB/s eta 0:00:00
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 268.8/268.8 kB 24.0 MB/s eta 0:00:00
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 7.8/7.8 MB 70.7 MB/s eta 0:00:00
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.3/1.3 MB 31.7 MB/s eta 0:00:00
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 244.2/244.2 kB 21.1 MB/s eta 0:00:00
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 110.5/110.5 kB 11.1 MB/s eta 0:00:00
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 212.5/212.5 kB 22.3 MB/s eta 0:00:00
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 134.3/134.3 kB 9.8 MB/s eta 0:00:00

[ ]:
import warnings
warnings.filterwarnings('ignore')

Cargamos el set de datos

[ ]:
import pandas as pd

tweets = pd.read_csv('Datasets/mascorpus/tweets_marketing.csv')

Trataremos de resolver entonces el mismo problema de clasificación con el que veniamos trabajando: clasificar los tweets dependiendo del sector al que pertenecen.Recordemos que tenemos 7 categorias distintas:

[ ]:
labels = tweets['SECTOR'].unique().tolist()
labels
['RETAIL',
 'TELCO',
 'ALIMENTACION',
 'AUTOMOCION',
 'BANCA',
 'BEBIDAS',
 'DEPORTES']

Para simplificar nuestra tarea con la libraria transformers, nos asguraremos de disponer de 2 columnas, una llamada text y otra labels, la cual inidica lo que queremos predecir.

[ ]:
tweets = tweets[['TEXTO', 'SECTOR']].rename(columns = {'TEXTO':'text', 'SECTOR':'labels'})

Utilizaremos la libraría datasets de ahora en adelante para trabajar con el conjunto de datos. La misma ofrece una integración mas sencilla con la librarías de HuggingFace.

[ ]:
from datasets import Dataset, ClassLabel, Features, Value

dataset = Dataset.from_pandas(
    df=tweets,
    features=Features({
        'text': Value("string"),
        'labels': ClassLabel(names=labels)
    })
)

Dividimos nuestro conjunto de datos en entrenamiento y testing.

[ ]:
train_test = dataset.train_test_split(test_size=0.33, stratify_by_column='labels')

Vea como se representan en esta libraría:

[ ]:
train_test
DatasetDict({
    train: Dataset({
        features: ['text', 'labels'],
        num_rows: 2521
    })
    test: Dataset({
        features: ['text', 'labels'],
        num_rows: 1242
    })
})

Verificando el hardware disponible

[ ]:
import torch
device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')

print("Este notebook se está ejecutando en", device)
Este notebook se está ejecutando en cuda

Transferencia de Aprendizaje y Fine-tuning

En general, existen 2 estrategias para utilizar modelos de lenguaje pre-entrenados en una tarea especifica:

  • Feature-based

  • Fine-tunning

Las técnicas que se conocen como Feature-based utiliza arquitecturas especificas para resolver cada una de las tareas de NLP, en donde los pesos de las representaciones vectoriales están «congeladas» y no son parámetros que el modelo deba optimizar. En consecuencia, estos modelos son más rápidos de entrenar y permiten aplicar arquitecturas especificas que sean diferenciales en cada una de las tareas.

Por el otro lado, las técnicas que emplean Fine-tunning tiene la flexibilidad de poder adaptar sus representaciones al permitir que todos los parametros sean optimizados en la tarea en particular. Además, estas arquitecturas permiten resolver multiples problemas de NLP utilizando una mínima cantidad de parametros específicos para la tarea.

BETO: BERT en español

Al igual que con word2vec, entrenar un modelo de lenguaje requiere de una gran cantidad de datos sumado a un poder de computo interesante (cuando BERT fué publicado en 2018, tomó 4 días entrenar el modelo usando 16 TPUs. Si se hubiera entrenado en 8 GPUs hubiera tomado entre 40–70 días). Por este motivo, utilizaremos un modelo pre-entrenado para un cuerpo de texto en español. Este modelo, BETO, fué entrenado sobre un gran corpora de textos. Pueden encontrar más información sobre en el sitio web del autor.

Tokenizers

BERT utiliza su propio tokenizer que está basado en WordPiece. Este tokenizer tiene un vocabulario de 30.000 tokens donde cada secuencia comienza con un token especial [CLS]. Recuerden que los tokenizers dependen del modelo con el que estamos trabajando:

[ ]:
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained('dccuchile/bert-base-spanish-wwm-uncased',
                                          do_lower_case=True)

Noten que el tokenizer depende del modelo que estamos utilizando

[ ]:
def tokenization(example):
    return tokenizer(example["text"], padding='max_length', truncation=True, return_tensors="pt")

train_test = train_test.map(tokenization, batched=True, batch_size=16)

Crando un modelo de clasificación basado en BERT

Necesitaremos contar con el numero de categorias para nuestro clasificador:

[ ]:
num_labels=len(labels)

Antes de hacer fine-tunning de nuestro modelo, tenemos que instanciar el modelo sobre el cual queremos aplicar esta técnica. Para ello instanciaremos el modelo base el cual no está entrenado en ninguna tarea en particular. De hecho, si habilitan las alertas en este notebook, verán que cuando se carga el modelo, la libreria HuggingFace les advierte sobre esto:

[ ]:
from transformers import AutoModelForSequenceClassification
model = AutoModelForSequenceClassification.from_pretrained('dccuchile/bert-base-spanish-wwm-uncased',
                                                           num_labels=num_labels)
Some weights of the model checkpoint at dccuchile/bert-base-spanish-wwm-uncased were not used when initializing BertForSequenceClassification: ['cls.predictions.transform.dense.bias', 'cls.predictions.transform.dense.weight', 'cls.predictions.transform.LayerNorm.weight', 'cls.predictions.transform.LayerNorm.bias', 'cls.predictions.bias', 'cls.predictions.decoder.weight', 'cls.predictions.decoder.bias']
- This IS expected if you are initializing BertForSequenceClassification from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model).
- This IS NOT expected if you are initializing BertForSequenceClassification from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model).
Some weights of BertForSequenceClassification were not initialized from the model checkpoint at dccuchile/bert-base-spanish-wwm-uncased and are newly initialized: ['bert.pooler.dense.weight', 'bert.pooler.dense.bias', 'classifier.bias', 'classifier.weight']
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.

Construiremos nuestro dataset sobre el que queremos entrenar el modelo. Recuerden que ya habíamos separado el set de datos en porciones para entrenar y para testear el modelo.

Como entrenar modelos con Transformers

La librería transformers puede entrenar modelos tanto utilizando TensorFlow como PyTorch como backend. En nuestro caso utilizaremos PyTorch simplemente porque generaremos código un poco más compacto, pero pueden utilizar el backend con el que más cómodos se sientan:

Trainer

Especificamos los parametros con los que entrenaremos nuestro modelo:

[ ]:
from transformers import Trainer, TrainingArguments, DataCollatorForLanguageModeling
[ ]:
training_args = TrainingArguments(
    output_dir='./results',          # Directorio de trabajo del Trainer
    num_train_epochs=3,              # Numero total de epochs sobre el que entrenaremos
    warmup_steps=500,                # Numero de pasos que se usaran para determinar la politica de Learning Rate
    weight_decay=0.01,               # Weight decay
    logging_dir='./logs',            # Directorio de logs
)

Instanciamos el Trainer

[ ]:
trainer = Trainer(
    model=model,                         # modelo sobre el que haremos fine tunning
    args=training_args,                  # parametros del entrenamiento
    train_dataset=train_test['train'],   # set de datos de entrenamiento
    eval_dataset=train_test['test'],     # set de datos de evaluación
)
[ ]:
history = trainer.train()
[948/948 11:43, Epoch 3/3]
Step Training Loss
500 0.584900

Verifiquemos la performance de nuestro modelo

[ ]:
predictions = trainer.predict(test_dataset=train_test['test']).predictions

Para evaluar el modelo, primero deberemos obtener cual es la categoria que obtuvo la mayor probabilidad en la clasificación:

[ ]:
import numpy as np

predictions = np.argmax(predictions, axis=1)

Convertimos los IDs de las categorias a los labels correctos

[ ]:
from sklearn.metrics import classification_report

print(classification_report(train_test['test']['labels'], predictions, target_names=labels))
              precision    recall  f1-score   support

      RETAIL       0.99      0.98      0.99       268
       TELCO       0.96      0.99      0.97        79
ALIMENTACION       0.99      0.97      0.98       110
  AUTOMOCION       0.99      0.99      0.99       148
       BANCA       1.00      0.99      1.00       198
     BEBIDAS       1.00      0.99      0.99       223
    DEPORTES       0.97      1.00      0.98       216

    accuracy                           0.99      1242
   macro avg       0.99      0.99      0.99      1242
weighted avg       0.99      0.99      0.99      1242

Persistiendo el modelo

Podemos persistir el modelo para utilizar posteriormente de la siguiente forma. Note que deberemos persistir tanto el modelo como el tokenizer ya que los dos funcionan de la mano.

[ ]:
model_name = "tweet_classifier_bert"

trainer.save_model(model_name)
tokenizer.save_pretrained(model_name)
('tweet_classifier_bert/tokenizer_config.json',
 'tweet_classifier_bert/special_tokens_map.json',
 'tweet_classifier_bert/vocab.txt',
 'tweet_classifier_bert/added_tokens.json',
 'tweet_classifier_bert/tokenizer.json')

De las lineas anteriores, vemos que tenemos dos elementos para persistir, el modelo propiamente dicho y su correspondiente tokenizer. De hecho, si exploramos un poco más a detalle los elementos que se guardaron, veremos varios archivos que se generan, incluyendo, por ejemplo, el vocabulario que el tokenizer utilizará.

Vea el contenido del directorio:

[ ]:
!ls $model_name
config.json        special_tokens_map.json  tokenizer.json     vocab.txt
pytorch_model.bin  tokenizer_config.json    training_args.bin

Una vez que su modelo esta guardado, puede publicarlo en HuggingFace simplemente volverlo a cargar utilizando la instrucción from_pretrained.

[ ]:
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name)

Inspeccionando el modelo utilizando TensorBoard

PRECAUCIÓN 😱: El tema presentado en esta sección está clasificado como avanzado. El entendimiento de este contenido es totalmente opcional.

Podemos inspeccionar nuestro modelo utilizando la herramienta TensorBoard

[ ]:
!tensorboard --logdir ./logs --bind_all

En Google Colab, podemos ver TensorBoard usando el siguiente Magic

[ ]:
%load_ext tensorboard
%tensorboard --logdir ./logs