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