[PYTHON] [Sprachverarbeitung 100 Schläge 2020] Kapitel 9: RNN, CNN

Einführung

Die Version 2020 von 100 Klopfen der Sprachverarbeitung, die als Sammlung von Problemen der Verarbeitung natürlicher Sprache bekannt ist, wurde veröffentlicht. Dieser Artikel fasst die Ergebnisse der Lösung von "Kapitel 9: RNN, CNN" aus den folgenden Kapiteln 1 bis 10 zusammen.

Vorbereitungen

Für die Antwort wird Google Colaboratory verwendet. Ausführliche Informationen zum Einrichten und Verwenden von Google Colaboratory finden Sie in diesem Artikel. ** Da in diesem Kapitel die GPU verwendet wird, ändern Sie den Hardwarebeschleuniger von "Laufzeit" -> "Laufzeittyp ändern" in "GPU" und speichern Sie ihn im Voraus. ** ** ** Das Notizbuch mit den Ausführungsergebnissen der folgenden Antworten ist unter [github] verfügbar (https://github.com/hana-mame/nlp100/blob/master/chapter09.ipynb).

Kapitel 9: RNN, CNN

80. Umwandlung in ID-Nummer

Ich möchte den Wörtern in den in Frage 51 erstellten Trainingsdaten eine eindeutige ID-Nummer geben. Das Wort, das in den Trainingsdaten am häufigsten vorkommt, ist "1", das Wort, das als zweites erscheint, ist "2" und so weiter, und das Wort, das mehr als einmal in den Trainingsdaten vorkommt, ist ID. Gib ihm eine Nummer. 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.

Lesen Sie die angegebenen Daten nach dem Herunterladen zunächst als Datenrahmen. Anschließend wird es in Trainingsdaten, Verifizierungsdaten und Bewertungsdaten unterteilt und gespeichert. Bis zu diesem Punkt ist der Prozess genau der gleiche wie bei Problem 50 in Kapitel 6, sodass die dort erstellten Daten problemlos gelesen werden können.

#Daten herunterladen
!wget https://archive.ics.uci.edu/ml/machine-learning-databases/00359/NewsAggregatorDataset.zip
!unzip NewsAggregatorDataset.zip
#Doppelte Anführungszeichen durch einfache Anführungszeichen ersetzt, um Fehler beim Lesen zu vermeiden
!sed -e 's/"/'\''/g' ./newsCorpora.csv > ./newsCorpora_re.csv
import pandas as pd
from sklearn.model_selection import train_test_split

#Daten lesen
df = pd.read_csv('./newsCorpora_re.csv', header=None, sep='\t', names=['ID', 'TITLE', 'URL', 'PUBLISHER', 'CATEGORY', 'STORY', 'HOSTNAME', 'TIMESTAMP'])

#Datenextraktion
df = df.loc[df['PUBLISHER'].isin(['Reuters', 'Huffington Post', 'Businessweek', 'Contactmusic.com', 'Daily Mail']), ['TITLE', 'CATEGORY']]

#Datenaufteilung
train, valid_test = train_test_split(df, test_size=0.2, shuffle=True, random_state=123, stratify=df['CATEGORY'])
valid, test = train_test_split(valid_test, test_size=0.5, shuffle=True, random_state=123, stratify=valid_test['CATEGORY'])

#Bestätigung der Anzahl der Fälle
print('[Lerndaten]')
print(train['CATEGORY'].value_counts())
print('[Verifizierungsdaten]')
print(valid['CATEGORY'].value_counts())
print('[Bewertungsdaten]')
print(test['CATEGORY'].value_counts())

Ausgabe


[Lerndaten]
b    4501
e    4235
t    1220
m     728
Name: CATEGORY, dtype: int64
[Verifizierungsdaten]
b    563
e    529
t    153
m     91
Name: CATEGORY, dtype: int64
[Bewertungsdaten]
b    563
e    530
t    152
m     91
Name: CATEGORY, dtype: int64

Erstellen Sie als Nächstes ein Wörterbuch mit Wörtern. Die Wörter in den Lerndaten werden gezählt und das Frequenzranking (ID) wird unter Verwendung desjenigen registriert, das mehr als einmal als Schlüssel erscheint.

from collections import defaultdict
import string

#Worthäufigkeitsaggregation
d = defaultdict(int)
table = str.maketrans(string.punctuation, ' '*len(string.punctuation))  #Eine Tabelle, die Symbole durch Leerzeichen ersetzt
for text in train['TITLE']:
  for word in text.translate(table).split():
    d[word] += 1
d = sorted(d.items(), key=lambda x:x[1], reverse=True)

#Erstellen eines Wort-ID-Wörterbuchs
word2id = {word: i + 1 for i, (word, cnt) in enumerate(d) if cnt > 1}  #Registrieren Sie Wörter, die mehrmals vorkommen

print(f'Anzahl der IDs: {len(set(word2id.values()))}\n')
print('---Top 20 Wörter in der Häufigkeit---')
for key in list(word2id)[:20]:
    print(f'{key}: {word2id[key]}')

Ausgabe


Anzahl der IDs: 9405

---Top 20 Wörter in der Häufigkeit---
to: 1
s: 2
in: 3
on: 4
UPDATE: 5
as: 6
US: 7
for: 8
The: 9
of: 10
1: 11
To: 12
2: 13
the: 14
and: 15
In: 16
Of: 17
a: 18
at: 19
A: 20

Definieren Sie abschließend eine Funktion, die ein Wörterbuch verwendet, um eine bestimmte Wortzeichenfolge in eine Zeichenfolge mit ID-Nummern umzuwandeln. Befolgen Sie zu diesem Zeitpunkt die Anweisungen im Fragensatz und geben Sie `` `0``` für Wörter zurück, die nicht im Wörterbuch enthalten sind.

def tokenizer(text, word2id=word2id, unk=0):
  """Teilen Sie den eingegebenen Text durch Leerzeichen und konvertieren Sie ihn in eine ID-Spalte(Wenn es nicht im Wörterbuch enthalten ist, legen Sie die von unk angegebene Nummer fest)"""
  table = str.maketrans(string.punctuation, ' '*len(string.punctuation))
  return [word2id.get(word, unk) for word in text.translate(table).split()]

Überprüfen Sie den zweiten Satz.

#Bestätigung
text = train.iloc[1, train.columns.get_loc('TITLE')]
print(f'Text: {text}')
print(f'ID-Spalte: {tokenizer(text)}')

Ausgabe


Text: Amazon Plans to Fight FTC Over Mobile-App Purchases
ID-Spalte: [169, 539, 1, 683, 1237, 82, 279, 1898, 4199]

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 \ overrightarrow {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.

>```math
{\rm \overrightarrow{RNN}}(x,h) = g(W^{(hx)} x + W^{(hh)}h + b^{(h)})

Allerdings ist $ 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).

Bevor wir auf die Antwort eingehen, wollen wir den Ablauf der Verarbeitung natürlicher Sprache mithilfe eines neuronalen Netzes organisieren, insbesondere bei der Textklassifizierung. Die Textklassifizierung unter Verwendung eines neuronalen Netzes besteht hauptsächlich aus den folgenden vier Schritten.

  1. Teilen Sie einen Satz in Token-Spalten (z. B. Wörter) auf.
  2. Konvertieren Sie jedes Token in einen Vektor
  3. Aggregieren Sie Token-Vektoren als Anweisungsvektor zu einem
  4. Klassifizieren Sie Beschriftungen mit Anweisungsvektor als Eingabe

Für jeden Prozess können verschiedene Methoden in Betracht gezogen werden, z. B. in Kapitel 8

  1. Teilen Sie einen Satz in Token-Spalten (z. B. Wörter) auf. ⇒ ** Durch Leerzeichen teilen **
  2. Konvertieren Sie jedes Token in einen Vektor ⇒ ** Konvertieren mit vorgelernten Word2Vec **
  3. Aggregieren Sie Token-Vektoren als Anweisungsvektor zu einem ⇒ ** Durchschnittlicher Token-Vektor **
  4. Klassifizieren Sie Beschriftungen mit Anweisungsvektor als Eingabe ⇒ ** Klassifiziert nach vollständig verbundener Schicht **

Ich habe den Ablauf von Nr. 4 implementiert und die Parameter von Nr. 4 gelernt (beim Targeting japanischer Dokumente in Nr. 1 Kapitel 4. E06014b146a18e97ca59) morphologische Analyse erforderlich).

Auf der anderen Seite, in diesem Kapitel,

  1. Teilen Sie einen Satz in Token-Spalten (z. B. Wörter) auf. ⇒ Durch Leerzeichen teilen
  2. Konvertieren Sie jedes Token in einen Vektor ⇒ ** Mit eingebetteter Ebene konvertieren **
  3. Aggregieren Sie Token-Vektoren als Anweisungsvektor zu einem ⇒ ** Aggregiert nach RNN oder CNN **
  4. Klassifizieren Sie Beschriftungen mit Anweisungsvektor als Eingabe ⇒ Klassifiziert nach vollständig verbundener Schicht

Dann lernen wir die Parameter des Netzwerks kennen, das die Nummern 2 bis 4 verbindet. Wie im Problem dieses Kapitels ist es häufig der Fall, dass die geteilten Token der Einfachheit halber in die entsprechenden IDs konvertiert werden, sie sind jedoch als Prozess in Nr. 1 enthalten.

Lassen Sie uns nun das Netzwerk dieser Frage sofort implementieren. Verwenden Sie `nn.Embedding``` für die eingebettete Ebene. Bei gegebener Wort-ID wird diese Ebene in einen One-Hot-Vektor und dann in einen Vektor der angegebenen Größe ( `emb_size```) konvertiert. Der folgende RNN-Teil kann durch den Prozess des rekursiven Durchlaufens der vollständig verbundenen Schicht realisiert werden, kann jedoch einfach unter Verwendung von "nn.RNN" geschrieben werden. Schließen Sie zum Schluss die vollständig verbundenen Ebenen an und Sie sind fertig.

import torch
from torch import nn

class RNN(nn.Module):
  def __init__(self, vocab_size, emb_size, padding_idx, output_size, hidden_size):
    super().__init__()
    self.hidden_size = hidden_size
    self.emb = nn.Embedding(vocab_size, emb_size, padding_idx=padding_idx)
    self.rnn = nn.RNN(emb_size, hidden_size, nonlinearity='tanh', batch_first=True)
    self.fc = nn.Linear(hidden_size, output_size)
    
  def forward(self, x):
    self.batch_size = x.size()[0]
    hidden = self.init_hidden()  #Erstellen Sie einen Nullvektor für h0
    emb = self.emb(x)
    # emb.size() = (batch_size, seq_len, emb_size)
    out, hidden = self.rnn(emb, hidden)
    # out.size() = (batch_size, seq_len, hidden_size)
    out = self.fc(out[:, -1, :])
    # out.size() = (batch_size, output_size)
    return out
    
  def init_hidden(self):
    hidden = torch.zeros(1, self.batch_size, self.hidden_size)
    return hidden

Definieren Sie als Nächstes die Klasse, die das `Dataset``` erstellt, wie im vorherigen Kapitel. Dieses Mal erhalten wir den Text und die Beschriftung, konvertieren den Text in eine ID mit dem angegebenen `Tokenizer``` und geben jedem eine Funktion zur Ausgabe im Tensortyp.

from torch.utils.data import Dataset

class CreateDataset(Dataset):
  def __init__(self, X, y, tokenizer):
    self.X = X
    self.y = y
    self.tokenizer = tokenizer

  def __len__(self):  # len(Dataset)Geben Sie den Wert an, mit dem zurückgegeben werden soll
    return len(self.y)

  def __getitem__(self, index):  # Dataset[index]Geben Sie den Wert an, mit dem zurückgegeben werden soll
    text = self.X[index]
    inputs = self.tokenizer(text)

    return {
      'inputs': torch.tensor(inputs, dtype=torch.int64),
      'labels': torch.tensor(self.y[index], dtype=torch.int64)
    }

Erstellen Sie einen `` `Datensatz``` mit den oben genannten. Geben Sie für "Tokenizer" die in der vorherigen Frage definierte Funktion an.

#Erstellen eines Beschriftungsvektors
category_dict = {'b': 0, 't': 1, 'e':2, 'm':3}
y_train = train['CATEGORY'].map(lambda x: category_dict[x]).values
y_valid = valid['CATEGORY'].map(lambda x: category_dict[x]).values
y_test = test['CATEGORY'].map(lambda x: category_dict[x]).values

#Erstellen eines Datensatzes
dataset_train = CreateDataset(train['TITLE'], y_train, tokenizer)
dataset_valid = CreateDataset(valid['TITLE'], y_valid, tokenizer)
dataset_test = CreateDataset(test['TITLE'], y_test, tokenizer)

print(f'len(Dataset)Ausgabe von: {len(dataset_train)}')
print('Dataset[index]Ausgabe von:')
for var in dataset_train[1]:
  print(f'  {var}: {dataset_train[1][var]}')

Ausgabe


len(Dataset)Ausgabe von: 10684
Dataset[index]Ausgabe von:
  inputs: tensor([ 169,  539,    1,  683, 1237,   82,  279, 1898, 4199])
  labels: 1

Da wir in dieser Frage nicht lernen werden, geben Sie dem Modell `Eingaben``` von` Dataset``` und überprüfen Sie die Ausgabe so, wie sie nach Softmax ist.

#Parametereinstellungen
VOCAB_SIZE = len(set(word2id.values())) + 1  #Anzahl der Wörterbuch-IDs+Polsterungs-ID
EMB_SIZE = 300
PADDING_IDX = len(set(word2id.values()))
OUTPUT_SIZE = 4
HIDDEN_SIZE = 50

#Modelldefinition
model = RNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, HIDDEN_SIZE)

#Holen Sie sich die ersten 10 vorhergesagten Werte
for i in range(10):
  X = dataset_train[i]['inputs']
  print(torch.softmax(model(X.unsqueeze(0)), dim=-1))

Ausgabe


tensor([[0.2667, 0.2074, 0.2974, 0.2285]], grad_fn=<SoftmaxBackward>)
tensor([[0.1660, 0.3465, 0.2154, 0.2720]], grad_fn=<SoftmaxBackward>)
tensor([[0.2133, 0.2987, 0.3097, 0.1783]], grad_fn=<SoftmaxBackward>)
tensor([[0.2512, 0.4107, 0.1825, 0.1556]], grad_fn=<SoftmaxBackward>)
tensor([[0.2784, 0.1307, 0.3715, 0.2194]], grad_fn=<SoftmaxBackward>)
tensor([[0.2625, 0.1569, 0.2339, 0.3466]], grad_fn=<SoftmaxBackward>)
tensor([[0.1331, 0.5129, 0.2220, 0.1319]], grad_fn=<SoftmaxBackward>)
tensor([[0.2404, 0.1314, 0.2023, 0.4260]], grad_fn=<SoftmaxBackward>)
tensor([[0.1162, 0.4576, 0.2588, 0.1674]], grad_fn=<SoftmaxBackward>)
tensor([[0.4685, 0.1414, 0.2633, 0.1268]], grad_fn=<SoftmaxBackward>)

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

Wie im vorherigen Kapitel definiert dies auch eine Reihe von Verarbeitungen zum Lernen als `` `train_model``` Funktion.

from torch.utils.data import DataLoader
import time
from torch import optim

def calculate_loss_and_accuracy(model, dataset, device=None, criterion=None):
  """Berechnen Sie den Verlust / die richtige Antwortrate"""
  dataloader = DataLoader(dataset, batch_size=1, shuffle=False)
  loss = 0.0
  total = 0
  correct = 0
  with torch.no_grad():
    for data in dataloader:
      #Gerätespezifikation
      inputs = data['inputs'].to(device)
      labels = data['labels'].to(device)

      #Vorwärtsausbreitung
      outputs = model(inputs)

      #Verlustberechnung
      if criterion != None:
        loss += criterion(outputs, labels).item()

      #Richtige Berechnung der Antwortrate
      pred = torch.argmax(outputs, dim=-1)
      total += len(inputs)
      correct += (pred == labels).sum().item()
      
  return loss / len(dataset), correct / total
  

def train_model(dataset_train, dataset_valid, batch_size, model, criterion, optimizer, num_epochs, collate_fn=None, device=None):
  """Führt ein Modelltraining durch und gibt ein Protokoll mit Verlust / korrekter Antwortrate zurück"""
  #Gerätespezifikation
  model.to(device)

  #Erstellen eines Datenladers
  dataloader_train = DataLoader(dataset_train, batch_size=batch_size, shuffle=True, collate_fn=collate_fn)
  dataloader_valid = DataLoader(dataset_valid, batch_size=1, shuffle=False)

  #Scheduler-Einstellungen
  scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, num_epochs, eta_min=1e-5, last_epoch=-1)

  #Lernen
  log_train = []
  log_valid = []
  for epoch in range(num_epochs):
    #Startzeit aufzeichnen
    s_time = time.time()

    #Auf Trainingsmodus einstellen
    model.train()
    for data in dataloader_train:
      #Gradient auf Null initialisieren
      optimizer.zero_grad()

      #Vorwärtsausbreitung+Fehler bei der Weitergabe+Gewichtsaktualisierung
      inputs = data['inputs'].to(device)
      labels = data['labels'].to(device)
      outputs = model.forward(inputs)
      loss = criterion(outputs, labels)
      loss.backward()
      optimizer.step()
    
    #In den Auswertemodus versetzen
    model.eval()

    #Verlustberechnung und korrekte Rücklaufquote
    loss_train, acc_train = calculate_loss_and_accuracy(model, dataset_train, device, criterion=criterion)
    loss_valid, acc_valid = calculate_loss_and_accuracy(model, dataset_valid, device, criterion=criterion)
    log_train.append([loss_train, acc_train])
    log_valid.append([loss_valid, acc_valid])

    #Checkpoint speichern
    torch.save({'epoch': epoch, 'model_state_dict': model.state_dict(), 'optimizer_state_dict': optimizer.state_dict()}, f'checkpoint{epoch + 1}.pt')

    #Endzeit aufzeichnen
    e_time = time.time()

    #Ausgabeprotokoll
    print(f'epoch: {epoch + 1}, loss_train: {loss_train:.4f}, accuracy_train: {acc_train:.4f}, loss_valid: {loss_valid:.4f}, accuracy_valid: {acc_valid:.4f}, {(e_time - s_time):.4f}sec') 

    #Das Lernen endet, wenn der Verlust von Verifizierungsdaten in drei aufeinander folgenden Epochen nicht abnimmt
    if epoch > 2 and log_valid[epoch - 3][0] <= log_valid[epoch - 2][0] <= log_valid[epoch - 1][0] <= log_valid[epoch][0]:
      break
      
    #Machen Sie mit dem Scheduler einen Schritt
    scheduler.step()

  return {'train': log_train, 'valid': log_valid}

Definieren Sie außerdem eine Funktion zur Visualisierung des Protokolls.

import numpy as np
from matplotlib import pyplot as plt

def visualize_logs(log):
  fig, ax = plt.subplots(1, 2, figsize=(15, 5))
  ax[0].plot(np.array(log['train']).T[0], label='train')
  ax[0].plot(np.array(log['valid']).T[0], label='valid')
  ax[0].set_xlabel('epoch')
  ax[0].set_ylabel('loss')
  ax[0].legend()
  ax[1].plot(np.array(log['train']).T[1], label='train')
  ax[1].plot(np.array(log['valid']).T[1], label='valid')
  ax[1].set_xlabel('epoch')
  ax[1].set_ylabel('accuracy')
  ax[1].legend()
  plt.show()

Stellen Sie die Parameter ein und trainieren Sie das Modell.

#Parametereinstellungen
VOCAB_SIZE = len(set(word2id.values())) + 1 
EMB_SIZE = 300
PADDING_IDX = len(set(word2id.values()))
OUTPUT_SIZE = 4
HIDDEN_SIZE = 50
LEARNING_RATE = 1e-3
BATCH_SIZE = 1
NUM_EPOCHS = 10

#Modelldefinition
model = RNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, HIDDEN_SIZE)

#Definition der Verlustfunktion
criterion = nn.CrossEntropyLoss()

#Optimierungsdefinition
optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE)

#Modelllernen
log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS)

Ausgabe


epoch: 1, loss_train: 1.0954, accuracy_train: 0.5356, loss_valid: 1.1334, accuracy_valid: 0.5015, 86.4033sec
epoch: 2, loss_train: 1.0040, accuracy_train: 0.6019, loss_valid: 1.0770, accuracy_valid: 0.5516, 85.2816sec
epoch: 3, loss_train: 0.8813, accuracy_train: 0.6689, loss_valid: 0.9793, accuracy_valid: 0.6287, 78.9026sec
epoch: 4, loss_train: 0.7384, accuracy_train: 0.7364, loss_valid: 0.8498, accuracy_valid: 0.7058, 78.4496sec
epoch: 5, loss_train: 0.6427, accuracy_train: 0.7696, loss_valid: 0.7878, accuracy_valid: 0.7253, 83.4453sec
epoch: 6, loss_train: 0.5730, accuracy_train: 0.7942, loss_valid: 0.7378, accuracy_valid: 0.7470, 79.6968sec
epoch: 7, loss_train: 0.5221, accuracy_train: 0.8064, loss_valid: 0.7058, accuracy_valid: 0.7530, 79.7377sec
epoch: 8, loss_train: 0.4924, accuracy_train: 0.8173, loss_valid: 0.7017, accuracy_valid: 0.7605, 78.2168sec
epoch: 9, loss_train: 0.4800, accuracy_train: 0.8234, loss_valid: 0.7014, accuracy_valid: 0.7575, 77.8689sec
epoch: 10, loss_train: 0.4706, accuracy_train: 0.8253, loss_valid: 0.6889, accuracy_valid: 0.7650, 79.4202sec
#Protokollvisualisierung
visualize_logs(log)

#Berechnung der richtigen Antwortrate
_, acc_train = calculate_loss_and_accuracy(model, dataset_train)
_, acc_test = calculate_loss_and_accuracy(model, dataset_test)
print(f'Richtige Antwortrate (Lerndaten):{acc_train:.3f}')
print(f'Richtige Antwortrate (Bewertungsdaten):{acc_test:.3f}')

82.png

Ausgabe


Richtige Antwortrate (Lerndaten): 0.825
Richtige Antwortrate (Bewertungsdaten): 0.773

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.

Derzeit ist die Serienlänge für jeden Satz unterschiedlich, es ist jedoch erforderlich, die Serienlänge auszurichten, um sie als Mini-Batch zusammenzustellen. Daher definieren wir eine neue `Padsequence``` -Klasse, die die Funktion hat, entsprechend der maximalen Sequenzlänge mehrerer Anweisungen aufzufüllen. Indem Sie dies dem Argument collate_fn``` von `` Dataloader``` geben, ist es möglich, den Prozess des Ausrichtens der Serienlänge jedes Mal zu realisieren, wenn ein Mini-Batch herausgenommen wird.

class Padsequence():
  """Polsterung mit maximaler Serienlänge jedes Mal, wenn eine Mini-Charge aus Dataloader entnommen wird"""
  def __init__(self, padding_idx):
    self.padding_idx = padding_idx

  def __call__(self, batch):
    sorted_batch = sorted(batch, key=lambda x: x['inputs'].shape[0], reverse=True)
    sequences = [x['inputs'] for x in sorted_batch]
    sequences_padded = torch.nn.utils.rnn.pad_sequence(sequences, batch_first=True, padding_value=self.padding_idx)
    labels = torch.LongTensor([x['labels'] for x in sorted_batch])

    return {'inputs': sequences_padded, 'labels': labels}
#Parametereinstellungen
VOCAB_SIZE = len(set(word2id.values())) + 1
EMB_SIZE = 300
PADDING_IDX = len(set(word2id.values()))
OUTPUT_SIZE = 4
HIDDEN_SIZE = 50
LEARNING_RATE = 5e-2
BATCH_SIZE = 32
NUM_EPOCHS = 10

#Modelldefinition
model = RNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, HIDDEN_SIZE)

#Definition der Verlustfunktion
criterion = nn.CrossEntropyLoss()

#Optimierungsdefinition
optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE)

#Gerätespezifikation
device = torch.device('cuda')

#Modelllernen
log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS, collate_fn=Padsequence(PADDING_IDX), device=device)

Ausgabe


epoch: 1, loss_train: 1.2605, accuracy_train: 0.3890, loss_valid: 1.2479, accuracy_valid: 0.4162, 12.1096sec
epoch: 2, loss_train: 1.2492, accuracy_train: 0.4246, loss_valid: 1.2541, accuracy_valid: 0.4424, 12.0607sec
epoch: 3, loss_train: 1.2034, accuracy_train: 0.4795, loss_valid: 1.2220, accuracy_valid: 0.4686, 11.8881sec
epoch: 4, loss_train: 1.1325, accuracy_train: 0.5392, loss_valid: 1.1542, accuracy_valid: 0.5210, 12.2269sec
epoch: 5, loss_train: 1.0543, accuracy_train: 0.6214, loss_valid: 1.0623, accuracy_valid: 0.6175, 11.8767sec
epoch: 6, loss_train: 1.0381, accuracy_train: 0.6316, loss_valid: 1.0556, accuracy_valid: 0.6145, 11.9757sec
epoch: 7, loss_train: 1.0546, accuracy_train: 0.6165, loss_valid: 1.0806, accuracy_valid: 0.5913, 12.0352sec
epoch: 8, loss_train: 0.9924, accuracy_train: 0.6689, loss_valid: 1.0150, accuracy_valid: 0.6587, 11.9090sec
epoch: 9, loss_train: 1.0123, accuracy_train: 0.6517, loss_valid: 1.0482, accuracy_valid: 0.6310, 12.0953sec
epoch: 10, loss_train: 1.0036, accuracy_train: 0.6623, loss_valid: 1.0319, accuracy_valid: 0.6504, 11.9331sec
#Protokollvisualisierung
visualize_logs(log)

#Berechnung der richtigen Antwortrate
_, acc_train = calculate_loss_and_accuracy(model, dataset_train, device)
_, acc_test = calculate_loss_and_accuracy(model, dataset_test, device)
print(f'Richtige Antwortrate (Lerndaten):{acc_train:.3f}')
print(f'Richtige Antwortrate (Bewertungsdaten):{acc_test:.3f}')

83.png

Ausgabe


Richtige Antwortrate (Lerndaten): 0.662
Richtige Antwortrate (Bewertungsdaten): 0.649

84. Einführung des Wortvektors

Initialisieren und lernen Sie das Wort, in das $ emb (x) $ eingebettet ist, mit einem vorgelernten Wortvektor (z. B. einem gelernten Wortvektor im Google News-Datensatz (ca. 100 Milliarden Wörter)).

Laden Sie den vorgelernten Wortvektor wie im vorherigen Kapitel herunter.

#Laden Sie den gelernten Wortvektor herunter
FILE_ID = "0B7XkCwpI5KDYNlNUTTlSS21pQmM"
FILE_NAME = "GoogleNews-vectors-negative300.bin.gz"
!wget --load-cookies /tmp/cookies.txt "https://docs.google.com/uc?export=download&confirm=$(wget --quiet --save-cookies /tmp/cookies.txt --keep-session-cookies --no-check-certificate 'https://docs.google.com/uc?export=download&id=$FILE_ID' -O- | sed -rn 's/.*confirm=([0-9A-Za-z_]+).*/\1\n/p')&id=$FILE_ID" -O $FILE_NAME && rm -rf /tmp/cookies.txt

Wenn Sie einen vorgelernten Wortvektor als Modell verwenden, gibt es zwei Methoden: Eine besteht darin, alle Wörter zu verwenden (das Wörterbuch zu ersetzen), und die andere darin, das Wörterbuch der vorliegenden Daten so zu verwenden, wie es ist, und es nur als Anfangswert dieser Wortvektoren zu verwenden. Es gibt. Dieses Mal wird das letztere Verfahren angewendet und der Wortvektor, der dem bereits erstellten Wörterbuch entspricht, wird extrahiert.

from gensim.models import KeyedVectors

#Geladenes trainiertes Modell laden
model = KeyedVectors.load_word2vec_format('./GoogleNews-vectors-negative300.bin.gz', binary=True)

#Holen Sie sich gelernt Wortvektor
VOCAB_SIZE = len(set(word2id.values())) + 1
EMB_SIZE = 300
weights = np.zeros((VOCAB_SIZE, EMB_SIZE))
words_in_pretrained = 0
for i, word in enumerate(word2id.keys()):
  try:
    weights[i] = model[word]
    words_in_pretrained += 1
  except KeyError:
    weights[i] = np.random.normal(scale=0.4, size=(EMB_SIZE,))
weights = torch.from_numpy(weights.astype((np.float32)))

print(f'Anzahl der im gelernten Vektor verwendeten Wörter: {words_in_pretrained} / {VOCAB_SIZE}')
print(weights.size())

Ausgabe


Anzahl der im gelernten Vektor verwendeten Wörter: 9174 / 9406
torch.Size([9406, 300])

Ändern Sie dies so, dass der Anfangswert für die eingebettete Schicht des Netzwerks festgelegt werden kann. Fügen Sie außerdem Einstellungen für bidirektional und mehrschichtig für das nächste Problem hinzu.

class RNN(nn.Module):
  def __init__(self, vocab_size, emb_size, padding_idx, output_size, hidden_size, num_layers, emb_weights=None, bidirectional=False):
    super().__init__()
    self.hidden_size = hidden_size
    self.num_layers = num_layers
    self.num_directions = bidirectional + 1  #Unidirektional: 1, bidirektional: 2
    if emb_weights != None:  #Geben Sie das Gewicht der eingebetteten Ebene an, falls angegeben_Mit Gewichten initialisieren
      self.emb = nn.Embedding.from_pretrained(emb_weights, padding_idx=padding_idx)
    else:
      self.emb = nn.Embedding(vocab_size, emb_size, padding_idx=padding_idx)
    self.rnn = nn.RNN(emb_size, hidden_size, num_layers, nonlinearity='tanh', bidirectional=bidirectional, batch_first=True)
    self.fc = nn.Linear(hidden_size * self.num_directions, output_size)
    
  def forward(self, x):
    self.batch_size = x.size()[0]
    hidden = self.init_hidden()  #Erstellen Sie einen Nullvektor für h0
    emb = self.emb(x)
    # emb.size() = (batch_size, seq_len, emb_size)
    out, hidden = self.rnn(emb, hidden)
    # out.size() = (batch_size, seq_len, hidden_size * num_directions)
    out = self.fc(out[:, -1, :])
    # out.size() = (batch_size, output_size)
    return out
    
  def init_hidden(self):
    hidden = torch.zeros(self.num_layers * self.num_directions, self.batch_size, self.hidden_size)
    return hidden

Lernen Sie, indem Sie den Anfangswert der eingebetteten Ebene angeben.

#Parametereinstellungen
VOCAB_SIZE = len(set(word2id.values())) + 1
EMB_SIZE = 300
PADDING_IDX = len(set(word2id.values()))
OUTPUT_SIZE = 4
HIDDEN_SIZE = 50
NUM_LAYERS = 1
LEARNING_RATE = 5e-2
BATCH_SIZE = 32
NUM_EPOCHS = 10

#Modelldefinition
model = RNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, HIDDEN_SIZE, NUM_LAYERS, emb_weights=weights)

#Definition der Verlustfunktion
criterion = nn.CrossEntropyLoss()

#Optimierungsdefinition
optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE)

#Gerätespezifikation
device = torch.device('cuda')

#Modelllernen
log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS, collate_fn=Padsequence(PADDING_IDX), device=device)

Ausgabe


epoch: 1, loss_train: 1.1655, accuracy_train: 0.4270, loss_valid: 1.1839, accuracy_valid: 0.4244, 9.7483sec
epoch: 2, loss_train: 1.1555, accuracy_train: 0.4635, loss_valid: 1.1404, accuracy_valid: 0.4865, 9.7553sec
epoch: 3, loss_train: 1.0189, accuracy_train: 0.6263, loss_valid: 1.0551, accuracy_valid: 0.6085, 10.0445sec
epoch: 4, loss_train: 1.0377, accuracy_train: 0.6221, loss_valid: 1.0947, accuracy_valid: 0.5951, 10.1138sec
epoch: 5, loss_train: 1.0392, accuracy_train: 0.6082, loss_valid: 1.0776, accuracy_valid: 0.5921, 9.8540sec
epoch: 6, loss_train: 1.0447, accuracy_train: 0.6087, loss_valid: 1.1020, accuracy_valid: 0.5793, 9.8598sec
epoch: 7, loss_train: 0.9999, accuracy_train: 0.6270, loss_valid: 1.0519, accuracy_valid: 0.6108, 9.7565sec
epoch: 8, loss_train: 0.9539, accuracy_train: 0.6557, loss_valid: 1.0092, accuracy_valid: 0.6385, 9.7457sec
epoch: 9, loss_train: 0.9287, accuracy_train: 0.6674, loss_valid: 0.9806, accuracy_valid: 0.6430, 9.6464sec
epoch: 10, loss_train: 0.9456, accuracy_train: 0.6593, loss_valid: 1.0029, accuracy_valid: 0.6377, 9.6835sec
#Protokollvisualisierung
visualize_logs(log)

#Berechnung der richtigen Antwortrate
_, acc_train = calculate_loss_and_accuracy(model, dataset_train, device)
_, acc_test = calculate_loss_and_accuracy(model, dataset_test, device)
print(f'Richtige Antwortrate (Lerndaten):{acc_train:.3f}')
print(f'Richtige Antwortrate (Bewertungsdaten):{acc_test:.3f}')

84.png

Ausgabe


Richtige Antwortrate (Lerndaten): 0.659
Richtige Antwortrate (Bewertungsdaten): 0.645

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

 Setzen Sie `` `bidirektional```, ein Argument, das die Bidirektionalität angibt, auf` `` True``` und setzen Sie `` `NUM_LAYERS``` auf` `` 2```, um das Lernen auszuführen. ..

```python
#Parametereinstellungen
VOCAB_SIZE = len(set(word2id.values())) + 1
EMB_SIZE = 300
PADDING_IDX = len(set(word2id.values()))
OUTPUT_SIZE = 4
HIDDEN_SIZE = 50
NUM_LAYERS = 2
LEARNING_RATE = 5e-2
BATCH_SIZE = 32
NUM_EPOCHS = 10

#Modelldefinition
model = RNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, HIDDEN_SIZE, NUM_LAYERS, emb_weights=weights, bidirectional=True)

#Definition der Verlustfunktion
criterion = nn.CrossEntropyLoss()

#Optimierungsdefinition
optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE)

#Gerätespezifikation
device = torch.device('cuda')

#Modelllernen
log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS, collate_fn=Padsequence(PADDING_IDX), device=device)

Ausgabe


epoch: 1, loss_train: 1.1731, accuracy_train: 0.4307, loss_valid: 1.1915, accuracy_valid: 0.4274, 19.3181sec
epoch: 2, loss_train: 1.0395, accuracy_train: 0.6116, loss_valid: 1.0555, accuracy_valid: 0.5996, 18.8118sec
epoch: 3, loss_train: 1.0529, accuracy_train: 0.5899, loss_valid: 1.0832, accuracy_valid: 0.5696, 18.9088sec
epoch: 4, loss_train: 0.9831, accuracy_train: 0.6351, loss_valid: 1.0144, accuracy_valid: 0.6235, 18.8913sec
epoch: 5, loss_train: 1.0622, accuracy_train: 0.5797, loss_valid: 1.1142, accuracy_valid: 0.5487, 19.0636sec
epoch: 6, loss_train: 1.0463, accuracy_train: 0.5741, loss_valid: 1.0972, accuracy_valid: 0.5367, 19.0612sec
epoch: 7, loss_train: 1.0056, accuracy_train: 0.6102, loss_valid: 1.0485, accuracy_valid: 0.5898, 19.0420sec
epoch: 8, loss_train: 0.9724, accuracy_train: 0.6294, loss_valid: 1.0278, accuracy_valid: 0.6093, 19.3077sec
epoch: 9, loss_train: 0.9469, accuracy_train: 0.6371, loss_valid: 0.9943, accuracy_valid: 0.6160, 19.2803sec
epoch: 10, loss_train: 0.9343, accuracy_train: 0.6451, loss_valid: 0.9867, accuracy_valid: 0.6235, 19.0755sec
#Protokollvisualisierung
visualize_logs(log)

#Berechnung der richtigen Antwortrate
_, acc_train = calculate_loss_and_accuracy(model, dataset_train, device)
_, acc_test = calculate_loss_and_accuracy(model, dataset_test, device)
print(f'Richtige Antwortrate (Lerndaten):{acc_train:.3f}')
print(f'Richtige Antwortrate (Bewertungsdaten):{acc_test:.3f}')

85.png

Ausgabe


Richtige Antwortrate (Lerndaten): 0.645
Richtige Antwortrate (Bewertungsdaten): 0.634

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.

  • Anzahl der Dimensionen zum Einbetten von Wörtern: $ d_w $

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 (z. B. $ \ 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 wird erhalten. Wenn $ c [i] $ den Wert der $ i $ -ten Dimension des Vektors $ c $ darstellt, wird das Maximalwert-Pooling durch die folgende Gleichung ausgedrückt.

>```math
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.

 Implementieren Sie das angegebene Netzwerk.
 Berechnen Sie nach der eingebetteten Schicht die Faltung mit `` `nn.Conv2d```. Der Maximalwert wird in der Serienlängenrichtung mit `` `max_pool``` erfasst, und die Vektoren werden in diesem Teil in Satzeinheiten aggregiert.

```python
from torch.nn import functional as F

class CNN(nn.Module):
  def __init__(self, vocab_size, emb_size, padding_idx, output_size, out_channels, kernel_heights, stride, padding, emb_weights=None):
    super().__init__()
    if emb_weights != None:  #Geben Sie das Gewicht der eingebetteten Ebene an, falls angegeben_Mit Gewichten initialisieren
      self.emb = nn.Embedding.from_pretrained(emb_weights, padding_idx=padding_idx)
    else:
      self.emb = nn.Embedding(vocab_size, emb_size, padding_idx=padding_idx)
    self.conv = nn.Conv2d(1, out_channels, (kernel_heights, emb_size), stride, (padding, 0))
    self.drop = nn.Dropout(0.3)
    self.fc = nn.Linear(out_channels, output_size)
    
  def forward(self, x):
    # x.size() = (batch_size, seq_len)
    emb = self.emb(x).unsqueeze(1)
    # emb.size() = (batch_size, 1, seq_len, emb_size)
    conv = self.conv(emb)
    # conv.size() = (batch_size, out_channels, seq_len, 1)
    act = F.relu(conv.squeeze(3))
    # act.size() = (batch_size, out_channels, seq_len)
    max_pool = F.max_pool1d(act, act.size()[2])
    # max_pool.size() = (batch_size, out_channels, 1) -> seq_Holen Sie sich den Maximalwert in Len-Richtung
    out = self.fc(self.drop(max_pool.squeeze(2)))
    # out.size() = (batch_size, output_size)
    return out
#Parametereinstellungen
VOCAB_SIZE = len(set(word2id.values())) + 1
EMB_SIZE = 300
PADDING_IDX = len(set(word2id.values()))
OUTPUT_SIZE = 4
OUT_CHANNELS = 100
KERNEL_HEIGHTS = 3
STRIDE = 1
PADDING = 1

#Modelldefinition
model = CNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, OUT_CHANNELS, KERNEL_HEIGHTS, STRIDE, PADDING, emb_weights=weights)

#Holen Sie sich die ersten 10 vorhergesagten Werte
for i in range(10):
  X = dataset_train[i]['inputs']
  print(torch.softmax(model(X.unsqueeze(0)), dim=-1))

Ausgabe


tensor([[0.2607, 0.2267, 0.2121, 0.3006]], grad_fn=<SoftmaxBackward>)
tensor([[0.2349, 0.2660, 0.2462, 0.2529]], grad_fn=<SoftmaxBackward>)
tensor([[0.2305, 0.2649, 0.2099, 0.2948]], grad_fn=<SoftmaxBackward>)
tensor([[0.2569, 0.2409, 0.2418, 0.2604]], grad_fn=<SoftmaxBackward>)
tensor([[0.2610, 0.2149, 0.2355, 0.2886]], grad_fn=<SoftmaxBackward>)
tensor([[0.2627, 0.2363, 0.2388, 0.2622]], grad_fn=<SoftmaxBackward>)
tensor([[0.2694, 0.2434, 0.2224, 0.2648]], grad_fn=<SoftmaxBackward>)
tensor([[0.2423, 0.2465, 0.2365, 0.2747]], grad_fn=<SoftmaxBackward>)
tensor([[0.2591, 0.2695, 0.2468, 0.2246]], grad_fn=<SoftmaxBackward>)
tensor([[0.2794, 0.2465, 0.2234, 0.2507]], grad_fn=<SoftmaxBackward>)

87. Lernen von CNN durch probabilistische Gradientenabstiegsmethode

Lernen Sie das in Aufgabe 86 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).

#Parametereinstellungen
VOCAB_SIZE = len(set(word2id.values())) + 1
EMB_SIZE = 300
PADDING_IDX = len(set(word2id.values()))
OUTPUT_SIZE = 4
OUT_CHANNELS = 100
KERNEL_HEIGHTS = 3
STRIDE = 1
PADDING = 1
LEARNING_RATE = 5e-2
BATCH_SIZE = 64
NUM_EPOCHS = 10

#Modelldefinition
model = CNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, OUT_CHANNELS, KERNEL_HEIGHTS, STRIDE, PADDING, emb_weights=weights)

#Definition der Verlustfunktion
criterion = nn.CrossEntropyLoss()

#Optimierungsdefinition
optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE)

#Gerätespezifikation
device = torch.device('cuda')

#Modelllernen
log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS, collate_fn=Padsequence(PADDING_IDX), device=device)

Ausgabe


epoch: 1, loss_train: 1.0671, accuracy_train: 0.5543, loss_valid: 1.0744, accuracy_valid: 0.5726, 12.9214sec
epoch: 2, loss_train: 0.9891, accuracy_train: 0.6594, loss_valid: 1.0148, accuracy_valid: 0.6452, 12.6483sec
epoch: 3, loss_train: 0.9098, accuracy_train: 0.6928, loss_valid: 0.9470, accuracy_valid: 0.6729, 12.7305sec
epoch: 4, loss_train: 0.8481, accuracy_train: 0.7139, loss_valid: 0.8956, accuracy_valid: 0.7028, 12.7967sec
epoch: 5, loss_train: 0.8055, accuracy_train: 0.7250, loss_valid: 0.8634, accuracy_valid: 0.7096, 12.6543sec
epoch: 6, loss_train: 0.7728, accuracy_train: 0.7361, loss_valid: 0.8425, accuracy_valid: 0.7141, 12.7423sec
epoch: 7, loss_train: 0.7527, accuracy_train: 0.7396, loss_valid: 0.8307, accuracy_valid: 0.7216, 12.6718sec
epoch: 8, loss_train: 0.7403, accuracy_train: 0.7432, loss_valid: 0.8227, accuracy_valid: 0.7246, 12.5854sec
epoch: 9, loss_train: 0.7346, accuracy_train: 0.7447, loss_valid: 0.8177, accuracy_valid: 0.7216, 12.4846sec
epoch: 10, loss_train: 0.7331, accuracy_train: 0.7448, loss_valid: 0.8167, accuracy_valid: 0.7231, 12.7443sec
#Protokollvisualisierung
visualize_logs(log)

#Berechnung der richtigen Antwortrate
_, acc_train = calculate_loss_and_accuracy(model, dataset_train, device)
_, acc_test = calculate_loss_and_accuracy(model, dataset_test, device)
print(f'Richtige Antwortrate (Lerndaten):{acc_train:.3f}')
print(f'Richtige Antwortrate (Bewertungsdaten):{acc_test:.3f}')

87.png

Ausgabe


Richtige Antwortrate (Lerndaten): 0.745
Richtige Antwortrate (Bewertungsdaten): 0.719

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.

Dieses Mal werde ich ein Netzwerk ausprobieren, das das in Convolutional Neural Networks for Satzklassifizierung vorgeschlagene TextCNN vereinfacht. In der vorherigen Frage hat CNN nur Filter mit einer Breite von 3 gelernt, aber dieses Netzwerk verwendet Filter mit drei Breiten von 2, 3 und 4.

from torch.nn import functional as F

class textCNN(nn.Module):
  def __init__(self, vocab_size, emb_size, padding_idx, output_size, out_channels, conv_params, drop_rate, emb_weights=None):
    super().__init__()
    if emb_weights != None:  #Geben Sie das Gewicht der eingebetteten Ebene an, falls angegeben_Mit Gewichten initialisieren
      self.emb = nn.Embedding.from_pretrained(emb_weights, padding_idx=padding_idx)
    else:
      self.emb = nn.Embedding(vocab_size, emb_size, padding_idx=padding_idx)
    self.convs = nn.ModuleList([nn.Conv2d(1, out_channels, (kernel_height, emb_size), padding=(padding, 0)) for kernel_height, padding in conv_params])
    self.drop = nn.Dropout(drop_rate)
    self.fc = nn.Linear(len(conv_params) * out_channels, output_size)
    
  def forward(self, x):
    # x.size() = (batch_size, seq_len)
    emb = self.emb(x).unsqueeze(1)
    # emb.size() = (batch_size, 1, seq_len, emb_size)
    conv = [F.relu(conv(emb)).squeeze(3) for i, conv in enumerate(self.convs)]
    # conv[i].size() = (batch_size, out_channels, seq_len + padding * 2 - kernel_height + 1)
    max_pool = [F.max_pool1d(i, i.size(2)) for i in conv]
    # max_pool[i].size() = (batch_size, out_channels, 1) -> seq_Holen Sie sich den Maximalwert in Len-Richtung
    max_pool_cat = torch.cat(max_pool, 1)
    # max_pool_cat.size() = (batch_size, len(conv_params) * out_channels, 1)  ->Kombinieren Sie die Ergebnisse nach Filter
    out = self.fc(self.drop(max_pool_cat.squeeze(2)))
    # out.size() = (batch_size, output_size)
    return out

Verwenden Sie auch optuna für die Parameteroptimierung wie in Kapitel 6.

!pip install optuna
import optuna

def objective(trial):
  #Parametersatz, der eingestellt werden soll
  emb_size = int(trial.suggest_discrete_uniform('emb_size', 100, 400, 100))
  out_channels = int(trial.suggest_discrete_uniform('out_channels', 50, 200, 50))
  drop_rate = trial.suggest_discrete_uniform('drop_rate', 0.0, 0.5, 0.1)
  learning_rate = trial.suggest_loguniform('learning_rate', 5e-4, 5e-2)
  momentum = trial.suggest_discrete_uniform('momentum', 0.5, 0.9, 0.1)
  batch_size = int(trial.suggest_discrete_uniform('batch_size', 16, 128, 16))

  #Feste Parametereinstellungen
  VOCAB_SIZE = len(set(word2id.values())) + 1
  PADDING_IDX = len(set(word2id.values()))
  OUTPUT_SIZE = 4
  CONV_PARAMS = [[2, 0], [3, 1], [4, 2]]
  NUM_EPOCHS = 30

  #Modelldefinition
  model = textCNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, out_channels, CONV_PARAMS, drop_rate, emb_weights=weights)

  #Definition der Verlustfunktion
  criterion = nn.CrossEntropyLoss()

  #Optimierungsdefinition
  optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate, momentum=momentum)

  #Gerätespezifikation
  device = torch.device('cuda')

  #Modelllernen
  log = train_model(dataset_train, dataset_valid, batch_size, model, criterion, optimizer, NUM_EPOCHS, collate_fn=Padsequence(PADDING_IDX), device=device)

  #Verlustberechnung
  loss_valid, _ = calculate_loss_and_accuracy(model, dataset_valid, device, criterion=criterion) 

  return loss_valid 

Führt eine Parametersuche durch.

#Optimierung
study = optuna.create_study()
study.optimize(objective, timeout=7200)

#Ergebnisse anzeigen
print('Best trial:')
trial = study.best_trial
print('  Value: {:.3f}'.format(trial.value))
print('  Params: ')
for key, value in trial.params.items():
  print('    {}: {}'.format(key, value))

Ausgabe


Best trial:
  Value: 0.469
  Params: 
    emb_size: 300.0
    out_channels: 100.0
    drop_rate: 0.4
    learning_rate: 0.013345934577557608
    momentum: 0.8
    batch_size: 32.0

Trainieren Sie das Modell mit den gesuchten Parametern.

#Parametereinstellungen
VOCAB_SIZE = len(set(word2id.values())) + 1
EMB_SIZE = int(trial.params['emb_size'])
PADDING_IDX = len(set(word2id.values()))
OUTPUT_SIZE = 4
OUT_CHANNELS = int(trial.params['out_channels'])
CONV_PARAMS = [[2, 0], [3, 1], [4, 2]]
DROP_RATE = trial.params['drop_rate']
LEARNING_RATE = trial.params['learning_rate']
BATCH_SIZE = int(trial.params['batch_size'])
NUM_EPOCHS = 30

#Modelldefinition
model = textCNN(VOCAB_SIZE, EMB_SIZE, PADDING_IDX, OUTPUT_SIZE, OUT_CHANNELS, CONV_PARAMS, DROP_RATE, emb_weights=weights)
print(model)

#Definition der Verlustfunktion
criterion = nn.CrossEntropyLoss()

#Optimierungsdefinition
optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE, momentum=0.9)

#Gerätespezifikation
device = torch.device('cuda')

#Modelllernen
log = train_model(dataset_train, dataset_valid, BATCH_SIZE, model, criterion, optimizer, NUM_EPOCHS, collate_fn=Padsequence(PADDING_IDX), device=device)

Ausgabe


textCNN(
  (emb): Embedding(9406, 300, padding_idx=9405)
  (convs): ModuleList(
    (0): Conv2d(1, 100, kernel_size=(2, 300), stride=(1, 1))
    (1): Conv2d(1, 100, kernel_size=(3, 300), stride=(1, 1), padding=(1, 0))
    (2): Conv2d(1, 100, kernel_size=(4, 300), stride=(1, 1), padding=(2, 0))
  )
  (drop): Dropout(p=0.4, inplace=False)
  (fc): Linear(in_features=300, out_features=4, bias=True)
)
epoch: 1, loss_train: 0.7908, accuracy_train: 0.7239, loss_valid: 0.8660, accuracy_valid: 0.6901, 12.2279sec
epoch: 2, loss_train: 0.5800, accuracy_train: 0.7944, loss_valid: 0.7384, accuracy_valid: 0.7485, 12.1637sec
epoch: 3, loss_train: 0.3951, accuracy_train: 0.8738, loss_valid: 0.6189, accuracy_valid: 0.7919, 12.1612sec
epoch: 4, loss_train: 0.2713, accuracy_train: 0.9217, loss_valid: 0.5499, accuracy_valid: 0.8136, 12.1877sec
epoch: 5, loss_train: 0.1913, accuracy_train: 0.9593, loss_valid: 0.5176, accuracy_valid: 0.8293, 12.1722sec
epoch: 6, loss_train: 0.1322, accuracy_train: 0.9749, loss_valid: 0.5042, accuracy_valid: 0.8234, 12.4483sec
epoch: 7, loss_train: 0.1033, accuracy_train: 0.9807, loss_valid: 0.4922, accuracy_valid: 0.8323, 12.1556sec
epoch: 8, loss_train: 0.0723, accuracy_train: 0.9943, loss_valid: 0.4900, accuracy_valid: 0.8308, 12.0309sec
epoch: 9, loss_train: 0.0537, accuracy_train: 0.9966, loss_valid: 0.4903, accuracy_valid: 0.8346, 11.9471sec
epoch: 10, loss_train: 0.0414, accuracy_train: 0.9966, loss_valid: 0.4801, accuracy_valid: 0.8421, 11.9275sec
epoch: 11, loss_train: 0.0366, accuracy_train: 0.9978, loss_valid: 0.4943, accuracy_valid: 0.8406, 11.9691sec
epoch: 12, loss_train: 0.0292, accuracy_train: 0.9983, loss_valid: 0.4839, accuracy_valid: 0.8436, 11.9665sec
epoch: 13, loss_train: 0.0271, accuracy_train: 0.9982, loss_valid: 0.5042, accuracy_valid: 0.8421, 11.9634sec
epoch: 14, loss_train: 0.0222, accuracy_train: 0.9986, loss_valid: 0.4912, accuracy_valid: 0.8458, 11.9298sec
epoch: 15, loss_train: 0.0194, accuracy_train: 0.9988, loss_valid: 0.4925, accuracy_valid: 0.8436, 11.9375sec
epoch: 16, loss_train: 0.0176, accuracy_train: 0.9988, loss_valid: 0.5074, accuracy_valid: 0.8451, 11.9333sec
epoch: 17, loss_train: 0.0163, accuracy_train: 0.9991, loss_valid: 0.5124, accuracy_valid: 0.8436, 11.9137sec
#Protokollvisualisierung
visualize_logs(log)

#Berechnung der richtigen Antwortrate
_, acc_train = calculate_loss_and_accuracy(model, dataset_train, device)
_, acc_test = calculate_loss_and_accuracy(model, dataset_test, device)
print(f'Richtige Antwortrate (Lerndaten):{acc_train:.3f}')
print(f'Richtige Antwortrate (Bewertungsdaten):{acc_test:.3f}')

88.png

Ausgabe


Richtige Antwortrate (Lerndaten): 0.999
Richtige Antwortrate (Bewertungsdaten): 0.851

89. Transferlernen aus einem vorgefertigten Sprachmodell

Erstellen Sie ein Modell, das die Überschriften von Nachrichtenartikeln in Kategorien unterteilt, beginnend mit einem vorab trainierten Sprachmodell (z. B. BERT).

[PyTorch] Einführung in die Dokumentklassifizierung mit BERT ist in einem anderen Artikel ausgeschnitten. Hier wird nur das Ergebnis der korrekten Antwortrate angezeigt.

Richtige Antwortrate (Lerndaten): 0.993
Richtige Antwortrate (Bewertungsdaten): 0.948

abschließend

Sprachverarbeitung 100 Klopfen sind so konzipiert, dass Sie nicht nur die Verarbeitung natürlicher Sprache selbst lernen können, sondern auch die grundlegende Datenverarbeitung und das allgemeine maschinelle Lernen. Sogar diejenigen, die maschinelles Lernen in Online-Kursen studieren, können sehr gute Ergebnisse erzielen. Probieren Sie es also bitte aus.

Zur Antwort auf alle 100 Fragen

Recommended Posts

[Sprachverarbeitung 100 Schläge 2020] Kapitel 9: RNN, CNN
100 Sprachverarbeitung Knock 2020 Kapitel 9: RNN, CNN
100 Sprachverarbeitungsklopfen ~ Kapitel 1
100 Sprachverarbeitung klopft Kapitel 2 (10 ~ 19)
100 Sprachverarbeitungsklopfen (2020): 40
100 Sprachverarbeitungsklopfen (2020): 32
100 Sprachverarbeitungsklopfen (2020): 35
[Sprachverarbeitung 100 Schläge 2020] Kapitel 3: Reguläre Ausdrücke
100 Sprachverarbeitungsklopfen (2020): 47
100 Sprachverarbeitungsklopfen (2020): 39
100 Sprachverarbeitungsklopfen (2020): 22
100 Sprachverarbeitungsklopfen (2020): 26
100 Sprachverarbeitungsklopfen (2020): 34
100 Sprachverarbeitungsklopfen 2020: Kapitel 4 (morphologische Analyse)
[Sprachverarbeitung 100 Schläge 2020] Kapitel 5: Abhängigkeitsanalyse
100 Sprachverarbeitungsklopfen (2020): 42
100 Sprachverarbeitungsklopfen (2020): 29
100 Sprachverarbeitungsklopfen (2020): 49
100 Sprachverarbeitungsklopfen 06 ~ 09
100 Sprachverarbeitungsklopfen (2020): 43
100 Sprachverarbeitungsklopfen (2020): 24
[Sprachverarbeitung 100 Schläge 2020] Kapitel 1: Vorbereitende Bewegung
100 Sprachverarbeitungsklopfen (2020): 45
100 Sprachverarbeitungsklopfen (2020): 10-19
[Sprachverarbeitung 100 Schläge 2020] Kapitel 7: Wortvektor
100 Sprachverarbeitungsklopfen (2020): 00-09
100 Sprachverarbeitung klopfen 2020: Kapitel 3 (regulärer Ausdruck)
100 Sprachverarbeitungsklopfen (2020): 31
[Sprachverarbeitung 100 Schläge 2020] Kapitel 8: Neuronales Netz
100 Sprachverarbeitungsklopfen (2020): 48
[Sprachverarbeitung 100 Schläge 2020] Kapitel 2: UNIX-Befehle
100 Sprachverarbeitungsklopfen (2020): 44
100 Sprachverarbeitungsklopfen (2020): 41
100 Sprachverarbeitungsklopfen (2020): 37
100 Sprachverarbeitungsklopfen (2020): 25
100 Sprachverarbeitungsklopfen (2020): 23
100 Sprachverarbeitungsklopfen (2020): 33
100 Sprachverarbeitungsklopfen (2020): 20
100 Sprachverarbeitungsklopfen (2020): 27
[Sprachverarbeitung 100 Schläge 2020] Kapitel 4: Morphologische Analyse
100 Sprachverarbeitungsklopfen (2020): 46
100 Sprachverarbeitungsklopfen (2020): 21
100 Sprachverarbeitungsklopfen (2020): 36
Sprachverarbeitung 100 Schläge Kapitel 4: Morphologische Analyse 31. Verben
100 Amateur-Sprachverarbeitungsklopfen: 41
100 Amateur-Sprachverarbeitungsklopfen: 71
100 Amateur-Sprachverarbeitungsklopfen: 56
100 Amateur-Sprachverarbeitungsklopfen: 24
100 Amateur-Sprachverarbeitungsklopfen: 50
100 Amateur-Sprachverarbeitungsklopfen: 59
100 Amateur-Sprachverarbeitungsklopfen: 62
100 Amateur-Sprachverarbeitungsklopfen: 60
100 Sprachverarbeitung Knock 2020 Kapitel 1
100 Amateur-Sprachverarbeitungsklopfen: 92
100 Amateur-Sprachverarbeitungsklopfen: 30
100 Amateur-Sprachverarbeitungsklopfen: 06
100 Amateur-Sprachverarbeitungsklopfen: 84
100 Amateur-Sprachverarbeitungsklopfen: 81
100 Amateur-Sprachverarbeitungsklopfen: 33
100 Amateur-Sprachverarbeitungsklopfen: 46
100 Amateur-Sprachverarbeitungsklopfen: 88