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

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

    

Ursprünglich veröffentlicht von 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]:

importieren os
importieren re
von tqdm importieren tqdm
importiere numpy als np
importieren Sie pandas als pd
importieren matplotlib.pyplot as plt
%matplotlib inline

2. Datensatz

2.1. Datensatz herunterladen

In [0]:

# Daten herunterladen
Anfragen importieren
request = requests.get("https://drive.google.com/uc?export=download&id=1wHt8PsMLsfX5yNSqrt2fSTcb8LEiclcf")
with open("data.zip", "wb") as file:
    file.write(request.content)
# Daten entpacken
importiere zipfile
with zipfile.ZipFile('daten.zip') as zip:
    zip.extractall('daten')

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]:

 # Daten laden und Beschriftungen setzen
data_complaint = pd.read_csv('data/complaint1700.csv')
daten_beschwerde['label'] = 0
data_non_complaint = pd.read_csv('data/noncomplaint1700.csv')
data_non_complaint['label'] = 1
# Verkettung von beschwerdeführenden und nicht beschwerdeführenden Daten
data = pd.concat([data_complaint, data_non_complaint], axis=0).reset_index(drop=True)
# Drop 'Fluggesellschaft' Spalte
data.drop(['Fluggesellschaft'], inplace=True, axis=1)
# Anzeige von 5 Zufallsstichproben
data.sample(5)

Out[0]:

idtweetEtikett
198824991Was für ein tolles Willkommen zurück. Lachhaft. Deplanin...1
129472380Sehr enttäuscht von @JetBlue heute Abend. Flug...0
1090127893@united meine Freunde haben eine höllische Zeit...0
55358278@united Alles, was ich mir zu Weihnachten wünsche, ist eine verlorene Tasche, die...0
207530695Ja, 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 = daten.tweet.werte
y = daten.marke.werte
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]:

# Testdaten laden
test_data = pd.read_csv('data/test_data.csv')
# Wichtige Spalten behalten
test_data = test_data[['id', 'tweet']]
# Anzeige von 5 Stichproben aus den Testdaten
test_data.sample(5)

Out[0]:

idtweet
153959336@AmericanAir Flug verspätet sich über 2 Stunden wegen...
60724101@SouthwestAir Ich bekomme immer noch diese Fehlermeldung...
33313179Ich warte am #SeaTac, um an Bord meines @JetBlue-Flugs zu gehen...
2696102948Ich hasse es, wenn ich durch das Sitzauswahlverfahren gehe...
3585135638Schande ü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]:

torch importieren
if torch.cuda.is_available():       
    device = torch.device("cuda")
    print(f'Es sind {torch.cuda.device_count()} GPU(s) verfügbar.')
    print('Gerätename:', torch.cuda.get_device_name(0))
sonst:
    print('Keine GPU verfügbar, stattdessen wird die CPU verwendet.')
    Gerät = 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]:

importieren nltk
# Kommentar zum Herunterladen von "Stoppwörter" entfernen
nltk.download("stoppwörter")
from nltk.corpus import stopwords
def text_preprocessing(s):
    """
    - Den Satz kleinschreiben
    - Ändere "'t" in "nicht"
    - "@name" entfernen
    - Interpunktionszeichen isolieren und entfernen, außer "?"
    - Andere Sonderzeichen entfernen
    - Stoppwörter außer "nicht" und "kann" entfernen
    - Nachfolgende Leerzeichen entfernen
    """
    s = s.lower()
    # Ändere 't in 'not'
    s = re.sub(r"'t", " nicht", s)
    # @name entfernen
    s = re.sub(r'(@.*?)[s]', ' ', s)
    # Isolieren und Entfernen von Interpunktionszeichen außer '?'
    s = re.sub(r'(['".()!?\/,])', r' 1 ', s)
    s = re.sub(r'[^ws?]', ' ', s)
    # Einige Sonderzeichen entfernen
    s = re.sub(r'([;:|-"n])', ' ', s)
    # Entfernen Sie Stoppwörter außer 'nicht' und 'kann'
    s = " ".join([Wort für Wort in s.split()
                  wenn Wort nicht in stopwords.words('englisch')
                  oder Wort in ['nicht', 'kann']])
    # Nachfolgende Whitespaces entfernen
    s = re.sub(r's+', ' ' ', s).strip()
    s zurückgeben
[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]:

%%zeit
from sklearn.feature_extraction.text import TfidfVectorizer
# Text vorverarbeiten
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])
# TF-IDF berechnen
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_vorverarbeitet)
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):
    """
    Gibt den durchschnittlichen AUC-Score aus der Kreuzvalidierung zurück.
    """
    # KFold setzen, um die Daten vor der Aufteilung zu mischen
    kf = StratifiedKFold(5, shuffle=True, random_state=1)
    # Ermitteln der AUC-Werte
    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('Bestes 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):
    """
    - AUC und Genauigkeit für die Testmenge ausgeben
    - ROC-Kurve zeichnen
    @params probs (np.array): ein Array von vorhergesagten Wahrscheinlichkeiten mit Form (len(y_true), 2)
    @params y_true (np.array): ein Array der wahren Werte mit der Form (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}')
    # Genauigkeit über die Testmenge ermitteln
    y_pred = np.where(preds >= 0.5, 1, 0)
    Genauigkeit = accuracy_score(y_true, y_pred)
    print(f'Accuracy: {accuracy*100:.2f}%')
    # ROC-AUC-Diagramm
    plt.title('Receiver Operating Characteristic')
    plt.plot(fpr, tpr, 'b', label = 'AUC = %0.2f' % roc_auc)
    plt.legend(loc = 'unten rechts')
    plt.plot([0, 1], [0, 1],'r--')
    plt.xlim([0, 1])
    plt.ylim([0, 1])
    plt.ylabel('True Positive Rate')
    plt.xlabel('Falsch-Positiv-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]:

# Berechnung der vorhergesagten Wahrscheinlichkeiten
nb_model = MultinomialNB(alpha=1.8)
nb_model.fit(X_train_tfidf, y_train)
probs = nb_model.predict_proba(X_val_tfidf)
# Bewerten Sie den Klassifikator
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):
    """
    - Entitätserwähnungen entfernen (z.B. '@united')
    - Fehler korrigieren (z.B. '&' zu '&')
    @param text (str): eine zu verarbeitende Zeichenkette.
    @Rückgabe text (Str): die verarbeitete Zeichenkette.
    """
    # Entfernen '@name'
    text = re.sub(r'(@.*?)[s]', ' ', text)
    # Ersetzen Sie '&' durch '&'
    text = re.sub(r'&', '&', text)
    # Nachfolgende Leerzeichen entfernen
    text = re.sub(r's+', ' ' ', text).strip()
    Text zurückgeben

In [0]:

# Drucksatz 0
print('Original: ', X[0])
print('Bearbeitet: ', text_Vorverarbeitung(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
# Laden des BERT-Tokenizers
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased', do_lower_case=True)
# Erstellen Sie eine Funktion zur Tokenisierung einer Menge von Texten
def preprocessing_for_bert(data):
    """Erforderliche Vorverarbeitungsschritte für vortrainiertes BERT durchführen.
    @param data (np.array): Array der zu verarbeitenden Texte.
    @return input_ids (torch.Tensor): Tensor von Token-IDs, die einem Modell zugeführt werden.
    @return attention_masks (torch.Tensor): Tensor von Indizes, die angeben, welche
                  Token vom Modell beachtet werden sollen.
    """
    # Leere Listen zum Speichern von Ausgaben erstellen
    input_ids = []
    attention_masks = []
    # Für jeden Satz...
    für gesendet in Daten:
        # kodieren_plus wird:
        # (1) Tokenisierung des Satzes
        # (2) Hinzufügen der [CLS] und [SEP] Token am Anfang und Ende
        # (3) Abschneiden/Pad Satz auf maximale Länge
        # (4) Zuordnen von Token zu ihren IDs
        # (5) Aufmerksamkeitsmaske erstellen
        # (6) Rückgabe eines Wörterbuchs der Ausgaben
        encoded_sent = tokenizer.encode_plus(
            text=text_preprocessing(sent), # Satz vorverarbeiten
            add_special_tokens=True, # Hinzufügen [CLS] und [SEP]
            max_length=MAX_LEN, # Maximale Länge zum Abschneiden/Pad
            pad_to_max_length=True, # Satz auf maximale Länge auffüllen
            #return_tensors='pt', # Rückgabe des PyTorch-Tensors
            return_attention_mask=True # Rückgabe der Aufmerksamkeitsmaske
            )
        # Fügen Sie die Ausgaben zu den Listen hinzu
        input_ids.append(encoded_sent.get('input_ids'))
        attention_masks.append(encoded_sent.get('attention_mask'))
    # Listen in Tensoren umwandeln
    input_ids = torch.tensor(input_ids)
    aufmerksamkeit_masken = torch.tensor(aufmerksamkeit_masken)
    return input_ids, attention_masks

Vor der Tokenisierung müssen wir die maximale Länge unserer Sätze angeben.

In [0]:

# Verkettung von Trainingsdaten und Testdaten
all_tweets = np.concatenate([data.tweet.values, test_data.tweet.values])
# Verschlüsseln der verketteten Daten
encoded_tweets = [tokenizer.encode(sent, add_special_tokens=True) for sent in all_tweets]
# Ermitteln der maximalen Länge
max_len = max([len(sent) for sent in encoded_tweets])
print('Maximale Länge: ', max_len)
Maximale Länge: 68

Lassen Sie uns nun unsere Daten tokenisieren.

In [0]:

# Angeben MAX_LEN
MAX_LEN = 64
# Satz 0 und seine kodierten Token-IDs ausgeben
token_ids = list(preprocessing_for_bert([X[0]]))[0].squeeze().numpy())
print('Original: ', X[0])
print('Token-IDs: ', token_ids)
# Funktion ausführen vorverarbeitung_für_bert auf dem Trainings- und dem Validierungsdatensatz
print('Tokenisierung der Daten...')
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]:

von torch.utils.data importieren TensorDatensatz, DataLoader, RandomSampler, SequentialSampler
# Andere Datentypen in torch.Tensor konvertieren
train_labels = torch.tensor(y_train)
val_labels = torch.tensor(y_val)
# Für die Feinabstimmung von BERT empfehlen die Autoren eine Stapelgröße von 16 oder 32.
batch_size = 32
# Erstellen Sie den DataLoader für unseren Trainingssatz
train_data = TensorDatensatz(train_inputs, train_masks, train_labels)
train_sampler = RandomSampler(train_data)
train_dataloader = DataLoader(train_data, sampler=train_sampler, batch_size=batch_size)
# Erstellen Sie den DataLoader für unseren Validierungssatz
val_data = TensorDatensatz(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]:

%%Zeit
torch importieren
import torch.nn as nn
from transformers import BertModel
# Erstellen Sie die Klasse BertClassfier
class BertClassifier(nn.Module):
    """Bert-Modell für Klassifizierungsaufgaben.
    """
    def __init__(self, freeze_bert=False):
        """
        @param bert: ein BertModel-Objekt
        @param classifier: ein torch.nn.Module Klassifikator
        @param freeze_bert (bool): setzen. Falsch zur Feinabstimmung des BERT-Modells
        """
        super(BertClassifier, self).__init__()
        # Festlegen der versteckten Größe von BERT, der versteckten Größe unseres Klassifikators und der Anzahl der Etiketten
        D_in, H, D_out = 768, 50, 2
        # BERT-Modell instanziieren
        self.bert = BertModel.from_pretrained('bert-base-uncased')
        # Instanziierung eines einschichtigen Feed-Forward-Klassifikators
        self.classifier = nn.Sequential(
            nn.Linear(D_in, H),
            nn.ReLU(),
            #nn.Dropout(0.5),
            nn.Linear(H, D_out)
        )
        # Einfrieren des BERT-Modells
        if freeze_bert:
            for param in self.bert.parameters():
                param.requires_grad = False
    def forward(self, input_ids, attention_mask):
        """
        Eingabe an BERT und den Klassifikator zur Berechnung von Logits weiterleiten.
        @param input_ids (torch.Tensor): ein Eingabe-Tensor mit Form (batch_size,
                      max_length)
        @param attention_mask (torch.Tensor): ein Tensor, der die Aufmerksamkeitsmaske enthält
                      Informationen mit Form (batch_size, max_length)
        @return logits (torch.Tensor): ein Ausgabe-Tensor mit der Form (batch_size,
                      num_labels)
        """
        # Eingabe in BERT einspeisen
        outputs = self.bert(input_ids=input_ids,
                            aufmerksamkeit_maske=aufmerksamkeit_maske)
        # Extrahieren des letzten verborgenen Zustands des Tokens [CLS] für die Klassifizierungsaufgabe
        letzter_versteckter_Zustand_cls = outputs[0][:, 0, :]
        # Eingabe in den Klassifikator zur Berechnung der Logits
        logits = self.classifier(last_hidden_state_cls)
        logits zurückgeben
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):
    """Initialisiere den Bert-Klassifikator, den Optimierer und den Lernraten-Scheduler.
    """
    # Bert-Klassifikator instanziieren
    bert_classifier = BertClassifier(freeze_bert=False)
    # Weisen Sie PyTorch an, das Modell auf der GPU auszuführen
    bert_classifier.to(Gerät)
    # Erstellen Sie den Optimierer
    optimizer = AdamW(bert_classifier.parameters(),
                      lr=5e-5, # Standard-Lernrate
                      eps=1e-8 # Standard-Epsilonwert
                      )
    # Gesamtzahl der Trainingsschritte
    total_steps = len(train_dataloader) * epochs
    # Einrichten des Schedulers für die Lernrate
    scheduler = get_linear_schedule_with_warmup(optimizer,
                                                num_warmup_steps=0, # Standardwert
                                                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) # Setzen des Seeds für die Reproduzierbarkeit
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):
    """Führt einen Vorwärtsdurchlauf auf dem trainierten BERT-Modell durch, um die Wahrscheinlichkeiten
    auf dem Testsatz vorherzusagen.
    """
    # Versetzt das Modell in den Evaluierungsmodus. Die Dropout-Schichten sind während der
    # der Testzeit deaktiviert.
    model.eval()
    all_logits = []
    # Für jede Charge in unserem Testsatz...
    for batch in test_dataloader:
        # Batch auf GPU laden
        b_input_ids, b_attn_mask = tuple(t.to(device) for t in batch)[:2]
        # Berechnen von Logits
        mit torch.no_grad():
            logits = model(b_input_ids, b_attn_mask)
        all_logits.append(logits)
    # Verkettung der Logits aus jeder Charge
    all_logits = torch.cat(all_logits, dim=0)
    # Softmax zur Berechnung der Wahrscheinlichkeiten anwenden
    probs = F.softmax(alle_logits, dim=1).cpu().numpy()
    Rückgabe probs

In [0]:

# Berechnung der vorhergesagten Wahrscheinlichkeiten für die Testmenge
probs = bert_predict(bert_classifier, val_dataloader)
# Bewerten Sie den Bert-Klassifikator
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]:

# Verketten Sie den Zugsatz und den Validierungssatz
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)
# Trainieren Sie den Bert-Klassifikator mit den gesamten Trainingsdaten
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]:

idtweet
47118654Freunde und Familie: Fliegen Sie niemals mit @JetBlue. Absolut...
197176265@DeltaAssist @rogerioad Ich hatte noch nie einen Pro...
23672Erster Flug seit Wochen. Ich zähle auf dich @Americ...
2702103263"@USAirways: You know that we can__t stay no m...
1355137@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]:

# Lauf vorverarbeitung_für_bert für die Testmenge
print('Tokenisierung der Daten...')
test_inputs, test_masks = preprocessing_for_bert(test_data.tweet)
# Erstellen Sie den DataLoader für unseren Testsatz
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]:

# Berechnung der vorhergesagten Wahrscheinlichkeiten für die Testmenge
probs = bert_predict(bert_classifier, test_dataloader)
# Abrufen der Vorhersagen aus den Wahrscheinlichkeiten
Schwellenwert = 0,9
preds = np.where(probs[:, 1] > threshold, 1, 0)
# Anzahl der Tweets, die als nicht-negativ vorhergesagt wurden
print("Anzahl der Tweets, die als nicht-negativ vorhergesagt wurden: ", 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.


Lassen Sie uns Ihre Idee besprechen

    Verwandte Beiträge

    Bereit, Ihr Geschäft aufzuladen

    LASST UNS
    TALK
    de_DEDeutsch