SKIM AI

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

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

    

Pubblicato originariamente dal ricercatore di apprendimento automatico di Skim AI, 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]:

importare os
importare re
da tqdm import tqdm
importare numpy come np
importare pandas come pd
importare matplotlib.pyplot come plt
%matplotlib inline

2. Set di dati

2.1. Scarica il set di dati

In [0]:

# Scaricare i dati
importare le richieste
request = requests.get("https://drive.google.com/uc?export=download&id=1wHt8PsMLsfX5yNSqrt2fSTcb8LEiclcf")
con open("data.zip", "wb") as file:
    file.write(request.content)
# Decomprimere i dati
importare zipfile
con zipfile.ZipFile('dati.zip') come 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]:

 # Caricare i dati e impostare le etichette
data_complaint = pd.read_csv('data/complaint1700.csv')
data_complaint['label'] = 0
dati_non_reclamo = pd.read_csv('data/noncomplaint1700.csv')
dati_non_denuncia['etichetta'] = 1
# Concatenare i dati dei reclami e dei non reclami
dati = pd.concat([dati_reclamo, dati_non_reclamo], asse=0).reset_index(drop=True)
# Drop 'compagnia aerea' colonna
data.drop(['airline'], inplace=True, axis=1)
# Visualizzare 5 campioni casuali
data.sample(5)

Out[0]:

idtweetetichetta
198824991Che bel bentornato. Ridicolo. Sbarco...1
129472380Molto deluso da @JetBlue stasera. Volo...0
1090127893@united i miei amici stanno passando un periodo infernale...0
55358278@united tutto quello che voglio per Natale è una borsa smarrita che...0
207530695Sì, 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]:

da sklearn.model_selection import train_test_split
X = dati.tweet.valori
y = dati.etichetta.valori
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]:

# Caricare i dati del test
test_data = pd.read_csv('data/test_data.csv')
# Mantenere le colonne importanti
test_data = test_data[['id', 'tweet']]
# Visualizzare 5 campioni dai dati del test
test_data.sample(5)

Out[0]:

idtweet
153959336Volo @AmericanAir in ritardo di oltre 2 ore per n...
60724101@SouthwestAir Ricevo ancora questo messaggio di errore...
33313179in attesa al #SeaTac per imbarcarmi sul mio volo @JetBlue...
2696102948Odio quando vado a selezionare i sedili in anticipo...
3585135638vergogna @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]:

importare torch
se torch.cuda.is_available():       
    device = torch.device("cuda")
    print(f'Ci sono {torch.cuda.device_count()} GPU disponibili.')
    print('Nome dispositivo:', torch.cuda.get_device_name(0))
altrimenti:
    print('Nessuna GPU disponibile, si utilizza invece la CPU.')
    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]:

importare nltk
# Togliere il commento per scaricare le "stopwords".
nltk.download("stopwords")
da nltk.corpus import stopwords
def text_preprocessing(s):
    """
    - Ridurre la frase in minuscolo
    - Cambiare "'t" in "not".
    - Rimuovere "@nome".
    - Isolare e rimuovere le punteggiature, tranne "?".
    - Rimuovere gli altri caratteri speciali
    - Rimuovere le parole di interruzione, tranne "non" e "può".
    - Rimuovere gli spazi bianchi finali
    """
    s = s.lower()
    # Cambia 't in 'not'
    s = re.sub(r"'t", " not", s)
    # Rimuovere @nome
    s = re.sub(r'(@.*?)[s]', ' ', s)
    # Isolare e rimuovere le punteggiature tranne '?'
    s = re.sub(r'(['".()!?\/,])', r' 1 ', s)
    s = re.sub(r'[^ws?]', ' ', s)
    # Rimuovere alcuni caratteri speciali
    s = re.sub(r'([;:|-"n])', ' ', s)
    # Rimuovere le stopword tranne 'not' e 'can'
    s = " ".join([parola per parola in s.split()
                  se parola non è in stopwords.words('english')
                  o parola in ['not', 'can']])
    # Rimuovere gli spazi bianchi di coda
    s = re.sub(r's+', ' ', s).strip()
    restituire 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]:

%%tempo
da sklearn.feature_extraction.text import TfidfVectorizer
# Preelaborazione del testo
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])
# Calcolo della TF-IDF
tf_idf = TfidfVectorizer(ngram_range=(1, 3),
                         binary=True,
                         smooth_idf=False)
X_train_tfidf = tf_idf.fit_transform(X_train_preprocessato)
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]:

da sklearn.model_selection import StratifiedKFold, cross_val_score
def get_auc_CV(modello):
    """
    Restituisce il punteggio medio AUC dalla convalida incrociata.
    """
    # Impostare KFold per mescolare i dati prima della divisione
    kf = StratifiedKFold(5, shuffle=True, random_state=1)
    # Ottenere i punteggi AUC
    auc = cross_val_score(
        model, X_train_tfidf, y_train, scoring="roc_auc", cv=kf)
    restituire auc.mean()

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

In [0]:

da sklearn.naive_bayes importa MultinomialNB
res = pd.Series([get_auc_CV(MultinomialNB(i))
                 per i in np.arange(1, 10, 0.1)],
                indice=np.arange(1, 10, 0.1))
best_alpha = np.round(res.idxmax(), 2)
print('Migliore alfa: ', 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]:

da sklearn.metrics import accuracy_score, roc_curve, auc
def evaluate_roc(probs, y_true):
    """
    - Stampa AUC e accuratezza sul set di test
    - Tracciare la ROC
    @params probs (np.array): un array di probabilità previste con forma (len(y_true), 2)
    @params y_true (np.array): un array di valori veri con forma (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}')
    # Ottenere l'accuratezza sul set di test
    y_pred = np.where(preds >= 0.5, 1, 0)
    precisione = accuracy_score(y_true, y_pred)
    print(f'Accuracy: {accuracy*100:.2f}%')
    # Traccia 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('Tasso di veri positivi')
    plt.xlabel('Tasso di falsi positivi')
    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]:

# Calcolo delle probabilità previste
nb_model = MultinomialNB(alpha=1,8)
nb_model.fit(X_train_tfidf, y_train)
probs = nb_model.predict_proba(X_val_tfidf)
# Valutare il classificatore
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(testo):
    """
    - Rimuove le menzioni di entità (ad esempio, '@united').
    - Correggere gli errori (es. '&' in '&')
    @param testo (str): una stringa da elaborare.
    @ritorno testo (Str): la stringa elaborata.
    """
    # Remove '@nome'
    testo = re.sub(r'(@.*?)[s]', ' ', testo)
    # Sostituire '&' con '&'
    testo = re.sub(r'&', '&', testo)
    # Rimuovere gli spazi bianchi finali
    testo = re.sub(r's+', ' ', testo).strip()
    restituire il testo

In [0]:

# Stampa la frase 0
print('Originale: ', X[0])
print('Elaborato: ', 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]:

da transformers import BertTokenizer
# Caricare il tokenizzatore BERT
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased', do_lower_case=True)
# Creare una funzione per tokenizzare un insieme di testi
def preprocessing_for_bert(data):
    """Esegue le fasi di pre-elaborazione necessarie per il BERT pre-addestrato.
    @param dati (np.array): Array di testi da elaborare.
    @return input_ids (torch.Tensor): Tensore di id di token da fornire al modello.
    @return attention_masks (torch.Tensor): Tensore di indici che specificano quali
                  token che devono essere presi in considerazione dal modello.
    """
    # Creare elenchi vuoti per memorizzare gli output
    input_ids = []
    maschere_di_attenzione = []
    # Per ogni frase...
    per i dati inviati:
        # codificare_in_più volontà:
        # (1) Tokenizzare la frase
        # (2) Aggiungere il [CLS] e [SEP] token all'inizio e alla fine
        # (3) Porta la frase alla massima lunghezza
        # (4) Mappare i token ai loro ID
        # (5) Crea la maschera di attenzione
        # (6) Restituisce un dizionario di output
        encoded_sent = tokenizer.encode_plus(
            text=text_preprocessing(sent), # Preelaborazione della frase
            add_special_tokens=True, # Aggiungi [CLS] e [SEP]
            max_length=MAX_LEN, # Lunghezza massima da troncare/padellare
            pad_to_max_length=True, # Imbottire la frase alla lunghezza massima
            #return_tensors='pt', # Tensore di ritorno PyTorch
            return_attention_mask=True # Restituzione della maschera di attenzione
            )
        # Aggiungere gli output agli elenchi
        input_ids.append(encoded_sent.get('input_ids'))
        attention_masks.append(encoded_sent.get('attention_mask'))
    # Convertire gli elenchi in tensori
    input_ids = torch.tensor(input_ids)
    attention_masks = torch.tensor(attention_masks)
    restituire input_ids, attention_masks

Prima di procedere alla tokenizzazione, è necessario specificare la lunghezza massima delle frasi.

In [0]:

# Concatenare i dati del treno e i dati del test
all_tweets = np.concatenate([data.tweet.values, test_data.tweet.values])
# Codificare i dati concatenati
encoded_tweets = [tokenizer.encode(sent, add_special_tokens=True) for sent in all_tweets]
# Trovare la lunghezza massima
max_len = max([len(sent) for sent in encoded_tweets])
print('Lunghezza massima: ', max_len)
Lunghezza massima: 68

Ora tokenizziamo i nostri dati.

In [0]:

# Specificare MAX_LEN
MAX_LEN = 64
# Stampa la frase 0 e i suoi id codificati dei token
token_ids = list(preprocessing_for_bert([X[0]])[0].squeeze().numpy())
print('Originale: ', X[0])
print('ID dei token: ', token_ids)
Funzione di esecuzione # preelaborazione_per_bert sull'insieme di addestramento e sull'insieme di validazione
print('Tokenizzazione dei dati...')
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]:

da torch.utils.data import TensorDataset, DataLoader, RandomSampler, SequentialSampler
# Convertire altri tipi di dati in torch.tensor
train_labels = torch.tensor(y_train)
val_labels = torch.tensor(y_val)
# Per la messa a punto di BERT, gli autori consigliano una dimensione del batch di 16 o 32.
batch_size = 32
# Creare il DataLoader per il nostro set di addestramento
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)
# Creare il DataLoader per il nostro set di validazione
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]:

%%tempo
importare torch
importare torch.nn come nn
da transformers importare BertModel
# Creare la classe BertClassfier
classe BertClassifier(nn.Module):
    """Modello Bert per compiti di classificazione.
    """
    def __init__(self, freeze_bert=False):
        """
        @param bert: un oggetto BertModel
        @param classifier: un classificatore torch.nn.Module
        @param freeze_bert (bool): Imposta Falso per mettere a punto il modello BERT
        """
        super(BertClassifier, self).__init__()
        # Specificare la dimensione nascosta di BERT, la dimensione nascosta del nostro classificatore e il numero di etichette
        D_in, H, D_out = 768, 50, 2
        # Istanziare il modello BERT
        self.bert = BertModel.from_pretrained('bert-base-uncased')
        # Istanziare un classificatore feed-forward a un solo livello
        self.classifier = nn.Sequential(
            nn.Lineare(D_in, H),
            nn.ReLU(),
            #nn.Dropout(0,5),
            nn.Lineare(H, D_out)
        )
        # Congelare il modello BERT
        se freeze_bert:
            per param in self.bert.parameters():
                param.requires_grad = False
    def forward(self, input_ids, attention_mask):
        """
        Fornisce l'input a BERT e al classificatore per calcolare i logit.
        @param input_ids (torch.Tensor): un tensore di input con forma (batch_size,
                      max_length)
        @param attention_mask (torch.Tensor): un tensore che contiene le informazioni sulla maschera di attenzione con forma (batch_size, max_length).
                      con forma (batch_size, max_length)
        @return logits (torch.Tensor): un tensore di output con forma (batch_size,
                      num_etichette)
        """
        # Alimentare l'input al BERT
        outputs = self.bert(input_ids=input_ids,
                            attention_mask=attention_mask)
        # Estrarre l'ultimo stato nascosto del token [CLS] per il compito di classificazione
        last_hidden_state_cls = outputs[0][:, 0, :]
        # Alimentare l'input al classificatore per calcolare i logits
        logits = self.classifier(last_hidden_state_cls)
        restituire 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]:

da transformers import AdamW, get_linear_schedule_with_warmup
def initialize_model(epochs=4):
    """Inizializza il classificatore Bert, l'ottimizzatore e lo schedulatore del tasso di apprendimento.
    """
    # Istanziare il classificatore di Bert
    bert_classifier = BertClassifier(freeze_bert=False)
    # Dire a PyTorch di eseguire il modello su GPU
    bert_classifier.to(device)
    # Creare l'ottimizzatore
    optimizer = AdamW(bert_classifier.parameters(),
                      lr=5e-5, # Tasso di apprendimento predefinito
                      eps=1e-8 # Valore epsilon predefinito
                      )
    # Numero totale di passi di addestramento
    total_steps = len(train_dataloader) * epochs
    # Impostazione dello scheduler del tasso di apprendimento
    scheduler = get_linear_schedule_with_warmup(optimizer,
                                                num_warmup_steps=0, # Valore di default
                                                num_training_steps=totale_steps)
    restituire 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) # Impostare il seme per la riproducibilità
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]:

importare torch.nn.functional come F
def bert_predict(modello, test_dataloader):
    """Esegue un passaggio in avanti sul modello BERT addestrato per prevedere le probabilità
    sul set di test.
    """
    # Mettere il modello in modalità di valutazione. I livelli di dropout sono disabilitati durante
    # il tempo di test.
    model.eval()
    all_logits = []
    # Per ogni lotto del nostro set di test...
    per batch in test_dataloader:
        # Caricare il batch sulla GPU
        b_input_ids, b_attn_mask = tuple(t.to(device) for t in batch)[:2]
        # Calcolo dei logit
        con torch.no_grad():
            logits = model(b_input_ids, b_attn_mask)
        all_logits.append(logits)
    # Concatenare i logit di ciascun batch
    all_logits = torch.cat(all_logits, dim=0)
    # Applicare softmax per calcolare le probabilità
    probs = F.softmax(all_logits, dim=1).cpu().numpy()
    restituire probs

In [0]:

# Calcolo delle probabilità previste sull'insieme di test
probs = bert_predict(bert_classifier, val_dataloader)
# Valutare il classificatore di Bert
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]:

# Concatenare l'insieme di addestramento e l'insieme di validazione
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)
# Addestrare il classificatore Bert su tutti i dati di addestramento
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 set di test.

In [0]:

test_data.sample(5)

Out[0]:

idtweet
47118654Amici e familiari: Non volate mai con @JetBlue. Assolutamente...
197176265@DeltaAssist @rogerioad Non ho mai avuto un pro...
23672Primo volo dopo settimane. Contiamo su di voi @Americ...
2702103263"@USAirways: Sapete che non possiamo rimanere senza...
1355137@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]:

Corsa # preelaborazione_per_bert sull'insieme di prova
print('Tokenizzazione dei dati...')
test_inputs, test_masks = preprocessing_for_bert(test_data.tweet)
# Creare il DataLoader per il nostro set di test
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]:

# Calcolo delle probabilità previste sull'insieme di test
probs = bert_predict(bert_classifier, test_dataloader)
# Ottenere le previsioni dalle probabilità
soglia = 0,9
preds = np.where(probs[:, 1] > soglia, 1, 0)
# Numero di tweet predetti non negativi
print("Numero di tweet previsti non negativi: ", 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]
lista(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.


Discutiamo la vostra idea

    Messaggi correlati

    Pronti a potenziare la vostra attività

    LET'S
    PARLARE
    it_ITItaliano