[PYTHON] 100 Sprachverarbeitung Knock 2020 Kapitel 9: RNN, CNN

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.

Kapitel 9: RNN, CNN

Wie Kapitel 8 und verwenden Sie PyTorch.

80. Umwandlung in ID-Nummer

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

81. Vorhersage durch RNN

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

82. Lernen durch probabilistische Gradientenabstiegsmethode

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.

83. Mini-Batch / Lernen auf GPU

Ä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.

84. Einführung des Wortvektors

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

85. Bidirektionales RNN / mehrschichtig

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

86. Convolutional Neural Network (CNN)

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.

87. Lernen von CNN durch probabilistische Gradientenabstiegsmethode

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

88. Parametereinstellung

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.

89. Transferlernen aus einem vorgefertigten Sprachmodell

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.

Weiter ist Kapitel 10

Sprachverarbeitung 100 Klopfen 2020 Kapitel 10: Maschinelle Übersetzung (90-98)

Recommended Posts

100 Sprachverarbeitung Knock 2020 Kapitel 9: RNN, CNN
[Sprachverarbeitung 100 Schläge 2020] Kapitel 9: RNN, CNN
100 Sprachverarbeitung Knock 2020 Kapitel 1
100 Sprachverarbeitung Knock Kapitel 1
100 Sprachverarbeitung Knock 2020 Kapitel 3
100 Sprachverarbeitung Knock 2020 Kapitel 2
100 Sprachverarbeitung Knock Kapitel 1 (Python)
100 Sprachverarbeitung Knock Kapitel 2 (Python)
100 Sprachverarbeitung Knock 2020 Kapitel 2: UNIX-Befehle
100 Sprachverarbeitung Knock 2015 Kapitel 5 Abhängigkeitsanalyse (40-49)
100 Sprachverarbeitungsklopfen mit Python (Kapitel 1)
100 Sprachverarbeitung Knock Kapitel 1 in Python
100 Sprachverarbeitung Knock 2020 Kapitel 4: Morphologische Analyse
100 Sprachverarbeitungsklopfen (2020): 28
Ich habe versucht, 100 Sprachverarbeitung klopfen 2020: Kapitel 3
100 Sprachverarbeitungsklopfen mit Python (Kapitel 3)
100 Sprachverarbeitungsklopfen: Kapitel 1 Vorbereitungsbewegung
100 Sprachverarbeitung Knock 2020 Kapitel 6: Maschinelles Lernen
100 Sprachverarbeitung Knock Kapitel 4: Morphologische Analyse
100 Sprachverarbeitung Knock 2020 Kapitel 10: Maschinelle Übersetzung (90-98)
100 Sprachverarbeitung Knock 2020 Kapitel 5: Abhängigkeitsanalyse
100 Sprachverarbeitung Knock 2020 Kapitel 7: Word Vector
100 Sprachverarbeitung Knock 2020 Kapitel 8: Neuronales Netz
100 Sprachverarbeitungsklopfen (2020): 38
Ich habe versucht, 100 Sprachverarbeitung klopfen 2020: Kapitel 1
100 Sprachverarbeitung klopfen 00 ~ 02
100 Sprachverarbeitung Knock 2020 Kapitel 1: Vorbereitende Bewegung
100 Sprachverarbeitung Knock Kapitel 1 von Python
100 Sprachverarbeitung Knock 2020 Kapitel 3: Reguläre Ausdrücke
100 Language Processing Knock 2015 Kapitel 4 Morphologische Analyse (30-39)
Ich habe versucht, 100 Sprachverarbeitung zu klopfen 2020: Kapitel 2
Ich habe versucht, 100 Sprachverarbeitung zu klopfen 2020: Kapitel 4
100 Sprachverarbeitungsklopfen mit Python (Kapitel 2, Teil 2)
100 Sprachverarbeitungsklopfen mit Python (Kapitel 2, Teil 1)
[Programmierer-Neuling "100 Sprachverarbeitung klopfen 2020"] Lösen Sie Kapitel 1
100 Sprachverarbeitung klopfen 2020 [00 ~ 39 Antwort]
100 Sprachverarbeitung klopfen 2020 [00-79 Antwort]
100 Sprachverarbeitung klopfen 2020 [00 ~ 69 Antwort]
100 Amateur-Sprachverarbeitungsklopfen: 17
100 Sprachverarbeitung Knock-52: Stemming
100 Sprachverarbeitungsklopfen ~ Kapitel 1
100 Amateur-Sprachverarbeitungsklopfen: 07
100 Sprachverarbeitung klopft Kapitel 2 (10 ~ 19)
100 Amateur-Sprachverarbeitungsklopfen: 09
100 Amateur-Sprachverarbeitungsklopfen: 47
100 Sprachverarbeitung Knock-53: Tokenisierung
100 Amateur-Sprachverarbeitungsklopfen: 97
100 Sprachverarbeitung klopfen 2020 [00 ~ 59 Antwort]
100 Amateur-Sprachverarbeitungsklopfen: 67
100 Sprachverarbeitung Knock UNIX-Befehle in Kapitel 2
100 Sprachverarbeitung Klopfen Sie auf reguläre Ausdrücke, die Sie in Kapitel 3 gelernt haben
100 Sprachverarbeitungsklopfen mit Python 2015
100 Sprachverarbeitung Knock-51: Wortausschnitt
100 Sprachverarbeitung Knock-57: Abhängigkeitsanalyse
100 Sprachverarbeitung Knock-50: Satzumbruch
100 Sprachverarbeitung Knock-25: Vorlagenextraktion
Sprachverarbeitung 100 Knock-87: Wortähnlichkeit
Ich habe versucht, 100 Sprachverarbeitung klopfen 2020
100 Sprachverarbeitung Knock-56: Co-Referenz-Analyse
Lösen von 100 Sprachverarbeitungsklopfen 2020 (01. "Patatokukashi")
100 Amateur-Sprachverarbeitungsklopfen: Zusammenfassung