Tutorial: BERT-Feinabstimmung für die Sentiment-Analyse

Tutorial: BERT-Feinabstimmung für die Sentiment-Analyse

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



BERT_für_die_Stimmungsanalyse





A - Einleitung

In den letzten Jahren hat die NLP-Gemeinschaft viele Durchbrüche in der natürlichen Sprachverarbeitung erlebt, insbesondere den Wechsel zum Transfer-Lernen. Modelle wie ELMo, ULMFiT von fast.ai, Transformer und GPT von OpenAI haben es den Forschern ermöglicht, bei mehreren Benchmarks Spitzenergebnisse zu erzielen und der Community große vortrainierte Modelle mit hoher Leistung zur Verfügung zu stellen. Dieser Wandel im NLP wird als der ImageNet-Moment im NLP angesehen, ein Wandel im Computer Vision vor einigen Jahren, als untere Schichten von Deep-Learning-Netzwerken mit Millionen von Parametern, die für eine bestimmte Aufgabe trainiert wurden, wiederverwendet und für andere Aufgaben feinabgestimmt werden können, anstatt neue Netzwerke von Grund auf zu trainieren.

Einer der größten Meilensteine in der Entwicklung des NLP in letzter Zeit ist die Veröffentlichung von Googles BERT, die als Beginn einer neuen Ära im NLP beschrieben wird. In diesem Notizbuch werde ich das HuggingFace's Transformatoren Bibliothek zur Feinabstimmung des vortrainierten BERT-Modells für eine Klassifizierungsaufgabe. Anschließend werde ich die Leistung des BERT mit einem Basismodell vergleichen, bei dem ich einen TF-IDF-Vektorisierer und einen Naive Bayes-Klassifikator verwende. Die Transformatoren Bibliothek helfen uns bei der schnellen und effizienten Feinabstimmung des hochmodernen BERT-Modells und liefern eine Genauigkeitsrate 10% höher als beim Basismodell.

Referenz:

Zu verstehen Transformator (die Architektur, auf der BERT aufbaut) und lernen, wie man BERT implementiert, empfehle ich dringend die Lektüre der folgenden Quellen:

B - Einrichtung

1. Wesentliche Bibliotheken laden

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. Datensatz

2.1. Datensatz herunterladen

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. Lastzugdaten

Die Zugdaten bestehen aus 2 Dateien, die jeweils 1700 beschwerdeführende/nicht beschwerdeführende Tweets enthalten. Alle Tweets in den Daten enthalten mindestens einen Hashtag einer Fluggesellschaft.

Wir laden die Trainingsdaten und beschriften sie. Da wir nur die Textdaten zur Klassifizierung verwenden, lassen wir unwichtige Spalten weg und behalten nur id, tweet und Etikett Spalten.
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 Etikett
1988 24991 Was für ein tolles Willkommen zurück. Lachhaft. Deplanin... 1
1294 72380 Sehr enttäuscht von @JetBlue heute Abend. Flug... 0
1090 127893 @united meine Freunde haben eine höllische Zeit... 0
553 58278 @united Alles, was ich mir zu Weihnachten wünsche, ist eine verlorene Tasche, die... 0
2075 30695 Ja, ich werde nicht lügen... ich bin sehr daran interessiert, es zu versuchen... 1

Die gesamten Trainingsdaten werden nach dem Zufallsprinzip in zwei Gruppen aufgeteilt: eine Trainingsgruppe mit 90% der Daten und eine Validierungsgruppe mit 10% der Daten. Wir führen die Abstimmung der Hyperparameter mittels Kreuzvalidierung auf dem Trainingssatz durch und verwenden den Validierungssatz zum Vergleich der Modelle.
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. Lasttest-Daten

Die Testdaten enthalten 4555 Beispiele ohne Kennzeichnung. Etwa 300 Beispiele sind nicht-beschwerende Tweets. Unsere Aufgabe ist es, ihre id und prüfen Sie manuell, ob unsere Ergebnisse korrekt sind.
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 @AmericanAir Flug verspätet sich über 2 Stunden wegen...
607 24101 @SouthwestAir Ich bekomme immer noch diese Fehlermeldung...
333 13179 Ich warte am #SeaTac, um an Bord meines @JetBlue-Flugs zu gehen...
2696 102948 Ich hasse es, wenn ich durch das Sitzauswahlverfahren gehe...
3585 135638 Schande über Sie @AlaskaAir

3. GPU für Schulungen einrichten

Google Colab bietet kostenlose GPUs und TPUs. Da wir ein großes neuronales Netzwerk trainieren werden, ist es am besten, diese Funktionen zu nutzen.

Eine GPU kann hinzugefügt werden, indem man das Menü aufruft und auswählt:

Laufzeit -> Laufzeittyp ändern -> Hardwarebeschleuniger: GPU

Dann müssen wir die folgende Zelle ausführen, um die GPU als Gerät anzugeben.
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")
Es sind 1 GPU(s) verfügbar.
Name des Geräts: Tesla T4

C - Grundlinie: TF-IDF + Naive Bayes Klassifikator

In diesem Basisansatz verwenden wir zunächst TF-IDF, um unsere Textdaten zu vektorisieren. Dann verwenden wir das Naive-Bayes-Modell als Klassifikator.

Warum Naive Bayes? Ich habe verschiedene Algorithmen für maschinelles Lernen ausprobiert, darunter Random Forest, Support Vectors Machine und XGBoost, und festgestellt, dass Naive Bayes die beste Leistung erbringt. Unter Leitfaden für Scikit-learn Um den richtigen Schätzer zu wählen, wird auch vorgeschlagen, Naive Bayes für Textdaten zu verwenden. Ich habe auch versucht, die Dimensionalität mit Hilfe von SVD zu reduzieren, was jedoch nicht zu einer besseren Leistung führte.

1. Datenaufbereitung

1.1. Vorverarbeitung

Beim Bag-of-Words-Modell wird ein Text als Tasche seiner Wörter dargestellt, wobei Grammatik und Wortreihenfolge außer Acht gelassen werden. Daher wollen wir Stoppwörter, Interpunktionszeichen und Zeichen, die nicht viel zur Bedeutung des Satzes beitragen, entfernen.
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] Herunterladen des Pakets stopwords nach /root/nltk_data...
[nltk_data] Das Paket stopwords ist bereits aktuell!

1.2. TF-IDF-Vektorisierer

In der Informationsbeschaffung, TF-IDF, kurz für Begriffshäufigkeit - inverse Dokumentenhäufigkeitist eine numerische Statistik, die wiedergeben soll, wie wichtig ein Wort für ein Dokument in einer Sammlung oder einem Korpus ist. Wir werden TF-IDF verwenden, um unsere Textdaten zu vektorisieren, bevor wir sie in Algorithmen für maschinelles Lernen einspeisen.
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)
CPU-Zeiten: user 5,47 s, sys: 519 ms, gesamt: 5.99 s
Wandzeit: 6 s

2. Naive Bayes Klassifikator trainieren

2.1. Abstimmung der Hyperparameter

Wir werden die Kreuzvalidierung und den AUC-Score verwenden, um die Hyperparameter unseres Modells abzustimmen. Die Funktion get_auc_CV gibt den durchschnittlichen AUC-Wert aus der Kreuzvalidierung zurück.
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()

Die MultinominalNB Klasse haben nur einen Hypterparameter - alpha. Der nachstehende Code hilft uns, den Alpha-Wert zu finden, der uns den höchsten CV-AUC-Wert beschert.
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()
Bestes Alpha:  1.3

2.2. Auswertung der Validierungsmenge

Um die Leistung unseres Modells zu bewerten, berechnen wir die Genauigkeitsrate und den AUC-Wert unseres Modells für die Validierungsmenge.
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()

Durch die Kombination von TF-IDF und dem Naive-Bayes-Algorithmus erreichen wir eine Trefferquote von 72.65% auf dem Validierungssatz. Dieser Wert ist die Basisleistung und wird zur Bewertung der Leistung unseres BERT-Feinabstimmungsmodells verwendet.
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
Genauigkeit: 75,59%

D - Feinabstimmung BERT

1. Installieren Sie die Hugging Face Library

Die Transformer-Bibliothek von Hugging Face enthält PyTorch-Implementierungen von hochmodernen NLP-Modellen wie BERT (von Google), GPT (von OpenAI) ... und vortrainierte Modellgewichte.
In [1]:

#!pip Transformatoren installieren

2. Tokenisierung und Eingabeformatierung

Bevor wir unseren Text in Token umwandeln, führen wir eine leichte Bearbeitung durch, bei der wir Erwähnungen von Entitäten (z. B. @united) und einige Sonderzeichen entfernen. Der Grad der Verarbeitung ist hier viel geringer als bei früheren Ansätzen, da BERT mit den gesamten Sätzen trainiert wurde.
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]))
Original:  @united I'm having issues. Gestern habe ich für 24 Stunden nach dem geplanten Flug umgebucht, und jetzt kann ich mich nicht mehr anmelden und einchecken. Können Sie mir helfen?
Bearbeitet:  I'm having issues. Gestern habe ich 24 Stunden, nachdem ich fliegen sollte, umgebucht, und jetzt kann ich mich nicht mehr anmelden und einchecken. Können Sie mir helfen?

2.1. BERT Tokenizer

Um den vortrainierten BERT anzuwenden, müssen wir den von der Bibliothek bereitgestellten Tokenizer verwenden. Der Grund dafür ist, dass (1) das Modell ein bestimmtes, festes Vokabular hat und (2) der BERT-Tokenizer eine bestimmte Art hat, Wörter außerhalb des Vokabulars zu behandeln.

Darüber hinaus müssen wir am Anfang und am Ende jedes Satzes spezielle Token hinzufügen, alle Sätze auf eine einzige konstante Länge auffüllen und abschneiden und mit der "Aufmerksamkeitsmaske" explizit angeben, welche Token aufgefüllt werden sollen.

Die kodieren_plus Methode von BERT tokenizer wird:

(1) unseren Text in Token aufteilen,

(2) fügen Sie die besondere [CLS] und [SEP] Wertmarken, und

(3) diese Token in Indizes des Tokenizer-Vokabulars umwandeln,

(4) Auffüllen oder Kürzen von Sätzen auf maximale Länge und

(5) Aufmerksamkeitsmaske erstellen.
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:
        # kodieren_plus will:
        #    (1) Tokenize the sentence
        #    (2) Add the [CLS] und [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] und [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

Vor der Tokenisierung müssen wir die maximale Länge unserer Sätze angeben.
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)
Maximale Länge: 68

Lassen Sie uns nun unsere Daten tokenisieren.
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 vorverarbeitung_für_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)
Original:  @united I'm having issues. Gestern habe ich für 24 Stunden nach dem geplanten Flug umgebucht, und jetzt kann ich mich nicht mehr anmelden und einchecken. Können Sie mir helfen?
Token IDs:  [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]
Tokenisierung von Daten...

2.2. PyTorch DataLoader erstellen

Wir werden einen Iterator für unseren Datensatz mithilfe der Torch DataLoader-Klasse erstellen. Dadurch wird beim Training Speicherplatz gespart und die Trainingsgeschwindigkeit erhöht.
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. Unser Modell trainieren

3.1. BertClassifier erstellen

BERT-base besteht aus 12 Transformationsschichten, wobei jede Transformationsschicht eine Liste von Token-Einbettungen aufnimmt und am Ausgang die gleiche Anzahl von Einbettungen mit der gleichen versteckten Größe (oder Dimension) erzeugt. Die Ausgabe der letzten Transformatorschicht der [CLS] Token wird als Merkmal der Sequenz verwendet, um einen Klassifikator zu speisen.

Die Transformatoren Bibliothek hat die BertForSequenceClassification Klasse, die für Klassifizierungsaufgaben konzipiert ist. Wir werden jedoch eine neue Klasse erstellen, damit wir unsere eigene Auswahl an Klassifikatoren angeben können.

Im Folgenden wird eine BertClassifier-Klasse mit einem BERT-Modell erstellt, um die letzte versteckte Schicht der [CLS] Token und ein einschichtiges neuronales Netz mit Vorwärtskopplung als Klassifikator.
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 Falsch 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
CPU-Zeiten: user 38 µs, sys: 0 ns, gesamt: 38 µs
Wandzeit: 40,1 µs

3.2. Optimierer & Lernratenplaner

Zur Feinabstimmung unseres Bert-Klassifikators müssen wir einen Optimierer erstellen. Die Autoren empfehlen folgende Hyper-Parameter:

  • Chargengröße: 16 oder 32
  • Lernrate (Adam): 5e-5, 3e-5 oder 2e-5
  • Anzahl der Epochen: 2, 3, 4

Huggingface lieferte die run_glue.py Skript, ein Beispiel für die Implementierung des Transformatoren Bibliothek. In dem Skript wird der AdamW-Optimierer verwendet.
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. Trainingsschleife

Wir werden unseren Bert-Klassifikator für 4 Epochen trainieren. In jeder Epoche trainieren wir unser Modell und bewerten seine Leistung anhand der Validierungsmenge. Genauer gesagt, werden wir:

Ausbildung:

  • Entpacken Sie unsere Daten aus dem Dataloader und laden Sie die Daten auf die GPU
  • Nullen der im vorherigen Durchgang berechneten Gradienten
  • Durchführen eines Vorwärtsdurchlaufs zur Berechnung von Logits und Verlusten
  • Führen Sie einen Rückwärtsdurchlauf zur Berechnung der Gradienten durch (verlust.rückwärts())
  • Begrenzung der Norm der Gradienten auf 1,0, um "explodierende Gradienten" zu verhindern
  • Aktualisieren Sie die Parameter des Modells (optimizer.step())
  • Aktualisieren Sie die Lernrate (scheduler.step())

Bewertung:

  • Entpacken unserer Daten und Laden auf die GPU
  • Vorwärtspass
  • Berechnung der Verlust- und Genauigkeitsrate über die Validierungsmenge

Das nachstehende Skript ist mit den Einzelheiten unserer Schulungs- und Bewertungsschleife kommentiert.
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

Beginnen wir nun mit dem Training unseres BertClassifiers!
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)

Ausbildung beginnen...

 Epoche | 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
----------------------------------------------------------------------


 Epoche | Batch | Zugverlust | 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
----------------------------------------------------------------------


Ausbildung abgeschlossen!

3.4. Auswertung der Validierungsmenge

Der Vorhersageschritt ähnelt dem Bewertungsschritt, den wir in der Trainingsschleife durchgeführt haben, ist aber einfacher. Wir führen einen Vorwärtsdurchlauf zur Berechnung von Logits durch und wenden Softmax an, um Wahrscheinlichkeiten zu berechnen.
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
Genauigkeit: 80,59%


Der Bert Classifer erreicht einen AUC-Wert von 0,90 und eine Genauigkeitsrate von 82,65% in der Validierungsmenge. Dieses Ergebnis ist 10 Punkte besser als die Basismethode.

3.5. Trainieren Sie unser Modell mit den gesamten Trainingsdaten

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)
Ausbildung beginnen...

 Epoche | 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
----------------------------------------------------------------------


 Epoche | Batch | Zugverlust | 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
----------------------------------------------------------------------


Ausbildung abgeschlossen!

4. Vorhersagen auf dem Testsatz

4.1. Datenaufbereitung

Lassen Sie uns kurz auf unseren Testsatz zurückkommen.
In [0]:

test_data.sample(5)

Out[0]:

id tweet
471 18654 Freunde und Familie: Fliegen Sie niemals mit @JetBlue. Absolut...
1971 76265 @DeltaAssist @rogerioad Ich hatte noch nie einen Pro...
23 672 Erster Flug seit Wochen. Ich zähle auf dich @Americ...
2702 103263 "@USAirways: You know that we can__t stay no m...
135 5137 @southwestair Hier am Flughafen SA beobachten die ...

Bevor wir Vorhersagen für den Testsatz treffen, müssen wir die Verarbeitungs- und Kodierungsschritte wiederholen, die wir für die Trainingsdaten durchgeführt haben. Glücklicherweise haben wir die vorverarbeitung_für_bert Funktion, um dies für uns zu tun.
In [0]:

# Run vorverarbeitung_für_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)
Tokenisierung von Daten...

4.2. Vorhersagen

In unserem Testsatz befinden sich etwa 300 nicht-negative Tweets. Daher werden wir die Entscheidungsschwelle so lange anpassen, bis wir etwa 300 nicht-negative Tweets haben.

Der von uns verwendete Schwellenwert beträgt 0,992, was bedeutet, dass Tweets mit einer Vorhersagewahrscheinlichkeit von mehr als 99,2% als positiv eingestuft werden. Dieser Wert ist im Vergleich zum Standardwert von 0,5 sehr hoch.

Nach einer manuellen Prüfung der Testmenge stelle ich fest, dass die Stimmungs-Klassifizierung hier sogar für Menschen schwierig ist. Daher wird uns ein hoher Schwellenwert sichere Vorhersagen liefern.
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())
Anzahl der Tweets mit nicht-negativer Vorhersage: 454

Nun werden wir 20 zufällige Tweets aus unseren Vorhersagen untersuchen. 17 von ihnen sind richtig, was zeigt, dass der BERT Classifier eine Genauigkeit von 0,85 erreicht.
In [0]:

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

Out[0]:

[@Delta @DeltaAssist Delta streikt erneut. Sky Lounge am verkehrsreichsten Flughafen des Landes'an Wochenenden geschlossen. Dumb & cheap. miss you @united",
 '.@SouthwestAir hat honiggeröstete Erdnüsse zurückgebracht. Ist es traurig, dass diese Erkenntnis der Höhepunkt meines Tages sein könnte?#SmallThingsInLife',
 '@DeltaAssist Ich habe vor zwei Wochen eine E-Mail an kana@delta und contactus.delta@delta geschickt, um Probleme zu lösen, ohne eine Antwort zu erhalten,
 "Woman With Kicked Off Flight By @AlaskaAir Because So Has #Cancer Plans to Donate Her Family's Airfare http://t.co/Uj6rispWLb",
 "@united (2/2) I didn't break the bag. Wenn ich nicht'für das Einchecken bezahlen müsste, wäre ich'nicht so verärgert. Ich fliege lieber mit @AmericanAir @SouthwestAir usw.",
 "I've flog fast jede Fluggesellschaft & nie hatte ich eine bessere Erfahrung als mit @JetBlue. Qualität, Service, Komfort und Erschwinglichkeit. A++",
 '@JetBlue Beste Fluggesellschaft, für die man arbeiten kann, vermisse dich sehr #keepingitminty ',
 'Überzeugte @firetweet, eine Last-Minute-Reise zu buchen, um mich in Austin tom! Seitdem singe ich das @VirginAmerica-Sicherheitslied. Armer Eric. ',
 '@AmericanAir wartet geduldig auf den Abflug von #DFW nach #ord http://t.co/j1oDSc6fht',
 'Oh @JetBlue heute ist ein trauriger Tag für die B6-Treuen. Ich weiß, dass ihr eure neuen "Optionen" anpreist, aber euer Service und die Gebühren für das Gepäck SIND das, was euch großartig macht',
 'Dinge, die an diesem Flug gut sind: @Gogo und die großartige Erfahrung von @VirginAmerica. Nicht so gut: der Geruch von Babykotze/verfaultem Thunfisch',
 '@USAirways @AmericanAir wird USAir vermissen :(',
 '@altonbrown @united Zeit, zu @VirginAmerica zu wechseln',
 'Es ist nie der falsche Zeitpunkt für Chobani, @AmericanAir Admirals Club! #brokenrecord #toomanywasabipeas #lunch',
 "Auf meinem Flug habe ich das Telefon meines Menschen gestohlen, um @alaskaair 's neues Streaming IFE auszuprobieren. Es'ist gut! Schade, dass sie kein iPad besitzt.",
 "Can't wait for the @USAirways and @AmericanAir merger to be completed, what a hassle for the customer!",
 "@JetBlue I'm a broke college kid so $150 is a huge deal.",
 "Can't wait to fly back to the Bay Area tonight on @SouthwestAir flight 2256!!!!",
 'Ich hänge an #SFO und warte darauf, dass sich der Nebel lichtet, um die nächste Verbindung @VirginAmerica nach #sxsw zu erreichen! #SXSW2015 #Austin',
 "@DeltaAssist irgendwie kann ich Flüge von 1308 zu einer, die isn't auf unbestimmte Zeit verzögert.... And get back to dc!"]

E - Schlussfolgerung

Durch das Hinzufügen eines einfachen Klassifizierers mit einer verborgenen Schicht eines neuronalen Netzwerks zu BERT und die Feinabstimmung von BERT können wir eine Leistung erzielen, die fast dem Stand der Technik entspricht und die 10 Punkte besser ist als die Basismethode, obwohl wir nur 3.400 Datenpunkte haben.

Und obwohl BERT sehr groß und kompliziert ist und Millionen von Parametern hat, müssen wir es nur in 2-4 Epochen feinabstimmen. Dieses Ergebnis kann erreicht werden, weil BERT auf der riesigen Menge trainiert wurde und bereits eine Menge Informationen über unsere Sprache kodiert. Eine beeindruckende Leistung, die in kurzer Zeit und mit einer geringen Datenmenge erzielt wurde, hat gezeigt, warum BERT eines der leistungsfähigsten NLP-Modelle ist, die derzeit verfügbar sind.

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 & Inhalt
    • 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

      LLMs / NLP
    • 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

      Start-ups + VC

    Ready To Supercharge Your Business

    LET’S
    TALK
    de_DEDeutsch