[PYTHON] 100 Language Processing Knock 2020 Chapitre 9: RNN, CNN

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.

Chapitre 9: RNN, CNN

Identique au chapitre 8 et utilisez PyTorch.

80. Conversion en numéro d'identification

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

81. Prédiction par RNN

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

82. Apprentissage par la méthode probabiliste de descente de gradient

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.

83. Mini-batch / apprentissage sur GPU

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.

84. Introduction du vecteur de mot

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

85. RNN bidirectionnel / multicouche

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

86. Réseau neuronal convolutif (CNN)

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.

87. Apprentissage de CNN par la méthode probabiliste de descente de gradient

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

88. Réglage des paramètres

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.

89. Transférer l'apprentissage à partir d'un modèle de langage pré-formé

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.

Vient ensuite le chapitre 10

Traitement du langage 100 coups 2020 Chapitre 10: Traduction automatique (90-98)

Recommended Posts

100 Language Processing Knock 2020 Chapitre 9: RNN, CNN
[Traitement du langage 100 coups 2020] Chapitre 9: RNN, CNN
100 Language Processing Knock 2020 Chapitre 1
100 Traitement du langage Knock Chapitre 1
100 Language Processing Knock 2020 Chapitre 3
100 Language Processing Knock 2020 Chapitre 2
100 Language Processing Knock Chapitre 1 (Python)
100 Language Processing Knock Chapitre 2 (Python)
100 Language Processing Knock 2020 Chapitre 2: Commandes UNIX
100 Language Processing Knock 2015 Chapitre 5 Analyse des dépendances (40-49)
100 traitements de langage avec Python
100 Language Processing Knock Chapitre 1 en Python
100 Language Processing Knock 2020 Chapitre 4: Analyse morphologique
100 coups de traitement linguistique (2020): 28
J'ai essayé 100 traitements linguistiques Knock 2020: Chapitre 3
100 traitements de langage avec Python (chapitre 3)
100 Language Processing Knock: Chapitre 1 Mouvement préparatoire
100 Language Processing Knock 2020 Chapitre 6: Apprentissage automatique
100 Traitement du langage Knock Chapitre 4: Analyse morphologique
100 Language Processing Knock 2020 Chapitre 10: Traduction automatique (90-98)
100 Language Processing Knock 2020 Chapitre 5: Analyse des dépendances
100 Traitement du langage Knock 2020 Chapitre 7: Vecteur de mots
100 Language Processing Knock 2020 Chapitre 8: Neural Net
100 coups de traitement linguistique (2020): 38
J'ai essayé 100 traitements linguistiques Knock 2020: Chapitre 1
100 traitement de la langue frapper 00 ~ 02
100 Language Processing Knock 2020 Chapitre 1: Mouvement préparatoire
100 Language Processing Knock Chapitre 1 par Python
100 Language Processing Knock 2020 Chapitre 3: Expressions régulières
100 Language Processing Knock 2015 Chapitre 4 Analyse morphologique (30-39)
J'ai essayé 100 traitements linguistiques Knock 2020: Chapitre 2
J'ai essayé 100 traitements linguistiques Knock 2020: Chapitre 4
100 traitements de langage avec Python (chapitre 2, partie 2)
100 traitements de langage avec Python (chapitre 2, partie 1)
[Programmeur nouveau venu "100 language processing knock 2020"] Résoudre le chapitre 1
100 traitements linguistiques Knock 2020 [00 ~ 39 réponse]
100 langues de traitement knock 2020 [00-79 réponse]
100 traitements linguistiques Knock 2020 [00 ~ 69 réponse]
100 coups de traitement du langage amateur: 17
100 Traitement du langage Knock-52: Stemming
100 coups de traitement du langage ~ Chapitre 1
100 coups de langue amateur: 07
Le traitement de 100 langues frappe le chapitre 2 (10 ~ 19)
100 coups de traitement du langage amateur: 09
100 coups en traitement du langage amateur: 47
Traitement 100 langues knock-53: Tokenisation
100 coups de traitement du langage amateur: 97
100 traitements linguistiques Knock 2020 [00 ~ 59 réponse]
100 coups de traitement du langage amateur: 67
100 Commandes de traitement du langage Knock UNIX apprises au chapitre 2
100 Traitement du langage Knock Expressions régulières apprises au chapitre 3
100 coups de traitement du langage avec Python 2015
100 traitement du langage Knock-51: découpage de mots
100 Language Processing Knock-57: Analyse des dépendances
100 traitement linguistique knock-50: coupure de phrase
100 Language Processing Knock-25: Extraction de modèles
Traitement du langage 100 Knock-87: similitude des mots
J'ai essayé 100 traitements linguistiques Knock 2020
100 Language Processing Knock-56: analyse de co-référence
Résolution de 100 traitements linguistiques Knock 2020 (01. "Patatokukashi")
100 coups de traitement du langage amateur: Résumé