In diesem Artikel werden wir den Prozess der Feinabstimmung eines vorab geschulten BERT-Modells durch die Kategorisierung der Überschriften englischer Nachrichtenartikel verfolgen. Im Fall von Japanisch ist im Gegensatz zu Englisch eine morphologische Analyse erforderlich, aber der Gesamtfluss entspricht dem Inhalt dieses Artikels.
Darüber hinaus ist diese Implementierung auch die Antwort auf Frage 89 von Version 100 Sprachverarbeitung Knock 2020. Beispielantworten auf andere Fragen finden Sie unter [Knock 100 Language Processing 2020] Zusammenfassung der Antwortbeispiele in Python.
Google Colaboratory wird für die Implementierung verwendet. Ausführliche Informationen zum Einrichten und Verwenden von Google Colaboratory finden Sie in [diesem Artikel](https: // cpp-fu learning.com/python_colaboratory/). ** Wenn Sie die GPU für die Reproduktion verwenden möchten, ändern Sie den Hardwarebeschleuniger von "Laufzeit" -> "Laufzeittyp ändern" auf "GPU" und speichern Sie ihn im Voraus. ** ** ** Das Notizbuch mit den Ausführungsergebnissen ist unter [github] verfügbar (https://github.com/hana-mame/nlp100/blob/master/chapter09_89.ipynb).
Die Überschriften von Nachrichtenartikeln, die die öffentlichen Daten News Aggregator Data Set verwenden, lauten "Business", "Science and Technology" und "Entertainment". Wir werden ein Dokumentklassifizierungsmodell von BERT für Aufgaben implementieren, die in die Kategorie "Gesundheit" fallen.
Laden Sie zunächst die Zieldaten herunter.
!wget https://archive.ics.uci.edu/ml/machine-learning-databases/00359/NewsAggregatorDataset.zip
!unzip NewsAggregatorDataset.zip
#Überprüfen Sie die Anzahl der Zeilen
!wc -l ./newsCorpora.csv
Ausgabe
422937 ./newsCorpora.csv
#Überprüfen Sie die ersten 10 Zeilen
!head -10 ./newsCorpora.csv
Ausgabe
1 Fed official says weak data caused by weather, should not slow taper http://www.latimes.com/business/money/la-fi-mo-federal-reserve-plosser-stimulus-economy-20140310,0,1312750.story\?track=rss Los Angeles Times b ddUyU0VZz0BRneMioxUPQVP6sIxvM www.latimes.com 1394470370698
2 Fed's Charles Plosser sees high bar for change in pace of tapering http://www.livemint.com/Politics/H2EvwJSK2VE6OF7iK1g3PP/Feds-Charles-Plosser-sees-high-bar-for-change-in-pace-of-ta.html Livemint b ddUyU0VZz0BRneMioxUPQVP6sIxvM www.livemint.com 1394470371207
3 US open: Stocks fall after Fed official hints at accelerated tapering http://www.ifamagazine.com/news/us-open-stocks-fall-after-fed-official-hints-at-accelerated-tapering-294436 IFA Magazine b ddUyU0VZz0BRneMioxUPQVP6sIxvM www.ifamagazine.com 1394470371550
4 Fed risks falling 'behind the curve', Charles Plosser says http://www.ifamagazine.com/news/fed-risks-falling-behind-the-curve-charles-plosser-says-294430 IFA Magazine b ddUyU0VZz0BRneMioxUPQVP6sIxvM www.ifamagazine.com 1394470371793
5 Fed's Plosser: Nasty Weather Has Curbed Job Growth http://www.moneynews.com/Economy/federal-reserve-charles-plosser-weather-job-growth/2014/03/10/id/557011 Moneynews b ddUyU0VZz0BRneMioxUPQVP6sIxvM www.moneynews.com 1394470372027
6 Plosser: Fed May Have to Accelerate Tapering Pace http://www.nasdaq.com/article/plosser-fed-may-have-to-accelerate-tapering-pace-20140310-00371 NASDAQ b ddUyU0VZz0BRneMioxUPQVP6sIxvM www.nasdaq.com 1394470372212
7 Fed's Plosser: Taper pace may be too slow http://www.marketwatch.com/story/feds-plosser-taper-pace-may-be-too-slow-2014-03-10\?reflink=MW_news_stmp MarketWatch b ddUyU0VZz0BRneMioxUPQVP6sIxvM www.marketwatch.com 1394470372405
8 Fed's Plosser expects US unemployment to fall to 6.2% by the end of 2014 http://www.fxstreet.com/news/forex-news/article.aspx\?storyid=23285020-b1b5-47ed-a8c4-96124bb91a39 FXstreet.com b ddUyU0VZz0BRneMioxUPQVP6sIxvM www.fxstreet.com 1394470372615
9 US jobs growth last month hit by weather:Fed President Charles Plosser http://economictimes.indiatimes.com/news/international/business/us-jobs-growth-last-month-hit-by-weatherfed-president-charles-plosser/articleshow/31788000.cms Economic Times b ddUyU0VZz0BRneMioxUPQVP6sIxvM economictimes.indiatimes.com 1394470372792
10 ECB unlikely to end sterilisation of SMP purchases - traders http://www.iii.co.uk/news-opinion/reuters/news/152615 Interactive Investor b dPhGU51DcrolUIMxbRm0InaHGA2XM www.iii.co.uk 1394470501265
#Doppelte Anführungszeichen durch einfache Anführungszeichen ersetzt, um Fehler beim Lesen zu vermeiden
!sed -e 's/"/'\''/g' ./newsCorpora.csv > ./newsCorpora_re.csv
Lesen Sie es als Datenrahmen, extrahieren Sie nur die Fälle, in denen die Informationsquelle (PUBLISHER) Reuters, Huffington Post, Businessweek, Contactmusic.com, Daily Mail ist, und teilen Sie es dann in Trainingsdaten, Verifizierungsdaten und Bewertungsdaten auf.
import pandas as pd
from sklearn.model_selection import train_test_split
#Daten lesen
df = pd.read_csv('./newsCorpora_re.csv', header=None, sep='\t', names=['ID', 'TITLE', 'URL', 'PUBLISHER', 'CATEGORY', 'STORY', 'HOSTNAME', 'TIMESTAMP'])
#Datenextraktion
df = df.loc[df['PUBLISHER'].isin(['Reuters', 'Huffington Post', 'Businessweek', 'Contactmusic.com', 'Daily Mail']), ['TITLE', 'CATEGORY']]
#Datenaufteilung
train, valid_test = train_test_split(df, test_size=0.2, shuffle=True, random_state=123, stratify=df['CATEGORY'])
valid, test = train_test_split(valid_test, test_size=0.5, shuffle=True, random_state=123, stratify=valid_test['CATEGORY'])
train.reset_index(drop=True, inplace=True)
valid.reset_index(drop=True, inplace=True)
test.reset_index(drop=True, inplace=True)
print(train.head())
Ausgabe
TITLE CATEGORY
0 REFILE-UPDATE 1-European car sales up for sixt... b
1 Amazon Plans to Fight FTC Over Mobile-App Purc... t
2 Kids Still Get Codeine In Emergency Rooms Desp... m
3 What On Earth Happened Between Solange And Jay... e
4 NATO Missile Defense Is Flight Tested Over Hawaii b
#Bestätigung der Anzahl der Fälle
print('[Lerndaten]')
print(train['CATEGORY'].value_counts())
print('[Verifizierungsdaten]')
print(valid['CATEGORY'].value_counts())
print('[Bewertungsdaten]')
print(test['CATEGORY'].value_counts())
Ausgabe
[Lerndaten]
b 4501
e 4235
t 1220
m 728
Name: CATEGORY, dtype: int64
[Verifizierungsdaten]
b 563
e 529
t 153
m 91
Name: CATEGORY, dtype: int64
[Bewertungsdaten]
b 563
e 530
t 152
m 91
Name: CATEGORY, dtype: int64
(b: Wirtschaft, e: Unterhaltung, t: Wissenschaft und Technologie, m: Gesundheit)
Installieren Sie die Bibliothek `transformers```, um das BERT-Modell zu verwenden. Durch
Transformatoren``
können viele vorgefertigte Modelle neben BERT sehr einfach mit Funktionscode verwendet werden.
!pip install transformers
Importieren Sie die Bibliotheken, die zum Trainieren und Bewerten Ihres Modells erforderlich sind.
import numpy as np
import transformers
import torch
from torch.utils.data import Dataset, DataLoader
from transformers import BertTokenizer, BertModel
from torch import optim
from torch import cuda
import time
from matplotlib import pyplot as plt
Als nächstes formen Sie die Daten in ein Formular, das in das Modell eingegeben werden kann.
Definieren Sie zunächst eine Klasse, um ein `Dataset``` zu erstellen, das den Merkmalsvektor und den Beschriftungsvektor zusammenhält, was in PyTorch häufig verwendet wird. Durch Übergeben von
Tokenizer``` an diese Klasse ist es möglich, den Eingabetext vorzuverarbeiten, ihn auf die angegebene längste Sequenzlänge aufzufüllen und ihn dann in eine Wort-ID zu konvertieren. Der ``
Tokenizer selbst, in dem die gesamte Verarbeitung für BERT geschrieben ist, wird jedoch später über die` `` Transformatoren
abgerufen. Was Sie also in der Klasse benötigen, ist der `Tokenizer
. Nur der Prozess der Übergabe an `und der Prozess des Empfangs des Ergebnisses.
#Datensatzdefinition
class CreateDataset(Dataset):
def __init__(self, X, y, tokenizer, max_len):
self.X = X
self.y = y
self.tokenizer = tokenizer
self.max_len = max_len
def __len__(self): # len(Dataset)Geben Sie den Wert an, mit dem zurückgegeben werden soll
return len(self.y)
def __getitem__(self, index): # Dataset[index]Geben Sie den Wert an, mit dem zurückgegeben werden soll
text = self.X[index]
inputs = self.tokenizer.encode_plus(
text,
add_special_tokens=True,
max_length=self.max_len,
pad_to_max_length=True
)
ids = inputs['input_ids']
mask = inputs['attention_mask']
return {
'ids': torch.LongTensor(ids),
'mask': torch.LongTensor(mask),
'labels': torch.Tensor(self.y[index])
}
Erstellen Sie einen `` `Datensatz``` mit den oben genannten. Das BERT, das als englische Version des vorab trainierten Modells verwendet werden kann, ist LARGE, eine Konfiguration mit höchster Genauigkeit, BASE mit weniger Parametern und jeweils 4 davon, nur in Kleinbuchstaben (ohne Gehäuse) und im Großbuchstaben (mit Groß- / Kleinschreibung). Es gibt ein Muster. Dieses Mal verwenden wir BASE's Uncased, das Sie ganz einfach ausprobieren können.
#Richtiges Etikett eins-Heiß
y_train = pd.get_dummies(train, columns=['CATEGORY'])[['CATEGORY_b', 'CATEGORY_e', 'CATEGORY_t', 'CATEGORY_m']].values
y_valid = pd.get_dummies(valid, columns=['CATEGORY'])[['CATEGORY_b', 'CATEGORY_e', 'CATEGORY_t', 'CATEGORY_m']].values
y_test = pd.get_dummies(test, columns=['CATEGORY'])[['CATEGORY_b', 'CATEGORY_e', 'CATEGORY_t', 'CATEGORY_m']].values
#Erstellen eines Datensatzes
max_len = 20
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
dataset_train = CreateDataset(train['TITLE'], y_train, tokenizer, max_len)
dataset_valid = CreateDataset(valid['TITLE'], y_valid, tokenizer, max_len)
dataset_test = CreateDataset(test['TITLE'], y_test, tokenizer, max_len)
for var in dataset_train[0]:
print(f'{var}: {dataset_train[0][var]}')
Ausgabe
ids: tensor([ 101, 25416, 9463, 1011, 10651, 1015, 1011, 2647, 2482, 4341,
2039, 2005, 4369, 3204, 2004, 18730, 8980, 102, 0, 0])
mask: tensor([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0])
labels: tensor([1., 0., 0., 0.])
Die Informationen des ersten Satzes werden ausgegeben.
Sie können sehen, dass die Eingabezeichenfolge als `ids``` in eine ID-Reihe konvertiert wurde. In BERT werden während des Konvertierungsprozesses am Anfang und am Ende des ursprünglichen Satzes spezielle Trennzeichen [CLS] und [SEP] eingefügt, sodass sie auch "101" und "102" sind. In der Serie enthalten als `.
`0``` steht für Polsterung.
Die richtigen Antwortetiketten werden auch im One-Hot-Format als "Etiketten" gespeichert.
Behalten Sie außerdem die "Maske" bei, die die Polsterposition darstellt, damit sie während des Trainings zusammen mit den "IDs" an das Modell übergeben werden kann.
Definieren Sie als Nächstes das Netzwerk.
transfomers
Durch die Verwendung des gesamten Bert-Teilsbertmodel
Es kann mit ausgedrückt werden. Um die Klassifizierungsaufgabe zu erfüllen, definieren Sie ein Dropout, das den Ausgabevektor von bert und eine vollständig verbundene Ebene empfängt, und fertig.
#Definition des BERT-Klassifizierungsmodells
class BERTClass(torch.nn.Module):
def __init__(self, drop_rate, otuput_size):
super().__init__()
self.bert = BertModel.from_pretrained('bert-base-uncased')
self.drop = torch.nn.Dropout(drop_rate)
self.fc = torch.nn.Linear(768, otuput_size) #Geben Sie 768 Dimensionen gemäß der Ausgabe von BERT an
def forward(self, ids, mask):
_, out = self.bert(ids, attention_mask=mask)
out = self.fc(self.drop(out))
return out
Jetzt, da Sie ein Netzwerk mit `Dataset``` haben, ist es Zeit, Ihre übliche Lernschleife zu erstellen. Hier wird eine Reihe von Flüssen als
`train_model``` Funktion definiert. Die Bedeutung der angezeigten Komponenten finden Sie im Ablauf des Problems im Artikel [Sprachverarbeitung 100 Knock 2020] Kapitel 8: Neuronales Netz. Bitte beachten Sie die Erklärung dazu.
def calculate_loss_and_accuracy(model, criterion, loader, device):
"""Berechnen Sie den Verlust / die richtige Antwortrate"""
model.eval()
loss = 0.0
total = 0
correct = 0
with torch.no_grad():
for data in loader:
#Gerätespezifikation
ids = data['ids'].to(device)
mask = data['mask'].to(device)
labels = data['labels'].to(device)
#Vorwärtsausbreitung
outputs = model.forward(ids, mask)
#Verlustberechnung
loss += criterion(outputs, labels).item()
#Richtige Berechnung der Antwortrate
pred = torch.argmax(outputs, dim=-1).cpu().numpy() #Voraussichtliches Etikettenarray für die Chargengrößenlänge
labels = torch.argmax(labels, dim=-1).cpu().numpy() #Batch-Größenlänge korrektes Etikettenarray
total += len(labels)
correct += (pred == labels).sum().item()
return loss / len(loader), correct / total
def train_model(dataset_train, dataset_valid, batch_size, model, criterion, optimizer, num_epochs, device=None):
"""Führt ein Modelltraining durch und gibt ein Protokoll mit Verlust / korrekter Antwortrate zurück"""
#Gerätespezifikation
model.to(device)
#Erstellen eines Datenladers
dataloader_train = DataLoader(dataset_train, batch_size=batch_size, shuffle=True)
dataloader_valid = DataLoader(dataset_valid, batch_size=len(dataset_valid), shuffle=False)
#Lernen
log_train = []
log_valid = []
for epoch in range(num_epochs):
#Startzeit aufzeichnen
s_time = time.time()
#Auf Trainingsmodus einstellen
model.train()
for data in dataloader_train:
#Gerätespezifikation
ids = data['ids'].to(device)
mask = data['mask'].to(device)
labels = data['labels'].to(device)
#Gradient auf Null initialisieren
optimizer.zero_grad()
#Vorwärtsausbreitung+Fehler bei der Weitergabe+Gewichtsaktualisierung
outputs = model.forward(ids, mask)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
#Verlustberechnung und korrekte Rücklaufquote
loss_train, acc_train = calculate_loss_and_accuracy(model, criterion, dataloader_train, device)
loss_valid, acc_valid = calculate_loss_and_accuracy(model, criterion, dataloader_valid, device)
log_train.append([loss_train, acc_train])
log_valid.append([loss_valid, acc_valid])
#Checkpoint speichern
torch.save({'epoch': epoch, 'model_state_dict': model.state_dict(), 'optimizer_state_dict': optimizer.state_dict()}, f'checkpoint{epoch + 1}.pt')
#Endzeit aufzeichnen
e_time = time.time()
#Ausgabeprotokoll
print(f'epoch: {epoch + 1}, loss_train: {loss_train:.4f}, accuracy_train: {acc_train:.4f}, loss_valid: {loss_valid:.4f}, accuracy_valid: {acc_valid:.4f}, {(e_time - s_time):.4f}sec')
return {'train': log_train, 'valid': log_valid}
Stellen Sie die Parameter ein und führen Sie eine Feinabstimmung durch.
#Parametereinstellung
DROP_RATE = 0.4
OUTPUT_SIZE = 4
BATCH_SIZE = 32
NUM_EPOCHS = 4
LEARNING_RATE = 2e-5
#Modelldefinition
model = BERTClass(DROP_RATE, OUTPUT_SIZE)
#Definition der Verlustfunktion
criterion = torch.nn.BCEWithLogitsLoss()
#Optimierungsdefinition
optimizer = torch.optim.AdamW(params=model.parameters(), lr=LEARNING_RATE)
#Gerätespezifikation
device = 'cuda' if cuda.is_available() else 'cpu'
#Modelllernen
log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS, device=device)
Ausgabe
epoch: 1, loss_train: 0.0859, accuracy_train: 0.9516, loss_valid: 0.1142, accuracy_valid: 0.9229, 49.9137sec
epoch: 2, loss_train: 0.0448, accuracy_train: 0.9766, loss_valid: 0.1046, accuracy_valid: 0.9259, 49.7376sec
epoch: 3, loss_train: 0.0316, accuracy_train: 0.9831, loss_valid: 0.1082, accuracy_valid: 0.9266, 49.5454sec
epoch: 4, loss_train: 0.0170, accuracy_train: 0.9932, loss_valid: 0.1179, accuracy_valid: 0.9289, 49.4525sec
Überprüfen Sie das Ergebnis.
#Protokollvisualisierung
x_axis = [x for x in range(1, len(log['train']) + 1)]
fig, ax = plt.subplots(1, 2, figsize=(15, 5))
ax[0].plot(x_axis, np.array(log['train']).T[0], label='train')
ax[0].plot(x_axis, np.array(log['valid']).T[0], label='valid')
ax[0].set_xlabel('epoch')
ax[0].set_ylabel('loss')
ax[0].legend()
ax[1].plot(x_axis, np.array(log['train']).T[1], label='train')
ax[1].plot(x_axis, np.array(log['valid']).T[1], label='valid')
ax[1].set_xlabel('epoch')
ax[1].set_ylabel('accuracy')
ax[1].legend()
plt.show()
#Berechnung der richtigen Antwortrate
def calculate_accuracy(model, dataset, device):
#Erstellen eines Datenladers
loader = DataLoader(dataset, batch_size=len(dataset), shuffle=False)
model.eval()
total = 0
correct = 0
with torch.no_grad():
for data in loader:
#Gerätespezifikation
ids = data['ids'].to(device)
mask = data['mask'].to(device)
labels = data['labels'].to(device)
#Vorwärtsausbreitung+Erhalten Sie den vorhergesagten Wert+Zählen der Anzahl der richtigen Antworten
outputs = model.forward(ids, mask)
pred = torch.argmax(outputs, dim=-1).cpu().numpy()
labels = torch.argmax(labels, dim=-1).cpu().numpy()
total += len(labels)
correct += (pred == labels).sum().item()
return correct / total
print(f'Richtige Antwortrate (Lerndaten):{calculate_accuracy(model, dataset_train, device):.3f}')
print(f'Richtige Antwortrate (Verifizierungsdaten):{calculate_accuracy(model, dataset_valid, device):.3f}')
print(f'Richtige Antwortrate (Bewertungsdaten):{calculate_accuracy(model, dataset_test, device):.3f}')
Ausgabe
Richtige Antwortrate (Lerndaten): 0.993
Richtige Antwortrate (Verifizierungsdaten): 0.929
Richtige Antwortrate (Bewertungsdaten): 0.948
Die korrekte Antwortrate in den Bewertungsdaten lag bei etwa 95%.
Normalerweise denke ich, dass es häufig vorkommt, dass Parameter wie die Festlegung von Gewichten für jede BERT-Schicht und die Lernrate angepasst werden, während die Genauigkeit der Verifizierungsdaten überprüft wird. Diesmal wurden die Parameter festgelegt, aber die Genauigkeit war relativ hoch, und das Ergebnis zeigte die Stärke des Vorlernens.
transformers BERT (offiziell) BERT: Vorschulung von tiefen bidirektionalen Transformatoren für das Sprachverständnis, Devlin, J. et al. (2018) (Originalartikel) [Sprachverarbeitung 100 Knock 2020] Zusammenfassung der Antwortbeispiele von Python
Recommended Posts