Da ich Sequenz zu Sequenz mit Chainer implementiert habe, ist sein Code und Verifikation
Ein bekanntes Modell zum Generieren von Anweisungen unter Verwendung eines RNN-basierten neuronalen Netzwerks ist Sequenz zu Sequenz (Seq2Seq).
Dieses Mal werde ich die Ergebnisse in der Methode und Überprüfung zusammenfassen, wenn ich diesen Seq2Seq mit Chainer implementiere.
Sequence to Sequence(Seq2Seq)
Seq2Seq ist eine Art Encoder-Decoder-Modell, das RNN verwendet und als Modell für maschinelle Dialoge und maschinelle Übersetzung verwendet werden kann.
Dies ist das Originalpapier Sutskever, Ilya, Oriol Vinyals, and Quoc V. Le. "Sequence to sequence learning with neural networks." Advances in neural information processing systems. 2014.
Der Umriss des Flusses von Seq2Seq sieht wie folgt aus
Wenn es beispielsweise eine Äußerung und Antwort wie "Wie geht es Ihnen?" Oder "Es ist ziemlich gut" gibt, vektorisiert die Seite des Encoders (blau in der Abbildung) die Äußerung und die Seite des Decoders (rot in der Abbildung). Trainieren Sie die RNN, um eine Antwort zu generieren.
"<'EOS'>" ist eine Abkürzung für End Of Statement, ein Signal dafür, dass der Satz hier endet.
Der Punkt von Seq2Seq besteht darin, die Äußerung aus der entgegengesetzten Richtung einzugeben. Wenn die Äußerung "Wie geht es Ihnen?" Lautet, geben Sie "<?>,
Auf der Encoder- und Decoder-Seite werden separate Einbettungen verwendet, und nur die generierte Zwischenschicht (rote Linie in der Abbildung) wird gemeinsam genutzt.
Ich habe Seq2Seq als RNN-basiertes neuronales Netzwerk geschrieben, diesmal jedoch mit Long Short Term Memory (LSTM) implementiert.
Für eine detaillierte Erklärung von LSTM, http://qiita.com/t_Signull/items/21b82be280b46f467d1b http://qiita.com/KojiOhki/items/89cd7b69a8a6239d67ca Die Gegend ist leicht zu verstehen.
Der Punkt von LSTM ist, dass LSTM selbst eine Speicherzelle hat (wie eine Sammlung von Speichern), und wenn eine neue Eingabe vorgenommen wird, wird die Speicherzelle vergessen (Forget Gate), gespeichert (Input Gate), Ausgabe (Output Gate). Es ist ein Punkt zu arbeiten.
Dieses Mal habe ich Seq2Seq mit Chainer implementiert.
Es gibt eine Menge Beispielcode, der Seq2Seq mit Chainer implementiert, aber dieses Mal habe ich versucht, ihn so einfach wie möglich zu schreiben (ich beabsichtige).
Der Referenzcode lautet https://github.com/odashi/chainer_examples ist. Danke, oda.
In Chainer wird das Modell von NN als Klasse beschrieben.
Encoder
Erstens Encoder zum Konvertieren von Äußerungen in Vektoren
encoder.py
class LSTM_Encoder(Chain):
def __init__(self, vocab_size, embed_size, hidden_size):
"""
Klasseninitialisierung
:param vocab_size:Anzahl der verwendeten Wortarten (Anzahl der Vokabeln)
:param embed_size:Größe der Wörter in der Vektordarstellung
:param hidden_size:Zwischenschichtgröße
"""
super(LSTM_Encoder, self).__init__(
#Ebene zum Konvertieren von Wörtern in Wortvektoren
xe = 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),
#Ebene zum Konvertieren der Ausgabe-Zwischenebene in die vierfache Größe
hh = links.Linear(hidden_size, 4 * hidden_size)
)
def __call__(self, x, c, h):
"""
Geberbetrieb
:param x: one-heißer Vektor
:param c:Interner Speicher
:param h:Versteckte Ebene
:return:Nächster interner Speicher, nächste versteckte Schicht
"""
#Konvertieren Sie mit xe in einen Wortvektor und multiplizieren Sie diesen Vektor mit tanh
e = functions.tanh(self.xe(x))
#Eingabe durch Addition des Werts des vorherigen internen Speichers, der vierfachen Größe des Wortvektors und der vierfachen Größe der mittleren Ebene.
return functions.lstm(c, self.eh(e) + self.hh(h))
Der Punkt des Encoders ist, warum der Vektor in die vierfache Größe der angegebenen verborgenen Ebene konvertiert wird.
Im offiziellen Dokument von Chainer, ein.
Mit anderen Worten: "Da der Eingabevektor in Vergessen, Eingabe, Ausgabe und Zelle unterteilt ist, machen Sie ihn viermal so groß."
Chainers functions.lstm berechnet nur Funktionen, kein Netzwerklernen. Also machen eh und hh im Code das stattdessen.
Tatsächlich gibt es eine praktische Klasse namens links.LSTM in chainer, die nur Ausgaben ausgibt und sogar lernt, wenn Sie sie eingeben, aber ich habe sie diesmal nicht verwendet. Weil ich den versteckten Layer-Wert zwischen Encoder und Decoder teilen möchte (ich denke, dass links.LSTM weiterhin verwendet werden kann, diesmal jedoch für die Zukunft ...).
Das Bild der Berechnung sieht also so aus, die Linien überlappen sich und es ist schwer zu sehen ...
Decoder
Als nächstes über Decoder
decoder.py
class LSTM_Decoder(Chain):
def __init__(self, vocab_size, embed_size, hidden_size):
"""
Klasseninitialisierung
:param vocab_size:Anzahl der verwendeten Wortarten (Anzahl der Vokabeln)
:param embed_size:Größe der Wörter in der Vektordarstellung
:param hidden_size:Zwischenvektorgröße
"""
super(LSTM_Decoder, self).__init__(
#Ebene zum Konvertieren von Eingabewö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 ein Zwischenvektor
eh = links.Linear(embed_size, 4 * hidden_size),
#Eine Schicht, die einen Zwischenvektor in einen Vektor umwandelt, der viermal so groß ist wie der Zwischenvektor
hh = links.Linear(hidden_size, 4 * hidden_size),
#Ebene zum Konvertieren des Ausgabevektors in die Größe des Wortvektors
he = links.Linear(hidden_size, embed_size),
#Wortvektor zu Vokabulargrößenvektor (eins-Ebene zum Konvertieren in heißen Vektor)
ey = links.Linear(embed_size, vocab_size)
)
def __call__(self, y, c, h):
"""
:param y: one-heißer Vektor
:param c:Interner Speicher
:param h:Zwischenvektor
:return:Vorausgesagtes Wort, nächster interner Speicher, nächster Zwischenvektor
"""
#Konvertieren Sie das eingegebene Wort in einen Wortvektor und wenden Sie es auf tanh an
e = functions.tanh(self.ye(y))
#Interner Speicher, 4-facher Wortvektor+Multiplizieren Sie LSTM mit dem 4-fachen des Zwischenvektors
c, h = functions.lstm(c, self.eh(e) + self.hh(h))
#Konvertieren Sie den Ausgabe-Zwischenvektor in einen Wortvektor und konvertieren Sie den Wortvektor in einen Ausgabevektor in Vokabulargröße
t = self.ey(functions.tanh(self.he(h)))
return t, c, h
Der Decoder macht den Vektor auch viermal so groß. Der Unterschied besteht darin, dass der Ausgabe-Zwischenvektor in einen Vektor mit der Größe der Anzahl der Vokabeln umgewandelt wird.
Deshalb brauchen wir Schichten, die Encoder nicht hatte.
Das Bild dieser Berechnung ist wie folgt
Decorder verwendet den Ausgabevektor für die Rückausbreitung.
Seq2Seq
Der folgende Code ist Seq2Seq, der durch Kombinieren dieser Encoder und Decoder erstellt wurde.
seq2seq.py
class Seq2Seq(Chain):
def __init__(self, vocab_size, embed_size, hidden_size, batch_size, flag_gpu=True):
"""
Initialisierung von Seq2Seq
:param vocab_size:Wortgröße
:param embed_size:Wortvektorgröße
:param hidden_size:Zwischenvektorgröße
:param batch_size:Mini-Chargengröße
:param flag_gpu:Gibt an, ob eine GPU verwendet werden soll
"""
super(Seq2Seq, self).__init__(
#Encoder-Instanziierung
encoder = LSTM_Encoder(vocab_size, embed_size, hidden_size),
#Decoder-Instanziierung
decoder = 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
#Verwenden Sie numpy, um cupy mit CPU zu berechnen, wenn Sie mit GPU berechnen
if flag_gpu:
self.ARR = cuda.cupy
else:
self.ARR = np
def encode(self, words):
"""
Der Teil, der den Encoder berechnet
:param words:Liste der aufgezeichneten Wörter
: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'))
#Lassen Sie den Encoder die Wörter der Reihe nach lesen
for w in words:
c, h = self.encoder(w, c, h)
#Machen Sie den berechneten Zwischenvektor zu einer Instanzvariablen, die vom Decoder geerbt werden soll
self.h = h
#Der interne Speicher wird nicht vererbt. Initialisieren Sie ihn daher
self.c = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))
def decode(self, w):
"""
Der Teil, der den Decoder berechnet
:param w:Wort
:return:Geben Sie einen Vektor der Wortnummerngröße aus
"""
t, self.c, self.h = self.decoder(w, self.c, self.h)
return t
def reset(self):
"""
Zwischenvektor, interner Speicher, Gradienteninitialisierung
:return:
"""
self.h = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))
self.c = Variable(self.ARR.zeros((self.batch_size, self.hidden_size), dtype='float32'))
self.zerograds()
Die Berechnung der Vorwärtsausbreitung unter Verwendung der Seq2Seq-Klasse wird wie folgt durchgeführt.
forward.py
def forward(enc_words, dec_words, model, ARR):
"""
Eine Funktion, die die Vorwärtsausbreitung berechnet
:param enc_words:Eine Liste der gesprochenen Wörter
:param dec_words:Eine Liste der Wörter im Antwortsatz
:param model:Seq2 Seq-Instanz
:param ARR: cuda.Cupy oder Numpy
:return:Gesamtverlust berechnet
"""
#Chargengröße aufzeichnen
batch_size = len(enc_words[0])
#Setzen Sie den im Modell gespeicherten Farbverlauf zurück
model.reset()
#Ändern Sie das Wort in der Äußerungsliste in den Variablentyp, der der Chainer-Typ ist.
enc_words = [Variable(ARR.array(row, dtype='int32')) for row in enc_words]
#Kodierungsberechnung ⑴
model.encode(enc_words)
#Verlustinitialisierung
loss = Variable(ARR.zeros((), dtype='float32'))
# <eos>Zum Decoder(2)
t = Variable(ARR.array([0 for _ in range(batch_size)], dtype='int32'))
#Decoderberechnung
for w in dec_words:
#Wort für Wort dekodieren(3)
y = model.decode(t)
#Konvertieren Sie das richtige Wort in den Variablentyp
t = Variable(ARR.array(w, dtype='int32'))
#Berechnen Sie den Verlust, indem Sie das richtige Wort mit dem vorhergesagten Wort vergleichen(4)
loss += functions.softmax_cross_entropy(y, t)
return loss
Der Ablauf dieser Berechnung wird folgendermaßen dargestellt
Zu lernende Wörter in enc_words und dec_words müssen im Voraus identifiziert (in Zahlen umgewandelt) werden.
Die Softmax-Funktion wird zur Berechnung des Verlusts verwendet.
Alles, was Sie tun müssen, ist, den Kettenhändler den durch Weiterleiten berechneten Verlust lernen zu lassen und das Netzwerk zu aktualisieren.
Der Hauptcode für das Lernen lautet wie folgt.
train.py
def train():
#Überprüfen Sie die Anzahl der Vokabeln
vocab_size = len(word_to_id)
#Modellinstanziierung
model = Seq2Seq(vocab_size=vocab_size,
embed_size=EMBED_SIZE,
hidden_size=HIDDEN_SIZE,
batch_size=BATCH_SIZE,
flag_gpu=FLAG_GPU)
#Modellinitialisierung
model.reset()
#Entscheiden Sie, ob Sie eine GPU verwenden möchten
if FLAG_GPU:
ARR = cuda.cupy
#Legen Sie das Modell in den Speicher der GPU
cuda.get_device(0).use()
model.to_gpu(0)
else:
ARR = np
#Fang an zu lernen
for epoch in range(EPOCH_NUM):
#Initialisieren Sie den Optimierer für jede Epoche
#Benutze Adam sicher
opt = optimizers.Adam()
#Stellen Sie das Modell auf Optimierer ein
opt.setup(model)
#Passen Sie an, wenn der Farbverlauf zu groß ist
opt.add_hook(optimizer.GradientClipping(5))
#Lesen Sie die im Voraus erstellten Trainingsdaten
data = Filer.read_pkl(path)
#Daten mischen
random.shuffle(data)
#Beginn des Batch-Lernens
for num in range(len(data)//BATCH_SIZE):
#Erstellen Sie eine Mini-Charge beliebiger Größe
minibatch = data[num*BATCH_SIZE: (num+1)*BATCH_SIZE]
#Datenerstellung zum Lesen
enc_words, dec_words = make_minibatch(minibatch)
#Berechnung des Verlustes durch Vorwärtsausbreitung
total_loss = forward(enc_words=enc_words,
dec_words=dec_words,
model=model,
ARR=ARR)
#Gradientenberechnung mit Fehlerrückausbreitung
total_loss.backward()
#Aktualisieren Sie das Netzwerk mit dem berechneten Gradienten
opt.update()
#Initialisieren Sie den aufgezeichneten Gradienten
opt.zero_grads()
#Speichern Sie das Modell für jede Epoche
serializers.save_hdf5(outputpath, model)
Es war ziemlich lange her, aber der Code ist vorbei. Der erstellte Code ist übrigens https://github.com/kenchin110100/machine_learning/blob/master/sampleSeq2Sep.py Es ist in.
Dialogfehlerkorpus https://sites.google.com/site/dialoguebreakdowndetection/chat-dialogue-corpus Wurde benutzt.
Ich wollte unbedingt mit einem längeren Korpus lernen, aber ich gab auf, weil das Lernen zu lange dauerte ...
Die folgenden vier Äußerungen
Schauen wir uns die Genauigkeit des Modells für jede Epoche an
Erste 1 Epoche
Sprechen:Guten Morgen=>Antwort: ['Ja', '</s>']
Sprechen:Wie geht's?=>Antwort: ['langweilig', 'Ist', 'Mögen', 'ist', 'Hallo', '</s>']
Sprechen:Ich bin hungrig=>Antwort: ['damit', 'ist', '</s>']
Sprechen:Es ist heiß heute=>Antwort: ['damit', 'ist', '</s>']
Bist du ein Philosoph?
Dann 3 Epoche
Sprechen:Guten Morgen=>Antwort: ['Guten Morgen', 'Vielen Dank', 'Masu', '</s>']
Sprechen:Wie geht's?=>Antwort: ['Wassermelone', 'Ist', 'Ich liebe dich', 'ist', 'Hallo', '</s>']
Sprechen:Ich bin hungrig=>Antwort: ['damit', 'Nana', 'Hmm', 'ist', 'Oder', '?', '</s>']
Sprechen:Es ist heiß heute=>Antwort: ['Was', 'Oder', 'Zu', 'Gehen', 'Hand', 'Masu', 'Oder', '?', '</s>']
Ich bin nirgendwo hingegangen ...
5 Epoch
Sprechen:Guten Morgen=>Antwort: ['Guten Morgen', 'Vielen Dank', 'Masu', '</s>']
Sprechen:Wie geht's?=>Antwort: ['Meer', 'Ist', 'einer', 'Mann', 'damit', 'Gehen', 'Hand', 'Masu', 'Oder', '?', '</s>']
Sprechen:Ich bin hungrig=>Antwort: ['Jep', '</s>']
Sprechen:Es ist heiß heute=>Antwort: ['Was', 'Zu', 'Essen', 'Besser', 'Ta', 'Oder', '?', '</s>']
Ich bin noch nicht einmal ans Meer gegangen ...
Mit 8 Epochen ...
Sprechen:Guten Morgen=>Antwort: ['Guten Morgen', 'Vielen Dank', 'Masu', '</s>']
Sprechen:Wie geht's?=>Antwort: ['Qualle', 'Ist', 'Gut', 'ist', 'Hallo', '</s>']
Sprechen:Ich bin hungrig=>Antwort: ['Ebenfalls', '</s>']
Sprechen:Es ist heiß heute=>Antwort: ['Hitzschlag', 'Zu', 'Qi', 'Zu', 'Anfügen', 'Abwesend', 'Hmm', 'ist', 'Oder', '?', '</s>']
Es wird bedauerlich, aber ist das die Grenze? Ich habe auch mehr Epoche versucht, aber die Genauigkeit hat sich nicht viel geändert.
Ich habe gerade Seq2Seq mit Chainer implementiert. Es scheint, dass die Verwendung eines größeren Korpus die Genauigkeit verbessert, aber wenn der Rechenaufwand zu groß wird, konvergiert er nicht leicht ...
Übrigens habe ich (1) zum Titel hinzugefügt, weil ich über die 2. und 3. Kugel nachdenke! !! Als nächstes möchte ich diesem Seq2Seq Aufmerksamkeit schenken.
Recommended Posts