[PYTHON] Implémentation d'un système de dialogue utilisant Chainer [seq2seq]

introduction

Récemment, les progrès des DNN (Deep Neural Networks) ont été remarquables et ont réussi dans tous les domaines. On entend souvent parler des domaines de la classification des images et de la reconnaissance vocale, et les systèmes de dialogue ne font pas exception. Maintenant que l'environnement de la bibliothèque Python est en cours d'amélioration, je voudrais présenter brièvement la construction d'un système interactif utilisant des DNN.

Modèle DNN pour les systèmes de dialogue

Il existe deux modèles principaux de DNN pour construire un système de dialogue.

Cet article traite de ce dernier modèle d'encodeur-décodeur. Grâce à la riche bibliothèque telle que Chainer, n'importe qui peut l'implémenter tant qu'il y a des données qui sont une paire de parole et de réponse.

Environnement de montage

Les packages dépendants sont résumés ci-dessous. Vous pouvez créer l'environnement d'un seul coup avec la commande pip install ou conda install.

seq2seq Le modèle décrit dans cet article s'appelle seq2seq (Sequence to Sequence), et est construit à partir de deux types de réseaux: un encodeur RNN (Reccurent Neural Network) pour l'entrée et un décodeur RNN pour la sortie (la partie RNN est générale). Est implémenté en utilisant LSTM).

Article original: Sequence to Sequence Learning with Neural Networks

Kobito.DW0UK9.png

Lorsqu'il est appliqué à un système de dialogue, l'énoncé d'entrée est passé à travers le codeur et la réponse à celui-ci est apprise mot par mot avec le décodeur.

Données d'entraînement

Cette fois, nous apprendrons à utiliser les données du défi de détection des échecs de dialogue 2. Le corpus de détection des échecs de dialogue est un corpus que tout le monde peut utiliser dans n'importe quel but, vous pouvez donc l'utiliser en toute confiance.

Corpus de détection des échecs de dialogue:

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

Le contenu du corpus est construit avec des fichiers json. En outre, un script qui affiche le dialogue stocké dans le fichier json de manière facile à lire est également inclus. Essayez d'exécuter comme suit.

show_dial.py


$ python show_dial.py 1470622453.log.json 

Résultat de l'exécution:

dialogue-id : 1470622453 speaker-id : DBD-01 group-id : S: Bonjour. Attention aux coups de chaleur. 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 O O U: Oui. Je vous remercie. Vous devez également faire attention. S: Ne fais-tu pas attention aux coups de chaleur? T O T X X T X X X T O T T T X X O X X X X X X T T O O T O ... (Omis) S: L'exercice est un changement de rythme, n'est-ce pas? Je me sens bien T T X O T O O T T T T O X O O X O O T O T X T T T T T T T T

Construction des données d'entraînement

En utilisant le corpus de détection d'échec de dialogue ci-dessus, seq2seq est appris en utilisant la parole de l'utilisateur (U) comme parole d'entrée pour l'apprentissage et la parole du système à ce moment comme parole de réponse.

Kobito.mTPH4G.png

Comme le montre la figure ci-dessus, un fichier dans lequel la parole (énoncé) et la réponse (réponse) sont un à un est créé et donné en tant que données d'apprentissage. Utilisez le script suivant pour convertir le fichier json en un fichier texte qui constituera les données d'apprentissage.

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)

Exécutez comme suit.

json2text.py


$ python json2text.py [json] [speaker]

Par ce traitement, il était terminé juste avant les données d'apprentissage (énoncé, réponse). Après cela, une analyse morphologique est effectuée et elle est écrite séparément.

$ mecab -Owakati Utterance.txt > Utterance_wakati.txt

Apprendre le modèle de dialogue

Les données d'apprentissage (énoncé, réponse) ont été créées jusqu'à présent par le traitement. Ensuite, nous allons former le modèle.

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)

L'exécution est la suivante.

learning.py


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

Let's Conversation! Enfin l'apprentissage est terminé. Maintenant parlons!

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]

Voici le résultat du dialogue proprement dit!

Epoch=1:

U: Bonjour S: J'y suis allé récemment, récemment. U: Quel est votre sexe? S: Eh bien, lequel allez-vous? U: Avez-vous un amoureux? S: Eh bien, lequel allez-vous?

Epoch=10:

U: Bonjour S: Bonjour U: Quel est votre sexe? S: Est-ce une bonne aventure! Je ne suis pas bon dans ce domaine. .. .. Les vêtements d'hiver sont lourds et chers. U: Avez-vous un amoureux? S: Y a-t-il quelque chose de récent?

Epoch=30:

U: Bonjour S: Bonjour, merci U: Quel est votre sexe? S: Il peut s'agir d'un enfant inversé et le sexe est inconnu U: Avez-vous un amoureux? S: Peut-être que vous voulez un amoureux

Quand Epoch est 1, j'ai l'impression de ne pas avoir appris du tout, mais au fur et à mesure que l'Époque monte, j'ai l'impression de mieux répondre! Cependant, cette fois, c'était un test fermé (saisissez l'énoncé dans le corpus), donc ce sera encore pire dans le dialogue réel. Il semble nécessaire d'augmenter le nombre de données d'entraînement d'environ 3 chiffres pour une amélioration.

Résumé

Cette fois, nous avons implémenté un système de dialogue qui peut générer des énoncés mot par mot en utilisant le modèle seq2seq. Le problème est qu'il est nécessaire de préparer une grande quantité de données de formation pour la mise en œuvre, mais inversement, s'il y a des données, un système qui peut avoir un tel dialogue peut être créé. La réponse variera considérablement en fonction de la distribution des données utilisées.

Si vous le trouvez intéressant, essayez-le! Let's Conversation!

Recommended Posts

Implémentation d'un système de dialogue utilisant Chainer [seq2seq]
Implémentation de réseaux neuronaux "flous" avec Chainer
Implémentation de TF-IDF à l'aide de gensim
Mise en œuvre de l'apprentissage en série de Chainer à l'aide de mini-lots de longueur variable
Apprentissage des classements à l'aide d'un réseau neuronal (implémentation RankNet par Chainer)
Implémentation simple d'un réseau neuronal à l'aide de Chainer
Implémentation des notifications de bureau à l'aide de Python
Construction d'un système de recommandation utilisant le bouche-à-oreille doc2vec
Seq2Seq (1) avec chainer
[Python] Implémentation du clustering à l'aide d'un modèle gaussien mixte
Exemple d'implémentation d'un système de traitement LISP simple (version Python)
Implémentation de la condition de jugement d'authenticité d'objet à l'aide de la méthode __bool__
Présentation de DNC (Differentiable Neural Computers) + Implémentation par Chainer
Mise en œuvre de l'optimisation bayésienne des hyper paramètres du réseau de neurones (Chainer + GPyOpt)
Analysons les émotions de Tweet en utilisant Chainer (2ème)
Implémentation d'un réseau de neurones convolutifs utilisant uniquement Numpy
Analysons les émotions de Tweet en utilisant Chainer (1er)
Implémentation de la séquence de Fibonacci
À propos de la variable du chainer
Exemple d'utilisation de lambda
Précautions lors de l'utilisation de Chainer
Implémentation de VGG16 à l'aide de Keras créé sans utiliser de modèle entraîné
Vérification et mise en œuvre de la méthode de reconstruction vidéo en utilisant GRU et Autoencoder