L'autre jour, 100 Language Processing Knock 2020 a été publié. Je ne travaille moi-même sur le langage naturel que depuis un an, et je ne connais pas les détails, mais je vais résoudre tous les problèmes et les publier afin d'améliorer mes compétences techniques.
Tout doit être exécuté sur le notebook jupyter, et les restrictions de l'énoncé du problème peuvent être brisées de manière pratique. Le code source est également sur github. Oui.
Le chapitre 8 est ici.
L'environnement est Python 3.8.2 et Ubuntu 18.04.
Identique au chapitre 8 et utilisez PyTorch.
Je souhaite attribuer un numéro d'identification unique aux mots des données d'entraînement construites à la question 51. Attribuez des numéros d'identification aux mots qui apparaissent plus d'une fois dans les données d'apprentissage, tels que «1» pour les mots les plus fréquents dans les données d'entraînement, «2» pour les deuxièmes mots les plus fréquents, et ainsi de suite. Ensuite, implémentez une fonction qui renvoie une chaîne de numéros d'identification pour une chaîne de mots donnée. Cependant, tous les numéros d'identification des mots qui apparaissent moins de deux fois doivent être «0».
code
import re
import spacy
import torch
Préparez le modèle et l'étiquette Spacy.
code
nlp = spacy.load('en')
categories = ['b', 't', 'e', 'm']
category_names = ['business', 'science and technology', 'entertainment', 'health']
Lisez le fichier et jetez-le avec 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')
Extrayez le vocabulaire. Seuls les mots qui apparaissent plus d'une fois sont ciblés.
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)
production
9700
Convertit une chaîne de mots en une chaîne de numéros d'identification.
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)
Jetons un jeton sur la première phrase des données d'entraînement.
code
print(train_x[0])
print(sent_to_ids(train_x[0]))
production
['Kathleen', 'Sebelius', "'", 'LGBT', 'legacy']
tensor([ 0, 0, 2, 2648, 0])
Convertissez-le en une colonne de numéros d'identification.
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]
production
[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])]
Il y a une chaîne de mot $ \ boldsymbol {x} = (x_1, x_2, \ dots, x_T) $ représentée par un numéro d'identification. Cependant, $ T $ est la longueur de la chaîne de mots, et $ x_t \ in \ mathbb {R} ^ {V} $ est la notation unique du numéro d'identification du mot ($ V $ est le nombre total de mots). En utilisant un réseau neuronal récurrent (RNN), implémentez l'équation suivante comme modèle pour prédire la catégorie $ y $ à partir de la chaîne de mots $ \ 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)}) $ Cependant, $ \ mathrm {emb} (x) \ in \ mathbb {R} ^ {d_w} $ est l'incorporation de mots (une fonction qui convertit un mot d'une notation one-hot en un vecteur de mots), $ \ overridearrow h_t \ in \ mathbb {R} ^ {d_h} $ est le vecteur d'état caché au temps $ t $, $ {\ rm \ overridearrow {RNN}} (x, h) $ provient de l'entrée $ x $ et de l'état caché $ h $ au temps précédent L'unité RNN qui calcule l'état suivant, $ W ^ {(yh)} \ in \ mathbb {R} ^ {L \ times d_h} $ est la matrice pour prédire la catégorie à partir du vecteur d'état caché, $ b ^ {(y) )} \ in \ mathbb {R} ^ {L} $ est un terme de biais ($ d_w, d_h, L $ sont respectivement le nombre de dimensions d'incorporation de mots, le nombre de dimensions du vecteur d'état masqué et le nombre d'étiquettes). L'unité RNN $ {\ rm \ overrightarrow {RNN}} (x, h) $ peut avoir différentes configurations, et l'équation suivante est un exemple typique. $ {\rm \overrightarrow{RNN}}(x,h) = g(W^{(hx)} x + W^{(hh)}h + b^{(h)}) $ Cependant, $ 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} $ est le paramètre de l'unité RNN, et $ g $ est la fonction d'activation (par exemple, $ \ tanh $ et ReLU). Notez que ce problème n'entraîne pas les paramètres, il nécessite seulement de calculer $ y $ avec les paramètres initialisés aléatoirement. Les paramètres Hyper tels que le nombre de dimensions doivent être définis sur des valeurs appropriées telles que $ d_w = 300, d_h = 50 $ (il en va de même pour les problèmes suivants).
Contrairement au chapitre 8, la longueur des données d'entrée diffère selon la phrase. Utilisez diverses choses dans torch.nn.utils.rnn
pour remplir la fin de la série de longueur variable afin qu'elle puisse être manipulée.
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
Créez une classe Dataset
qui contient l'ensemble de données.
En plus de l'instruction d'entrée «source» et de l'étiquette cible «cible», elle a les longueurs d'instruction d'entrée «lengths» comme membres.
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)
}
Préparez l'ensemble de données.
code
train_dataset = Dataset(train_s, train_t)
valid_dataset = Dataset(valid_s, valid_t)
test_dataset = Dataset(test_s, test_t)
Définissez le même Sampler
qu'au chapitre 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
Puisqu'il est pratique d'emballer le remplissage lorsque les séries du lot sont dans l'ordre décroissant, nous définissons un échantillonneur qui satisfait un tel contrat.
Tout ce que vous avez à faire est de trier les index par ordre décroissant de longueur à l'avance et de les charger en lots par l'avant.
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)]
De plus, pendant la formation, moins il y a de remplissage dans le lot, moins les calculs inutiles et l'apprentissage plus rapide, nous allons donc mettre en œuvre une telle méthode de conditionnement. Dans les deux exemples ci-dessus, l'index est séparé par le nombre de lots, mais le nombre d'observations dans le lot n'est pas toujours constant car l'enfant suivant est séparé par le nombre maximal de jetons dans le lot.
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
Préparez une fonction pour créer DataLoader
.
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)
Définit un classificateur LSTM unidirectionnel à une couche. La longueur de chaque instruction est requise lors de la mise en «pack» du tenseur «pad» dans le «collate» du jeu de données.
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)
Je vais prédire.
code
model = LSTMClassifier(len(vocab_dict), 300, 50, 4)
loader = gen_loader(test_dataset, 10, DescendingSampler, False)
model(iter(loader).next()).argmax(dim=-1)
production
tensor([1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
Apprenez le modèle construit dans le problème 81 à l'aide de la méthode SGD (Stochastic Gradient Descent). Entraînez le modèle tout en affichant le taux de perte et de réponse correcte sur les données d'entraînement et le taux de perte et de réponse correcte sur les données d'évaluation, et terminez avec une norme appropriée (par exemple, 10 époques).
Tout ce que vous avez à faire est de définir la tâche et le formateur et d'exécuter la formation.
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))
J'apprendrai.
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()
J'essaierai de prédire.
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('Taux de réponse correct dans les données d'entraînement:', accuracy(train_t, pred))
predictor = Predictor(model, gen_loader(test_dataset, 1), device)
pred = predictor.predict()
print('Taux de réponse correct dans les données d'évaluation:', accuracy(test_t, pred))
production
Taux de réponse correct dans les données d'entraînement: 0.7592661924372894
Taux de réponse correct dans les données d'évaluation: 0.6384730538922155
Si vous tournez un peu plus l'époque, la précision s'améliorera un peu, mais je m'en fiche.
Modifiez le code du problème 82 afin de pouvoir apprendre en calculant la perte / gradient pour chaque cas $ B $ (choisissez la valeur de $ B $ de manière appropriée). Exécutez également l'apprentissage sur le GPU.
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()
production
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('Taux de réponse correct dans les données d'entraînement:', accuracy(train_t, pred))
predictor = Predictor(model, gen_loader(test_dataset, 1), device)
pred = predictor.predict()
print('Taux de réponse correct dans les données d'évaluation:', accuracy(test_t, pred))
production
Taux de réponse correct dans les données d'entraînement: 0.7202358667165856
Taux de réponse correct dans les données d'évaluation: 0.6773952095808383
Pour une raison quelconque, la perte a augmenté et la précision a diminué, mais vivons fortement sans nous en soucier.
Vecteur de mots pré-appris (par exemple, [vecteur de mots appris] dans l'ensemble de données Google Actualités (environ 100 milliards de mots)](https://drive.google.com/file/d/0B7XkCwpI5KDYNlNUTTlSS21pQmM/edit?usp=sharing )) Initialisez le mot incorporant $ \ mathrm {emb} (x) $ et apprenez.
code
from gensim.models import KeyedVectors
vectors = KeyedVectors.load_word2vec_format('data/GoogleNews-vectors-negative300.bin.gz', binary=True)
Initialisez le mot d'intégration avant d'apprendre.
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()
production
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('Taux de réponse correct dans les données d'entraînement:', accuracy(train_t, pred))
predictor = Predictor(model, gen_loader(test_dataset, 1), device)
pred = predictor.predict()
print('Taux de réponse correct dans les données d'évaluation:', accuracy(test_t, pred))
production
Taux de réponse correct dans les données d'entraînement: 0.925028079371022
Taux de réponse correct dans les données d'évaluation: 0.8839820359281437
Encodez le texte d'entrée à l'aide des RNN avant et arrière et entraînez le modèle. $ \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)}) $ Cependant, $ \ overrightarrow h_t \ in \ mathbb {R} ^ {d_h}, \ overleftarrow h_t \ in \ mathbb {R} ^ {d_h} $ sont les temps $ t obtenus par les RNN avant et arrière, respectivement. Le vecteur d'état caché de $, $ {\ rm \ overleftarrow {RNN}} (x, h) $ est l'unité RNN qui calcule l'état précédent à partir de l'entrée $ x $ et l'état caché $ h $ de la prochaine fois, $ W ^ {( yh)} \ in \ mathbb {R} ^ {L \ times 2d_h} $ est une matrice pour prédire les catégories à partir de vecteurs d'état cachés, $ b ^ {(y)} \ in \ mathbb {R} ^ {L} $ Est un terme de biais. De plus, $ [a; b] $ représente la concaténation des vecteurs $ a $ et $ b $. De plus, expérimentez des RNN bidirectionnels multicouches.
Si vous modifiez un peu les paramètres de nn.LSTM
, vous pouvez le rendre multicouche ou bidirectionnel.
Puisque l'état caché augmente, les deux derniers (états cachés dans les directions avant et arrière de la dernière couche) sont acquis.
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('Taux de réponse correct dans les données d'entraînement:', accuracy(train_t, pred))
predictor = Predictor(model, gen_loader(test_dataset, 1), device)
pred = predictor.predict()
print('Taux de réponse correct dans les données d'évaluation:', accuracy(test_t, pred))
Il y a une chaîne de mot $ \ boldsymbol x = (x_1, x_2, \ dots, x_T) $ représentée par un numéro d'identification. Cependant, $ T $ est la longueur de la chaîne de mots, et $ x_t \ in \ mathbb {R} ^ {V} $ est la notation unique du numéro d'identification du mot ($ V $ est le nombre total de mots). Implémentez un modèle qui prédit la catégorie $ y $ à partir de la chaîne de mots $ \ boldsymbol x $ en utilisant le réseau de neurones convolutifs (CNN). Cependant, la configuration du réseau de neurones convolutifs est la suivante.
- Dimensions d'intégration Word: $ d_w $
Autrement dit, le vecteur de caractéristiques $ p_t \ in \ mathbb {R} ^ {d_h} $ au temps $ t $ est exprimé par l'équation suivante. $ p_t = g(W^{(px)} [\mathrm{emb}(x_{t-1}); \mathrm{emb}(x_t); \mathrm{emb}(x_{t+1})] + b^{(p)}) $ Cependant, $ W ^ {(px)} \ in \ mathbb {R} ^ {d_h \ times 3d_w}, b ^ {(p)} \ in \ mathbb {R} ^ {d_h} $ est un paramètre CNN. $ g $ est une fonction d'activation (par exemple $ \ tanh $ ou ReLU), et $ [a; b; c] $ est une concaténation de vecteurs $ a, b, c $. Le nombre de colonnes dans la matrice $ W ^ {(px)} $ est $ 3d_w $ car la conversion linéaire est effectuée sur les embeddings de mots concaténés de trois jetons. Dans la mise en commun des valeurs maximales, la valeur maximale à tout moment est prise pour chaque dimension du vecteur de caractéristiques, et le vecteur de caractéristiques $ c \ in \ mathbb {R} ^ {d_h} $ du document d'entrée est obtenu. Si $ c [i] $ représente la valeur de la $ i $ ème dimension du vecteur $ c $, la mise en commun de la valeur maximale est exprimée par l'équation suivante. $ c[i] = \max_{1 \leq t \leq T} p_t[i] $ Enfin, le vecteur caractéristique du document d'entrée $ c $ avec la matrice $ W ^ {(yc)} \ in \ mathbb {R} ^ {L \ times d_h} $ et le terme de biais $ b ^ {(y)} \ in Appliquez la transformation linéaire par \ mathbb {R} ^ {L} $ et la fonction softmax pour prédire la catégorie $ y $. $ y = {\rm softmax}(W^{(yc)} c + b^{(y)}) $ Notez que ce problème n'entraîne pas le modèle, il nécessite seulement de calculer $ y $ avec une matrice de poids initialisée aléatoirement.
Je veux ajouter des jetons PAD
aux deux extrémités des données d'entrée, je vais donc utiliser un tel ensemble de données.
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]))
production
['Kathleen', 'Sebelius', "'", 'LGBT', 'legacy']
tensor([ 1, 1, 3, 2649, 1])
Puisque la largeur de la fenêtre est de 3, j'ai ajouté deux ʻEOS`.
production
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]
production
[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)
Créez un modèle CNN.
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
Puisque padding = 1
est spécifié pour nn.Conv1d
, un jeton de remplissage est inséré à chaque extrémité. Ce jeton est 0 par défaut, mais il n'y a pas de problème car l'ID de remplissage qui correspond à la longueur de la série est également 0.
Lors du pliage dans le sens du temps, «transposer» est fait pour faire de la dernière dimension l'axe du temps.
La valeur de la partie de remplissage est définie sur «-1» afin que la valeur ne soit pas extraite par le regroupement de valeurs maximum.
Lors du regroupement de la valeur maximale avec max_pool1d
, il est nécessaire de spécifier la dimension.
Apprenez le modèle construit dans le problème 86 à l'aide de la méthode SGD (Stochastic Gradient Descent) Entraînez le modèle tout en affichant le taux de perte et de réponse correcte sur les données d'entraînement et le taux de perte et de réponse correcte sur les données d'évaluation, et terminez avec une norme appropriée (par exemple, 10 époques).
Laisse-moi apprendre.
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()
production
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('Taux de réponse correct dans les données d'entraînement:', accuracy(train_t, pred))
predictor = Predictor(model, gen_loader(cnn_test_dataset, 1), device)
pred = predictor.predict()
print('Taux de réponse correct dans les données d'évaluation:', accuracy(test_t, pred))
production
Taux de réponse correct dans les données d'entraînement: 0.9106140022463497
Taux de réponse correct dans les données d'évaluation: 0.8847305389221557
Construire un classificateur de catégories performant en modifiant le code des questions 85 et 87 et en ajustant la forme et les hyperparamètres du réseau de neurones.
Comme c'est un gros problème, j'ai écrit un modèle qui regroupe la sortie de LSTM avec CNN à la valeur maximale.
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('Taux de réponse correct dans les données d'évaluation:', accuracy(test_t, pred))
production
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
Taux de réponse correct dans les données d'évaluation: 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
Taux de réponse correct dans les données d'évaluation: 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
Taux de réponse correct dans les données d'évaluation: 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
Taux de réponse correct dans les données d'évaluation: 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
Taux de réponse correct dans les données d'évaluation: 0.8944610778443114
J'ai essayé d'apprendre en changeant la taille de l'état caché. Le résultat est que plus la taille de l'état caché est grande, mieux c'est.
Construisez un modèle qui classe les titres des articles d'actualité en catégories, à partir d'un modèle de langage pré-appris (par exemple BERT).
Utilisez huggingface / transformers.
code
from transformers import *
L'entrée BERT est un mot, vous devez donc le transmettre via le tokenizer. Préparez un tokenizer.
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')
Préparez une classe de jeu de données pour BERT. Je fais du rembourrage et ainsi de suite. «mask» est un masque d'attention. J'essaie de ne pas attirer l'attention sur le jeton de remplissage.
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)
Chargez le modèle de pré-formation.
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('Taux de réponse correct dans les données d'entraînement:', accuracy(train_t, pred))
predictor = Predictor(model, gen_loader(bert_test_dataset, 1), device)
pred = predictor.predict()
print('Taux de réponse correct dans les données d'entraînement:', accuracy(test_t, pred))
production
Taux de réponse correct dans les données d'entraînement: 0.9927929614376638
Taux de réponse correct dans les données d'entraînement: 0.9229041916167665
Ça m'a l'air bien.
Traitement du langage 100 coups 2020 Chapitre 10: Traduction automatique (90-98)
Recommended Posts