Tutorial: Messa a punto del BERT per l'analisi dei sentimenti

Tutorial: Messa a punto del BERT per l'analisi dei sentimenti

    Originally published by Skim AI's Machine Learning Researcher, Chris Tran.



BERT_per_l'analisi_dei_sentimenti





A - Introduzione

Negli ultimi anni la comunità NLP ha assistito a molte innovazioni nell'elaborazione del linguaggio naturale, in particolare il passaggio all'apprendimento per trasferimento. Modelli come ELMo, ULMFiT di fast.ai, Transformer e GPT di OpenAI hanno permesso ai ricercatori di raggiungere risultati all'avanguardia su diversi benchmark e hanno fornito alla comunità modelli pre-addestrati di grandi dimensioni con prestazioni elevate. Questo cambiamento in NLP è visto come il momento ImageNet di NLP, un cambiamento avvenuto qualche anno fa nella computer vision, quando gli strati inferiori delle reti di deep learning con milioni di parametri addestrati per un compito specifico possono essere riutilizzati e messi a punto per altri compiti, piuttosto che addestrare nuove reti da zero.

Una delle pietre miliari più importanti nell'evoluzione della PNL è il rilascio del BERT di Google, descritto come l'inizio di una nuova era nella PNL. In questo quaderno utilizzerò il sistema HuggingFace. trasformatori per mettere a punto il modello BERT preaddestrato per un compito di classificazione. Quindi confronterò le prestazioni del BERT con un modello di base, in cui utilizzo un vettorizzatore TF-IDF e un classificatore Naive Bayes. Il trasformatori ci aiutano a mettere a punto in modo rapido ed efficiente il modello BERT allo stato dell'arte e a ottenere un tasso di accuratezza 10% superiore a quello del modello di base.

Riferimento:

Per capire Trasformatore (l'architettura su cui si basa il BERT) e imparare a implementare il BERT, consiglio vivamente di leggere le seguenti fonti:

B - Impostazione

1. Caricare le librerie essenziali

In [0]:

import os
import re
from tqdm import tqdm
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

2. Set di dati

2.1. Scarica il set di dati

In [0]:

# Download data
import requests
request = requests.get("https://drive.google.com/uc?export=download&id=1wHt8PsMLsfX5yNSqrt2fSTcb8LEiclcf")
with open("data.zip", "wb") as file:
    file.write(request.content)
# Unzip data
import zipfile
with zipfile.ZipFile('data.zip') as zip:
    zip.extractall('data')

2.2. Dati del treno di carico

I dati del treno sono costituiti da 2 file, ciascuno contenente 1700 tweet di reclamo/non reclamo. Ogni tweet nei dati contiene almeno un hashtag di una compagnia aerea.

Carichiamo i dati del treno e li etichettiamo. Poiché utilizziamo solo i dati di testo per la classificazione, elimineremo le colonne non importanti e manterremo solo le colonne id, tweet e etichetta colonne.
In [0]:

 # Load data and set labels
data_complaint = pd.read_csv('data/complaint1700.csv')
data_complaint['label'] = 0
data_non_complaint = pd.read_csv('data/noncomplaint1700.csv')
data_non_complaint['label'] = 1
# Concatenate complaining and non-complaining data
data = pd.concat([data_complaint, data_non_complaint], axis=0).reset_index(drop=True)
# Drop 'airline' column
data.drop(['airline'], inplace=True, axis=1)
# Display 5 random samples
data.sample(5)

Out[0]:

id tweet etichetta
1988 24991 Che bel bentornato. Ridicolo. Sbarco... 1
1294 72380 Molto deluso da @JetBlue stasera. Volo... 0
1090 127893 @united i miei amici stanno passando un periodo infernale... 0
553 58278 @united tutto quello che voglio per Natale è una borsa smarrita che... 0
2075 30695 Sì, non voglio mentire... sono super interessato a provare... 1

Divideremo casualmente gli interi dati di addestramento in due set: un set di addestramento con 90% di dati e un set di validazione con 10% di dati. Eseguiremo la regolazione degli iperparametri utilizzando la convalida incrociata sull'insieme di addestramento e useremo l'insieme di convalida per confrontare i modelli.
In [0]:

from sklearn.model_selection import train_test_split
X = data.tweet.values
y = data.label.values
X_train, X_val, y_train, y_val =
    train_test_split(X, y, test_size=0.1, random_state=2020)

2.3. Dati della prova di carico

I dati del test contengono 4555 esempi senza etichetta. Circa 300 esempi sono tweets non lamentosi. Il nostro compito è identificare i loro id ed esaminare manualmente se i nostri risultati sono corretti.
In [0]:

# Load test data
test_data = pd.read_csv('data/test_data.csv')
# Keep important columns
test_data = test_data[['id', 'tweet']]
# Display 5 samples from the test data
test_data.sample(5)

Out[0]:

id tweet
1539 59336 Volo @AmericanAir in ritardo di oltre 2 ore per n...
607 24101 @SouthwestAir Ricevo ancora questo messaggio di errore...
333 13179 in attesa al #SeaTac per imbarcarmi sul mio volo @JetBlue...
2696 102948 Odio quando vado a selezionare i sedili in anticipo...
3585 135638 vergogna @AlaskaAir

3. Impostazione della GPU per la formazione

Google Colab offre GPU e TPU gratuite. Poiché dovremo addestrare una rete neurale di grandi dimensioni, è meglio utilizzare queste funzionalità.

È possibile aggiungere una GPU accedendo al menu e selezionando:

Runtime -> Cambia tipo di runtime -> Acceleratore hardware: GPU

Quindi è necessario eseguire la seguente cella per specificare la GPU come dispositivo.
In [0]:

import torch
if torch.cuda.is_available():       
    device = torch.device("cuda")
    print(f'There are {torch.cuda.device_count()} GPU(s) available.')
    print('Device name:', torch.cuda.get_device_name(0))
else:
    print('No GPU available, using the CPU instead.')
    device = torch.device("cpu")
Sono disponibili 1 GPU.
Nome dispositivo: Tesla T4

C - Linea di base: TF-IDF + Classificatore Naive Bayes

In questo approccio di base, per prima cosa utilizzeremo TF-IDF per vettorizzare i dati di testo. Poi utilizzeremo il modello Naive Bayes come classificatore.

Perché Naive Bayes? Ho sperimentato diversi algoritmi di apprendimento automatico, tra cui Random Forest, Support Vectors Machine, XGBoost e ho osservato che Naive Bayes offre le migliori prestazioni. In Guida di Scikit-learn per scegliere lo stimatore giusto, si suggerisce anche di usare Naive Bayes per i dati di testo. Ho anche provato a utilizzare SVD per ridurre la dimensionalità, ma le prestazioni non sono migliorate.

1. Preparazione dei dati

1.1. Preelaborazione

Nel modello bag-of-words, un testo è rappresentato come il bagaglio delle sue parole, senza tenere conto della grammatica e dell'ordine delle parole. Per questo motivo, si vogliono rimuovere le stop words, le punteggiature e i caratteri che non contribuiscono molto al significato della frase.
In [0]:

import nltk
# Uncomment to download "stopwords"
nltk.download("stopwords")
from nltk.corpus import stopwords
def text_preprocessing(s):
    """
    - Lowercase the sentence
    - Change "'t" to "not"
    - Remove "@name"
    - Isolate and remove punctuations except "?"
    - Remove other special characters
    - Remove stop words except "not" and "can"
    - Remove trailing whitespace
    """
    s = s.lower()
    # Change 't to 'not'
    s = re.sub(r"'t", " not", s)
    # Remove @name
    s = re.sub(r'(@.*?)[s]', ' ', s)
    # Isolate and remove punctuations except '?'
    s = re.sub(r'(['".()!?\/,])', r' 1 ', s)
    s = re.sub(r'[^ws?]', ' ', s)
    # Remove some special characters
    s = re.sub(r'([;:|•«n])', ' ', s)
    # Remove stopwords except 'not' and 'can'
    s = " ".join([word for word in s.split()
                  if word not in stopwords.words('english')
                  or word in ['not', 'can']])
    # Remove trailing whitespace
    s = re.sub(r's+', ' ', s).strip()
    return s
[nltk_data] Download del pacchetto stopwords in /root/nltk_data...
[nltk_data] Il pacchetto stopwords è già aggiornato!

1.2. Vettorizzatore TF-IDF

Nel recupero delle informazioni, TF-IDF, abbreviazione di frequenza dei termini - frequenza inversa dei documentiè una statistica numerica che riflette l'importanza di una parola per un documento in una raccolta o in un corpus. Utilizzeremo TF-IDF per vettorializzare i nostri dati testuali prima di darli in pasto agli algoritmi di apprendimento automatico.
In [0]:

%%time
from sklearn.feature_extraction.text import TfidfVectorizer
# Preprocess text
X_train_preprocessed = np.array([text_preprocessing(text) for text in X_train])
X_val_preprocessed = np.array([text_preprocessing(text) for text in X_val])
# Calculate TF-IDF
tf_idf = TfidfVectorizer(ngram_range=(1, 3),
                         binary=True,
                         smooth_idf=False)
X_train_tfidf = tf_idf.fit_transform(X_train_preprocessed)
X_val_tfidf = tf_idf.transform(X_val_preprocessed)
Tempi della CPU: utente 5,47 s, sistema: 519 ms, totale: 5,99 s
Tempo di parete: 6 s

2. Addestrare il classificatore Naive Bayes

2.1. Regolazione degli iperparametri

Utilizzeremo la convalida incrociata e il punteggio AUC per mettere a punto gli iperparametri del nostro modello. La funzione get_auc_CV restituirà il punteggio medio AUC della convalida incrociata.
In [0]:

from sklearn.model_selection import StratifiedKFold, cross_val_score
def get_auc_CV(model):
    """
    Return the average AUC score from cross-validation.
    """
    # Set KFold to shuffle data before the split
    kf = StratifiedKFold(5, shuffle=True, random_state=1)
    # Get AUC scores
    auc = cross_val_score(
        model, X_train_tfidf, y_train, scoring="roc_auc", cv=kf)
    return auc.mean()

Il MultinominaleNB hanno un solo ipterparametro - alfa. Il codice seguente ci aiuterà a trovare il valore alfa che fornisce il punteggio CV AUC più alto.
In [0]:

from sklearn.naive_bayes import MultinomialNB
res = pd.Series([get_auc_CV(MultinomialNB(i))
                 for i in np.arange(1, 10, 0.1)],
                index=np.arange(1, 10, 0.1))
best_alpha = np.round(res.idxmax(), 2)
print('Best alpha: ', best_alpha)
plt.plot(res)
plt.title('AUC vs. Alpha')
plt.xlabel('Alpha')
plt.ylabel('AUC')
plt.show()
Miglior alfa:  1.3

2.2. Valutazione sull'insieme di convalida

Per valutare le prestazioni del nostro modello, calcoleremo il tasso di precisione e il punteggio AUC del nostro modello sul set di validazione.
In [0]:

from sklearn.metrics import accuracy_score, roc_curve, auc
def evaluate_roc(probs, y_true):
    """
    - Print AUC and accuracy on the test set
    - Plot ROC
    @params    probs (np.array): an array of predicted probabilities with shape (len(y_true), 2)
    @params    y_true (np.array): an array of the true values with shape (len(y_true),)
    """
    preds = probs[:, 1]
    fpr, tpr, threshold = roc_curve(y_true, preds)
    roc_auc = auc(fpr, tpr)
    print(f'AUC: {roc_auc:.4f}')
    # Get accuracy over the test set
    y_pred = np.where(preds >= 0.5, 1, 0)
    accuracy = accuracy_score(y_true, y_pred)
    print(f'Accuracy: {accuracy*100:.2f}%')
    # Plot ROC AUC
    plt.title('Receiver Operating Characteristic')
    plt.plot(fpr, tpr, 'b', label = 'AUC = %0.2f' % roc_auc)
    plt.legend(loc = 'lower right')
    plt.plot([0, 1], [0, 1],'r--')
    plt.xlim([0, 1])
    plt.ylim([0, 1])
    plt.ylabel('True Positive Rate')
    plt.xlabel('False Positive Rate')
    plt.show()

Combinando TF-IDF e l'algoritmo Naive Bayes, si ottiene un tasso di accuratezza di 72.65% sul set di validazione. Questo valore rappresenta la prestazione di base e sarà utilizzato per valutare le prestazioni del nostro modello BERT a regolazione fine.
In [0]:

# Compute predicted probabilities
nb_model = MultinomialNB(alpha=1.8)
nb_model.fit(X_train_tfidf, y_train)
probs = nb_model.predict_proba(X_val_tfidf)
# Evaluate the classifier
evaluate_roc(probs, y_val)
AUC: 0.8451
Precisione: 75,59%

D - Messa a punto del BERT

1. Installare la libreria Hugging Face

La libreria di trasformatori di Hugging Face contiene l'implementazione in PyTorch di modelli NLP all'avanguardia, tra cui BERT (di Google), GPT (di OpenAI) ... e i pesi dei modelli pre-addestrati.
In [1]:

#!pip installare trasformatori

2. Tokenizzazione e formattazione dell'input

Prima di tokenizzare il nostro testo, effettueremo una leggera elaborazione del testo, tra cui la rimozione delle menzioni di entità (ad esempio, @united) e di alcuni caratteri speciali. Il livello di elaborazione è molto inferiore rispetto agli approcci precedenti, perché BERT è stato addestrato con le frasi intere.
In [0]:

def text_preprocessing(text):
    """
    - Remove entity mentions (eg. '@united')
    - Correct errors (eg. '&' to '&')
    @param    text (str): a string to be processed.
    @return   text (Str): the processed string.
    """
    # Remove '@name'
    text = re.sub(r'(@.*?)[s]', ' ', text)
    # Replace '&' with '&'
    text = re.sub(r'&', '&', text)
    # Remove trailing whitespace
    text = re.sub(r's+', ' ', text).strip()
    return text

In [0]:

# Print sentence 0
print('Original: ', X[0])
print('Processed: ', text_preprocessing(X[0]))
Originale:  @united Sto avendo problemi. Ieri ho rifatto la prenotazione per 24 ore dopo che avrei dovuto volare, ora non riesco ad accedere e fare il check-in. Potete aiutarmi?
Elaborato:  Ho dei problemi. Ieri ho rifatto la prenotazione per 24 ore dopo il volo previsto, ora non riesco ad accedere e a fare il check-in. Potete aiutarmi?

2.1. Tokenizzatore BERT

Per applicare il BERT pre-addestrato, dobbiamo usare il tokenizer fornito dalla libreria. Questo perché (1) il modello ha un vocabolario specifico e fisso e (2) il tokenizer del BERT ha un modo particolare di gestire le parole fuori vocabolario.

Inoltre, dobbiamo aggiungere dei token speciali all'inizio e alla fine di ogni frase, troncare tutte le frasi a una lunghezza costante e specificare esplicitamente quali sono i token di riempimento con la "maschera di attenzione".

Il codificare_in_più del tokenizer BERT:

(1) dividere il testo in token,

(2) aggiungere lo speciale [CLS] e [SEP] gettoni e

(3) convertire questi token in indici del vocabolario del tokenizer,

(4) aggiungere o troncare le frasi alla lunghezza massima e

(5) creare una maschera di attenzione.
In [0]:

from transformers import BertTokenizer
# Load the BERT tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased', do_lower_case=True)
# Create a function to tokenize a set of texts
def preprocessing_for_bert(data):
    """Perform required preprocessing steps for pretrained BERT.
    @param    data (np.array): Array of texts to be processed.
    @return   input_ids (torch.Tensor): Tensor of token ids to be fed to a model.
    @return   attention_masks (torch.Tensor): Tensor of indices specifying which
                  tokens should be attended to by the model.
    """
    # Create empty lists to store outputs
    input_ids = []
    attention_masks = []
    # For every sentence...
    for sent in data:
        # codificare_in_più will:
        #    (1) Tokenize the sentence
        #    (2) Add the [CLS] e [SEP] token to the start and end
        #    (3) Truncate/Pad sentence to max length
        #    (4) Map tokens to their IDs
        #    (5) Create attention mask
        #    (6) Return a dictionary of outputs
        encoded_sent = tokenizer.encode_plus(
            text=text_preprocessing(sent),  # Preprocess sentence
            add_special_tokens=True,        # Add [CLS] e [SEP]
            max_length=MAX_LEN,                  # Max length to truncate/pad
            pad_to_max_length=True,         # Pad sentence to max length
            #return_tensors='pt',           # Return PyTorch tensor
            return_attention_mask=True      # Return attention mask
            )
        # Add the outputs to the lists
        input_ids.append(encoded_sent.get('input_ids'))
        attention_masks.append(encoded_sent.get('attention_mask'))
    # Convert lists to tensors
    input_ids = torch.tensor(input_ids)
    attention_masks = torch.tensor(attention_masks)
    return input_ids, attention_masks

Prima di procedere alla tokenizzazione, è necessario specificare la lunghezza massima delle frasi.
In [0]:

# Concatenate train data and test data
all_tweets = np.concatenate([data.tweet.values, test_data.tweet.values])
# Encode our concatenated data
encoded_tweets = [tokenizer.encode(sent, add_special_tokens=True) for sent in all_tweets]
# Find the maximum length
max_len = max([len(sent) for sent in encoded_tweets])
print('Max length: ', max_len)
Lunghezza massima: 68

Ora tokenizziamo i nostri dati.
In [0]:

# Specify MAX_LEN
MAX_LEN = 64
# Print sentence 0 and its encoded token ids
token_ids = list(preprocessing_for_bert([X[0]])[0].squeeze().numpy())
print('Original: ', X[0])
print('Token IDs: ', token_ids)
# Run function preelaborazione_per_bert on the train set and the validation set
print('Tokenizing data...')
train_inputs, train_masks = preprocessing_for_bert(X_train)
val_inputs, val_masks = preprocessing_for_bert(X_val)
Originale:  @united Sto avendo problemi. Ieri ho rifatto la prenotazione per 24 ore dopo che avrei dovuto volare, ora non riesco ad accedere e fare il check-in. Potete aiutarmi?
ID gettone:  [101, 1045, 1005, 1049, 2383, 3314, 1012, 7483, 1045, 2128, 8654, 2098, 2005, 2484, 2847, 2044, 1045, 2001, 4011, 2000, 4875, 1010, 2085, 1045, 2064, 1005, 1056, 8833, 2006, 1004, 4638, 1999, 1012, 2064, 2017, 2393, 1029, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Tokenizzazione dei dati...

2.2. Creare il DataLoader di PyTorch

Creeremo un iteratore per il nostro set di dati usando la classe torch DataLoader. Questo ci aiuterà a risparmiare memoria durante l'addestramento e ad aumentare la velocità dell'addestramento.
In [0]:

from torch.utils.data import TensorDataset, DataLoader, RandomSampler, SequentialSampler
# Convert other data types to torch.Tensor
train_labels = torch.tensor(y_train)
val_labels = torch.tensor(y_val)
# For fine-tuning BERT, the authors recommend a batch size of 16 or 32.
batch_size = 32
# Create the DataLoader for our training set
train_data = TensorDataset(train_inputs, train_masks, train_labels)
train_sampler = RandomSampler(train_data)
train_dataloader = DataLoader(train_data, sampler=train_sampler, batch_size=batch_size)
# Create the DataLoader for our validation set
val_data = TensorDataset(val_inputs, val_masks, val_labels)
val_sampler = SequentialSampler(val_data)
val_dataloader = DataLoader(val_data, sampler=val_sampler, batch_size=batch_size)

3. Addestrare il nostro modello

3.1. Creare BertClassifier

BERT-base è composto da 12 strati trasformatori; ogni strato trasformatore riceve un elenco di embeddings di token e produce in uscita lo stesso numero di embeddings con la stessa dimensione nascosta (o dimensioni). L'uscita dello strato trasformatore finale del sistema [CLS] viene utilizzato come caratteristica della sequenza per alimentare un classificatore.

Il trasformatori ha la libreria BertForSequenceClassification che è stata progettata per compiti di classificazione. Tuttavia, creeremo una nuova classe in modo da poter specificare la nostra scelta di classificatori.

Di seguito, creeremo una classe BertClassifier con un modello BERT per estrarre l'ultimo strato nascosto del modello BERT. [CLS] e una rete neurale feed-forward a singolo strato nascosto come classificatore.
In [0]:

%%time
import torch
import torch.nn as nn
from transformers import BertModel
# Create the BertClassfier class
class BertClassifier(nn.Module):
    """Bert Model for Classification Tasks.
    """
    def __init__(self, freeze_bert=False):
        """
        @param    bert: a BertModel object
        @param    classifier: a torch.nn.Module classifier
        @param    freeze_bert (bool): Set Falso to fine-tune the BERT model
        """
        super(BertClassifier, self).__init__()
        # Specify hidden size of BERT, hidden size of our classifier, and number of labels
        D_in, H, D_out = 768, 50, 2
        # Instantiate BERT model
        self.bert = BertModel.from_pretrained('bert-base-uncased')
        # Instantiate an one-layer feed-forward classifier
        self.classifier = nn.Sequential(
            nn.Linear(D_in, H),
            nn.ReLU(),
            #nn.Dropout(0.5),
            nn.Linear(H, D_out)
        )
        # Freeze the BERT model
        if freeze_bert:
            for param in self.bert.parameters():
                param.requires_grad = False
    def forward(self, input_ids, attention_mask):
        """
        Feed input to BERT and the classifier to compute logits.
        @param    input_ids (torch.Tensor): an input tensor with shape (batch_size,
                      max_length)
        @param    attention_mask (torch.Tensor): a tensor that hold attention mask
                      information with shape (batch_size, max_length)
        @return   logits (torch.Tensor): an output tensor with shape (batch_size,
                      num_labels)
        """
        # Feed input to BERT
        outputs = self.bert(input_ids=input_ids,
                            attention_mask=attention_mask)
        # Extract the last hidden state of the token [CLS] for classification task
        last_hidden_state_cls = outputs[0][:, 0, :]
        # Feed input to classifier to compute logits
        logits = self.classifier(last_hidden_state_cls)
        return logits
Tempi CPU: utente 38 µs, sistema: 0 ns, totale: 38 µs
Tempo di parete: 40,1 µs

3.2. Ottimizzatore e programmatore del tasso di apprendimento

Per mettere a punto il nostro classificatore Bert, dobbiamo creare un ottimizzatore. Gli autori raccomandano i seguenti iperparametri:

  • Dimensione del lotto: 16 o 32
  • Tasso di apprendimento (Adam): 5e-5, 3e-5 o 2e-5
  • Numero di epoche: 2, 3, 4

Huggingface ha fornito il run_glue.py un esempio di implementazione dello script trasformatori libreria. Nello script viene utilizzato l'ottimizzatore AdamW.
In [0]:

from transformers import AdamW, get_linear_schedule_with_warmup
def initialize_model(epochs=4):
    """Initialize the Bert Classifier, the optimizer and the learning rate scheduler.
    """
    # Instantiate Bert Classifier
    bert_classifier = BertClassifier(freeze_bert=False)
    # Tell PyTorch to run the model on GPU
    bert_classifier.to(device)
    # Create the optimizer
    optimizer = AdamW(bert_classifier.parameters(),
                      lr=5e-5,    # Default learning rate
                      eps=1e-8    # Default epsilon value
                      )
    # Total number of training steps
    total_steps = len(train_dataloader) * epochs
    # Set up the learning rate scheduler
    scheduler = get_linear_schedule_with_warmup(optimizer,
                                                num_warmup_steps=0, # Default value
                                                num_training_steps=total_steps)
    return bert_classifier, optimizer, scheduler

3.3. Ciclo di formazione

Alleneremo il nostro classificatore Bert per 4 epoche. In ogni epoch, alleneremo il nostro modello e valuteremo le sue prestazioni sul set di validazione. Più in dettaglio, ci occuperemo di:

Formazione:

  • Disimballare i dati dal dataloader e caricarli sulla GPU
  • Azzeramento dei gradienti calcolati nel passaggio precedente
  • Eseguire un passaggio in avanti per calcolare i logit e le perdite.
  • Eseguire un passaggio all'indietro per calcolare i gradienti (perdita.indietro())
  • Ridurre la norma dei gradienti a 1,0 per evitare "gradienti esplosivi".
  • Aggiornare i parametri del modello (ottimizzatore.step())
  • Aggiornare il tasso di apprendimento (scheduler.step())

Valutazione:

  • Disimballare i dati e caricarli sulla GPU
  • Passaggio in avanti
  • Calcolo della perdita e del tasso di accuratezza sull'insieme di validazione

Lo script sottostante è commentato con i dettagli del nostro ciclo di formazione e valutazione.
In [0]:

import random
import time
# Specify loss function
loss_fn = nn.CrossEntropyLoss()
def set_seed(seed_value=42):
    """Set seed for reproducibility.
    """
    random.seed(seed_value)
    np.random.seed(seed_value)
    torch.manual_seed(seed_value)
    torch.cuda.manual_seed_all(seed_value)
def train(model, train_dataloader, val_dataloader=None, epochs=4, evaluation=False):
    """Train the BertClassifier model.
    """
    # Start training loop
    print("Start training...n")
    for epoch_i in range(epochs):
        # =======================================
        #               Training
        # =======================================
        # Print the header of the result table
        print(f"{'Epoch':^7} | {'Batch':^7} | {'Train Loss':^12} | {'Val Loss':^10} | {'Val Acc':^9} | {'Elapsed':^9}")
        print("-"*70)
        # Measure the elapsed time of each epoch
        t0_epoch, t0_batch = time.time(), time.time()
        # Reset tracking variables at the beginning of each epoch
        total_loss, batch_loss, batch_counts = 0, 0, 0
        # Put the model into the training mode
        model.train()
        # For each batch of training data...
        for step, batch in enumerate(train_dataloader):
            batch_counts +=1
            # Load batch to GPU
            b_input_ids, b_attn_mask, b_labels = tuple(t.to(device) for t in batch)
            # Zero out any previously calculated gradients
            model.zero_grad()
            # Perform a forward pass. This will return logits.
            logits = model(b_input_ids, b_attn_mask)
            # Compute loss and accumulate the loss values
            loss = loss_fn(logits, b_labels)
            batch_loss += loss.item()
            total_loss += loss.item()
            # Perform a backward pass to calculate gradients
            loss.backward()
            # Clip the norm of the gradients to 1.0 to prevent "exploding gradients"
            torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
            # Update parameters and the learning rate
            optimizer.step()
            scheduler.step()
            # Print the loss values and time elapsed for every 20 batches
            if (step % 20 == 0 and step != 0) or (step == len(train_dataloader) - 1):
                # Calculate time elapsed for 20 batches
                time_elapsed = time.time() - t0_batch
                # Print training results
                print(f"{epoch_i + 1:^7} | {step:^7} | {batch_loss / batch_counts:^12.6f} | {'-':^10} | {'-':^9} | {time_elapsed:^9.2f}")
                # Reset batch tracking variables
                batch_loss, batch_counts = 0, 0
                t0_batch = time.time()
        # Calculate the average loss over the entire training data
        avg_train_loss = total_loss / len(train_dataloader)
        print("-"*70)
        # =======================================
        #               Evaluation
        # =======================================
        if evaluation == True:
            # After the completion of each training epoch, measure the model's performance
            # on our validation set.
            val_loss, val_accuracy = evaluate(model, val_dataloader)
            # Print performance over the entire training data
            time_elapsed = time.time() - t0_epoch
            print(f"{epoch_i + 1:^7} | {'-':^7} | {avg_train_loss:^12.6f} | {val_loss:^10.6f} | {val_accuracy:^9.2f} | {time_elapsed:^9.2f}")
            print("-"*70)
        print("n")
    print("Training complete!")
def evaluate(model, val_dataloader):
    """After the completion of each training epoch, measure the model's performance
    on our validation set.
    """
    # Put the model into the evaluation mode. The dropout layers are disabled during
    # the test time.
    model.eval()
    # Tracking variables
    val_accuracy = []
    val_loss = []
    # For each batch in our validation set...
    for batch in val_dataloader:
        # Load batch to GPU
        b_input_ids, b_attn_mask, b_labels = tuple(t.to(device) for t in batch)
        # Compute logits
        with torch.no_grad():
            logits = model(b_input_ids, b_attn_mask)
        # Compute loss
        loss = loss_fn(logits, b_labels)
        val_loss.append(loss.item())
        # Get the predictions
        preds = torch.argmax(logits, dim=1).flatten()
        # Calculate the accuracy rate
        accuracy = (preds == b_labels).cpu().numpy().mean() * 100
        val_accuracy.append(accuracy)
    # Compute the average accuracy and loss over the validation set.
    val_loss = np.mean(val_loss)
    val_accuracy = np.mean(val_accuracy)
    return val_loss, val_accuracy

Ora, iniziamo ad addestrare il nostro BertClassifier!
In [0]:

set_seed(42)    # Set seed for reproducibility
bert_classifier, optimizer, scheduler = initialize_model(epochs=2)
train(bert_classifier, train_dataloader, val_dataloader, epochs=2, evaluation=True)

Iniziare la formazione...

 Epoch | Batch | Train Loss | Val Loss | Val Acc | Elapsed
----------------------------------------------------------------------
   1 | 20 | 0.630467 | - | - | 7.58
   1 | 40 | 0.497330 | - | - | 7.01
   1 | 60 | 0.502320 | - | - | 7.11
   1 | 80 | 0.491438 | - | - | 7.19
   1 | 95 | 0.486125 | - | - | 5.35
----------------------------------------------------------------------
   1 | - | 0.524515 | 0.439601 | 78.81 | 35.54
----------------------------------------------------------------------


 Epoch | Batch | Train Loss | Val Loss | Val Acc | Elapsed
----------------------------------------------------------------------
   2 | 20 | 0.287401 | - | - | 7.83
   2 | 40 | 0.260870 | - | - | 7.60
   2 | 60 | 0.287706 | - | - | 7.67
   2 | 80 | 0.283311 | - | - | 7.87
   2 | 95 | 0.280315 | - | - | 5.87
----------------------------------------------------------------------
   2 | - | 0.279978 | 0.454067 | 80.40 | 38.31
----------------------------------------------------------------------


Formazione completata!

3.4. Valutazione sull'insieme di convalida

La fase di previsione è simile alla fase di valutazione eseguita nel ciclo di addestramento, ma più semplice. Eseguiremo un passaggio in avanti per calcolare i logit e applicheremo softmax per calcolare le probabilità.
In [0]:

import torch.nn.functional as F
def bert_predict(model, test_dataloader):
    """Perform a forward pass on the trained BERT model to predict probabilities
    on the test set.
    """
    # Put the model into the evaluation mode. The dropout layers are disabled during
    # the test time.
    model.eval()
    all_logits = []
    # For each batch in our test set...
    for batch in test_dataloader:
        # Load batch to GPU
        b_input_ids, b_attn_mask = tuple(t.to(device) for t in batch)[:2]
        # Compute logits
        with torch.no_grad():
            logits = model(b_input_ids, b_attn_mask)
        all_logits.append(logits)
    # Concatenate logits from each batch
    all_logits = torch.cat(all_logits, dim=0)
    # Apply softmax to calculate probabilities
    probs = F.softmax(all_logits, dim=1).cpu().numpy()
    return probs

In [0]:

# Compute predicted probabilities on the test set
probs = bert_predict(bert_classifier, val_dataloader)
# Evaluate the Bert classifier
evaluate_roc(probs, y_val)
AUC: 0.9048
Precisione: 80,59%


Bert Classifer ottiene un punteggio AUC di 0,90 e un tasso di accuratezza di 82,65% sul set di validazione. Questo risultato è migliore di 10 punti rispetto al metodo di base.

3.5. Addestrare il modello su tutti i dati di addestramento

In [0]:

# Concatenate the train set and the validation set
full_train_data = torch.utils.data.ConcatDataset([train_data, val_data])
full_train_sampler = RandomSampler(full_train_data)
full_train_dataloader = DataLoader(full_train_data, sampler=full_train_sampler, batch_size=32)
# Train the Bert Classifier on the entire training data
set_seed(42)
bert_classifier, optimizer, scheduler = initialize_model(epochs=2)
train(bert_classifier, full_train_dataloader, epochs=2)
Iniziare la formazione...

 Epoch | Batch | Train Loss | Val Loss | Val Acc | Elapsed
----------------------------------------------------------------------
   1 | 20 | 0.664452 | - | - | 8.63
   1 | 40 | 0.587205 | - | - | 8.42
   1 | 60 | 0.522831 | - | - | 8.44
   1 | 80 | 0.476442 | - | - | 8.23
   1 | 100 | 0.467542 | - | - | 8.10
   1 | 106 | 0.483039 | - | - | 2.14
----------------------------------------------------------------------


 Epoch | Batch | Train Loss | Val Loss | Val Acc | Elapsed
----------------------------------------------------------------------
   2 | 20 | 0.338174 | - | - | 8.36
   2 | 40 | 0.296080 | - | - | 7.93
   2 | 60 | 0.295626 | - | - | 7.96
   2 | 80 | 0.277470 | - | - | 7.99
   2 | 100 | 0.314746 | - | - | 8.07
   2 | 106 | 0.293359 | - | - | 2.17
----------------------------------------------------------------------


Formazione completata!

4. Previsioni sul set di prova

4.1. Preparazione dei dati

Rivediamo a breve il nostro set di test.
In [0]:

test_data.sample(5)

Out[0]:

id tweet
471 18654 Amici e familiari: Non volate mai con @JetBlue. Assolutamente...
1971 76265 @DeltaAssist @rogerioad Non ho mai avuto un pro...
23 672 Primo volo dopo settimane. Contiamo su di voi @Americ...
2702 103263 "@USAirways: Sapete che non possiamo rimanere senza...
135 5137 @southwestair Qui all'aeroporto di SA a guardare il ...

Prima di fare previsioni sull'insieme di test, è necessario ripetere le operazioni di elaborazione e codifica effettuate sui dati di addestramento. Fortunatamente, abbiamo scritto il programma preelaborazione_per_bert per farlo al posto nostro.
In [0]:

# Run preelaborazione_per_bert on the test set
print('Tokenizing data...')
test_inputs, test_masks = preprocessing_for_bert(test_data.tweet)
# Create the DataLoader for our test set
test_dataset = TensorDataset(test_inputs, test_masks)
test_sampler = SequentialSampler(test_dataset)
test_dataloader = DataLoader(test_dataset, sampler=test_sampler, batch_size=32)
Tokenizzazione dei dati...

4.2. Previsioni

Nel nostro set di test ci sono circa 300 tweet non negativi. Pertanto, continueremo a regolare la soglia di decisione finché non avremo circa 300 tweet non negativi.

La soglia che utilizzeremo è 0,992, il che significa che i tweet con una probabilità di previsione superiore a 99,2% saranno considerati positivi. Questo valore è molto elevato rispetto alla soglia predefinita di 0,5.

Dopo aver esaminato manualmente il set di test, ho scoperto che il compito di classificazione del sentiment è difficile anche per gli esseri umani. Pertanto, una soglia elevata ci darà previsioni sicure.
In [0]:

# Compute predicted probabilities on the test set
probs = bert_predict(bert_classifier, test_dataloader)
# Get predictions from the probabilities
threshold = 0.9
preds = np.where(probs[:, 1] > threshold, 1, 0)
# Number of tweets predicted non-negative
print("Number of tweets predicted non-negative: ", preds.sum())
Numero di tweet previsti non negativi: 454

Ora esamineremo 20 tweet casuali dalle nostre previsioni. 17 di essi sono corretti, il che dimostra che il classificatore BERT acquisisce un tasso di precisione dello 0,85 circa.
In [0]:

output = test_data[preds==1]
list(output.sample(20).tweet)

Out[0]:

[@Delta @DeltaAssist Delta colpisce ancora. La Sky lounge dell'aeroporto più trafficato del paese'chiusa nei fine settimana. Stupido ed economico. ci manchi @united",
 @SouthwestAir ha riportato le arachidi tostate al miele. È triste che questa realizzazione sia il punto più alto della mia giornata? #SmallThingsInLife',
 '@DeltaAssist Ho inviato un'e-mail a kana@delta e a contactus.delta@delta per risolvere i problemi due settimane fa senza ottenere risposta. consigli su chi contattare?',
 "La donna cacciata dal volo da @AlaskaAir perché ha il cancro ha intenzione di donare alla sua famiglia il biglietto aereo http://t.co/Uj6rispWLb",
 "@united (2/2) Non ho rotto la borsa. Se non avessi dovuto pagare per il check-in, non sarei così arrabbiato. Preferisco volare con @AmericanAir @SouthwestAir ecc",
 "Ho volato con quasi tutte le compagnie aeree e non ho mai avuto un'esperienza migliore di quella di @JetBlue. Qualità, servizio, comfort e convenienza. A++",
 '@JetBlue La migliore compagnia aerea per cui lavorare, mi manchi molto #keepingitminty ',
 'Ho convinto @firetweet a prenotare un viaggio last minute per raggiungermi ad Austin! Da allora canto la canzone della sicurezza di @VirginAmerica. Povero Eric.',
 '@AmericanAir aspetta pazientemente di decollare da #DFW a #ord http://t.co/j1oDSc6fht',
 @JetBlue oggi è un giorno triste per i fedelissimi del B6. So che state pubblicizzando le vostre nuove "opzioni", ma il vostro servizio e le vostre tariffe senza bagaglio sono ciò che vi rende grandi',
 'Le cose positive di questo volo: @Gogo e la grande esperienza di @VirginAmerica. Non così buone: l'odore di vomito di bambino/tonno marcio.',
 '@USAirways @AmericanAir sentirà la mancanza di USAir :(',
 '@altonbrown @united È ora di passare a @VirginAmerica',
 Non è mai il momento sbagliato per Chobani, @AmericanAir Admirals Club! #rovato il record #aomanywasabipeas #lunch',
 "Durante il mio volo, ho rubato il telefono del mio umano'per provare il nuovo IFE in streaming di @alaskaair '. È buono! Peccato che non possieda un iPad",
 "Non vedo l'ora che la fusione tra @USAirways e @AmericanAir sia completata, che seccatura per i clienti!",
 "@JetBlue Io'sono un ragazzo universitario al verde, quindi $150 è un affare enorme",
 "Non vedo l'ora di tornare nella Bay Area stasera con il volo 2256 di @SouthwestAir",
 'Appeso al #SFO in attesa che la nebbia si diradi per il prossimo collegamento @VirginAmerica per il #sxsw! #SXSW2015 #Austin',
 "@DeltaAssist in ogni caso posso cambiare volo dal 1308 a uno che non sia'indefinitamente in ritardo.... E tornare a Washington!"]

E - Conclusione

Aggiungendo al BERT un semplice classificatore di rete neurale a uno strato nascosto e regolando con precisione il BERT, possiamo ottenere prestazioni vicine allo stato dell'arte, con un miglioramento di 10 punti rispetto al metodo di base, pur disponendo di soli 3.400 punti dati.

Inoltre, sebbene BERT sia molto grande, complicato e con milioni di parametri, abbiamo bisogno di metterlo a punto in sole 2-4 epoche. Questo risultato può essere ottenuto perché BERT è stato addestrato su una quantità enorme di parametri e codifica già molte informazioni sulla nostra lingua. Una performance impressionante ottenuta in poco tempo, con una piccola quantità di dati, ha dimostrato perché BERT è uno dei modelli NLP più potenti disponibili al momento.

Let’s Discuss Your Idea

    Related Posts

    • 10 facts and statistics about stability.ai

      Few AI companies have risen as meteorically or fallen as spectacularly as Stability AI. Founded in 2019 by Emad Mostaque, the startup behind the wildly popular Stable Diffusion image generator seemed poised to revolutionize the AI landscape and ensure

      TV, film e contenuti
    • Adobe AI Video takes on Open AI Sora

      The AI-driven content creation market is witnessing a fierce battle as tech giants compete to develop groundbreaking text-to-video technologies. OpenAI's recent unveiling of Sora, a pioneering AI model capable of generating videos from text descriptions, has set the stage

      LLM / PNL
    • AI Tool Stack for content Creation

      Stat of the Week: $5.2 billion is the estimated size of the universal AI content creation market, projected to grow to $16.9 billion by 2028. In this week’s edition, we are providing insight into the tools that make up

      Startup + VC

    Ready To Supercharge Your Business

    LET’S
    TALK
    it_ITItaliano