Erläuterung von CopyNet, der dritten Version von seq2seq, und seiner Implementierung
Synopse bis zum letzten Mal http://qiita.com/kenchin110100/items/b34f5106d5a211f4c004 http://qiita.com/kenchin110100/items/eb70d69d1d65fb451b67
Normal seq2seq, Attention Model, diesmal habe ich CopyNet implementiert.
Wir werden zuerst CopyNet erklären, dann die Implementierung und ihre Ergebnisse.
CopyNet
Beginnen wir mit einer Überprüfung von Seq2Seq, um CopyNet zu erklären.
Sequence to Sequence |
---|
Seq2Seq ist eine Art Encoder-Decoder-Modell, bei dem der Encoder den gesprochenen Satz ("Wie geht es Ihnen?") In einen Vektor konvertiert und der Decoder den Antwortsatz ("Ich fühle mich gut") von diesem Vektor ausgibt.
In Seq2Seqs Encoder wurde nur der letzte Ausgangszwischenvektor berücksichtigt, aber das Aufmerksamkeitsmodell sollte mehr verschiedene Zwischenvektoren berücksichtigen.
Attention Model |
---|
Denken Sie jetzt darüber nach, was Sie mit CopyNet tun, wenn die Äußerung "Wie geht es Ihnen?" Lautet und die Antwort "Ich fühle mich gut" lautet.
Das Wort
CopyNet |
---|
(Die Figur ist nur ein Bild)
Der Grund, warum CopyNet gut ist, ist, dass es unbekannte Wörter verarbeiten kann.
Selbst wenn Sie beim Lernen beispielsweise nicht das Wort
Im Folgenden werde ich zwei Artikel zu CopyNet vorstellen.
Jiatao Gu et al.
Dies ist das Original-Copynet-Papier Gu, Jiatao, et al. "Incorporating copying mechanism in sequence-to-sequence learning." arXiv preprint arXiv:1603.06393 (2016).
Gu, Jiatao, et al |
---|
Die im Papier verwendete Abbildung ist die obige, aber wenn Sie genauer hinschauen, sieht sie wie die folgende Abbildung aus.
Copy mode and StateUpdate |
---|
Bei dem von Gu et al. Vorgeschlagenen Verfahren gibt es zwei Hauptmechanismen, StateUpdate und CopyMode.
Wenn in StateUpdate das in Decoder eingegebene Wort ein Wort (
Wenn im CopyMode das Wort, dessen Ausgabe Sie erwarten, im gesprochenen Satz (
(Die Erklärung ist ziemlich schlecht, aber bitte lesen Sie das Papier für Details ...)
Ziqiang Cao et al.
Ich möchte ein weiteres Papier zu CopyNet vorstellen. Genau genommen handelt es sich nicht um CopyNet, aber es gibt die folgenden Dokumente, die einen ähnlichen Mechanismus implementieren.
Ziqiang Cao et al. |
---|
(Abbildung in der Zeitung verwendet)
Dieser ist etwas einfacher, und wenn Sie ihn kurz erklären, wird er wie folgt aussehen.
Restricted Generative Decoder |
---|
Die Richtlinie besteht darin, das vom Aufmerksamkeitsmodell berechnete Gewicht unverändert zu verwenden.
Wenn die Eingabe kein Wort enthält, dessen Ausgabe erwartet wird, wird die Wahrscheinlichkeit des generierten Wortes unverändert verwendet.
Wenn die Eingabe ein Wort enthält, dessen Ausgabe erwartet wird (
Der Punkt ist, wie man dieses λ ausgleicht, aber wir werden auch λ lernen. (Bitte lesen Sie das Papier für Details ...)
Diesmal implementierte Chainer die Methode von Ziqiang Cao et al. Es gibt nicht viele CopyNet-Implementierungen im Internet, und es tut mir leid, wenn ich einen Fehler gemacht habe ...
Encoder und Decoder verwenden das zum Zeitpunkt des Aufmerksamkeitsmodells verwendete Modell so wie es ist.
Attention
Es ist im Grunde dasselbe wie das Aufmerksamkeitsmodell, aber das Gewicht jedes Zwischenvektors wird auch geändert, um ausgegeben zu werden.
attention.py
class Copy_Attention(Attention):
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 att_f:Gewichteter Durchschnitt des Zwischenvektors des Vorwärtscodierers
:return att_b:Gewichteter Durchschnitt des Zwischenvektors des Umkehrcodierers
:return att:Gewicht jedes Zwischenvektors
"""
#Denken Sie an die Größe der Mini-Charge
batch_size = h.data.shape[0]
#Initialisieren der Liste zum Aufzeichnen von Gewichten
ws = []
att = []
#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 = self.hw(functions.tanh(self.fh(f)+self.bh(b)+self.hh(h)))
att.append(w)
#Normalisieren Sie mit der Softmax-Funktion
w = functions.exp(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 i, (f, b, w) in enumerate(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(f, w), (batch_size, self.hidden_size))
att = functions.concat(att, axis=1)
return att_f, att_b, att
Seq2Seq with CopyNet
Das Modell, das Encoder, Decorder und Attention kombiniert, ist wie folgt.
copy_seq2seq.py
class Copy_Seq2Seq(Chain):
def __init__(self, vocab_size, embed_size, hidden_size, batch_size, flag_gpu=True):
super(Copy_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=Copy_Attention(hidden_size, flag_gpu),
# Decoder
decoder=Att_LSTM_Decoder(vocab_size, embed_size, hidden_size),
#Netzwerk zur Berechnung des Gewichts von λ
predictor=links.Linear(hidden_size, 1)
)
self.vocab_size = vocab_size
self.embed_size = embed_size
self.hidden_size = hidden_size
self.batch_size = batch_size
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 t:Voraussagendes Wort
:return att:Aufmerksamkeitsgewicht für jedes Wort
:return lambda_:Das Gewicht zum Bestimmen, ob Kopieren wichtig oder Generieren wichtig ist
"""
#Berechnen Sie den Eingabevektor mit dem Aufmerksamkeitsmodell
att_f, att_b, att = self.attention(self.fs, self.bs, self.h)
#Eingabevektor in Decoder
t, self.c, self.h = self.decoder(w, self.c, self.h, att_f, att_b)
#Berechnung von λ unter Verwendung des berechneten Zwischenvektors
lambda_ = self.predictor(self.h)
return t, att, lambda_
Tatsächlich unterscheidet sich dies nicht wesentlich vom Aufmerksamkeitsmodell. Die Änderung besteht darin, dass auch das Aufmerksamkeitsgewicht ausgegeben wird, das λ berechnet, um den Kopiermodus und den generativen Modus auszugleichen.
forward
Die große Veränderung liegt in der Vorwärtsfunktion. Die Vorwärtsfunktion überprüft den Eingabesatz und das Wort, das Sie ausgeben möchten, um festzustellen, ob der Kopiermodus berechnet werden soll.
forward.py
def forward(enc_words, dec_words, model, ARR):
"""
Funktion zur Vorwärtsberechnung
:param enc_words:Eingabeanweisung
:param dec_words:Ausgabeanweisung
:param model:Modell-
:param ARR:numpy oder cuda.Entweder Cupy
:return loss:Verlust
"""
#Chargengröße aufzeichnen
batch_size = len(enc_words[0])
#Setzen Sie den im Modell aufgezeichneten Gradienten zurück
model.reset()
#Bereiten Sie eine Liste vor, um die im Eingabesatz verwendeten Wörter zu überprüfen
enc_key = enc_words.T
#Ändern Sie die in Encoder eingegebene Anweisung in Variablentyp
enc_words = [Variable(ARR.array(row, dtype='int32')) for row in enc_words]
#Geberberechnung
model.encode(enc_words)
#Verlustinitialisierung
loss = Variable(ARR.zeros((), dtype='float32'))
# <eos>Zum Decoder
t = Variable(ARR.array([0 for _ in range(batch_size)], dtype='int32'))
#Decoderberechnung
for w in dec_words:
#Wort für Wort dekodieren
y, att, lambda_ = model.decode(t)
#Konvertieren Sie das richtige Wort in den Variablentyp
t = Variable(ARR.array(w, dtype='int32'))
#Protokoll der vom generativen Modus berechneten Wörter_Nimm Softmax
s = functions.log_softmax(y)
#Achtung Gewichtsprotokoll_Nimm Softmax
att_s = functions.log_softmax(att)
#Durch Multiplikation von Lambda mit Sigmoidfunktion wird 0~Ändern Sie auf einen Wert von 1
lambda_s = functions.reshape(functions.sigmoid(lambda_), (batch_size,))
#Generative Modusverlustinitialisierung
Pg = Variable(ARR.zeros((), dtype='float32'))
#Initialisierung des Verlusts des Kopiermodus
Pc = Variable(ARR.zeros((), dtype='float32'))
#Verlustinitialisierung zum Erlernen des Lambda-Gleichgewichts
epsilon = Variable(ARR.zeros((), dtype='float32'))
#Von hier aus wird der Verlust jedes Wortes im Stapel berechnet und die for-Anweisung wird umgedreht ...
counter = 0
for i, words in enumerate(w):
# -1 ist die Bezeichnung für das Wort, das Sie nicht lernen. Ignoriere das.
if words != -1:
#Berechnung des generativen Modusverlusts
Pg += functions.get_item(functions.get_item(s, i), words) * functions.reshape((1.0 - functions.get_item(lambda_s, i)), ())
counter += 1
#Wenn es ein Wort gibt, das Sie im Eingabesatz ausgeben möchten
if words in enc_key[i]:
#Kopiermodus berechnen
Pc += functions.get_item(functions.get_item(att_s, i), list(enc_key[i]).index(words)) * functions.reshape(functions.get_item(lambda_s, i), ())
#Lernen Sie, Lambda besser zu machen als den Kopiermodus
epsilon += functions.log(functions.get_item(lambda_s, i))
#Wenn es kein Wort gibt, das Sie im Eingabesatz ausgeben möchten
else:
#Lernen Sie, Lambda besser zu machen als den generativen Modus
epsilon += functions.log(1.0 - functions.get_item(lambda_s, i))
#Teilen Sie jeden Verlust durch die Chargengröße und addieren Sie ihn
Pg *= (-1.0 / np.max([1, counter]))
Pc *= (-1.0 / np.max([1, counter]))
epsilon *= (-1.0 / np.max([1, counter]))
loss += Pg + Pc + epsilon
return loss
Im Code werden drei Verluste, Pg, Pc und epsilon, definiert und berechnet, um jeweils den generativen Modus, den Kopiermodus und λ zu lernen.
Es geht darum, functions.log_softmax zu verwenden. Wenn Sie log (softmax (x)) einstellen, tritt ein Fehler auf, wenn die Berechnung von softmax 0 wird, aber diese Funktion macht es gut (wie es funktioniert, ist ein Rätsel ...).
Wenn Sie die Funktion functions.softmax_cross_entropy verwenden, benötigen Sie keine so mühsame Berechnung, aber dieses Mal möchte ich den Verlust des Kopiermodus und den Verlust des generativen Modus mit λ ausgleichen, also verwende ich die Funktionen functions.get_items und functions.log_softmax, um den Verlust zu machen. Wird berechnet.
Wenn Sie eine bessere Implementierung kennen, lassen Sie es mich bitte wissen ...
Der erstellte Code ist https://github.com/kenchin110100/machine_learning/blob/master/sampleCopySeq2Seq.py Es ist in.
Ich habe den Dialogfehlerkorpus wie zuvor verwendet. https://sites.google.com/site/dialoguebreakdowndetection/chat-dialogue-corpus
Die folgenden 4 Arten von Äußerungen
Wir werden uns die Antwortergebnisse für jede Epoche ansehen.
Epoch 1
Sprechen:Guten Morgen=>Antwort: ['Guten Morgen', '</s>'] ['copy', 'copy']
Sprechen:Wie geht's?=>Antwort: ['Bedingung', 'Ist', 'Ist', 'ist', 'ist', '</s>'] ['copy', 'copy', 'copy', 'copy', 'copy', 'copy']
Sprechen:Ich bin hungrig=>Antwort: ['Bauch', 'Aber', 'Aber', 'Aber', 'Ta', 'Ta', 'ist', '</s>'] ['copy', 'copy', 'copy', 'copy', 'copy', 'copy', 'gen', 'copy']
Sprechen:Es ist heiß heute=>Antwort: ['heute', 'Ist', 'Ist', 'ist', 'ist', '</s>'] ['copy', 'copy', 'copy', 'copy', 'copy', 'copy']
Es ist völlig kaputt ...
Epoch 3
Sprechen:Guten Morgen=>Antwort: ['Guten Morgen', '</s>'] ['copy', 'copy']
Sprechen:Wie geht's?=>Antwort: ['Bedingung', 'Ist', '</s>'] ['copy', 'gen', 'copy']
Sprechen:Ich bin hungrig=>Antwort: ['Bauch', '</s>'] ['copy', 'copy']
Sprechen:Es ist heiß heute=>Antwort: ['heiß', 'Ist', 'Mögen', 'ist', 'Hallo', '</s>'] ['copy', 'copy', 'gen', 'gen', 'gen', 'copy']
Epoch 5
Sprechen:Guten Morgen=>Antwort: ['Guten Morgen', '</s>'] ['copy', 'copy']
Sprechen:Wie geht's?=>Antwort: ['Bedingung', 'Ist', 'Mögen', 'ist', 'Oder', '</s>'] ['copy', 'copy', 'gen', 'copy', 'gen', 'copy']
Sprechen:Ich bin hungrig=>Antwort: ['Bauch', '</s>'] ['copy', 'copy']
Sprechen:Es ist heiß heute=>Antwort: ['heiß', 'ist', '</s>'] ['copy', 'gen', 'copy']
Auch wenn Sie für hungrig erklärt werden ...
Epoch 7
Sprechen:Guten Morgen=>Antwort: ['Guten Morgen', 'Vielen Dank', 'Masu', '</s>'] ['copy', 'gen', 'gen', 'copy']
Sprechen:Wie geht's?=>Antwort: ['Bedingung', 'Ist', '</s>'] ['copy', 'gen', 'copy']
Sprechen:Ich bin hungrig=>Antwort: ['Bauch', 'Aber', 'Frei', 'Besser', 'Ta', '</s>'] ['copy', 'gen', 'copy', 'copy', 'gen', 'gen']
Sprechen:Es ist heiß heute=>Antwort: ['heiß', 'ist', '</s>'] ['copy', 'gen', 'copy']
Das Wort
Ehrlich gesagt möchte ich Sie jedoch bitten, etwas besser zu antworten.
Es lernt sowohl den Kopiermodus als auch den Generierungsmodus, so dass der Decoder das Sprachmodell anscheinend nicht vollständig trainiert hat.
Dies kann damit zusammenhängen, dass das Papier nicht von der Dialogaufgabe, sondern von der Zusammenfassungsaufgabe bewertet wurde. (Nun, die Hauptursache kann die Implementierung sein ...)
Ich habe CopyNet mit Chainer implementiert. Ich habe das Dialogmodell dreimal gemacht, also bin ich schon voll lol Nächstes Mal mache ich etwas anderes.
Recommended Posts