[PYTHON] Implementierung eines Dialogsystems mit Chainer [seq2seq]

Einführung

In letzter Zeit war der Fortschritt von DNNs (Deep Neural Networks) bemerkenswert und in allen Bereichen erfolgreich. Wir hören oft über die Bereiche Bildklassifizierung und Spracherkennung, und Dialogsysteme sind keine Ausnahme. Nachdem die Python-Bibliotheksumgebung erweitert wurde, möchte ich kurz den Aufbau eines interaktiven Systems mit DNNs vorstellen.

DNN-Modell für Dialogsysteme

Es gibt zwei Hauptmodelle von DNNs zum Aufbau eines Dialogsystems.

Dieser Artikel befasst sich mit dem letzteren Encoder-Decoder-Modell. Dank der umfangreichen Bibliothek wie Chainer kann jeder sie implementieren, solange Daten vorhanden sind, bei denen es sich um ein Paar aus Sprache und Antwort handelt.

Montageumgebung

Die abhängigen Pakete sind unten zusammengefasst. Mit dem Befehl pip install oder conda install können Sie die Umgebung auf einmal erstellen.

seq2seq Das in diesem Artikel beschriebene Modell heißt seq2seq (Sequence to Sequence) und besteht aus zwei Arten von Netzwerken: einem Encoder-RNN (Reccurent Neural Network) für die Eingabe und einem Decoder-RNN für die Ausgabe (der RNN-Teil ist allgemein). Wird mit LSTM implementiert).

Originalarbeit: Sequenz-zu-Sequenz-Lernen mit neuronalen Netzen

Kobito.DW0UK9.png

Bei Anwendung auf ein Dialogsystem wird die Eingangsäußerung durch den Codierer geleitet und die Antwort darauf wird Wort für Wort mit dem Decodierer gelernt.

Trainingsdaten

Dieses Mal lernen wir die Verwendung der Daten von Dialogue Failure Detection Challenge 2. Der Korpus zur Erkennung von Dialogfehlern ist ein Korpus, den jeder für jeden Zweck verwenden kann, sodass Sie ihn mit Sicherheit verwenden können.

Korpus zur Erkennung von Dialogfehlern:

URL: https://sites.google.com/site/dialoguebreakdowndetection2/downloads

Der Inhalt des Korpus besteht aus JSON-Dateien. Darüber hinaus ist ein Skript enthalten, das den in der JSON-Datei gespeicherten Dialog auf einfach zu lesende Weise ausgibt. Versuchen Sie, wie folgt auszuführen.

show_dial.py


$ python show_dial.py 1470622453.log.json 

Ausführungsergebnis:

dialogue-id : 1470622453 speaker-id : DBD-01 group-id : S: Hallo. Achten Sie auf Hitzschlag. O O O O O O O O O O O O O O O O O O O O O O O O O O O O. U: Ja. Vielen Dank. Sie sollten auch vorsichtig sein. S: Sei nicht vorsichtig mit Hitzschlag? T O T X X T X X T O T T T X X O X X X X X T T O O T O. ... (weggelassen) S: Übung ist eine Abwechslung, nicht wahr? Ich fühle mich in Ordnung

Erstellung von Trainingsdaten

Unter Verwendung des obigen Dialogfehlererkennungskorpus wird seq2seq unter Verwendung der Benutzersprache (U) als Eingabesprache zum Lernen und der Systemsprache zu diesem Zeitpunkt als Antwortsprache gelernt.

Kobito.mTPH4G.png

Wie in der obigen Abbildung gezeigt, wird eine Datei mit einer Eins-zu-Eins-Entsprechung zwischen Sprache (Äußerung) und Antwort (Antwort) erstellt und als Trainingsdaten angegeben. Verwenden Sie das folgende Skript, um die JSON-Datei in eine Textdatei zu konvertieren, die die Trainingsdaten enthält.

json2text.py


#!/usr/bin/env python                                                                                                                                                    
# -*- coding: utf-8 -*-                                                                                                                                                  

import sys
import os
import json


def loadingJson(dirpath, f):

    fpath = dirpath + '/' + f
    fj = open(fpath,'r')
    json_data = json.load(fj)
    fj.close()

    return json_data

def output(data, mod):

    for i in range(len(data['turns'])):
        if mod == "U" and data['turns'][i]['speaker'] == mod:
            print data['turns'][i]['utterance'].encode('utf-8')
        elif mod == "S" and data['turns'][i]['speaker'] == mod and i != 0:
            print data['turns'][i]['utterance'].encode('utf-8')
        else:
            continue


if __name__ == "__main__":

    argvs = sys.argv

    _usage = """--                                                                                                                                                       
Usage:                                                                                                                                                                   
    python json2text.py [json] [speaker]                                                                                                                                 
Args:                                                                                                                                                                    
    [json]: The argument is input directory that is contained files of json that is objective to convert to sql.                                                         
    [speaker]: The argument is "U" or "S" that is speaker in dialogue.                                                                                                   
""".rstrip()

    if len(argvs) < 3:
        print _usage
        sys.exit(0)

    # one file ver                                                                                                                                                       
    '''                                                                                                                                                                  
    fj = open(argvs[1],'r')                                                                                                                                              
    json_data = json.load(fj)                                                                                                                                            
    fj.close()      
    
    output(json_data, mod)                                                                                                                                                                                                                                                                                                        
    '''

    # more than two files ver                                                                                                                                            
    branch = os.walk(argvs[1])
    mod = argvs[2]

    for dirpath, dirs, files in branch:
        for f in files:
            json_data = loadingJson(dirpath, f)
            output(json_data, mod)

Führen Sie wie folgt aus.

json2text.py


$ python json2text.py [json] [speaker]

Durch diese Verarbeitung wurde es kurz vor den Trainingsdaten (Utterance, Response) abgeschlossen. Danach wird eine morphologische Analyse durchgeführt und separat geschrieben.

$ mecab -Owakati Utterance.txt > Utterance_wakati.txt

Das Dialogmodell lernen

Bis zu diesem Punkt wurden durch die Verarbeitung Lerndaten (Äußerung, Antwort) erstellt. Als nächstes werden wir das Modell trainieren.

learning.py


#!/usr/bin/env python                                                                                                                                                    
# -*- coding: utf-8 -*-                                                                                                                                                  

import sys
import numpy as np
import chainer
from chainer import cuda, Function, gradient_check, Variable, optimizers, serializers, utils
from chainer import Link, Chain, ChainList
import chainer.functions as F
import chainer.links as L


class seq2seq(chainer.Chain):
    def __init__(self, jv, ev, k, jvocab, evocab):
        super(seq2seq, self).__init__(
            embedx = L.EmbedID(jv, k),
            embedy = L.EmbedID(ev, k),
            H = L.LSTM(k, k),
            W = L.Linear(k, ev),
            )

    def __call__(self, jline, eline, jvocab, evocab):
        for i in range(len(jline)):
            wid = jvocab[jline[i]]
            x_k = self.embedx(Variable(np.array([wid], dtype=np.int32)))
            h = self.H(x_k)
        x_k = self.embedx(Variable(np.array([jvocab['<eos>']], dtype=np.int32)))
        tx = Variable(np.array([evocab[eline[0]]], dtype=np.int32))
        h = self.H(x_k)
        accum_loss = F.softmax_cross_entropy(self.W(h), tx)
        for i in range(len(eline)):
            wid = evocab[eline[i]]
            x_k = self.embedy(Variable(np.array([wid], dtype=np.int32)))
            next_wid = evocab['<eos>'] if (i == len(eline) - 1) else evocab[eline[i+1]]
            tx = Variable(np.array([next_wid], dtype=np.int32))
            h = self.H(x_k)
            loss = F.softmax_cross_entropy(self.W(h), tx)
            accum_loss += loss

        return accum_loss

def main(epochs, urr_file, res_file, out_path):

    jvocab = {}
    jlines = open(utt_file).read().split('\n')
    for i in range(len(jlines)):
        lt = jlines[i].split()
        for w in lt:
            if w not in jvocab:
                jvocab[w] = len(jvocab)

    jvocab['<eos>'] = len(jvocab)
    jv = len(jvocab)

    evocab = {}
    elines = open(res_file).read().split('\n')
    for i in range(len(elines)):
        lt = elines[i].split()
        for w in lt:
            if w not in evocab:
		evocab[w] = len(evocab)
	    ev = len(evocab)

    	demb = 100
    	model = seq2seq(jv, ev, demb, jvocab, evocab)
    	optimizer = optimizers.Adam()
    	optimizer.setup(model)

    	for epoch in range(epochs):
       		for i in range(len(jlines)-1):
           	jln = jlines[i].split()
            	jlnr = jln[::-1]
            	eln = elines[i].split()
            	model.H.reset_state()
            	model.zerograds()
            	loss = model(jlnr, eln, jvocab, evocab)
            	loss.backward()
            	loss.unchain_backward()
            	optimizer.update()
            	print i, " finished"		

        	outfile = out_path + "/seq2seq-" + str(epoch) + ".model"
        	serializers.save_npz(outfile, model)



if __name__ == "__main__":

    argvs = sys.argv

    _usage = """--                                                                                                                                                       
Usage:                                                                                                                                                                   
    python learning.py [epoch] [utteranceDB] [responseDB] [save_link]                                                                                                    
Args:                                                                                                                                                                    
    [epoch]: The argument is the number of max epochs to train models.                                                                                                   
    [utteranceDB]: The argument is input file to train model that is to convert as pre-utterance.                                                                        
    [responseDB]: The argument is input file to train model that is to convert as response to utterance.                                                                 
    [save_link]: The argument is output directory to save trained models.                                                                                                
""".rstrip()

    if len(argvs) < 5:
        print _usage
        sys.exit(0)


    epochs = int(argvs[1])
    utt_file = argvs[2]
    res_file = argvs[3]
    out_path = argvs[4]

    main(epochs, utt_file, res_file, out_path)

Die Ausführung ist wie folgt.

learning.py


$ python learning.py [epoch] [utternceDB] [responseDB] [savelink]

Let's Conversation! Endlich ist das Lernen vorbei. Jetzt lass uns reden!

generating.py


#!/usr/bin/env python                                                                                                                                                    
# -*- coding: utf-8 -*-                                                                                                                                                  

import sys
import numpy as np
import mecab as mcb
import chainer
from chainer import cuda, Function, gradient_check, Variable, optimizers, serializers, utils
from chainer import Link, Chain, ChainList
import chainer.functions as F
import chainer.links as L



class seq2seq(chainer.Chain):
    def __init__(self, jv, ev, k, jvocab, evocab):
        super(seq2seq, self).__init__(
            embedx = L.EmbedID(jv, k),
            embedy = L.EmbedID(ev, k),
            H = L.LSTM(k, k),
            W = L.Linear(k, ev),
            )

    def __call__(self, jline, eline, jvocab, evocab):
        for i in range(len(jline)):
            wid = jvocab[jline[i]]
            x_k = self.embedx(Variable(np.array([wid], dtype=np.int32)))
            h = self.H(x_k)
        x_k = self.embedx(Variable(np.array([jvocab['<eos>']], dtype=np.int32)))
        tx = Variable(np.array([evocab[eline[0]]], dtype=np.int32))
        h = self.H(x_k)
        accum_loss = F.softmax_cross_entropy(self.W(h), tx)
        for i in range(1,len(eline)):
            wid = evocab[eline[i]]
            x_k = self.embedy(Variable(np.array([wid], dtype=np.int32)))
            next_wid = evocab['<eos>'] if (i == len(eline) - 1) else evocab[eline[i+1]]
            tx = Variable(np.array([next_wid], dtype=np.int32))
            h = self.H(x_k)
            loss = F.softmax_cross_entropy(self.W(h), tx)
            accum_loss = loss if accum_loss is None else accum_loss + loss
            
        return accum_loss
        
def mt(model, jline, id2wd, jvocab, evocab):
    for i in range(len(jline)):
        wid = jvocab[jline[i]]
        x_k = model.embedx(Variable(np.array([wid], dtype=np.int32), volatile='on'))
        h = model.H(x_k)
    x_k = model.embedx(Variable(np.array([jvocab['<eos>']], dtype=np.int32), volatile='on'))
    h = model.H(x_k)
    wid = np.argmax(F.softmax(model.W(h)).data[0])
    if wid in id2wd:
        print id2wd[wid],
    else:
        print wid,
    loop = 0
    while (wid != evocab['<eos>']) and (loop <= 30):
        x_k = model.embedy(Variable(np.array([wid], dtype=np.int32), volatile='on'))
        h = model.H(x_k)
        wid = np.argmax(F.softmax(model.W(h)).data[0])
        if wid in id2wd:
            print id2wd[wid],
        else:
            print wid,
        loop += 1
    print

def constructVocabs(corpus, mod):

    vocab = {}
    id2wd = {}
    lines = open(corpus).read().split('\n')
    for i in range(len(lines)):
        lt = lines[i].split()
        for w in lt:
            if w not in vocab:
                if mod == "U":
                    vocab[w] = len(vocab)
                elif mod == "R":
                    id2wd[len(vocab)] = w
                    vocab[w] = len(vocab)

    if mod == "U":
        vocab['<eos>'] = len(vocab)
        v = len(vocab)
        return vocab, v
    elif mod == "R":
        id2wd[len(vocab)] = '<eos>'
        vocab['<eos>'] = len(vocab)
        v = len(vocab)
        return vocab, v, id2wd
         
def main(mpath, utt_file, res_file):

    jvocab, jv = constructVocabs(utt_file, mod="U")
    evocab, ev, id2wd = constructVocabs(res_file, mod="R")

    demb = 100
    model = seq2seq(jv, ev, demb, jvocab, evocab)
    serializers.load_npz(mpath, model)

    while True:
        utterance = raw_input()
        if utterance == "exit":
            print "Bye!!"
            sys.exit(0)
                                                                                                                                   
        jln = mcb.construct_BoW(utterance)
        jlnr = jln[::-1]
        mt(model, jlnr, id2wd, jvocab, evocab)

                                        
if __name__ == "__main__":

    argvs = sys.argv

    _usage = """--                                                                                                                                                       
Usage:                                                                                                                                                                   
    python generating.py [model] [uttranceDB] [responseDB]                                                                                                               
Args:                                                                                                                                                                    
    [model]: The argument is seq2seq model to be trained using dialog corpus.                                                                                            
    [utteranceDB]: The argument is utterance corpus to gain the distributed representation of words.                                                                     
    [responseDB]: The argument is response corpus to gain the distributed representation of words.                                                                       
""".rstrip()

    if len(argvs) < 4:
        print _usage
        sys.exit(0)


    model = argvs[1]
    utt_file = argvs[2]
    res_file = argvs[3]

    main(model, utt_file, res_file)                                                                                                                 

generating.py


$ python generating.py [model]

Hier ist das Ergebnis des eigentlichen Dialogs!

Epoch=1:

U: Hallo S: Ich war vor kurzem dort, vor kurzem. U: Was ist dein Geschlecht? S: Nun, zu welchem gehst du? U: Hast du einen Liebhaber? S: Nun, zu welchem gehst du?

Epoch=10:

U: Hallo S: Hallo U: Was ist dein Geschlecht? S: Ist es Wahrsagerei! Ich bin darin nicht gut. .. .. Winterkleidung ist schwer und teuer. U: Hast du einen Liebhaber? S: Gibt es etwas Neues?

Epoch=30:

U: Hallo S: Hallo, danke U: Was ist dein Geschlecht? S: Es kann sich um ein umgekehrtes Kind handeln und das Geschlecht ist unbekannt U: Hast du einen Liebhaber? S: Vielleicht willst du einen Liebhaber

Wenn Epoche 1 ist, habe ich das Gefühl, dass ich überhaupt nichts gelernt habe, aber mit zunehmender Epoche wird die Antwort immer besser! Diesmal war es jedoch ein geschlossener Test (Eingabe der Äußerung in den Korpus), so dass es im eigentlichen Dialog noch schlimmer wird. Es scheint notwendig, die Anzahl der Trainingsdaten zur Verbesserung um etwa 3 Stellen zu erhöhen.

Zusammenfassung

Dieses Mal haben wir ein Dialogsystem implementiert, das mithilfe des seq2seq-Modells Wort für Wort Äußerungen erzeugen kann. Das Problem ist, dass eine große Menge an Trainingsdaten für die Implementierung vorbereitet werden muss. Umgekehrt kann jedoch ein System erstellt werden, das auf diese Weise interagieren kann, wenn Daten vorhanden sind. Die Antwort hängt stark von der Verteilung der verwendeten Daten ab.

Wenn Sie es interessant finden, probieren Sie es bitte aus! Let's Conversation!

Recommended Posts

Implementierung eines Dialogsystems mit Chainer [seq2seq]
Implementierung von "verschwommenen" neuronalen Netzen mit Chainer
Implementierung von TF-IDF mit Gensim
Implementierung des Chainer-Serienlernens mit Mini-Batch variabler Länge
Rank Learning über ein neuronales Netzwerk (RankNet-Implementierung von Chainer)
Einfache Implementierung eines neuronalen Netzwerks mit Chainer
Implementierung von Desktop-Benachrichtigungen mit Python
Aufbau eines Empfehlungssystems mit Mundpropaganda doc2vec
Seq2Seq (1) mit Chainer
[Python] Implementierung von Clustering mit einem gemischten Gaußschen Modell
Implementierungsbeispiel eines einfachen LISP-Verarbeitungssystems (Python-Version)
Implementierung der Bedingung zur Beurteilung der Objektauthentizität unter Verwendung der __bool__- Methode
Übersicht über DNC (Differentiable Neural Computers) + Implementierung durch Chainer
Bayesianische Optimierungsimplementierung von Hyperparametern des neuronalen Netzwerks (Chainer + GPyOpt)
Lassen Sie uns die Emotionen von Tweet mit Chainer (2.) analysieren.
Implementierung eines Faltungs-Neuronalen Netzwerks mit nur Numpy
Lassen Sie uns die Emotionen von Tweet mit Chainer (1.) analysieren.
Implementierung der Fibonacci-Sequenz
Über Variable von Chainer
Beispiel für die Verwendung von Lambda
Vorsichtsmaßnahmen bei der Verwendung von Chainer
Implementierung von VGG16 mit Keras, die ohne Verwendung eines trainierten Modells erstellt wurden
Überprüfung und Implementierung der Videorekonstruktionsmethode mit GRU und Autoencoder