[PYTHON] Seq2Seq (1) mit Chainer

Da ich Sequenz zu Sequenz mit Chainer implementiert habe, ist sein Code und Verifikation

Einführung

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

seq2seq.png

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 "<?>, , , " in dieser Reihenfolge in den Encoder ein.

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.

Implementierung

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, スクリーンショット 2017-02-23 18.06.40.png 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 ...

encoder.png

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

decoder.png

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

forward.png

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.

Experiment

Korpus

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

Versuchsergebnis

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.

Fazit

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

Seq2Seq (1) mit Chainer
Seq2Seq (3) ~ CopyNet Edition ~ mit Chainer
Seq2Seq (2) ~ Achtung Model Edition ~ mit Chainer
Verwenden Sie Tensorboard mit Chainer
Versuchen Sie, RBM mit Chainer zu implementieren.
Lernen Sie mit Chainer elliptische Bahnen
Verwendung von Chainer mit Jetson TK1
Neuronales Netz beginnend mit Chainer
Bedingte GAN mit Chainer implementiert
SmoothGrad mit Chainer v2 implementiert
Deep Embedded Clustering mit Chainer 2.0
Ein bisschen im Kettenschiff stecken
[Chainer] Lernen von XOR mit mehrschichtigem Perzeptron
Erste Anime-Gesichtserkennung mit Chainer
Verwenden von Chainer mit CentOS7 [Umgebungskonstruktion]
Versuchen Sie Common Representation Learning mit Chainer
Lernen Sie mit Chainer, monochrome Bilder einzufärben
Durchketten von Kuchen mit Chainer durchschauen
Klassifizieren Sie Anime-Gesichter mit tiefem Lernen mit Chainer
Fordern Sie DQN (Modoki) mit Chainer heraus ✕ Öffnen Sie AI Gym!
Kettenproben
Versuchen Sie es mit Chainer Deep Q Learning - Launch
Persönliche Best Practices bei der Feinabstimmung mit Chainer
Laden Sie das Kaffeemodell mit Chainer und klassifizieren Sie die Bilder
Kategorisieren Sie Gesichtsbilder von Anime-Charakteren mit Chainer
Ich habe mit Chainer eine supereinfache lineare Trennung versucht
Bilderkennung mit Caffe Model Chainer Yo!