简易中文聊天机器人的构建

一、引言
本教程目的是为了能够让读者从0到1构建一个简易版本的聊天机器人,同时对其中使用的API进行较为详细的介绍,目的是为了能够让读者在搭建完成一个聊天机器人的同时,也能知道其中的各个“零部件”到底是起着什么样的作用?而在本文中,为了叙述的连续性,所以并没有涉及到任何的理论知识,后续将会对理论知识进行整体的梳理。
二、开发环境
2.1. python-3.6.4
2.2. jieba-0.39
2.3. tensorflow-1.8.0
2.3. flask-0.12.2
三、语料准备
未分词处理的小黄鸡语料库:xiaohuangji50w_nofenci.conv(23.3MB)
四、整体流程
在这里插入图片描述
五、开发步骤
4.1. 类的初始化工作
首先我们需要做一些类的初始化工作,例如模型超参数的设置工作、隐藏层节点数等等。
在这里插入图片描述
4.2. 输入结构的定义
接着我们定义输入数据的结构,假设我们已经有了一个模型,我们知道由两个模块构成,一个是Encoder端,一个是Decoder端,我需要给这个模型输入数据吧,那么对这两个模块输入的数据结构是怎样的了?
在这里插入图片描述
4.3. 读取数据,构建字典表
接着我们需要单独一个方法进行数据的读取,同时建立字符及其索引的字典表以及其倒排表,因为后面的Embedding操作需要这个字典表。
在这里插入图片描述
4.4. 构建Seq2Seq模型
这个步骤是构建聊天机器人过程中最重要的一步,在这个步骤中,我们需要依次完成以下的工作:
(1)对Encoder端输入数据的Embedding操作
(2)构建Encoder端
(3)获取Encoder端的输出
(4)对Decoder端输入数据的Embedding操作
(5)构建Decoder端
(6)获取Decoder端的输出
最后对Encoder端与Decoder端进行连接并返回Decoder端的输出结果
在这里插入图片描述
4.5. 开始训练
当完成了前期的准备工作,也完成了模型定义工作,接着就需要对这个模型进行训练,需要进行如下的工作:
(1)构建训练图
(2)获取Mini-Batch训练数据并喂给训练图
(3)训练完成后保存模型
在这里插入图片描述
4.6. 开始预测
在完成模型的训练后,同时保存了训练好的模型,当需要进行预测的时候,即输入一条语句后,我们需要进行如下工作:
(1)构建输入
(2)加载模型
(3)开始预测
在这里插入图片描述
六、API介绍
当我们对整体的过程有了较好的了解后,我们再深入到代码里面去瞧瞧Tensorflow框架里面的API细节。
6.1. Embedding相关API
涉及到的方法有如下几个:
tf.contrib.layers.embed_sequence
tf.nn.embedding_lookup
这个在博客https://blog.csdn.net/jack_jmsking/article/details/82465019中进行了详细的介绍。
6.2. RNN单元相关API
涉及到的方法有如下几个:
tf.contrib.rnn.LSTMCell
tf.contrib.rnn.MultiRNNCell
tf.nn.dynamic_rnn
我们先介绍tf.contrib.rnn.LSTMCell,其原型为:
在这里插入图片描述
目前我们只需要了解其中的红框标注的参数,分别为单元个数、权重初始化器、忘记门偏置、状态输出形式以及激励函数,而我们的简易版本只需要设置参数num_units以及initializer。其它的默认就可以了(其中激励函数默认为tanh),假设我们设置num_units = 2,最后得到的单层LSTM单元如下图所示:
在这里插入图片描述
其中上方的图为经典的LSTM结构图,下方的图为2个LSTM节点的单层LSTM,而T表示的是时间序列,如果需要多层,那么就需要用到tf.contrib.rnn.MultiRNNCell方法了,其中原型为:
在这里插入图片描述
其中cells就是RNN单元列表。什么意思了?就是说假设开始我构建了2个节点LSTM单元lstm_cell,然后我需要两层,那么就可以传入cells = [lstm_cell, lstm_cell]。
有了LSTM模型了,那么肯定就会有输入和输出,那么该如何输入数据了,又该如何得到输出,输出的又是什么了?这个时候就需要重要的“角色”上场了,她就是tf.nn.dynamic_rnn方法,其原型为:
在这里插入图片描述
目前我们只需要了解红框里面的参数,一个是传入的RNN单元(单层或者多层),而inputs即为输入,其格式为[batch_size, time_steps, embed_size],time_steps表示的是在时间上展开后的迭代的次数,对于中文而言,假如输入了一条中文:“你在干什么?”,最后通过分词得到词组“你/在/干什么/?”共4个词组,所以单看这一条的话,那么time_steps则为4。而这个方法的输出为:
outputs和states,这两个参数有什么区别了?目前只需简单了解一下:
outputs记录了每条样本每个词组在模型最后一层各个节点的输出
states记录了每条样本最后一个词组在模型各个层各个节点的状态输出(c,h)
具体对这个函数的测试见
https://github.com/jmsking/TF_Learn/tree/master/tf_function_test/test_dynamic_rnn.py
6.3. Seq2Seq相关API
涉及到的方法有如下几个:
tf.layers.Dense
tf.contrib.seq2seq.TrainingHelper
tf.contrib.seq2seq.BasicDecoder
tf.contrib.seq2seq.dynamic_decode
tf.contrib.seq2seq.GreedyEmbeddingHelper
tf.contrib.seq2seq.sequence_loss
tf.train.AdamOptimizer
其中tf.layers.Dense建立一个全连接层,其原型为:
在这里插入图片描述
一个是全连接层的节点数,一个是连接到该层各个节点的权重初始化器。
接着需要tf.contrib.seq2seq.TrainingHelper控制Decoder端的数据的读入,其原型为:
在这里插入图片描述
其中inputs为Decoder端的输入,格式为:[batch_size, time_steps, embed_size],而sequence_length记录了batch_size各条样本的序列长度,该方法每次生成下一个时间阶段的输入。接着就是BasicDecoder方法,其原型为:
在这里插入图片描述
其输出为BasicDecoderOutput,即元组(rnn_output, sample_id)
为了能更容易理解TrainingHelper和BasicDecoder,我们首先观察一下Tensorflow自带的测试文件basic_decoder_test.py,我们重点看一下BasicDecoder的输出,如下所示:
在这里插入图片描述
其中expected_output_depth为输出节点数,cell_depth为LSTM中节点数,
step_outputs为每次迭代的输出,为BasicDecoderOutput类型,其中第一个元素为rnn_output,第二个元素为sample_id,在这里我们需要注意在Tensorflow内部的处理都是以time_major=True的形式进行处理的(虽然这个参数默认为False,但是在程序里面会进行转换)。举个例子:假设输入一个句子“你/真好”,这里的batch_size = 1, time_step = 2, 程序里面是以[time_step, batch_size, embed_size]形式进行处理,所以输入time_step=1时的[batch_size, embed_size],获得输出BasicDecoderOutput,其中rnn_output形状为(batch_size, expected_output_depth),而每个阶段(time_step)中的状态变量(c,h)的形状都为(batch_size, cell_depth)。而step_outputs中的第二个元素,即sample_id为rnn_ouput中值最大对应的位置。
接着就是dynamic_decode方法,其原型为:
在这里插入图片描述
在这个方法里面的参数一个是decoder,传入Decoder模型,而参数impute_finished = True时会忽略掉某个time_step阶段中为零的输入,maximum_iterations控制着Decoder端迭代的最大次数。而该方法的输出共两个:final_outputs, final_state,其中final_outputs为BasicDecoderOutput类型,即(rnn_output, sample_id)元组,而final_state是(c,h)元组,一般我们只需要final_outputs。这里注意final_outputs和final_state的形状,可以从decoder_test.py文件中得到:
在这里插入图片描述
TrainingHelper用在训练阶段,而GreedyEmbeddingHelper则用在预测阶段,其原型为:
在这里插入图片描述
其中start_tokens为开始标志,其形状为(batch_size,),而end_token为结束标志。
接着我们将定义损失函数,见sequence_loss,其原型为:
在这里插入图片描述
文档中的说明很清楚,在这我就直接COPY过来了:
在这里插入图片描述
其中这里的weights,我们主要是为了忽略掉那些PAD(因为一个batch_size的输入序列的长度不一致,处理时会通过PAD进行补齐)。同时在这里还有几个重要的方法用来自己设置梯度值:
在这里插入图片描述
这个方法其实就是minimize的第一个阶段,返回的是(gradient, variable),即针对某个变量的梯度值。当重新设置了变量的梯度值后,然后将其应用Adam优化器上,即:
在这里插入图片描述
这个方法就是minimize的第二阶段。
PS:通常我们的用法是:AdamOptimizer.minimize(loss),其实这个过程一共分成了两个阶段,一个阶段是通过compute_gradients计算各个变量的梯度值,另一个阶段是使用该计算得到的梯度值,即apply_gradients。

代码见:https://github.com/jmsking/Chat.git

  • 0
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值