Dieser Artikel ist der Artikel zum 25. Tag von Pytorch Adventskalender 2019!
In Letztes Mal haben wir Attention im Encoder-Decoder-Modell implementiert, diesmal jedoch die Satzklassifizierung in Self Attention.
Die eingebetteten Ausdrücke von Sätzen in Self Attention werden in den folgenden Abhandlungen vorgestellt und auch in der berühmten Abhandlung "Attention Is All You Need" von Transformer zitiert.
Dieser Artikel implementiert die in diesem Dokument eingeführte Selbstaufmerksamkeit.
In Bezug auf die Implementierung habe ich auf den folgenden Artikel Fast Marupakuri </ s> verwiesen.
Darüber hinaus verwenden wir torchtext, das zur bequemen Vorverarbeitung für die Implementierung verwendet werden kann. Für torchtext habe ich jedoch auch auf den folgenden Artikel derselben Person verwiesen.
② Einfache und tiefe Verarbeitung natürlicher Sprache mit torchtext
Der Mechanismus dieses Papiers wird in Referenz (1) kurz erläutert, der Algorithmus ist jedoch grob in die folgenden drei Schritte unterteilt.
Hier sind $ d_a $ und $ r $ bei der Berechnung der Aufmerksamkeit Hyperparameter. $ d_a $ stellt die Größe der Gewichtsmatrix dar, wenn die Aufmerksamkeit mit einem neuronalen Netzwerk vorhergesagt wird, und $ r $ ist ein Parameter, der der Anzahl der gestapelten Aufmerksamkeitsebenen entspricht.
Die Idee ist sehr einfach. Es geht darum, das Neuronale Netz lernen zu lassen, welche Wörter bei der Klassifizierung von Sätzen hervorgehoben (gewichtet) werden sollen.
Dann werden wir den obigen Mechanismus mit PyTorch implementieren. Die zu lösende Aufgabe ist das negative / positive Urteil der IMDb-Filmkritik. Die Daten können von folgenden heruntergeladen werden.
# torchtext
import torchtext
from torchtext import data
from torchtext import datasets
from torchtext.vocab import GloVe
from torchtext.vocab import Vectors
# pytorch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torch
#Andere
import os
import pickle
import numpy as np
import pandas as pd
from itertools import chain
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
#Schließlich wird es verwendet, um die Aufmerksamkeit zu visualisieren.
import itertools
import random
from IPython.display import display, HTML
#Zur Vorverarbeitung durch nltk
import re
import nltk
from nltk import stem
nltk.download('punkt')
#Vorbereiteter morphologischer Motor von nltk
def nltk_analyzer(text):
stemmer = stem.LancasterStemmer()
text = re.sub(re.compile(r'[!-\/:-@[-`{-~]'), ' ', text)
text = stemmer.stem(text)
text = text.replace('\n', '') #Zeilenumbrüche löschen
text = text.replace('\t', '') #Registerkarte löschen
morph = nltk.word_tokenize(text)
return morph
Die Vorbereitung der Daten war beispielsweise wie folgt.
train_pos_dir = 'aclImdb/train/pos/'
train_neg_dir = 'aclImdb/train/neg/'
test_pos_dir = 'aclImdb/test/pos/'
test_neg_dir = 'aclImdb/test/neg/'
header = ['text', 'label', 'label_id']
train_pos_files = os.listdir(train_pos_dir)
train_neg_files = os.listdir(train_neg_dir)
test_pos_files = os.listdir(test_pos_dir)
test_neg_files = os.listdir(test_neg_dir)
def make_row(root_dir, files, label, idx):
row = []
for file in files:
tmp = []
with open(root_dir + file, 'r') as f:
text = f.read()
tmp.append(text)
tmp.append(label)
tmp.append(idx)
row.append(tmp)
return row
row = make_row(train_pos_dir, train_pos_files, 'pos', 0)
row += make_row(train_neg_dir, train_neg_files, 'neg', 1)
train_df = pd.DataFrame(row, columns=header)
row = make_row(test_pos_dir, test_pos_files, 'pos', 0)
row += make_row(test_neg_dir, test_neg_files, 'neg', 1)
test_df = pd.DataFrame(row, columns=header)
Bereiten Sie die Daten wie oben beschrieben vor und erstellen Sie schließlich den folgenden Datenrahmen (während Sie die Beschriftungsspalte löschen, da sie nicht einmal benötigt wird).
train_df = pd.read_csv(imdb_dir + 'train.tsv', delimiter="\t", header=None)
train_df
# train.tsv, test.Setzen Sie tsv hier
imdb_dir = "drive/My Drive/Colab Notebooks/imdb_datasets/"
# glove.6B.200d.Geben Sie hier txt ein
word_embedding_dir = "drive/My Drive/Colab Notebooks/word_embedding_models/"
TEXT = data.Field(sequential=True, tokenize=nltk_analyzer, lower=True, include_lengths=True, batch_first=True)
LABEL = data.Field(sequential=False, use_vocab=False, is_target=True)
train, test = data.TabularDataset.splits(
path=imdb_dir, train='train.tsv', test='test.tsv', format='tsv',
fields=[('Text', TEXT), ('Label', LABEL)])
glove_vectors = Vectors(name=word_embedding_dir + "glove.6B.200d.txt")
TEXT.build_vocab(train, test, vectors=glove_vectors, min_freq=1)
――Es gibt keinen besonderen Grund, aber ich habe die folgenden Parameter verwendet. ――Achtungsebene Die folgende Abbildung zeigt 3 Ebenen.
#Ich möchte GPU verwenden
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
BATCH_SIZE = 100 #Chargengröße
EMBEDDING_DIM = 200 #Anzahl der eingebetteten Dimensionen von Wörtern
LSTM_DIM = 128 #Anzahl der Dimensionen der verborgenen Schicht von LSTM
VOCAB_SIZE =TEXT.vocab.vectors.size()[0] #Gesamtzahl der Wörter
TAG_SIZE = 2 #Dieses Mal werden wir eine negative / positive Beurteilung abgeben, sodass die endgültige Größe des Netzwerks 2 beträgt.
DA = 64 #Größe der Gewichtsmatrix bei der Berechnung der Aufmerksamkeit mit dem neuronalen Netz
R = 3 #Aufmerksamkeit in 3 Ebenen anzeigen
Bidirectional LSTM
class BiLSTMEncoder(nn.Module):
def __init__(self, embedding_dim, lstm_dim, vocab_size):
super(BiLSTMEncoder, self).__init__()
self.lstm_dim = lstm_dim
self.word_embeddings = nn.Embedding(vocab_size, embedding_dim)
#Stellen Sie den gelernten Wortvektor als Einbettung ein
self.word_embeddings.weight.data.copy_(TEXT.vocab.vectors)
#Erfordert, um zu verhindern, dass der Wortvektor durch Fehler-Backpropagation aktualisiert wird_Setze grad auf False
self.word_embeddings.requires_grad_ = False
# bidirectional=Richtig und einfach, bidirektionales LSTM zu erstellen
self.bilstm = nn.LSTM(embedding_dim, lstm_dim, batch_first=True, bidirectional=True)
def forward(self, text):
embeds = self.word_embeddings(text)
#Erhalten Sie den ersten Rückgabewert, da wir den Vektor jeder verborgenen Ebene wollen
out, _ = self.bilstm(embeds)
#Gibt die vorderen und hinteren verborgenen Ebenenvektoren zurück, wenn sie kombiniert werden
return out
class SelfAttention(nn.Module):
def __init__(self, lstm_dim, da, r):
super(SelfAttention, self).__init__()
self.lstm_dim = lstm_dim
self.da = da
self.r = r
self.main = nn.Sequential(
#Da es bidirektional ist, wird die Größe des Vektors jeder verborgenen Schicht in der Größe verdoppelt.
nn.Linear(lstm_dim * 2, da),
nn.Tanh(),
nn.Linear(da, r)
)
def forward(self, out):
return F.softmax(self.main(out), dim=1)
class SelfAttentionClassifier(nn.Module):
def __init__(self, lstm_dim, da, r, tagset_size):
super(SelfAttentionClassifier, self).__init__()
self.lstm_dim = lstm_dim
self.r = r
self.attn = SelfAttention(lstm_dim, da, r)
self.main = nn.Linear(lstm_dim * 6, tagset_size)
def forward(self, out):
attention_weight = self.attn(out)
m1 = (out * attention_weight[:,:,0].unsqueeze(2)).sum(dim=1)
m2 = (out * attention_weight[:,:,1].unsqueeze(2)).sum(dim=1)
m3 = (out * attention_weight[:,:,2].unsqueeze(2)).sum(dim=1)
feats = torch.cat([m1, m2, m3], dim=1)
return F.log_softmax(self.main(feats)), attention_weight
encoder = BiLSTMEncoder(EMBEDDING_DIM, LSTM_DIM, VOCAB_SIZE).to(device)
classifier = SelfAttentionClassifier(LSTM_DIM, DA, R, TAG_SIZE).to(device)
loss_function = nn.NLLLoss()
#Wenn Sie mehrere Modelle aus der itertools-Importkette einschließen, können Sie Optimierer zu einem kombinieren.
optimizer = optim.Adam(chain(encoder.parameters(), classifier.parameters()), lr=0.001)
train_iter, test_iter = data.Iterator.splits((train, test), batch_sizes=(BATCH_SIZE, BATCH_SIZE), device=device, repeat=False, sort=False)
»Vorerst habe ich versucht, mit Epoche 10 zu lernen.
losses = []
for epoch in range(10):
all_loss = 0
for idx, batch in enumerate(train_iter):
batch_loss = 0
encoder.zero_grad()
classifier.zero_grad()
text_tensor = batch.Text[0]
label_tensor = batch.Label
out = encoder(text_tensor)
score, attn = classifier(out)
batch_loss = loss_function(score, label_tensor)
batch_loss.backward()
optimizer.step()
all_loss += batch_loss.item()
print("epoch", epoch, "\t" , "loss", all_loss)
#epoch 0 loss 97.37978366017342
#epoch 1 loss 50.07680431008339
#epoch 2 loss 27.79373042844236
#epoch 3 loss 9.353876578621566
#epoch 4 loss 1.9509600398596376
#epoch 5 loss 0.22650832029466983
#epoch 6 loss 0.021685686125238135
#epoch 7 loss 0.011305359620109812
#epoch 8 loss 0.007448446772286843
#epoch 9 loss 0.005398457038154447
――Ich glaube nicht, dass die Genauigkeit besser ist als ich erwartet hatte ... --Referenz (1) besagt, dass die Genauigkeit etwa 90% betrug, und es scheint, dass es in einigen verschiedenen Implementierungen verschiedene Fehlzündungen gibt ...
answer = []
prediction = []
with torch.no_grad():
for batch in test_iter:
text_tensor = batch.Text[0]
label_tensor = batch.Label
out = encoder(text_tensor)
score, _ = classifier(out)
_, pred = torch.max(score, 1)
prediction += list(pred.cpu().numpy())
answer += list(label_tensor.cpu().numpy())
print(classification_report(prediction, answer, target_names=['positive', 'negative']))
# precision recall f1-score support
#
# positive 0.86 0.88 0.87 12103
# negative 0.89 0.86 0.87 12897
#
# accuracy 0.87 25000
# macro avg 0.87 0.87 0.87 25000
#weighted avg 0.87 0.87 0.87 25000
def highlight(word, attn):
html_color = '#%02X%02X%02X' % (255, int(255*(1 - attn)), int(255*(1 - attn)))
return '<span style="background-color: {}">{}</span>'.format(html_color, word)
def mk_html(sentence, attns):
html = ""
for word, attn in zip(sentence, attns):
html += ' ' + highlight(
TEXT.vocab.itos[word],
attn
)
return html
id2ans = {'0': 'positive', '1':'negative'}
_, test_iter = data.Iterator.splits((train, test), batch_sizes=(1, 1), device=device, repeat=False, sort=False)
n = random.randrange(len(test_iter))
for batch in itertools.islice(test_iter, n-1,n):
x = batch.Text[0]
y = batch.Label
encoder_outputs = encoder(x)
output, attn = classifier(encoder_outputs)
pred = output.data.max(1, keepdim=True)[1]
display(HTML('[Richtige Antwort]' + id2ans[str(y.item())] + '\t [Vorhersage]' + id2ans[str(pred.item())] + '<br><br>'))
for i in range(attn.size()[2]):
display(HTML(mk_html(x.data[0], attn.data[0,:,i]) + '<br><br>'))
Es tut mir leid, dass es winzig ist, aber wenn Sie es sich vorstellen, sieht es so aus. Es werden drei gleiche Sätze angezeigt, aber da es drei Aufmerksamkeitsebenen gibt, zeigt jede Ebene, welches Wort Aufmerksamkeit ist. Der Grad der Aufmerksamkeit von Wörtern ist in jeder Aufmerksamkeitsebene leicht unterschiedlich, aber es scheint, dass sie fast auf die gleiche Weise anziehen.
Übrigens, als ich dieses negative / positive Urteil nur mit bidirektionalem LSTM ohne Selbstaufmerksamkeit löste, betrug die Genauigkeit etwa 79,4%.
class BiLSTMEncoder(nn.Module):
def __init__(self, embedding_dim, lstm_dim, vocab_size, tagset_size):
super(BiLSTMEncoder, self).__init__()
self.lstm_dim = lstm_dim
self.word_embeddings = nn.Embedding(vocab_size, embedding_dim)
self.word_embeddings.weight.data.copy_(TEXT.vocab.vectors)
self.word_embeddings.requires_grad_ = False
self.bilstm = nn.LSTM(embedding_dim, lstm_dim, batch_first=True, bidirectional=True)
self.hidden2tag = nn.Linear(lstm_dim * 2, tagset_size)
self.softmax = nn.LogSoftmax()
def forward(self, text):
embeds = self.word_embeddings(text)
_, bilstm_hc = self.bilstm(embeds)
bilstm_out = torch.cat([bilstm_hc[0][0], bilstm_hc[0][1]], dim=1)
tag_space = self.hidden2tag(bilstm_out)
tag_scores = self.softmax(tag_space.squeeze())
return tag_scores
――Ich bin ein wenig besorgt über das Muster, das die Aufmerksamkeit lexikografisch berechnet, wie es in Transformer usw. implementiert ist (das die Einbettung von Wörtern in Abfrage, Schlüssel, Wert unterteilt), und über das neuronale Netzwerk dieses Dokuments, um die Aufmerksamkeit vorherzusagen. Ich verstehe den Unterschied in den Mustern nicht wirklich. Bevor ich dieses Papier kannte, dachte ich, wenn ich zu Attention gehen würde, wäre es eine Menge Arbeit, das innere Produkt zu erhalten, also frage ich mich, ob es verschiedene Berechnungsmethoden für Attention gibt. ――Nächste, ich möchte etwas über Transformer schreiben!
Ende
Recommended Posts