Neulich wurde 100 Language Processing Knock 2020 veröffentlicht. Ich selbst arbeite erst seit einem Jahr an natürlicher Sprache und kenne die Details nicht, aber ich werde alle Probleme lösen und veröffentlichen, um meine technischen Fähigkeiten zu verbessern.
Alle müssen auf dem Jupiter-Notizbuch ausgeführt werden, und die Einschränkungen der Problemstellung können bequem verletzt werden. Der Quellcode ist auch auf Github. Ja.
Kapitel 8 ist hier.
Die Umgebung ist Python 3.8.2 und Ubuntu 18.04.
Wie Kapitel 8 und verwenden Sie PyTorch.
Ich möchte den Wörtern in den in Frage 51 erstellten Trainingsdaten eine eindeutige ID-Nummer geben. Geben Sie ID-Nummern für Wörter ein, die mehr als einmal in den Trainingsdaten vorkommen, z. B. "1" für die häufigsten Wörter in den Trainingsdaten, "2" für die zweithäufigsten Wörter usw. Implementieren Sie dann eine Funktion, die eine Zeichenfolge mit ID-Nummern für eine bestimmte Wortzeichenfolge zurückgibt. Alle ID-Nummern von Wörtern, die weniger als zweimal vorkommen, sollten jedoch "0" sein.
Code
import re
import spacy
import torch
Bereiten Sie das Raummodell und das Etikett vor.
Code
nlp = spacy.load('en')
categories = ['b', 't', 'e', 'm']
category_names = ['business', 'science and technology', 'entertainment', 'health']
Lesen Sie die Datei und markieren Sie sie mit Spacy.
Code
def tokenize(x):
x = re.sub(r'\s+', ' ', x)
x = nlp.make_doc(x)
x = [d.text for d in x]
return x
def read_feature_dataset(filename):
with open(filename) as f:
dataset = f.read().splitlines()
dataset = [line.split('\t') for line in dataset]
dataset_t = [categories.index(line[0]) for line in dataset]
dataset_x = [tokenize(line[1]) for line in dataset]
return dataset_x, torch.tensor(dataset_t, dtype=torch.long)
Code
train_x, train_t = read_feature_dataset('data/train.txt')
valid_x, valid_t = read_feature_dataset('data/valid.txt')
test_x, test_t = read_feature_dataset('data/test.txt')
Extrahieren Sie den Wortschatz. Es werden nur Wörter ausgewählt, die mehr als einmal vorkommen.
Code
from collections import Counter
Code
counter = Counter([
x
for sent in train_x
for x in sent
])
vocab_in_train = [
token
for token, freq in counter.most_common()
if freq > 1
]
len(vocab_in_train)
Ausgabe
9700
Konvertiert eine Wortzeichenfolge in eine Zeichenfolge mit ID-Nummern.
Code
vocab_list = ['[UNK]'] + vocab_in_train
vocab_dict = {x:n for n, x in enumerate(vocab_list)}
Code
def sent_to_ids(sent):
return torch.tensor([vocab_dict[x if x in vocab_dict else '[UNK]'] for x in sent], dtype=torch.long)
Lassen Sie uns den ersten Satz der Trainingsdaten tokenisieren.
Code
print(train_x[0])
print(sent_to_ids(train_x[0]))
Ausgabe
['Kathleen', 'Sebelius', "'", 'LGBT', 'legacy']
tensor([ 0, 0, 2, 2648, 0])
Konvertieren Sie es in eine Spalte mit ID-Nummern.
Code
def dataset_to_ids(dataset):
return [sent_to_ids(x) for x in dataset]
Code
train_s = dataset_to_ids(train_x)
valid_s = dataset_to_ids(valid_x)
test_s = dataset_to_ids(test_x)
train_s[:3]
Ausgabe
[tensor([ 0, 0, 2, 2648, 0]),
tensor([ 9, 6740, 1445, 2076, 583, 10, 547, 32, 51, 873, 6741]),
tensor([ 0, 205, 4198, 315, 1899, 1232, 0])]
Es gibt eine Wortzeichenfolge $ \ boldsymbol {x} = (x_1, x_2, \ dots, x_T) $, die durch eine ID-Nummer dargestellt wird. $ T $ ist jedoch die Länge der Wortzeichenfolge, und $ x_t \ in \ mathbb {R} ^ {V} $ ist die One-Hot-Notation der Wort-ID-Nummer ($ V $ ist die Gesamtzahl der Wörter). Implementieren Sie unter Verwendung eines wiederkehrenden neuronalen Netzwerks (RNN) die folgende Gleichung als Modell für die Vorhersage der Kategorie $ y $ aus der Wortfolge $ \ boldsymbol {x} $. $ \overrightarrow h_0 = 0, \ \overrightarrow h_t = {\rm \overrightarrow{RNN}}(\mathrm{emb}(x_t), \overrightarrow h_{t-1}), \ y = {\rm softmax}(W^{(yh)} \overrightarrow h_T + b^{(y)}) $ $ \ Mathrm {emb} (x) \ in \ mathbb {R} ^ {d_w} $ ist jedoch eine Worteinbettung (eine Funktion, die ein Wort von einer One-Hot-Notation in einen Wortvektor konvertiert), $ \ overridearrow h_t \ in \ mathbb {R} ^ {d_h} $ ist der verborgene Zustandsvektor zum Zeitpunkt $ t $, $ {\ rm \ overridearrow {RNN}} (x, h) $ stammt aus der Eingabe $ x $ und dem verborgenen Zustand $ h $ zum vorherigen Zeitpunkt Die RNN-Einheit, die den nächsten Zustand berechnet, $ W ^ {(yh)} \ in \ mathbb {R} ^ {L \ times d_h} $, ist die Matrix zur Vorhersage der Kategorie aus dem verborgenen Zustandsvektor $ b ^ {(y) )} \ in \ mathbb {R} ^ {L} $ ist ein Bias-Term ($ d_w, d_h, L $ sind die Anzahl der Worteinbettungsdimensionen, die Anzahl der versteckten Zustandsvektordimensionen bzw. die Anzahl der Beschriftungen). Die RNN-Einheit $ {\ rm \ overrightarrow {RNN}} (x, h) $ kann verschiedene Konfigurationen haben, und die folgende Gleichung ist ein typisches Beispiel. $ {\rm \overrightarrow{RNN}}(x,h) = g(W^{(hx)} x + W^{(hh)}h + b^{(h)}) $ Jedoch $ W ^ {(hx)} \ in \ mathbb {R} ^ {d_h \ times d_w}, W ^ {(hh)} \ in \ mathbb {R} ^ {d_h \ times d_h}, b ^ {(h)} \ in \ mathbb {R} ^ {d_h} $ ist der Parameter der RNN-Einheit und $ g $ ist die Aktivierungsfunktion (z. B. $ \ tanh $ und ReLU). Beachten Sie, dass dieses Problem die Parameter nicht trainiert, sondern nur die Berechnung von $ y $ mit den zufällig initialisierten Parametern erfordert. Hyperparameter wie die Anzahl der Dimensionen sollten auf geeignete Werte wie $ d_w = 300, d_h = 50 $ eingestellt werden (dasselbe gilt für die folgenden Probleme).
Im Gegensatz zu Kapitel 8 unterscheidet sich die Länge der Eingabedaten je nach Satz. Verwenden Sie verschiedene Dinge in torch.nn.utils.rnn
, um das Ende der Reihe variabler Länge aufzufüllen, damit es behandelt werden kann.
Code
import random as rd
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.nn.utils.rnn import pad_sequence as pad
from torch.nn.utils.rnn import pack_padded_sequence as pack
from torch.nn.utils.rnn import pad_packed_sequence as unpack
Erstellen Sie eine "Dataset" -Klasse, die das Dataset enthält. Zusätzlich zu der Eingabeanweisung "Quelle" und der Zielbezeichnung "Ziel" hat sie die Eingabeanweisungslängen "Längen" als Elemente.
Code
class Dataset(torch.utils.data.Dataset):
def __init__(self, source, target):
self.source = source
self.target = target
self.lengths = torch.tensor([len(x) for x in source])
self.size = len(source)
def __len__(self):
return self.size
def __getitem__(self, index):
return {
'src':self.source[index],
'trg':self.target[index],
'lengths':self.lengths[index],
}
def collate(self, xs):
return {
'src':pad([x['src'] for x in xs]),
'trg':torch.stack([x['trg'] for x in xs], dim=-1),
'lengths':torch.stack([x['lengths'] for x in xs], dim=-1)
}
Bereiten Sie den Datensatz vor.
Code
train_dataset = Dataset(train_s, train_t)
valid_dataset = Dataset(valid_s, valid_t)
test_dataset = Dataset(test_s, test_t)
Definieren Sie den gleichen "Sampler" wie in Kapitel 8.
Code
class Sampler(torch.utils.data.Sampler):
def __init__(self, dataset, width, shuffle = False):
self.dataset = dataset
self.width = width
self.shuffle = shuffle
if not shuffle:
self.indices = torch.arange(len(dataset))
def __iter__(self):
if self.shuffle:
self.indices = torch.randperm(len(self.dataset))
index = 0
while index < len(self.dataset):
yield self.indices[index : index + self.width]
index += self.width
Da es praktisch ist, die Polsterung zu verpacken, wenn die Serien in der Charge in absteigender Reihenfolge sind, definieren wir einen Probenehmer, der einen solchen Vertrag erfüllt.
Alles, was Sie tun müssen, ist, die Indizes im Voraus in absteigender Reihenfolge der Länge zu sortieren und sie von vorne in Stapel zu laden.
Code
class DescendingSampler(Sampler):
def __init__(self, dataset, width, shuffle = False):
assert not shuffle
super().__init__(dataset, width, shuffle)
self.indices = self.indices[self.dataset.lengths[self.indices].argsort(descending=True)]
Während des Trainings gilt: Je weniger Polsterung in der Charge, desto weniger unnötige Berechnungen und desto schnelleres Lernen. Daher werden wir eine solche Verpackungsmethode implementieren. In den beiden obigen Beispielen wird der Index durch die Anzahl der Stapel getrennt, aber die Anzahl der Fälle im Stapel ist nicht immer konstant, da das nächste untergeordnete Element durch die maximale Anzahl der Token im Stapel getrennt ist.
Code
class MaxTokensSampler(Sampler):
def __iter__(self):
self.indices = torch.randperm(len(self.dataset))
self.indices = self.indices[self.dataset.lengths[self.indices].argsort(descending=True)]
for batch in self.generate_batches():
yield batch
def generate_batches(self):
batches = []
batch = []
acc = 0
max_len = 0
for index in self.indices:
acc += 1
this_len = self.dataset.lengths[index]
max_len = max(max_len, this_len)
if acc * max_len > self.width:
batches.append(batch)
batch = [index]
acc = 1
max_len = this_len
else:
batch.append(index)
if batch != []:
batches.append(batch)
rd.shuffle(batches)
return batches
Bereiten Sie eine Funktion zum Erstellen von "DataLoader" vor.
Code
def gen_loader(dataset, width, sampler=Sampler, shuffle=False, num_workers=8):
return torch.utils.data.DataLoader(
dataset,
batch_sampler = sampler(dataset, width, shuffle),
collate_fn = dataset.collate,
num_workers = num_workers,
)
def gen_descending_loader(dataset, width, num_workers=0):
return gen_loader(dataset, width, sampler = DescendingSampler, shuffle = False, num_workers = num_workers)
def gen_maxtokens_loader(dataset, width, num_workers=0):
return gen_loader(dataset, width, sampler = MaxTokensSampler, shuffle = True, num_workers = num_workers)
Definiert einen einschichtigen unidirektionalen LSTM-Klassifizierer. Die Länge jeder Anweisung ist erforderlich, wenn der aufgefüllte Tensor in die Zusammenstellung des Datensatzes gepackt wird.
Code
class LSTMClassifier(nn.Module):
def __init__(self, v_size, e_size, h_size, c_size, dropout=0.2):
super().__init__()
self.embed = nn.Embedding(v_size, e_size)
self.rnn = nn.LSTM(e_size, h_size, num_layers = 1)
self.out = nn.Linear(h_size, c_size)
self.dropout = nn.Dropout(dropout)
self.embed.weight.data.uniform_(-0.1, 0.1)
for name, param in self.rnn.named_parameters():
if 'weight' in name or 'bias' in name:
param.data.uniform_(-0.1, 0.1)
self.out.weight.data.uniform_(-0.1, 0.1)
def forward(self, batch, h=None):
x = self.embed(batch['src'])
x = pack(x, batch['lengths'])
x, (h, c) = self.rnn(x, h)
h = self.out(h)
return h.squeeze(0)
Ich werde vorhersagen.
Code
model = LSTMClassifier(len(vocab_dict), 300, 50, 4)
loader = gen_loader(test_dataset, 10, DescendingSampler, False)
model(iter(loader).next()).argmax(dim=-1)
Ausgabe
tensor([1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
Lernen Sie das in Aufgabe 81 konstruierte Modell mit der SGD-Methode (Stochastic Gradient Descent). Trainieren Sie das Modell, während Sie den Verlust und die richtige Antwortrate in den Trainingsdaten sowie den Verlust und die richtige Antwortrate in den Bewertungsdaten anzeigen, und beenden Sie den Vorgang mit einem geeigneten Standard (z. B. 10 Epochen).
Alles was Sie tun müssen, ist Aufgabe und Trainer zu definieren und das Training durchzuführen.
Code
class Task:
def __init__(self):
self.criterion = nn.CrossEntropyLoss()
def train_step(self, model, batch):
model.zero_grad()
loss = self.criterion(model(batch), batch['trg'])
loss.backward()
return loss.item()
def valid_step(self, model, batch):
with torch.no_grad():
loss = self.criterion(model(batch), batch['trg'])
return loss.item()
Code
class Trainer:
def __init__(self, model, loaders, task, optimizer, max_iter, device = None):
self.model = model
self.model.to(device)
self.train_loader, self.valid_loader = loaders
self.task = task
self.optimizer = optimizer
self.max_iter = max_iter
self.device = device
def send(self, batch):
for key in batch:
batch[key] = batch[key].to(self.device)
return batch
def train_epoch(self):
self.model.train()
acc = 0
for n, batch in enumerate(self.train_loader):
batch = self.send(batch)
acc += self.task.train_step(self.model, batch)
self.optimizer.step()
return acc / n
def valid_epoch(self):
self.model.eval()
acc = 0
for n, batch in enumerate(self.valid_loader):
batch = self.send(batch)
acc += self.task.valid_step(self.model, batch)
return acc / n
def train(self):
for epoch in range(self.max_iter):
train_loss = self.train_epoch()
valid_loss = self.valid_epoch()
print('epoch {}, train_loss:{:.5f}, valid_loss:{:.5f}'.format(epoch, train_loss, valid_loss))
Ich werde lernen.
Code
device = torch.device('cuda')
model = LSTMClassifier(len(vocab_dict), 300, 128, 4)
loaders = (
gen_loader(train_dataset, 1),
gen_loader(valid_dataset, 1),
)
task = Task()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9, nesterov=True)
trainer = Trainer(model, loaders, task, optimizer, 3, device)
trainer.train()
Ich werde versuchen vorherzusagen.
Code
import numpy as np
Code
class Predictor:
def __init__(self, model, loader, device=None):
self.model = model
self.loader = loader
self.device = device
def send(self, batch):
for key in batch:
batch[key] = batch[key].to(self.device)
return batch
def infer(self, batch):
self.model.eval()
batch = self.send(batch)
return self.model(batch).argmax(dim=-1).item()
def predict(self):
lst = []
for batch in self.loader:
lst.append(self.infer(batch))
return lst
Code
def accuracy(true, pred):
return np.mean([t == p for t, p in zip(true, pred)])
predictor = Predictor(model, gen_loader(train_dataset, 1), device)
pred = predictor.predict()
print('Richtige Antwortrate in den Trainingsdaten:', accuracy(train_t, pred))
predictor = Predictor(model, gen_loader(test_dataset, 1), device)
pred = predictor.predict()
print('Richtige Antwortrate in den Bewertungsdaten:', accuracy(test_t, pred))
Ausgabe
Richtige Antwortrate in den Trainingsdaten: 0.7592661924372894
Richtige Antwortrate in den Bewertungsdaten: 0.6384730538922155
Wenn Sie die Epoche ein wenig weiter drehen, wird sich die Genauigkeit ein wenig verbessern, aber es ist mir egal.
Ändern Sie den Code von Problem 82 so, dass Sie lernen können, indem Sie den Verlust / Gradienten für jeden $ B $ -Fall berechnen (wählen Sie den Wert von $ B $ entsprechend aus). Führen Sie das Lernen auch auf der GPU aus.
Code
model = LSTMClassifier(len(vocab_dict), 300, 128, 4)
loaders = (
gen_maxtokens_loader(train_dataset, 4000),
gen_descending_loader(valid_dataset, 128),
)
task = Task()
optimizer = optim.SGD(model.parameters(), lr=0.2, momentum=0.9, nesterov=True)
trainer = Trainer(model, loaders, task, optimizer, 10, device)
trainer.train()
Ausgabe
epoch 0, train_loss:1.22489, valid_loss:1.26302
epoch 1, train_loss:1.11631, valid_loss:1.19404
epoch 2, train_loss:1.07750, valid_loss:1.18451
epoch 3, train_loss:0.96149, valid_loss:1.06748
epoch 4, train_loss:0.81597, valid_loss:0.86547
epoch 5, train_loss:0.74748, valid_loss:0.81049
epoch 6, train_loss:0.80179, valid_loss:0.89621
epoch 7, train_loss:0.60231, valid_loss:0.78494
epoch 8, train_loss:0.52551, valid_loss:0.73272
epoch 9, train_loss:0.97286, valid_loss:1.05034
Code
predictor = Predictor(model, gen_loader(train_dataset, 1), device)
pred = predictor.predict()
print('Richtige Antwortrate in den Trainingsdaten:', accuracy(train_t, pred))
predictor = Predictor(model, gen_loader(test_dataset, 1), device)
pred = predictor.predict()
print('Richtige Antwortrate in den Bewertungsdaten:', accuracy(test_t, pred))
Ausgabe
Richtige Antwortrate in den Trainingsdaten: 0.7202358667165856
Richtige Antwortrate in den Bewertungsdaten: 0.6773952095808383
Aus irgendeinem Grund hat der Verlust zugenommen und die Genauigkeit hat abgenommen, aber lassen Sie uns stark leben, ohne uns darüber Gedanken zu machen.
Vorgelernter Wortvektor (z. B. [gelernter Wortvektor] im Google News-Datensatz (ca. 100 Milliarden Wörter)](https://drive.google.com/file/d/0B7XkCwpI5KDYNlNUTTlSS21pQmM/edit?usp=sharing) )) Initialisieren Sie das Wort, in das $ \ mathrm {emb} (x) $ eingebettet ist, und lernen Sie.
Code
from gensim.models import KeyedVectors
vectors = KeyedVectors.load_word2vec_format('data/GoogleNews-vectors-negative300.bin.gz', binary=True)
Initialisieren Sie die Worteinbettung, bevor Sie lernen.
Code
def init_embed(embed):
for i, token in enumerate(vocab_list):
if token in vectors:
embed.weight.data[i] = torch.from_numpy(vectors[token])
return embed
Code
model = LSTMClassifier(len(vocab_dict), 300, 128, 4)
init_embed(model.embed)
task = Task()
optimizer = optim.SGD(model.parameters(), lr=0.05, momentum=0.9, nesterov=True)
trainer = Trainer(model, loaders, task, optimizer, 10, device)
trainer.train()
Ausgabe
epoch 0, train_loss:1.21390, valid_loss:1.19333
epoch 1, train_loss:0.88751, valid_loss:0.74930
epoch 2, train_loss:0.57240, valid_loss:0.65822
epoch 3, train_loss:0.50240, valid_loss:0.62686
epoch 4, train_loss:0.45800, valid_loss:0.59535
epoch 5, train_loss:0.44051, valid_loss:0.55849
epoch 6, train_loss:0.38251, valid_loss:0.51837
epoch 7, train_loss:0.35731, valid_loss:0.47709
epoch 8, train_loss:0.30278, valid_loss:0.43797
epoch 9, train_loss:0.25518, valid_loss:0.41287
Code
predictor = Predictor(model, gen_loader(train_dataset, 1), device)
pred = predictor.predict()
print('Richtige Antwortrate in den Trainingsdaten:', accuracy(train_t, pred))
predictor = Predictor(model, gen_loader(test_dataset, 1), device)
pred = predictor.predict()
print('Richtige Antwortrate in den Bewertungsdaten:', accuracy(test_t, pred))
Ausgabe
Richtige Antwortrate in den Trainingsdaten: 0.925028079371022
Richtige Antwortrate in den Bewertungsdaten: 0.8839820359281437
Codieren Sie den Eingabetext mit Vorwärts- und Rückwärts-RNNs und trainieren Sie das Modell. $ \overleftarrow h_{T+1} = 0, \ \overleftarrow h_t = {\rm \overleftarrow{RNN}}(\mathrm{emb}(x_t), \overleftarrow h_{t+1}), \ y = {\rm softmax}(W^{(yh)} [\overrightarrow h_T; \overleftarrow h_1] + b^{(y)}) $ $ \ Overrightarrow h_t \ in \ mathbb {R} ^ {d_h}, \ overleftarrow h_t \ in \ mathbb {R} ^ {d_h} $ sind jedoch die Zeiten $ t, die von den Vorwärts- bzw. Rückwärts-RNNs erhalten werden. Der verborgene Zustandsvektor von $, $ {\ rm \ overleftarrow {RNN}} (x, h) $ ist die RNN-Einheit, die den vorherigen Zustand aus der Eingabe $ x $ und dem verborgenen Zustand $ h $ des nächsten Males berechnet, $ W ^ {( yh)} \ in \ mathbb {R} ^ {L \ times 2d_h} $ ist eine Matrix zur Vorhersage von Kategorien aus versteckten Zustandsvektoren, $ b ^ {(y)} \ in \ mathbb {R} ^ {L} $ Ist ein Bias-Begriff. Außerdem repräsentiert $ [a; b] $ die Verkettung der Vektoren $ a $ und $ b $. Experimentieren Sie außerdem mit mehrschichtigen bidirektionalen RNNs.
Wenn Sie die Parameter von nn.LSTM
ein wenig ändern, können Sie es mehrschichtig oder bidirektional machen.
Da der verborgene Zustand zunimmt, werden die letzten beiden (verborgene Zustände in Vorwärts- und Rückwärtsrichtung der letzten Schicht) erfasst.
Code
class BiLSTMClassifier(nn.Module):
def __init__(self, v_size, e_size, h_size, c_size, dropout=0.2):
super().__init__()
self.embed = nn.Embedding(v_size, e_size)
self.rnn = nn.LSTM(e_size, h_size, num_layers = 2, bidirectional = True)
self.out = nn.Linear(h_size * 2, c_size)
self.dropout = nn.Dropout(dropout)
nn.init.uniform_(self.embed.weight, -0.1, 0.1)
for name, param in self.rnn.named_parameters():
if 'weight' in name or 'bias' in name:
nn.init.uniform_(param, -0.1, 0.1)
nn.init.uniform_(self.out.weight, -0.1, 0.1)
def forward(self, batch, h=None):
x = self.embed(batch['src'])
x = pack(x, batch['lengths'])
x, (h, c) = self.rnn(x, h)
h = h[-2:]
h = h.transpose(0,1)
h = h.contiguous().view(-1, h.size(1) * h.size(2))
h = self.out(h)
return h
Code
model = BiLSTMClassifier(len(vocab_dict), 300, 128, 4)
init_embed(model.embed)
task = Task()
optimizer = optim.SGD(model.parameters(), lr=0.05, momentum=0.9, nesterov=True)
trainer = Trainer(model, loaders, task, optimizer, 10, device)
trainer.train()
Code
predictor = Predictor(model, gen_loader(train_dataset, 1), device)
pred = predictor.predict()
print('Richtige Antwortrate in den Trainingsdaten:', accuracy(train_t, pred))
predictor = Predictor(model, gen_loader(test_dataset, 1), device)
pred = predictor.predict()
print('Richtige Antwortrate in den Bewertungsdaten:', accuracy(test_t, pred))
Es gibt eine Wortfolge $ \ boldsymbol x = (x_1, x_2, \ dots, x_T) $, die durch eine ID-Nummer dargestellt wird. $ T $ ist jedoch die Länge der Wortzeichenfolge, und $ x_t \ in \ mathbb {R} ^ {V} $ ist die One-Hot-Notation der Wort-ID-Nummer ($ V $ ist die Gesamtzahl der Wörter). Implementieren Sie mithilfe des Convolutional Neural Network (CNN) ein Modell, das die Kategorie $ y $ aus der Wortzeichenfolge $ \ boldsymbol x $ vorhersagt. Die Konfiguration des Faltungsnetzwerks ist jedoch wie folgt.
- Dimensionen zum Einbetten von Wörtern: $ d_w $
Das heißt, der Merkmalsvektor $ p_t \ in \ mathbb {R} ^ {d_h} $ zum Zeitpunkt $ t $ wird durch die folgende Gleichung ausgedrückt. $ p_t = g(W^{(px)} [\mathrm{emb}(x_{t-1}); \mathrm{emb}(x_t); \mathrm{emb}(x_{t+1})] + b^{(p)}) $ $ W ^ {(px)} \ in \ mathbb {R} ^ {d_h \ times 3d_w}, b ^ {(p)} \ in \ mathbb {R} ^ {d_h} $ ist jedoch ein CNN-Parameter. $ g $ ist eine Aktivierungsfunktion (zB $ \ tanh $ oder ReLU) und $ [a; b; c] $ ist eine Verkettung von Vektoren $ a, b, c $. Die Anzahl der Spalten in der Matrix $ W ^ {(px)} $ beträgt $ 3d_w $, da die lineare Konvertierung für die verketteten Worteinbettungen von drei Token durchgeführt wird. Beim Maximalwertpooling wird zu jeder Zeit der Maximalwert für jede Dimension des Merkmalsvektors verwendet und der Merkmalsvektor $ c \ in \ mathbb {R} ^ {d_h} $ des Eingabedokuments erhalten. Wenn $ c [i] $ den Wert der $ i $ -ten Dimension des Vektors $ c $ darstellt, wird das Maximalwert-Pooling durch die folgende Gleichung ausgedrückt. $ c[i] = \max_{1 \leq t \leq T} p_t[i] $ Schließlich enthält der Eingabedokument-Merkmalsvektor $ c $ die Matrix $ W ^ {(yc)} \ in \ mathbb {R} ^ {L \ times d_h} $ und den Bias-Term $ b ^ {(y)} \ in Wenden Sie die lineare Transformation mit \ mathbb {R} ^ {L} $ und der Softmax-Funktion an, um die Kategorie $ y $ vorherzusagen. $ y = {\rm softmax}(W^{(yc)} c + b^{(y)}) $ Beachten Sie, dass dieses Problem das Modell nicht trainiert, sondern nur die Berechnung von $ y $ mit einer zufällig initialisierten Gewichtsmatrix erfordert.
Ich möchte an beiden Enden der Eingabedaten PAD-Token hinzufügen, daher werde ich einen solchen Datensatz verwenden.
Code
cnn_vocab_list = ['[PAD]', '[UNK]'] + vocab_in_train
cnn_vocab_dict = {x:n for n, x in enumerate(cnn_vocab_list)}
def cnn_sent_to_ids(sent):
return torch.tensor([cnn_vocab_dict[x if x in cnn_vocab_dict else '[UNK]'] for x in sent], dtype=torch.long)
print(train_x[0])
print(cnn_sent_to_ids(train_x[0]))
Ausgabe
['Kathleen', 'Sebelius', "'", 'LGBT', 'legacy']
tensor([ 1, 1, 3, 2649, 1])
Da die Fensterbreite 3 ist, habe ich zwei EOS hinzugefügt.
Ausgabe
def cnn_dataset_to_ids(dataset):
return [cnn_sent_to_ids(x) for x in dataset]
cnn_train_s = cnn_dataset_to_ids(train_x)
cnn_valid_s = cnn_dataset_to_ids(valid_x)
cnn_test_s = cnn_dataset_to_ids(test_x)
cnn_train_s[:3]
Ausgabe
[tensor([ 1, 1, 3, 2649, 1]),
tensor([ 10, 6741, 1446, 2077, 584, 11, 548, 33, 52, 874, 6742]),
tensor([ 1, 206, 4199, 316, 1900, 1233, 1])]
Code
class CNNDataset(Dataset):
def collate(self, xs):
max_seq_len = max([x['lengths'] for x in xs])
src = [torch.cat([x['src'], torch.zeros(max_seq_len - x['lengths'], dtype=torch.long)], dim=-1) for x in xs]
src = torch.stack(src)
mask = [[1] * x['lengths'] + [0] * (max_seq_len - x['lengths']) for x in xs]
mask = torch.tensor(mask, dtype=torch.long)
return {
'src':src,
'trg':torch.tensor([x['trg'] for x in xs]),
'mask':mask,
}
cnn_train_dataset = CNNDataset(cnn_train_s, train_t)
cnn_valid_dataset = CNNDataset(cnn_valid_s, valid_t)
cnn_test_dataset = CNNDataset(cnn_test_s, test_t)
Erstellen Sie ein CNN-Modell.
Code
class CNNClassifier(nn.Module):
def __init__(self, v_size, e_size, h_size, c_size, dropout=0.2):
super().__init__()
self.embed = nn.Embedding(v_size, e_size)
self.conv = nn.Conv1d(e_size, h_size, 3, padding=1)
self.act = nn.ReLU()
self.out = nn.Linear(h_size, c_size)
self.dropout = nn.Dropout(dropout)
nn.init.normal_(self.embed.weight, 0, 0.1)
nn.init.kaiming_normal_(self.conv.weight)
nn.init.constant_(self.conv.bias, 0)
nn.init.kaiming_normal_(self.out.weight)
nn.init.constant_(self.out.bias, 0)
def forward(self, batch):
x = self.embed(batch['src'])
x = self.dropout(x)
x = self.conv(x.transpose(-1, -2))
x = self.act(x)
x = self.dropout(x)
x.masked_fill_(batch['mask'].unsqueeze(-2) == 0, -1e4)
x = F.max_pool1d(x, x.size(-1)).squeeze(-1)
x = self.out(x)
return x
Da für "nn.Conv1d" "padding = 1" angegeben ist, wird an jedem Ende ein Füllzeichen eingefügt. Dieses Token ist standardmäßig 0, aber es gibt kein Problem, da die Auffüll-ID, die der Länge der Serienlänge entspricht, ebenfalls 0 ist. Beim Falten in Zeitrichtung wird "transponieren" durchgeführt, um die letzte Dimension zur Zeitachse zu machen. Der Wert des Füllteils wird auf "-1" gesetzt, damit der Wert nicht durch das Maximalwert-Pooling abgerufen wird. Wenn der Maximalwert mit "max_pool1d" zusammengefasst wird, muss die Dimension angegeben werden.
Lernen Sie das in Aufgabe 86 erstellte Modell mit der SGD-Methode (Stochastic Gradient Descent). Trainieren Sie das Modell, während Sie den Verlust und die richtige Antwortrate in den Trainingsdaten sowie den Verlust und die richtige Antwortrate in den Bewertungsdaten anzeigen, und beenden Sie den Vorgang mit einem geeigneten Standard (z. B. 10 Epochen).
Lass mich lernen.
Code
def init_cnn_embed(embed):
for i, token in enumerate(cnn_vocab_list):
if token in vectors:
embed.weight.data[i] = torch.from_numpy(vectors[token])
return embed
Code
model = CNNClassifier(len(cnn_vocab_dict), 300, 128, 4)
init_cnn_embed(model.embed)
loaders = (
gen_maxtokens_loader(cnn_train_dataset, 4000),
gen_descending_loader(cnn_valid_dataset, 32),
)
task = Task()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9, nesterov=True)
trainer = Trainer(model, loaders, task, optimizer, 10, device)
trainer.train()
Ausgabe
epoch 0, train_loss:1.03501, valid_loss:0.85454
epoch 1, train_loss:0.68068, valid_loss:0.70825
epoch 2, train_loss:0.56784, valid_loss:0.60257
epoch 3, train_loss:0.50570, valid_loss:0.55611
epoch 4, train_loss:0.45707, valid_loss:0.52386
epoch 5, train_loss:0.42078, valid_loss:0.48479
epoch 6, train_loss:0.38858, valid_loss:0.45933
epoch 7, train_loss:0.36667, valid_loss:0.43547
epoch 8, train_loss:0.34746, valid_loss:0.41509
epoch 9, train_loss:0.32849, valid_loss:0.40350
Code
predictor = Predictor(model, gen_loader(cnn_train_dataset, 1), device)
pred = predictor.predict()
print('Richtige Antwortrate in den Trainingsdaten:', accuracy(train_t, pred))
predictor = Predictor(model, gen_loader(cnn_test_dataset, 1), device)
pred = predictor.predict()
print('Richtige Antwortrate in den Bewertungsdaten:', accuracy(test_t, pred))
Ausgabe
Richtige Antwortrate in den Trainingsdaten: 0.9106140022463497
Richtige Antwortrate in den Bewertungsdaten: 0.8847305389221557
Erstellen Sie einen Hochleistungskategorie-Klassifikator, indem Sie den Code in Frage 85 und Frage 87 ändern und die Form und die Hyperparameter des neuronalen Netzwerks anpassen.
Da es eine große Sache ist, habe ich ein Modell geschrieben, das die Ausgabe von LSTM mit CNN zum Maximalwert zusammenfasst.
Code
class BiLSTMCNNDataset(Dataset):
def collate(self, xs):
max_seq_len = max([x['lengths'] for x in xs])
mask = [[1] * (x['lengths'] - 2) + [0] * (max_seq_len - x['lengths']) for x in xs]
mask = torch.tensor(mask, dtype=torch.long)
return {
'src':pad([x['src'] for x in xs]),
'trg':torch.stack([x['trg'] for x in xs], dim=-1),
'mask':mask,
'lengths':torch.stack([x['lengths'] for x in xs], dim=-1)
}
rnncnn_train_dataset = BiLSTMCNNDataset(cnn_train_s, train_t)
rnncnn_valid_dataset = BiLSTMCNNDataset(cnn_valid_s, valid_t)
rnncnn_test_dataset = BiLSTMCNNDataset(cnn_test_s, test_t)
Code
class BiLSTMCNNClassifier(nn.Module):
def __init__(self, v_size, e_size, h_size, c_size, dropout=0.2):
super().__init__()
self.embed = nn.Embedding(v_size, e_size)
self.rnn = nn.LSTM(e_size, h_size, bidirectional = True)
self.conv = nn.Conv1d(h_size* 2, h_size, 3, padding=1)
self.act = nn.ReLU()
self.out = nn.Linear(h_size, c_size)
self.dropout = nn.Dropout(dropout)
nn.init.uniform_(self.embed.weight, -0.1, 0.1)
for name, param in self.rnn.named_parameters():
if 'weight' in name or 'bias' in name:
nn.init.uniform_(param, -0.1, 0.1)
nn.init.kaiming_normal_(self.conv.weight)
nn.init.constant_(self.conv.bias, 0)
nn.init.kaiming_normal_(self.out.weight)
nn.init.constant_(self.out.bias, 0)
def forward(self, batch, h=None):
x = self.embed(batch['src'])
x = self.dropout(x)
x = pack(x, batch['lengths'])
x, (h, c) = self.rnn(x, h)
x, _ = unpack(x)
x = self.dropout(x)
x = self.conv(x.permute(1, 2, 0))
x = self.act(x)
x = self.dropout(x)
x.masked_fill_(batch['mask'].unsqueeze(-2) == 0, -1)
x = F.max_pool1d(x, x.size(-1)).squeeze(-1)
x = self.out(x)
return x
Code
loaders = (
gen_maxtokens_loader(rnncnn_train_dataset, 4000),
gen_descending_loader(rnncnn_valid_dataset, 32),
)
task = Task()
for h in [32, 64, 128, 256, 512]:
model = BiLSTMCNNClassifier(len(cnn_vocab_dict), 300, h, 4)
init_cnn_embed(model.embed)
optimizer = optim.SGD(model.parameters(), lr=0.02, momentum=0.9, nesterov=True)
trainer = Trainer(model, loaders, task, optimizer, 10, device)
trainer.train()
predictor = Predictor(model, gen_loader(rnncnn_test_dataset, 1), device)
pred = predictor.predict()
print('Richtige Antwortrate in den Bewertungsdaten:', accuracy(test_t, pred))
Ausgabe
epoch 0, train_loss:1.21905, valid_loss:1.12725
epoch 1, train_loss:0.95913, valid_loss:0.84094
epoch 2, train_loss:0.66851, valid_loss:0.66997
epoch 3, train_loss:0.57141, valid_loss:0.61373
epoch 4, train_loss:0.52795, valid_loss:0.59354
epoch 5, train_loss:0.49844, valid_loss:0.57013
epoch 6, train_loss:0.47408, valid_loss:0.55163
epoch 7, train_loss:0.44922, valid_loss:0.52349
epoch 8, train_loss:0.41864, valid_loss:0.49231
epoch 9, train_loss:0.38975, valid_loss:0.46807
Richtige Antwortrate in den Bewertungsdaten: 0.8690119760479041
epoch 0, train_loss:1.16516, valid_loss:1.06582
epoch 1, train_loss:0.81246, valid_loss:0.71224
epoch 2, train_loss:0.58068, valid_loss:0.61988
epoch 3, train_loss:0.52451, valid_loss:0.58465
epoch 4, train_loss:0.48807, valid_loss:0.55663
epoch 5, train_loss:0.45712, valid_loss:0.52742
epoch 6, train_loss:0.41639, valid_loss:0.50089
epoch 7, train_loss:0.38595, valid_loss:0.46442
epoch 8, train_loss:0.35262, valid_loss:0.43459
epoch 9, train_loss:0.32527, valid_loss:0.40692
Richtige Antwortrate in den Bewertungsdaten: 0.8772455089820359
epoch 0, train_loss:1.12191, valid_loss:0.97533
epoch 1, train_loss:0.71378, valid_loss:0.66554
epoch 2, train_loss:0.55280, valid_loss:0.59733
epoch 3, train_loss:0.50526, valid_loss:0.57163
epoch 4, train_loss:0.46889, valid_loss:0.53955
epoch 5, train_loss:0.43500, valid_loss:0.50500
epoch 6, train_loss:0.40006, valid_loss:0.47222
epoch 7, train_loss:0.36444, valid_loss:0.43941
epoch 8, train_loss:0.33329, valid_loss:0.41224
epoch 9, train_loss:0.30588, valid_loss:0.39965
Richtige Antwortrate in den Bewertungsdaten: 0.8839820359281437
epoch 0, train_loss:1.04536, valid_loss:0.84626
epoch 1, train_loss:0.61410, valid_loss:0.62255
epoch 2, train_loss:0.49830, valid_loss:0.55984
epoch 3, train_loss:0.44190, valid_loss:0.51720
epoch 4, train_loss:0.39713, valid_loss:0.46718
epoch 5, train_loss:0.35052, valid_loss:0.43181
epoch 6, train_loss:0.32145, valid_loss:0.39898
epoch 7, train_loss:0.30279, valid_loss:0.37586
epoch 8, train_loss:0.28171, valid_loss:0.37333
epoch 9, train_loss:0.26904, valid_loss:0.37849
Richtige Antwortrate in den Bewertungsdaten: 0.8884730538922155
epoch 0, train_loss:0.93974, valid_loss:0.71999
epoch 1, train_loss:0.53687, valid_loss:0.58747
epoch 2, train_loss:0.44848, valid_loss:0.52432
epoch 3, train_loss:0.38761, valid_loss:0.46509
epoch 4, train_loss:0.34431, valid_loss:0.43651
epoch 5, train_loss:0.31699, valid_loss:0.39881
epoch 6, train_loss:0.28963, valid_loss:0.38732
epoch 7, train_loss:0.27550, valid_loss:0.37152
epoch 8, train_loss:0.26003, valid_loss:0.36476
epoch 9, train_loss:0.24991, valid_loss:0.36012
Richtige Antwortrate in den Bewertungsdaten: 0.8944610778443114
Ich habe versucht zu lernen, während ich die Größe des verborgenen Zustands geändert habe. Das Ergebnis ist, dass je größer der verborgene Zustand ist, desto besser.
Erstellen Sie ein Modell, das die Überschriften von Nachrichtenartikeln ausgehend von einem vorgelernten Sprachmodell (z. B. BERT) in Kategorien klassifiziert.
Verwenden Sie huggingface / transformers.
Code
from transformers import *
Die BERT-Eingabe ist ein Wortstück, daher müssen Sie sie über den Tokenizer eingeben. Bereiten Sie einen Tokenizer vor.
Code
tokenizer = BertTokenizer.from_pretrained('bert-base-cased')
Tokenize.
Code
def read_for_bert(filename):
with open(filename) as f:
dataset = f.read().splitlines()
dataset = [line.split('\t') for line in dataset]
dataset_t = [categories.index(line[0]) for line in dataset]
dataset_x = [torch.tensor(tokenizer.encode(line[1]), dtype=torch.long) for line in dataset]
return dataset_x, torch.tensor(dataset_t, dtype=torch.long)
bert_train_x, bert_train_t = read_for_bert('data/train.txt')
bert_valid_x, bert_valid_t = read_for_bert('data/valid.txt')
bert_test_x, bert_test_t = read_for_bert('data/test.txt')
Bereiten Sie eine Datensatzklasse für BERT vor. Ich mache Polsterung und so weiter.
mask
ist eine Aufmerksamkeitsmaske. Ich versuche, keine Aufmerksamkeit auf das Polsterzeichen zu lenken.
Code
class BertDataset(Dataset):
def collate(self, xs):
max_seq_len = max([x['lengths'] for x in xs])
src = [torch.cat([x['src'], torch.zeros(max_seq_len - x['lengths'], dtype=torch.long)], dim=-1) for x in xs]
src = torch.stack(src)
mask = [[1] * x['lengths'] + [0] * (max_seq_len - x['lengths']) for x in xs]
mask = torch.tensor(mask, dtype=torch.long)
return {
'src':src,
'trg':torch.tensor([x['trg'] for x in xs]),
'mask':mask,
}
Code
bert_train_dataset = BertDataset(bert_train_x, bert_train_t)
bert_valid_dataset = BertDataset(bert_valid_x, bert_valid_t)
bert_test_dataset = BertDataset(bert_test_x, bert_test_t)
Laden Sie das Pre-Training-Modell.
Code
class BertClassifier(nn.Module):
def __init__(self):
super().__init__()
config = BertConfig.from_pretrained('bert-base-cased', num_labels=4)
self.bert = BertForSequenceClassification.from_pretrained('bert-base-cased', config=config)
def forward(self, batch):
x = self.bert(batch['src'], attention_mask=batch['mask'])
return x[0]
Code
model = BertClassifier()
loaders = (
gen_maxtokens_loader(bert_train_dataset, 1000),
gen_descending_loader(bert_valid_dataset, 32),
)
task = Task()
optimizer = optim.AdamW(model.parameters(), lr=1e-5)
trainer = Trainer(model, loaders, task, optimizer, 5, device)
trainer.train()
Code
predictor = Predictor(model, gen_loader(bert_train_dataset, 1), device)
pred = predictor.predict()
print('Richtige Antwortrate in den Trainingsdaten:', accuracy(train_t, pred))
predictor = Predictor(model, gen_loader(bert_test_dataset, 1), device)
pred = predictor.predict()
print('Richtige Antwortrate in den Trainingsdaten:', accuracy(test_t, pred))
Ausgabe
Richtige Antwortrate in den Trainingsdaten: 0.9927929614376638
Richtige Antwortrate in den Trainingsdaten: 0.9229041916167665
Hört sich gut an.
Sprachverarbeitung 100 Klopfen 2020 Kapitel 10: Maschinelle Übersetzung (90-98)
Recommended Posts