设为首页 收藏本站
查看: 1496|回复: 0

[经验分享] Tensorflow搞一个聊天机器人

[复制链接]

尚未签到

发表于 2017-6-7 11:38:15 | 显示全部楼层 |阅读模式
  catalogue



0. 前言
1. 训练语料库
2. 数据预处理
3. 词汇转向量
4. 训练
5. 聊天机器人 - 验证效果
  0. 前言
  不是搞机器学习算法专业的,3个月前开始补了一些神经网络,卷积,神经网络一大堆基础概念,尼玛,还真有点复杂,不过搞懂这些基本数学概念,再看tensorflow的api和python代码觉得跌跌撞撞竟然能看懂了,背后的意思也能明白一点点
  0x1: 模型分类
  1. 基于检索的模型 vs. 产生式模型
  基于检索的模型(Retrieval-Based Models)有一个预先定义的"回答集(repository)",包含了许多回答(responses),还有一些根据输入的问句和上下文(context),以及用于挑选出合适的回答的启发式规则。这些启发式规则可能是简单的基于规则的表达式匹配,或是相对复杂的机器学习分类器的集成。基于检索的模型不会产生新的文字,它只能从预先定义的"回答集"中挑选出一个较为合适的回答。
产生式模型(Generative Models)不依赖于预先定义的回答集,它会产生一个新的回答。经典的产生式模型是基于机器翻译技术的,只不过不是将一种语言翻译成另一种语言,而是将问句"翻译"成回答(response)
DSC0000.png

  2. 长对话模型 vs. 短对话模型
  短对话(Short Conversation)指的是一问一答式的单轮(single turn)对话。举例来说,当机器收到用户的一个提问时,会返回一个合适的回答。对应地,长对话(Long Conversation)指的是你来我往的多轮(multi-turn)对话,例如两个朋友对某个话题交流意见的一段聊天。在这个场景中,需要谈话双方(聊天机器人可能是其中一方)记得双方曾经谈论过什么,这是和短对话的场景的区别之一。现下,机器人客服系统通常是长对话模型
  3. 开放话题模型 vs. 封闭话题模型
  开放话题(Open Domain)场景下,用户可以说任何内容,不需要是有特定的目的或是意图的询问。人们在Twitter、Reddit等社交网络上的对话形式就是典型的开放话题情景。由于该场景下,可谈论的主题的数量不限,而且需要一些常识作为聊天基础,使得搭建一个这样的聊天机器人变得相对困难。
封闭话题(Closed Domain)场景,又称为目标驱动型(goal-driven),系统致力于解决特定领域的问题,因此可能的询问和回答的数量相对有限。技术客服系统或是购物助手等应用就是封闭话题模型的例子。我们不要求这些系统能够谈论政治,只需要它们能够尽可能有效地解决我们的问题。虽然用户还是可以向这些系统问一些不着边际的问题,但是系统同样可以不着边际地给你回复 ;)
  Relevant Link:



http://naturali.io/deeplearning/chatbot/introduction/2016/04/28/chatbot-part1.html
http://blog.topspeedsnail.com/archives/10735/comment-page-1#comment-1161
http://blog.csdn.net/malefactor/article/details/51901115
  1. 训练语料库



wget https://raw.githubusercontent.com/rustch3n/dgk_lost_conv/master/dgk_shooter_min.conv.zip
解压
unzip dgk_shooter_min.conv.zip
  Relevant Link:



https://github.com/rustch3n/dgk_lost_conv
  2. 数据预处理
  一般来说,我们拿到的基础语料库可能是一些电影台词对话,或者是UBUNTU对话语料库(Ubuntu Dialog Corpus),但基本上我们都要完成以下几大步骤



1. 分词(tokenized)
2. 英文单词取词根(stemmed)
3. 英文单词变形的归类(lemmatized)(例如单复数归类)等
4. 此外,例如人名、地名、组织名、URL链接、系统路径等专有名词,我们也可以统一用类型标识符来替代
  M 表示话语,E 表示分割,遇到M就吧当前对话片段加入临时对话集,遇到E就说明遇到一个中断或者交谈双方转换了,一口气吧临时对话集加入convs总对话集,一次加入一个对话集,可以理解为拍电影里面的一个"咔"



convs = []  # conversation set
with open(conv_path, encoding="utf8") as f:
one_conv = []  # a complete conversation
for line in f:
line = line.strip('\n').replace('/', '')
if line == '':
continue
if line[0] == 'E':
if one_conv:
convs.append(one_conv)
one_conv = []
elif line[0] == 'M':
one_conv.append(line.split(' ')[1])
  因为场景是聊天机器人,影视剧的台词也是一人一句对答的,所以这里需要忽略2种特殊情况,只有一问或者只有一答,以及问和答的数量不一致,即最后一个人问完了没有得到回答



# Grasping calligraphy answer answer
ask = []  # ask
response = []  # answers
for conv in convs:
if len(conv) == 1:
continue
if len(conv) % 2 != 0:
conv = conv[:-1]
for i in range(len(conv)):
if i % 2 == 0:
ask.append(conv)
else:
response.append(conv)
   DSC0001.png
DSC0002.png

  Relevant Link:
  3. 词汇转向量
  我们知道图像识别、语音识别之所以能率先在深度学习领域取得较大成就,其中一个原因在于这2个领域的原始输入数据本身就带有很强的样本关联性,例如像素权重分布在同一类物体的不同图像中,表现是基本一致的,这本质上也人脑识别同类物体的机制是一样的,即我们常说的"举一反三"能力,我们学过的文字越多,就越可能驾驭甚至能创造组合出新的文字用法,写出华丽的文章
  但是NPL或者语义识别领域的输入数据,对话或者叫语料往往是不具备这种强关联性的,为此,就需要引入一个概念模型,叫词向量(word2vec)或短语向量(seq2seq),简单来说就是将语料库中的词汇抽象映射到一个向量空间中,向量的排布是根据预发和词义语境决定的,例如,"中国->人"(中国后面紧跟着一个人字的可能性是极大的)、"你今年几岁了->我 ** 岁了"
  0x1: Token化处理、词编码
  将训练集中的对话的每个文件拆分成单独的一个个文字,形成一个词表(word table)



def gen_vocabulary_file(input_file, output_file):
vocabulary = {}
with open(input_file) as f:
counter = 0
for line in f:
counter += 1
tokens = [word for word in line.strip()]
for word in tokens:
if word in vocabulary:
vocabulary[word] += 1
else:
vocabulary[word] = 1
vocabulary_list = START_VOCABULART + sorted(vocabulary, key=vocabulary.get, reverse=True)
# For taking 10000 custom character kanji
if len(vocabulary_list) > 10000:
vocabulary_list = vocabulary_list[:10000]
print(input_file + " phrase table size:", len(vocabulary_list))
with open(output_file, "w") as ff:
for word in vocabulary_list:
ff.write(word + "\n")
   DSC0003.png
  完成了Token化之后,需要对单词进行数字编码,方便后续的向量空间处理,这里依据的核心思想是这样的
  我们的训练语料库的对话之间都是有强关联的,基于这份有关联的对话集获得的词表的词之间也有逻辑关联性,那么我们只要按照此表原生的顺序对词进行编码,这个编码后的[work, id]就是一个有向量空间关联性的词表



def convert_conversation_to_vector(input_file, vocabulary_file, output_file):
tmp_vocab = []
with open(vocabulary_file, "r") as f:
tmp_vocab.extend(f.readlines())
tmp_vocab = [line.strip() for line in tmp_vocab]
vocab = dict([(x, y) for (y, x) in enumerate(tmp_vocab)])
for item in vocab:
print item.encode('utf-8')
  所以我们根据训练预料集得到的此表可以作为对话训练集和对话测试机进行向量化的依据,我们的目的是将对话(包括训练集和测试集)的问和答都转化映射到向量空间



土 968
"土"字在训练集词汇表中的位置是968,我们就给该字设置一个编码968
  0x2: 对话转为向量
  原作者在词表的选取上作了裁剪,只选取前5000个词汇,但是仔细思考了一下,感觉问题源头还是在训练语料库不够丰富,不能完全覆盖所有的对话语言场景
DSC0004.png DSC0005.png

  这一步得到一个ask/answer的语句seq向量空间集,对于训练集,我们将ask和answer建立映射关系
  Relevant Link:
  4. 训练
  0x1: Sequence-to-sequence basics
  A basic sequence-to-sequence model, as introduced in Cho et al., 2014, consists of two recurrent neural networks (RNNs): an encoder that processes the input and a decoder that generates the output. This basic architecture is depicted below.
   DSC0006.png
  Each box in the picture above represents a cell of the RNN, most commonly a GRU cell or an LSTM cell. Encoder and decoder can share weights or, as is more common, use a different set of parameters. Multi-layer cells have been successfully used in sequence-to-sequence models too
In the basic model depicted above, every input has to be encoded into a fixed-size state vector, as that is the only thing passed to the decoder. To allow the decoder more direct access to the input, an attention mechanism was introduced in Bahdanau et al., 2014.; suffice it to say that it allows the decoder to peek into the input at every decoding step. A multi-layer sequence-to-sequence network with LSTM cells and attention mechanism in the decoder looks like this.
DSC0007.png

  0x2: 训练过程
  利用ask/answer的训练集输入神经网络,并使用ask/answer测试向量映射集实现BP反馈与,使用一个三层神经网络,让tensorflow自动调整权重参数,获得一个ask-?的模型



# -*- coding: utf-8 -*-
import tensorflow as tf  # 0.12
from tensorflow.models.rnn.translate import seq2seq_model
import os
import numpy as np
import math
PAD_ID = 0
GO_ID = 1
EOS_ID = 2
UNK_ID = 3
# ask/answer conversation vector file
train_ask_vec_file = 'train_ask.vec'
train_answer_vec_file = 'train_answer.vec'
test_ask_vec_file = 'test_ask.vec'
test_answer_vec_file = 'test_answer.vec'
# word table 6000
vocabulary_ask_size = 6000
vocabulary_answer_size = 6000
buckets = [(5, 10), (10, 15), (20, 25), (40, 50)]
layer_size = 256
num_layers = 3
batch_size = 64

# read *dencode.vec和*decode.vec data into memory
def read_data(source_path, target_path, max_size=None):
data_set = [[] for _ in buckets]
with tf.gfile.GFile(source_path, mode="r") as source_file:
with tf.gfile.GFile(target_path, mode="r") as target_file:
source, target = source_file.readline(), target_file.readline()
counter = 0
while source and target and (not max_size or counter < max_size):
counter += 1
source_ids = [int(x) for x in source.split()]
target_ids = [int(x) for x in target.split()]
target_ids.append(EOS_ID)
for bucket_id, (source_size, target_size) in enumerate(buckets):
if len(source_ids) < source_size and len(target_ids) < target_size:
data_set[bucket_id].append([source_ids, target_ids])
break
source, target = source_file.readline(), target_file.readline()
return data_set
if __name__ == '__main__':
model = seq2seq_model.Seq2SeqModel(source_vocab_size=vocabulary_ask_size,
target_vocab_size=vocabulary_answer_size,
buckets=buckets, size=layer_size, num_layers=num_layers, max_gradient_norm=5.0,
batch_size=batch_size, learning_rate=0.5, learning_rate_decay_factor=0.97,
forward_only=False)
config = tf.ConfigProto()
config.gpu_options.allocator_type = 'BFC'  # forbidden out of memory
with tf.Session(config=config) as sess:
# 恢复前一次训练
ckpt = tf.train.get_checkpoint_state('.')
if ckpt != None:
print(ckpt.model_checkpoint_path)
model.saver.restore(sess, ckpt.model_checkpoint_path)
else:
sess.run(tf.global_variables_initializer())
train_set = read_data(train_ask_vec_file, train_answer_vec_file)
test_set = read_data(test_ask_vec_file, test_answer_vec_file)
train_bucket_sizes = [len(train_set) for b in range(len(buckets))]
train_total_size = float(sum(train_bucket_sizes))
train_buckets_scale = [sum(train_bucket_sizes[:i + 1]) / train_total_size for i in range(len(train_bucket_sizes))]
loss = 0.0
total_step = 0
previous_losses = []
# continue train,save modle after a decade of time
while True:
random_number_01 = np.random.random_sample()
bucket_id = min([i for i in range(len(train_buckets_scale)) if train_buckets_scale > random_number_01])
encoder_inputs, decoder_inputs, target_weights = model.get_batch(train_set, bucket_id)
_, step_loss, _ = model.step(sess, encoder_inputs, decoder_inputs, target_weights, bucket_id, False)
loss += step_loss / 500
total_step += 1
print(total_step)
if total_step % 500 == 0:
print(model.global_step.eval(), model.learning_rate.eval(), loss)
# if model has't not improve,decrese the learning rate
if len(previous_losses) > 2 and loss > max(previous_losses[-3:]):
sess.run(model.learning_rate_decay_op)
previous_losses.append(loss)
# save model
checkpoint_path = "chatbot_seq2seq.ckpt"
model.saver.save(sess, checkpoint_path, global_step=model.global_step)
loss = 0.0
# evaluation the model by test dataset
for bucket_id in range(len(buckets)):
if len(test_set[bucket_id]) == 0:
continue
encoder_inputs, decoder_inputs, target_weights = model.get_batch(test_set, bucket_id)
_, eval_loss, _ = model.step(sess, encoder_inputs, decoder_inputs, target_weights, bucket_id, True)
eval_ppx = math.exp(eval_loss) if eval_loss < 300 else float('inf')
print(bucket_id, eval_ppx)
  Relevant Link:



https://www.tensorflow.org/tutorials/seq2seq
http://suriyadeepan.github.io/2016-06-28-easy-seq2seq/
  5. 聊天机器人 - 验证效果



# -*- coding: utf-8 -*-
import tensorflow as tf  # 0.12
from tensorflow.models.rnn.translate import seq2seq_model
import os
import sys
import locale
import numpy as np
PAD_ID = 0
GO_ID = 1
EOS_ID = 2
UNK_ID = 3
train_ask_vocabulary_file = "train_ask_vocabulary.vec"
train_answer_vocabulary_file = "train_answer_vocabulary.vec"

def read_vocabulary(input_file):
tmp_vocab = []
with open(input_file, "r") as f:
tmp_vocab.extend(f.readlines())
tmp_vocab = [line.strip() for line in tmp_vocab]
vocab = dict([(x, y) for (y, x) in enumerate(tmp_vocab)])
return vocab, tmp_vocab

if __name__ == '__main__':
vocab_en, _, = read_vocabulary(train_ask_vocabulary_file)
_, vocab_de, = read_vocabulary(train_answer_vocabulary_file)
# word table 6000
vocabulary_ask_size = 6000
vocabulary_answer_size = 6000
buckets = [(5, 10), (10, 15), (20, 25), (40, 50)]
layer_size = 256
num_layers = 3
batch_size = 1
model = seq2seq_model.Seq2SeqModel(source_vocab_size=vocabulary_ask_size,
target_vocab_size=vocabulary_answer_size,
buckets=buckets, size=layer_size, num_layers=num_layers, max_gradient_norm=5.0,
batch_size=batch_size, learning_rate=0.5, learning_rate_decay_factor=0.99,
forward_only=True)
model.batch_size = 1
with tf.Session() as sess:
# restore last train
ckpt = tf.train.get_checkpoint_state('.')
if ckpt != None:
print(ckpt.model_checkpoint_path)
model.saver.restore(sess, ckpt.model_checkpoint_path)
else:
print("model not found")
while True:
input_string = raw_input('me > ').decode(sys.stdin.encoding or locale.getpreferredencoding(True)).strip()
# 退出
if input_string == 'quit':
exit()
# convert the user's input to vector
input_string_vec = []
for words in input_string.strip():
input_string_vec.append(vocab_en.get(words, UNK_ID))
bucket_id = min([b for b in range(len(buckets)) if buckets[0] > len(input_string_vec)])
encoder_inputs, decoder_inputs, target_weights = model.get_batch({bucket_id: [(input_string_vec, [])]},
bucket_id)
_, _, output_logits = model.step(sess, encoder_inputs, decoder_inputs, target_weights, bucket_id, True)
outputs = [int(np.argmax(logit, axis=1)) for logit in output_logits]
if EOS_ID in outputs:
outputs = outputs[:outputs.index(EOS_ID)]
response = "".join([tf.compat.as_str(vocab_de[output]) for output in outputs])
print('AI > ' + response)
  神经网络还是很依赖样本的训练的,我在实验的过程中发现,用GPU跑到20000 step之后,模型的效果才逐渐显现出来,才开始逐渐像正常的人机对话了
   DSC0008.png
  Relevant Link:
  Copyright (c) 2017 LittleHann All rights reserved

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.iyunv.com/thread-384766-1-1.html 上篇帖子: opencv 震撼你的视觉-------基础篇 下篇帖子: 我拖拖拖--H5拖放API基础篇
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表