In Sequence to Sequence (Seq2Seq), einer Art EncoderDecoder-Modell, Das Aufmerksamkeitsmodell wird vorgestellt und seine Implementierungs- und Verifizierungsergebnisse werden erläutert.
Letztes Mal http://qiita.com/kenchin110100/items/b34f5106d5a211f4c004 Ich habe das Seq2Seq-Modell (Sequence to Sequence) mit Chainer implementiert. Dieses Mal habe ich diesem Modell ein Aufmerksamkeitsmodell hinzugefügt.
Im Folgenden werden das Aufmerksamkeitsmodell, seine Implementierungsmethode und die Überprüfungsergebnisse erläutert.
Attention Model
Durch Verwendung eines RNN-Netzwerks wie LSTM können Seriendaten wie Anweisungen in Merkmalsvektoren konvertiert werden.
Es ist jedoch weniger wahrscheinlich, dass die anfänglich eingegebenen Daten im endgültigen Ausgabe-Merkmalsvektor wiedergegeben werden.
Mit anderen Worten, der Satz "Mama hat sich geschminkt und einen Rock angezogen und ist in die Stadt gegangen" und der Satz "Papa hat sich geschminkt und einen Rock angezogen und ist in die Stadt gegangen" wurden fast zum gleichen Merkmalsvektor. Es bedeutet, dass es enden wird.
Das Aufmerksamkeitsmodell ist ein Mechanismus, der die zu Beginn eingegebenen Daten richtig berücksichtigt.
Sequence to Sequence with Attention Model
Die folgende Abbildung zeigt den Berechnungsablauf des zuletzt implementierten Seq2Seq-Modells.
Sequence to Sequence |
---|
(Die Abbildung unterscheidet sich geringfügig von der vorherigen)
Der blaue Teil ist der Codierer, der die Äußerung vektorisiert, und der rote Teil ist der Decodierer, der die Antwort vom Vektor ausgibt.
Wenn Sie das Aufmerksamkeitsmodell hinzufügen, sieht es wie in der folgenden Abbildung aus.
Sequence to Sequence with Attention model |
---|
Es wird ein wenig kompliziert sein, aber der Ort, an dem [At] in der Abbildung geschrieben ist, ist das Aufmerksamkeitsmodell.
Auf der Encoderseite wird der Zwischenvektor, der jedes Mal ausgegeben wird, im Aufmerksamkeitsmodell gespeichert.
Geben Sie auf der Decoderseite den vorherigen Zwischenvektor in das Aufmerksamkeitsmodell ein. Basierend auf dem Eingabevektor nimmt das Aufmerksamkeitsmodell den gewichteten Durchschnitt der Zwischenvektoreingabe auf der Encoderseite und kehrt zurück.
Durch Eingabe des gewichteten Durchschnitts des Zwischenvektors von Encoder in Decoder ermöglicht das Aufmerksamkeitsmodell, das Wort vor, das Wort nach und überall zu beachten.
Es gibt zwei Haupttypen von Aufmerksamkeitsmodellen: Globale Aufmerksamkeit und Lokale Aufmerksamkeit.
Im Folgenden werden globale Aufmerksamkeit und lokale Aufmerksamkeit erläutert.
Global Attention
Das folgende Papier schlägt Global Attention vor.
Bahdanau, Dzmitry, Kyunghyun Cho, and Yoshua Bengio. "Neural machine translation by jointly learning to align and translate." arXiv preprint arXiv:1409.0473 (2014).
Ursprünglich wurde es in der maschinellen Übersetzung verwendet.
Das Material, das Global Attention erklärt, ist https://www.slideshare.net/yutakikuchi927/deep-learning-nlp-attention Ist leicht zu verstehen.
Es wird in Englisch sein, https://talbaumel.github.io/attention/ Ist auch leicht zu verstehen.
Der Mechanismus zum Ermitteln des gewichteten Durchschnitts der Zwischenvektoreingabe auf der Encoderseite ist unten gezeigt.
Global Attention |
---|
Die Figur betrachtet einen Zustand, in dem drei Vektoren [Zwischenvektor 1], [Zwischenvektor 2] und [Zwischenvektor 3] auf der Codiererseite eingegeben werden.
In der Figur sind [eh] und [hh] lineare Kopplungsschichten, die den Vektor der Größe der verborgenen Schicht aus dem Vektor der Größe der verborgenen Schicht ausgeben, [+] ist die Addition der Vektoren und [×] ist die Multiplikation jedes Elements des Vektors. Ich bin.
[tanh] ist eine hyperbolische Tangente, die die Elemente eines Vektors von -1 nach 1 transformiert.
[hw] ist eine lineare Kopplungsschicht, die einen Skalar der Größe 1 aus der Größe der verborgenen Schicht ausgibt.
[soft max] ist eine SoftMax-Funktion, die die eingegebenen Werte so normalisiert, dass die Summe 1 ist.
Der durch [Soft max] berechnete Wert wird als Gewicht des gewichteten Durchschnitts verwendet, und das Ergebnis der Verwendung des gewichteten Durchschnitts des Zwischenvektors wird ausgegeben.
So funktioniert Global Attention.
Local Attention
Die Papiere, für die lokale Aufmerksamkeit vorgeschlagen wurde, sind wie folgt
Luong, Minh-Thang, Hieu Pham, and Christopher D. Manning. "Effective approaches to attention-based neural machine translation." arXiv preprint arXiv:1508.04025 (2015).
Dies ist auch ein maschinelles Übersetzungspapier.
Das Referenzmaterial ist https://www.slideshare.net/yutakikuchi927/deep-learning-nlp-attention ist.
Unten finden Sie ein Berechnungsflussdiagramm der lokalen Aufmerksamkeit.
Local Attention |
---|
Weitere Netzwerke wurden zu Global Attention hinzugefügt.
Der Hauptunterschied ist das Netzwerk auf der rechten Seite.
[ht] ist eine lineare Verknüpfung, die einen verborgenen Schichtgrößenvektor aus einem verborgenen Schichtgrößenvektor ausgibt, und [tanh] skaliert die Elemente des Vektors wie zuvor von -1 auf 1.
[tw] ist eine linear gekoppelte Schicht, die einen verborgenen Schichtgrößenvektor in einen Skalar umwandelt, und [sigmoid] ist eine Sigmoidfunktion, die den Eingabewert von 0 auf 1 skaliert. Daher ist der bisher eingegebene Vektor ein Skalar im Bereich von 0 bis 1.
Als nächstes werde ich in der Abbildung erklären, was Sie mit [ga] machen. Die Berechnung von ga ist wie folgt.
output = \exp\bigl(-\frac{(s - input * Len(S))^2}{\sigma^2}\bigl)
Während $ input $ ein von 0 bis 1 skalierter Skalar ist, ist $ Len (S) $ die Anzahl der vom Codierer eingegebenen Zwischenvektoren und $ s $ die Reihenfolge der Zwischenschichtvektoren ([Zwischenvektor 1]]. Wenn es 1 ist, repräsentiert es 2) wenn es [Zwischenvektor 2] ist.
Wenn der von der Sigmoidfunktion ausgegebene Wert 0,1 ist, ist [ga] ein großer Wert, wenn der Zwischenvektor 1 ist, und ein kleiner Wert, wenn der Zwischenvektor 3 ist.
Durch Multiplizieren dieser Ausgabe mit dem von Global Attention berechneten Gewicht ist es möglich, sich stärker auf einen bestimmten Zwischenvektor zu konzentrieren.
Ich habe es wie zuvor mit Chainer implementiert. Der Encoder-Teil ist der gleiche wie für Seq2Seq.
Der Code, auf den ich mich bezog, stammt von oda. Vielen Dank. https://github.com/odashi/chainer_examples
Attention
Ich habe Global Attention implementiert. Der Code lautet wie folgt
attention.py
class Attention(Chain):
def __init__(self, hidden_size, flag_gpu):
"""
Aufmerksamkeitsinstanziierung
:param hidden_size:Versteckte Ebenengröße
:param flag_gpu:Gibt an, ob eine GPU verwendet werden soll
"""
super(Attention, self).__init__(
#Eine lineare Kopplungsschicht, die den Vorwärtscodierer-Zwischenvektor in einen verborgenen Schichtgrößenvektor umwandelt
fh=links.Linear(hidden_size, hidden_size),
#Eine lineare Kopplungsschicht, die den Zwischenvektor des Rückwärtscodierers in einen verborgenen Schichtgrößenvektor umwandelt
bh=links.Linear(hidden_size, hidden_size),
#Eine lineare Kopplungsschicht, die den Zwischenvektor des Decoders in einen versteckten Schichtgrößenvektor umwandelt
hh=links.Linear(hidden_size, hidden_size),
#Lineare Kopplungsschicht zum Konvertieren versteckter Schichtgrößenvektoren in Skalare
hw=links.Linear(hidden_size, 1),
)
#Merken Sie sich die Größe der ausgeblendeten Ebene
self.hidden_size = hidden_size
#Verwenden Sie numpy, wenn Sie bei Verwendung der GPU kein cupy verwenden
if flag_gpu:
self.ARR = cuda.cupy
else:
self.ARR = np
def __call__(self, fs, bs, h):
"""
Aufmerksamkeitsberechnung
:param fs:Eine Liste von Forward-Encoder-Zwischenvektoren
:param bs:Liste der Zwischenvektoren des Umkehrcodierers
:param h:Zwischenvektorausgabe durch Decoder
:return:Gewichteter Durchschnitt des Zwischenvektors des Vorwärtscodierers und gewichteter Durchschnitt des Zwischenvektors des Rückwärtscodierers
"""
#Denken Sie an die Größe der Mini-Charge
batch_size = h.data.shape[0]
#Initialisieren der Liste zum Aufzeichnen von Gewichten
ws = []
#Initialisieren Sie den Wert, um den Gesamtwert des Gewichts zu berechnen
sum_w = Variable(self.ARR.zeros((batch_size, 1), dtype='float32'))
#Gewichtsberechnung unter Verwendung des Zwischenvektors des Encoders und des Zwischenvektors des Decoders
for f, b in zip(fs, bs):
#Gewichtsberechnung mit Vorwärtscodierer-Zwischenvektor, Rückwärtscodierer-Zwischenvektor, Decoder-Zwischenvektor
w = functions.tanh(self.fh(f)+self.bh(b)+self.hh(h))
#Normalisieren Sie mit der Softmax-Funktion
w = functions.exp(self.hw(w))
#Notieren Sie das berechnete Gewicht
ws.append(w)
sum_w += w
#Initialisierung des ausgegebenen gewichteten Durchschnittsvektors
att_f = Variable(self.ARR.zeros((batch_size, self.hidden_size), dtype='float32'))
att_b = Variable(self.ARR.zeros((batch_size, self.hidden_size), dtype='float32'))
for f, b, w in zip(fs, bs, ws):
#Normalisiert, so dass die Summe der Gewichte 1 ist.
w /= sum_w
#Gewicht*Fügen Sie den Zwischenvektor von Encoder zum Ausgabevektor hinzu
att_f += functions.reshape(functions.batch_matmul(f, w), (batch_size, self.hidden_size))
att_b += functions.reshape(functions.batch_matmul(b, w), (batch_size, self.hidden_size))
return att_f, att_b
In der Erklärung wurde nur ein Encoder verwendet, aber tatsächlich ist es üblich, zwei Typen von Encodern zu verwenden, den Vorwärtscodierer und den Rückwärtscodierer im Aufmerksamkeitsmodell.
Bei der Berechnung der Aufmerksamkeit übergeben wir also zwei Listen von Zwischenvektoren, die vom Vorwärtscodierer berechnet wurden, und eine Liste von Zwischenvektoren, die vom Rückwärtscodierer berechnet wurden.
Decoder
Im Gegensatz zu Seq2Seq sind die vom Decoder eingegebenen Werte drei, der Wortvektor, der vom Decoder berechnete Zwischenvektor und der gewichtete Durchschnitt des Zwischenvektors des Codierers. Also schreibe ich die Implementierung von Decoder neu.
att_decoder.py
class Att_LSTM_Decoder(Chain):
def __init__(self, vocab_size, embed_size, hidden_size):
"""
Decoder-Instanziierung für das Aufmerksamkeitsmodell
:param vocab_size:Anzahl der Vokabeln
:param embed_size:Wortvektorgröße
:param hidden_size:Versteckte Ebenengröße
"""
super(Att_LSTM_Decoder, self).__init__(
#Ebene zum Konvertieren von Wörtern in Wortvektoren
ye=links.EmbedID(vocab_size, embed_size, ignore_label=-1),
#Eine Ebene, die einen Wortvektor in einen Vektor umwandelt, der viermal so groß ist wie die verborgene Ebene
eh=links.Linear(embed_size, 4 * hidden_size),
#Eine Ebene, die den Zwischenvektor des Decoders in einen Vektor umwandelt, der viermal so groß ist wie die verborgene Ebene
hh=links.Linear(hidden_size, 4 * hidden_size),
#Eine Ebene, die den gewichteten Durchschnitt des Zwischenvektors des Vorwärtscodierers in einen Vektor umwandelt, der viermal so groß ist wie die verborgene Ebene
fh=links.Linear(hidden_size, 4 * hidden_size),
#Eine Ebene, die den gewichteten Durchschnitt des Zwischenvektors des Vorwärtscodierers in einen Vektor umwandelt, der viermal so groß ist wie die verborgene Ebene
bh=links.Linear(hidden_size, 4 * hidden_size),
#Eine Ebene, die einen versteckten Ebenengrößenvektor in die Größe eines Wortvektors konvertiert
he=links.Linear(hidden_size, embed_size),
#Ebene zum Konvertieren des Wortvektors in einen Vokabulargrößenvektor
ey=links.Linear(embed_size, vocab_size)
)
def __call__(self, y, c, h, f, b):
"""
Decoderberechnung
:param y:Wörter, die in Decoder eingegeben werden müssen
:param c:Interner Speicher
:param h:Decoder-Zwischenvektor
:param f:Gewichteter Durchschnitt des Forward-Encoders, berechnet nach dem Aufmerksamkeitsmodell
:param b:Gewichteter Durchschnitt des Rückwärtscodierers, berechnet durch das Aufmerksamkeitsmodell
:return:Wörterbuch der Vokabulargröße, aktualisierter interner Speicher, aktualisierter Zwischenvektor
"""
#Konvertieren Sie Wörter in Wortvektoren
e = functions.tanh(self.ye(y))
#LSTM unter Verwendung eines Wortvektors, Zwischenvektors des Decoders, Aufmerksamkeit des Vorwärtscodierers, Aufmerksamkeit des Rückwärtscodierers
c, h = functions.lstm(c, self.eh(e) + self.hh(h) + self.fh(f) + self.bh(b))
#Konvertieren Sie die von LSTM ausgegebene Zwischenvektorversion in einen lexikalischen Größenvektor
t = self.ey(functions.tanh(self.he(h)))
return t, c, h
Die Verwendung eines Vektors, der viermal so groß ist wie die verborgene Ebene, ist der gleiche Grund, den ich beim letzten Mal erklärt habe.
Wir haben die Schichten [fh] und [bh] hinzugefügt, um den gewichteten Durchschnitt der durch Attention berechneten Zwischenvektoren des Encoders zu verwenden, aber ansonsten sind sie gleich.
Seq2Seq with Attention
Das Modell, das Encoder, Decoder und Attention kombiniert, ist wie folgt.
att_seq2seq.py
class Att_Seq2Seq(Chain):
def __init__(self, vocab_size, embed_size, hidden_size, batch_size, flag_gpu=True):
"""
Seq2Seq +Aufmerksamkeitsinstanziierung
:param vocab_size:Anzahl der Vokabeln
:param embed_size:Wortvektorgröße
:param hidden_size:Versteckte Ebenengröße
:param batch_size:Mini-Chargengröße
:param flag_gpu:Gibt an, ob eine GPU verwendet werden soll
"""
super(Att_Seq2Seq, self).__init__(
#Vorwärtscodierer
f_encoder = LSTM_Encoder(vocab_size, embed_size, hidden_size),
#Rückwärtsgeber
b_encoder = LSTM_Encoder(vocab_size, embed_size, hidden_size),
# Attention Model
attention = Attention(hidden_size, flag_gpu),
# Decoder
decoder = Att_LSTM_Decoder(vocab_size, embed_size, hidden_size)
)
self.vocab_size = vocab_size
self.embed_size = embed_size
self.hidden_size = hidden_size
self.batch_size = batch_size
#Cupy bei Verwendung der GPU, numpy bei Nichtgebrauch
if flag_gpu:
self.ARR = cuda.cupy
else:
self.ARR = np
#Initialisieren Sie die Liste, um den Vorwärtscodierer-Zwischenvektor und den Rückwärtscodierer-Zwischenvektor zu speichern
self.fs = []
self.bs = []
def encode(self, words):
"""
Geberberechnung
:param words:Eine aufgezeichnete Liste von Wörtern, die für Ihre Eingabe verwendet werden sollen
:return:
"""
#Interner Speicher, Initialisierung des Zwischenvektors
c = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))
h = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))
#Berechnen Sie zunächst den Vorwärtscodierer
for w in words:
c, h = self.f_encoder(w, c, h)
#Notieren Sie den berechneten Zwischenvektor
self.fs.append(h)
#Interner Speicher, Initialisierung des Zwischenvektors
c = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))
h = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))
#Reverse Encoder Berechnung
for w in reversed(words):
c, h = self.b_encoder(w, c, h)
#Notieren Sie den berechneten Zwischenvektor
self.bs.insert(0, h)
#Interner Speicher, Initialisierung des Zwischenvektors
self.c = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))
self.h = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))
def decode(self, w):
"""
Decoderberechnung
:param w:Mit Decoder einzugebende Wörter
:return:Voraussagendes Wort
"""
#Berechnen Sie den gewichteten Durchschnitt der mittleren Ebene des Encoders mithilfe des Aufmerksamkeitsmodells
att_f, att_b = self.attention(self.fs, self.bs, self.h)
#Unter Verwendung des Decoder-Zwischenvektors Vorwärtsaufmerksamkeit, Rückwärtsaufmerksamkeit
#Berechnung des nächsten Zwischenvektors, des internen Speichers und des vorhergesagten Wortes
t, self.c, self.h = self.decoder(w, self.c, self.h, att_f, att_b)
return t
def reset(self):
"""
Instanzvariablen initialisieren
:return:
"""
#Interner Speicher, Initialisierung des Zwischenvektors
self.c = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))
self.h = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))
#Initialisierung der Liste, die den Zwischenvektor von Encoder aufzeichnet
self.fs = []
self.bs = []
#Gradienteninitialisierung
self.zerograds()
Es werden insgesamt drei LSTMs verwendet: Vorwärtscodierer, Rückwärtscodierer und Decodierer.
Die Vorwärtsberechnung und die Zugberechnung sind die gleichen wie für Seq2Seq.
Der erstellte Code ist https://github.com/kenchin110100/machine_learning/blob/master/sampleAttSeq2Seq.py Es ist in.
Ich habe den Dialogfehlerkorpus wie beim letzten Mal verwendet. https://sites.google.com/site/dialoguebreakdowndetection/chat-dialogue-corpus
Es gibt 4 Arten von Äußerungen wie zuvor
Lassen Sie uns die Antwort für jede Epoche sehen.
Erste 1 Epoche
Sprechen:Guten Morgen=>Antwort: ['damit', 'ist', 'Hallo', '</s>']
Sprechen:Wie geht's?=>Antwort: ['Ja', '、', 'Was', 'Zu', 'Sie sehen', 'Hand', 'Masu', 'Oder', '?', '</s>']
Sprechen:Ich bin hungrig=>Antwort: ['Ja', '</s>']
Sprechen:Es ist heiß heute=>Antwort: ['Ja', '、', 'Was', 'Zu', 'Sie sehen', 'Hand', 'Masu', 'Oder', '?', '</s>']
Hattest du so einen bösen Blick ...
3Epoch
Sprechen:Guten Morgen=>Antwort: ['Hallo.', '</s>']
Sprechen:Wie geht's?=>Antwort: ['damit', 'ist', '</s>']
Sprechen:Ich bin hungrig=>Antwort: ['Hitzschlag', 'Zu', 'Qi', 'Zu', 'Anfügen', 'Hand', 'Masu', 'Oder', '?', '</s>']
Sprechen:Es ist heiß heute=>Antwort: ['Hitzschlag', 'Zu', 'Qi', 'Zu', 'Anfügen', 'Abwesend', 'Hmm', 'ist', 'Oder', '?', '</s>']
5Epoch
Sprechen:Guten Morgen=>Antwort: ['Vielen Dank', '</s>']
Sprechen:Wie geht's?=>Antwort: ['Wassermelone', 'Ist', 'Mögen', 'ist', 'Hallo', '</s>']
Sprechen:Ich bin hungrig=>Antwort: ['Hitzschlag', 'Zu', 'Qi', 'Zu', 'Anfügen', 'Hand', 'Wollen', 'ist', 'Oder', '?', '</s>']
Sprechen:Es ist heiß heute=>Antwort: ['Hitzschlag', 'Zu', 'Qi', 'Zu', 'Anfügen', 'Hand', 'Wollen', 'ist', 'Oder', '?', '</s>']
Ich weiß bereits über Hitzschlag ...
7Epoch
Sprechen:Guten Morgen=>Antwort: ['Guten Abend', '</s>']
Sprechen:Wie geht's?=>Antwort: ['Wassermelone', 'Ist', 'Ich liebe dich', 'ist', 'Hallo', '</s>']
Sprechen:Ich bin hungrig=>Antwort: ['Bai', 'Bai', '</s>']
Sprechen:Es ist heiß heute=>Antwort: ['Hitzschlag', 'Zu', 'Qi', 'Zu', 'Anfügen', 'Abwesend', 'Hmm', 'ist', 'Oder', '?', '</s>']
Guten Morgen => Guten Abend, es ist schrecklich ...
Ich habe das Gefühl, dass die Genauigkeit schlechter ist als die von Seq2Seq ...
Wie bei Seq2Seq konnte ich die Frage "Wie geht es dir?" Nicht gut beantworten. Vielleicht wurde das Wort "Ton" im Korpus nicht verwendet.
Da es sich bei dem Original um ein Dialogfehlerkorpus handelt, wird das Ergebnis des Fehlers zurückgegeben, dh es kann sein, dass Sie damit gut lernen ...
Der Übergang des Gesamtverlustwertes und der Vergleich der Berechnungszeit werden am Ende zusammengefasst.
Ich habe das Seq2Seq + Attention Model mit Chainer berechnet.
Ich hatte das Gefühl, dass die Berechnungszeit im Vergleich zu Seq2Seq allein extrem lang war. Der Vergleich dieses Gebiets wird ...
Nächstes Mal werde ich CopyNet implementieren ... Ich möchte es tun.
Recommended Posts