Tensorflow打造聊天机器⼈
Tensorflow聊天机器⼈
聊天机器⼈也叫做对话系统,是⼀个热门领域。微软、facebook、苹果、google、、slack都在上⾯做了⼤的投⼊,这是⼀波新的试图改变⼈和服务交流的创业浪潮。例如 ai,,以及⼀些库例如,。
许多公司都希望机器⼈可以⾃然对话,和⼈类没有区别。并且许多对外声明说⽤了NLP和深度学习技术来实现这个⽬标。但围绕AI这些天花乱坠的宣传有时候也很难区别现实和虚化的差别。
我要在这个系列⽂章⾥将⼀些构建对话系统⽤到的深度学习技术。开始介绍⼀下我们的现状、哪些是可能做到的,哪些是短期内还⼏乎不可能实现的。本⽂是引⾔,后续还有介绍实现⽅式的
模型分类
检索式  VS ⽣成式
检索式模型(简单些)⽤⼀个预定义好的回复库并且在输⼊和上下⽂基础上启发式挑选近似回答。i挑选的办法可以简单⽤基于规则的正则匹配,也可以复杂⼀点⽤⼀些机器学习的分类器。系统不产⽣任何新的⽂本,都是在固定的集合⾥挑选答案。
⽣成式模型(难)不依赖预定义好的回复库,⽣成新的语料答复。典型的依赖于机器翻译技术,但是并不是真正的从⼀门语⾔翻译到另外⼀门语⾔。我们是从⼀个输⼊翻译到⼀个输出(回复)
两种模型都有各⾃明显的优缺点。检索式因为依赖了预定义的语料,不会犯语法错误,然⽽可能没法处理语料库⾥没有遇到过的问题。同样的原因,也不能引⽤回上下⽂相关的实体信息例如对话前⾯提到名字、地点等。⽣成式模型更“聪明”,理论上可以识别上下⽂的实体信息(其实没见过业界有实现到的),但是模型⾮常难训练,且很容易犯语法错误(特别是长句⼦)并需要⾮常⾮常海量的训练数据。
两种模型都可以应⽤深度学习技术,但是学界研究都主要转移到⽣成式模型。像深度学习架构就⾮常适合⽣成⽂本,学者⾮常希望在这个领域有很快的进展。但是我们仍然在让这个模型建设且能好好⼯作的早期阶段。正式的产品系统现在还是更多采⽤检索式架构。
长 vs 短对话
对话越长机器⼈越难⾃动跟上,另外⼀种情况就是⽬标是针对每个问题做⼀个答复的短⽂本对话。例如⼀个问题很容易得到满意的答复,但是多个问题的长对话就很难跟上,需要跟踪多轮对话并记住已经说过的话。客服机器⼈就是典型的需要在多轮对话上保持长对话的情景
开域 vs 闭域
开域的机器⼈,设定⽤户就是随时随地的开聊,不需要⼀个定义很好的⽬标和意图。例如在社交平台twitter、reddit上就是典型的开域。⽆限的话题、海量的知识库需求才能满⾜⽤户的聊天答复,所以这就⾮常难
闭域设定⽤户的输⼊以及机器⼈的输出都是在有限的空间⾥的,因为机器⼈有设定好的服务⽬的。技术⽀持、商店助⼿就是典型的闭域问题。这些系统不需要陪客⼈聊政治,只需要满⾜指定任务就好了。当然⽤户可以随时输⼊其他领域的问题,但是⽤户也没有期望机器⼈有好的互答
通⽤的挑战
当建设机器⼈的时候,还是有很多明显或者没那么明显的挑战存在,⼤多数已经研究的⽐较热门了。
综合上下⽂
去产⽣⼀个合理的回答,需要考虑语⾔学、语境、⾃然环境综合起来的上下⽂结果。在长对话⾥,⼈记住说过的话、交流过的信息。那是⼀个语⾔学的上下⽂,最常⽤的⽅式是把对话内容嵌⼊到⼀个向量,但是在长对话领域做这个事情⾮常难。和两篇论⽂都⾛进这个领域做了些尝试。还需要结合其他环境信息例如:⽇期时间、地点、⽤户信息。
COHERENT PERSONALITY
⼈格⼀致性
理想情况下机器⼈应该对同⼀个语义输⼊保持⼀致的答复,例如问:“你多⼤?” “你的年龄是多少?”应该有同样的答复。这个听起来简单,但是没有固定的知识库或者模型⾥的“⼈格化"就是⼀个⾮常棘⼿的问题。许多系统学着产⽣⼀些花⾔巧语搪塞性质的话术来答复,但并没有训练成产⽣语义⼀致的回答。因为通常训练数据都是来⾃很多不同⽤户的海量数据,⽽不是同⼀个⽤户。这个论⽂在尝试在建设⼀个⼈格化的模型⽅向迈出了第⼀步。
EVALUATION OF MODELS
模型评估
评估⼀个对话机器⼈的理想⽅式是量化它是否完成任务例如在特点对话⾥解决了⼀个客服问题。但是需要⼈⼯来判断这个⽬标,所以这个评估办法成本⾮常⾼,⾼到很难实现。有时候连⽬标都不是很明确,例如开放式领域的聊天机器⼈,什么都聊本⾝也没什么⽬标。常规的量化指标例如⽤于机器翻译,并且基于⽂本匹配的办法也不怎么合适,因为⼀个合理的答复可能包含很多不同的词语短语。实际有这篇论⽂:研究组发现没有任何⼀种通⽤的评估办法和真正的和⼈类判断是相关的。
INTENTION AND DIVERSITY
意图和多样化
⽣成式模型有个常见的问题就是趋向于⽣成⼀些普适性的答案例如:“不错” 或者“不知道”。google的smart relpy早期版本就⼗分喜欢对⼏乎所有输⼊都回答:“我爱你”  可以参看:。部分原因是系统学习训练的⽅式,包括数据和⽤的算法模型相关。。但是⼈类读输⼊做出的回复是带有明显的意图的,⽽⽣成式模型(特别是开域型系统)训练的时候是不带意图的,所以缺少多样性的。
那到底该怎样开展实际⼯作呢?
已经给到了所有的前沿研究现在,那我们到底处在什么阶段并且这些聊天机器⼈系统实际怎么搭建的呢?让我们再次考虑架构的分类上来,⼀个开域的机器⼈明显不可能利⽤检索式架构完成,因为你没法⼿⼯创造⾜够覆盖所有领域的回复库。⼀个⽣成式架构的开源机器⼈⼏乎达到⼈造的综合智能了(AGI),因为他需要处理所有可能的场景。我们离这个阶段还⾮常⾮常远,尽管很多⼈在这个领域做研究
剩下给我们的就是在闭域机器⼈上搭建检索式和⽣成式架构还算⽐较合适,但是随着对话越长、上下⽂更重要后,问题也变得更加难。
在最近的⼀个采访⾥:andrew Ng ,百度的⾸席科学家,说的不错:
“现在⼤量的深度学习的价值是在你能得到⼤量数据的垂直领域⾥。这⾥就有⼀些我们没法做的领域例如:
产⽣⼀个有意义的对话。现在有⼀些demo,但如果你在⼀些看起来有意义的对话上稍微挑剔的试⼀下,很快就会偏离轨道”
许多公司开始开放他们的对话机器⼈产品给⽤户,并承诺他们能随着收集⾜够多的数据后会变得越来越好。其实这更容易在⼀些⾮常垂直的领域上容易取得成功,例如聊天呼叫uber打车。在开放⼀点的领域⾥就超越我们现在的能⼒范围了。然⽽我们可以⽤这些系统的提议和修正回复去辅助⼈⼯更可⾏⼀些。
在外⽹产品系统上犯语法错误是⾮常有代价的并且会赶⾛我们的⽤户。这也是为啥⼤部分外⽹产品优先选择检索式架构,它不会发语法错误或者冒犯⽤户的回答。如果公司可以放⼿在海量数据上做,⽣成式模型也可⾏,不但是也必须有其他技术去辅助防⽌偏离轨道,就像微软的
本⽂将要实现⼀个检索式的机器⼈。检索式架构有预定好的语料答复库。检索式模型的输⼊是上下⽂潜在的答复。模型输出对这些答复的打分,可以选择最⾼分的答案作为回复。
既然⽣成式的模型更弹性,也不需要预定义的语料,为何不选择它呢?
⽣成式模型的问题就是实际使⽤起来并不能好好⼯作,⾄少现在是。因为答复⽐较⾃由,容易犯语法错误和不相关、不合逻辑的答案,并且需要⼤量的数据且很难做优化。⼤量的⽣产系统上还是采⽤检索模型或者检索模型和⽣成模型结合的⽅式。例如google的。⽣成模型是研究的热门领域,但是我们还没到应⽤它的程度。如果你想要做⼀个聊天机器⼈,最好还是选⽤检索式模型
ubuntu语料库
本⽂采⽤了ubuntu语料库(UDC),它是⽬前公开的最⼤的数据集。介绍了这个语料库建设细节。我们就不重复说这个⽅⾯了。但是了解你所⽤到的语料是⾮常重要的。
训练数据包含100w的例⼦,⼀半是正⾯(label=1)⼀半是负⾯(lable=0),每个例⼦包含⼀个上下⽂,⼀个正⾯的标注意味着答复是符合上下⽂的,负⾯的标注意味着答复不符合(答复是从其他语料对⾥去随机挑选搭配过来的)
下⾯是⼀些例⼦:
注意下,上⾯的数据集⽣成脚本已经使⽤做了⼀系列的语料处理包括(,,),脚本也做了把名字、地点、组织、URL。系统路径等实体信息⽤特殊的token来替代。这些预处理不是严格必要的,但是能改善⼀些系统的表现。语料的上下⽂平均有86个词语,答复平均有17个词语长。有⼈做了语料的统计分析:
数据集有测试和验证集。他们的格式和训练集不⼀样。每个测试验证集记录包含上下⽂、正确答复和9个不正确的⼲扰项。模型的⽬标是给争取答复打上⾼分,给⼲扰项⽬打上低分reddit
有多个办法评估我们的模型,⼀种常⽤的办法是 K召回,它的意思是让模型在10个备选答案(1个正确答案和9个⼲扰项)⾥出最好的K个答案,如果正确的那个答案在K个答案⾥,就标记为成功案例。所
以K越⼤,模型的任务就越轻松,如果我们让K=10,这就得到⼀个100%的召回率,因为我们最多就10个备选。如果K=1,模型只有⼀次机会选中正确答案。
这⾥你或许好奇9个⼲扰项⽬怎么选出来的,这个数据集⾥是随机的⽅法选择的。但是现实世界⾥你可能数百万的可能答复,并且你并不知道答复是否合理正确。你没能⼒从数百万的可能的答复⾥去挑选⼀个得分最⾼的正确答复。成本太⾼了! google的smart reply⽤分布式集技术计算⼀系列的可能答复去挑选,或者你只有百来个备选答案,可以去评估每⼀个
BASELINES
在开始梦幻的神经⽹络模型之前,我们先建⽴⼀些简单的基础模型去帮助我们理解我们模型有些什么表现可以期待。将要⽤下⾯的函数去评估K召回量化办法:
这⾥y是根据模型预测的分数降序排列的,y_test是数据集⾥的标注数据。例如y=[0,3,1,2,5,6,4,7,8,9]意味这0是分数最⾼的预测答案,9是最低分数。也y_test是10个备选答案,且下标为0的答案⽤于存正确的答案,后⾯都是⼲扰项。
直觉的来看,模型完全随机的预测正确答案,在K=1的时候,召回率是10%,K=2的时候,召回率是20%。
好的,看起来recall运⾏结果符合预期。当然我们不需要⼀个随机模型。另外⼀个基础模型还采⽤过tf-idf预测代表:“term frequency –inverse document” frequency,(词频/⽂档),它可以量化⼀个词语在⼀个⽂档⾥的重要程度。这也是和整个语料库相关的指标。不继续深⼊tf idf,你可以⽹上资料看看。含有相似内容的⽂档有相似的tf idf向量,直觉的看⼀个上下⽂问题和答复有相似的词语,更像是合格的问答对。⾄少⽐随机的办法好。需要多库都有内置的tf idf库函数实现例如.所以很容易使⽤,我们可以构造⼀个tf idf模型来看看表现如何
评估:
Recall @ (1, 10): 0.495032
Recall @ (2, 10): 0.596882
Recall @ (5, 10): 0.766121
Recall @ (10, 10): 1
可以看到,tf idf模型表现的⽐随机模型好⾮常多。但是这个效果⾥完美还很远,⾸先因为我们基于tf-idf是假设问答是相似的,其实正确的答案并不需要和问题相似,其次tfidf忽略了词语的顺序,⽽这个是⼀个重要的信息。⽤神经⽹络模型可以做的更好⼀点。
DUAL ENCODER LSTM
本⽂要构造的深度学习模型:Dual Encoder LSTM ,(双编码LSTM)
这是我们可以应⽤的神经⽹络中的⼀种,并不需要是最好的⼀个,你也可以继续看看所有的没有使⽤过的神经⽹络架构,这是个热门研究领域,例如句⼦到句⼦的模型经常⽤到机器翻译⽅⾯,或许也能做的不错。⽽我们这次采⽤双编码的LSTM模型,因为被论⽂证明在这个语料库上表现不错,有相当不错的改进。这就意味着我们知道对这个模型的该有什么期待以及确保我们的实现是正确的。在这个问题上应⽤其他模型也是很有趣的项⽬。
双编码LSTM看起来是这样的:
h'g
它⼤致⼯作原理如下:
1、上下⽂问题和答案都分成每个词语,每个词语嵌⼊成向量,词向量⾸先⽤standford的⼯具,并在训练中进⾏调优(这个是可选项,并没有在上图⾥表达出来。我也发现⽤Glove来初始化词向量并没有显著改善模型的表现)
2、向量化后的问题和答案都输⼊⼀个词⼀个词的输⼊到同⼀个循环神经⽹络RNN⾥,RNN就会产⽣⼀个向量,⼤致可以当作是问题和答案(图⾥的c和r)的“语义”,我们可以选择向量的维度⼤⼩,这⾥我们⽤了256维
3、⽤⼀个矩阵M乘以c得到⼀个答案的预测结果r' ,c是⼀个256d的向量,M就是⼀个256x256的矩阵,所以结果是另外⼀个256d的向量,当作是⽣成的回复。M需要在训练中学习
4、我们计算r'和r的相似度。⽅法是对向量就点积,点积越⼤说明向量越相似,这个答案就应该得到更⾼的分数,然后我们⽤sigmoid逻辑函数把这个分数转化为⼀个概率。注意上图⾥把第三步和第四步放到⼀起了。
我们需要⼀个损失(代价)函数来训练⽹络。将要使⽤分类问题常⽤到的⼆分cross-entropy.对QA对(上下⽂问题和答复)的真实标注为y,y可以是1(实际答复)或者0(不正确答案),上⾯第四步计算出来的概率记作 y‘ 。所以这个corss entropy计算公式是:L= −y * ln(y') −(1 − y) * ln(1−y') 公式背后的意义很简单:如果y=1,L = -ln(y'),这就惩罚了那些预测值远离1的情况,如果y=0,L= −ln(1−y'),惩罚了那些预测值远离0的情况。
实现代码上我们使⽤了以及(有⼀些对⾼层次tensorflow函数的组合使⽤)
DATA PREPROCESSING 数据预处理
数据集本来是CSV格式,虽然我们可以直接使⽤,但是最好是把数据转化成tensorflow⽤到的格式,(也有tf.SequenceExample,但是看起来tf.learn不⽀持)⽤这个格式的好处是允许tensorflow直接load输⼊数据,并接着完成清洗、批量操作、排队等环节。我们也需要建设⼀个词库,和意味着需要映射每⼀个词语为⼀个整数,例如 cat 这个词语或许可以⽤ 2631表⽰,TFRecord⽂件会⽣成并存储这些数字,⽽不是存储词语。我们也要存储这个映射表⽅便⽤整数到词语。
每个例⼦包含下⾯的字段:
1、上下⽂(Q):代表上下⽂⽂本的⼀系列词id序列例如[231,2190,737,0,912]
2、上下⽂长度
3、回复:同1⼀样的词id序列
4、回复长度
5、标注:训练数据⾥才有 0 or 1
6、⼲扰项[N] 测试/验证集⾥有,N 属于0-8.都是词id的序列
7、⼲扰项[N]长度
预处理代码,产⽣三个⽂件train.tfrecords, validation.tfrecords  test.tfrecords ,你可以下载⾃⼰运⾏下。
CREATING AN INPUT FUNCTION
创建⼀个输⼊函数
为了利⽤tesorflow内置的⽀持训练和评估的功能,我们需要创建⼀个输⼊函数,输⼊函数返回输⼊数据的批量结果。实际上因为训练数据和测试数据格式不同,我们需要分别些对应的输⼊函数。输⼊函数应该返回⼀系列特征和标注(如果允许)例如下⾯的代码⾏:
因为需要在训练和评估中⽤不同的输⼊函数,且我们讨厌代码拷贝,于是我们创建⼀个叫做create_input_fn的封装,⾥⾯为适当的模式(mode)创建合适的输⼊函数。当然需要⼀些其他参数帮忙,下⾯是我们正在⽤的定义:
完整的代码可以看,概括起来,这个函数实现下列功能:
1、定义了描述example ⽂件的字段的特征
2、⽤tf.TFRecordReader从输⼊⽂件中读取数据
3、根据字段特征定义解析记录
4、提取训练标注
5、批量处理examples和训练标注
6、返回批量处理好的examples和训练标注
DEFINING EVALUATION METRICS
定义评估标准
我们已经提到要⽤recall@K来评价模型,幸运的是tensorflow已经提供许多标准的评估办法,包括recall@K, 要使⽤这些功能,需要创建⼀个映射评估⽅法名到⼀个以预测结果和标注数据作为参数的处理函数的字典。
上⾯我们⽤到了去把⼀个需要3个参数的函数转化只需要2个参数。别让streaming_sparse_recall_at_k 这个名字把你搞晕
了,Streaming(流)只是代表这个量化办法是在多个批量结果上的累加求和,并且sparse(稀疏)和我们的标注格式有关。
这带来⼀个重要的点:评估期间,到底我们的预测结果是⼀个什么形式\格式呢?在训练阶段,我们预测样本例⼦⾥答复是否正确的概率。但是在评估阶段,我们的⽬的是给测试样本⾥的答复和9个⼲扰项⽬打分并且挑选最好的1个答案---我们没有简单预测正确或者错误。这就代表我们给每个样本评估的时候要返回⼀个10个分数的向量,例如
[0.34, 0.11, 0.22, 0.45, 0.01, 0.02, 0.03, 0.08, 0.33, 0.11]
这些分数和10个备选答案各⾃独⽴相关,所以概率之和不需要为1。因为在测试样本⾥下标为0的答案是真的回答。测试集⾥每个样本的标注数据全是0(译注:这是在介绍数据集格式,有些⼲扰⽂章)。上⾯这个得分向量结果来看,因为第⼀个答复是第⼆⼤的(第⼀⼤的
0.45),所以这个样本的测试结果在recall@1⾥,第⼀个答复只能算错误,那么测试为模型失败。⽽在recall@2⾥,则测试通过。BOILERPLATE TRAINING CODE
训练代码的范式化
在些实际的神经⽹络之前,我想要写⼀个针对训练和评估模型的代码范式。因为⼀旦你依赖了正确的接⼝⽅式,就⾮常⽅便更换你⽤过任何类型神经⽹络模型。我们假设有⼀个模型函数model_fn⽤批量特征、标注数据、模式(训练或者评估)为参数,并返回预测结果。那么我们可以写出训练模型的通⽤代码:
这⾥我们为模型创建了评估器modle_fn,训练和评估数据的两个输⼊函数,和我们的评估办法(⼀堆映射了recall k的函数集合),也定义了⼀个监视函数,在训练过程中每FLAGS.eval_every评价⼀下模型。训练程序是⽆休⽌的运⾏,但是tensorflow会⾃动保存节点数据到MODEL_DIR⽬录下,所以你可以任何时候停⽌训练。还有⼀个⾮常不错的技术就是early stopping要⽤上,意味着当验证集结果已经没有什么改善的时候(例如模型开始过拟合了)就要⾃动停⽌训练。可以看完整的代码
上⾯提到FLAGS,关于它我简要提两点:
1、FLAGS是⼀种拿到命令⾏⽅法的途径,类似python的参数解析。
2、hparams是在代码⾥⽤户⾃定义的对象,保存了假设参数、⽹络节点数等我们可以调整模型的数据。这些hparams对象实例化的时候就给到模型。
创建模型
现在我们已经设置好了数据输⼊、解析、评估、训练⽅⾯的代码规范接⼝,该是我们些DUAL LSTM神经⽹络模型代码的时候了。我们已经写了⼀个的封装来透明训练数据和验证数据格式的不同,把数据都转为模型需要的格式。需要⼀个model_impl作为参数,这是实际做预测的函数,在我们这⾥就是要⽤的这个Dual Encoder LSTM,但是我们可以简单更换模型为其他神经⽹络。我们看看到底是怎样的代码:
接下图
完整的代码在,到这⾥我们可以给训练过程代码实例化模型了。
好了,我们可以运⾏:
python udc_train.py
它就可以开始训练⽹络,并时⽽验证出召回率数据(你可以设置这个验证频率,参数是eval_every),可以运⾏python udc_train.py --help. 得到我们定义tf.flags和hparams的完整命令⾏标志列表。
评估模型:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。