Motivation

对话是一个很大的概念,有非常非常多的子问题,刚入坑的小伙伴很可能迷失在对话的一小块区域里无法自拔,本文就是为解决这一类问题的。希望读者在看完本文后,可以理清楚对话的每个概念为什么而存在,以及它在整个对话王国中的位置。

不过,小夕也未能关注到对话领域的每个角落,一些小topic也属于仅听过但是没有深入研究过的状态,因此本文表述有失偏颇的地方还望大佬们多多指出~~

本文结构

  1. 对话系统总览
  2. 生成式闲聊
  3. 检索式对话
  4. 任务完成型对话
    4.1 难做的对话数据集
    4.2 对话动作
    4.3 理解用户输入
    4.4 记录对话状态
    4.5 多轮决策完成对话目标
    4.6 NLG:最后一公里
  5. 40+篇参考文献

注1:由于本文参考文献较多,为了方便查阅,有需要的小伙伴可以在后台回复「对话系统」领取本文全部参考文献
注2:本文后续的update(错误修订,内容新增等)见知乎链接
 

1 总览

一种偷懒的介绍方法是说对话任务分为任务完成型和闲聊型,分别对应限定域对话和开放域对话。其实这样是不准确的,属于过度简化了。在用户实际的对话需求中,除了闲聊和完成特定任务,还有一大类知识获取(即问答)的需求。而这一类问答型对话又可以分为基于知识图谱的对话(KB-Dialogue)和检索式多轮问答等更细化的方向。除此之外,闲聊、任务完成与问答三者之间也不是完全割裂的,还有一些类似于“任务完成型闲聊”的奇怪场景,使得闲聊也带有一定的目的性(比如撩妹)。因此可以用一张图总结一下这三类对话需求,图1所示。

对话系统的设计艺术(完结)_生成式

闲聊型和任务完成型对话本文接下来会展开讲,问答型对话一方面可以参考检索式闲聊(完成FAQ型问答),另一方面可以参考CoQA[22] benchmark上的近期工作以及一些KB Dialogue的工作[27,28],写不动了,以后再讲╮( ̄▽ ̄””)╭

同样的道理,它们与限定域和开放域的关系也不是绝对一一对应的,任务完成为导向的对话也不一定就是限定域的,闲聊也不一定是开放域的(比如给定一个topic或文章而展开的场景化闲聊[1])

不过我们还是回到主要问题上来。对于右上角这类典型的闲聊任务,主流方法分为两类,基于检索的方法与基于生成的方法。显然兵分两路的话就代表各自都有各自的优缺点,当然也有一些工作试图将两者进行结合,比如融合检索知识的生成式对话[3,4]以及工程视角下的简单结合[5]。

2 生成式对话

对于生成式对话,可以说一言难尽。虽然看似可以将对话看作是seq2seq问题,然而我们知道在机器翻译问题上成功的seq2seq范式很大程度上是因为翻译的source和target有相对确定的token-level的对应关系,例如中英翻译任务上,输入为”I love Xiaoxi”,那么对应的输出空间其实非常有限

source: 我爱小夕  
target:I love Xiaoxi

然而在对话任务上,输入为”I love Xiaoxi”时对应的输出空间非常大

source: 我爱小夕
target1: 小夕是谁?
target2: 她也爱你鸭
target3: 诶?你不是有女朋友嘛!
target4: 哦 ….

显然,如果同样建模成生成问题,机器翻译任务相当于每个输入问题都有一个差不多的标准答案,即属于“客观题”;而对话生成任务则相当于一个“主观题”,只要不偏题就行,没有标准回答,好坏全看个人文采。然而主观题的阅卷难度显然要比客观题的难度大得多啊(想象一下给作文打分的难度和给文言文翻译打分的难度差异),因此对生成式对话系统(开放域)的评估一直是一个很让人头疼的问题,需要高度依赖人工评定,当然啦也有一些对话自动评估相关的工作如ADEM[17],RUBER[18]等,感兴趣的小伙伴可以自行了解一下。

更加糟糕的是,将一句文言文翻译成白话文基本上不需要关心语境问题(一句文言文或一句英文的意思很少会因为语境变了而发生非常大的改变),然而,对话回复则是跟语境高度耦合的。这里的“语境”包括显式的聊天历史以及隐式的说话人的属性、性格以及聊天发生时的场合和受众等。

想象一下,如果在之前显式的聊天历史中你已经说过了“今天生病了不想出门”,然后对于一个query,“你接下来想去干啥”,如果忽略语境的话,“我想去爬山”这个回复完全没毛病,但是考虑到语境的话,爬山这一类回复就完全不应该在输出空间中存在了。

同样的道理,说话人的性别、年龄以及聊天对象的身份、地位等隐式的“语境”也会对输出空间作出极大的限制。因此可以说,对话生成问题是一个条件非常非常多的条件生成问题(conditional generation)。由此,决定回复内容的“条件”到底有哪些?应该怎么建模?这些条件给出来后,怎么让这些条件有效的控制生成器的生成内容?这类问题是对话生成相比机器翻译任务来说困难的多的另一大原因。贴几篇对话生成的控制相关的paper[23,24,25,26],感兴趣的小伙伴自行去刷啦~~~或者催更催更催更

那么在对话问题上,有没有办法规避或者缓解生成式对话带来的上面这个评估以及控制这两大问题呢?这就是工业界偏爱检索式对话系统的原因了。

3 检索式对话

检索式对话系统的一般框架[2]如图所示。

对话系统的设计艺术(完结)_建模_02

检索式对话可以用于解决闲聊型对话或FAQ问答型对话问题。对于FAQ问题,大部分情况下单轮对话就能解决问题,而闲聊型对话就要依赖上下文了,否则容易出现前后话语矛盾的情况(虽然现在聊天一致性问题依然是一个未解难题)。

FAQ问题一般是限定域,相当于容易解决。但是,闲聊型问题一般是不限定对话范围的,也就是说是开放域的,那么这时候存放query-response pairs(上图中的message-response pairs)的语料库必须要是非常大非常丰富的,否则难以应对用户千奇百怪的问题和回复,这时候语料库往往要从一些开放的社交网站上爬取,如Twitter、微博、贴吧等。

显然我们不可能让一个深度神经网络在每次寻找回复时遍历整个语料库(可能9102年的计算机可以做到),因此在用神经网络深度匹配合适的回复之前,一般要先经过一个“粗筛”的模块召回若干相关的回复,减少q-r匹配的工作量。这个模块一般将用户当前轮的query与语料库里query进行快速匹配(当然你也可以加更多feature提高合适回复的召回率),得到几十上百个候选回复,完成第一轮的匹配。

显然这就要求q-q粗召模块非常轻量级且匹配的相关度可接受,因此一些常用的信息检索模型如BM25以及一些轻量级文本特征表示模型如bow就派上用场了。小夕这里推荐一个专业的开源文本匹配工具AnyQ[6],不仅将上面的常用模型集成进来,方便“一键调用”,而且部署阶段做了非常多的优化,所以想自己搭检索式对话/问答系统的小伙伴就不用在该模块上重复造轮子啦。

有了若干相关的候选回复后,我们假设其中一定至少存在一个合适的回复,那么就需要一个非常精良的q-r精排模块来将这个合适的回复挑出来。这部分也是学术界研究主要focus的点。在多轮对话匹配这一方面百度的对话团队做了大量的工作,属于长期霸榜的状态,比如Multi-view[7]、DAM[8]以及DGU[9]。最新的DGU刷爆了去年的DAM 9个点了╮( ̄▽ ̄””)╭

对DGU之前的多轮对话匹配模型感兴趣的同学可以看我一年前写的这篇《小哥哥,检索式chatbot了解一下》,详解了Multi-view、SMN、DUA以及当时最好的DAM模型。

如果说生成式对话是在茫茫实数语义空间中寻找那几个合适的回复,那么检索式对话就是给你有限数量的选项,让你选出里面最合适作为回复的选项(有没有联想到word2vec与语言模型的关系)。显然做选择题要比大海捞针容易多了。而且生成式系统中,合适的回复是位于一个抽象、不可解释的实数语义空间,我们当然很难直接干预捞针范围,而在检索式系统中,合适的回复是在一堆干扰回复中,我们要滤掉那些干扰回复或者缩小搜索范围非常容易——给q-q粗召模块添油加醋甚至直接对语料库开刀就好啦╮(╯▽╰)╭

然而,很多时候我们的对话是带有目的性的。虽然检索式对话与生成式对话系统可以为每个query找到合适的回复,但是这些模型是在纯文本语料上训练的,无力建模对话过程中的意图或者发出一个结构化的动作,比如

“小夕你帮我看看现在几点啦”
小夕:“好呀好呀”

然后没了。。。显然用户期待的并不是“好呀好呀”就结束了,而是当前时刻具体的时间!而要想知道当前的时间,就必须要调用一些服务/API(不然你觉得对话语料库中会写着当前时刻么)去查询一下,显然这些隐式的操作不可能出现在检索式对话或生成式对话模型的训练集中。

更不用说,如果对话开始之前你的目的就很明确——比如找到一家适合今晚约约约的餐厅并预定上座位,那么整个对话的过程一定都是全程高能的(需要频繁的查询数据库,记录和更新前面对话的关键信息),任何一个检索式和生成式对话系统都会崩溃的。

路人:小夕,帮我推荐一家餐厅吧~
小夕:好呀好呀,几个人呢~
路人:2个人哦 (小夕赶紧拿出本本记下)
小夕:好的哦,今天中午吗?路人:不是呢,今天晚上~差不多6点左右到 (小夕赶紧记下)
小夕:好滴,想吃中餐还是西餐呢?
路人:想吃牛排!算了算了都快200斤了,而且单位附近貌似没有,那就。。吃椰子鸡吧!(小夕努力理解了一下,然后打开百度地图APP开始寻找附近的椰子鸡) 小夕:找到一家!距离我们2.4km!

怎么解决这类问题呢?显然这类对话问题已经远远超出了文本匹配和文本生成问题的范畴,于是就有了“任务完成型对话”这个重要问题。

4 任务完成型对话

与闲聊为目的的对话有很大的不同,任务完成型的对话的最终目标是完成任务,就相当于打通关一个游戏一样,需要在每一轮对话都采取合适的决策,保证自己不死掉(比如被用户骂死),然后慢慢的打怪升级直到打败最终boss,用户送出好评。

显然这就是一个多步决策求取reward(对话目标完成情况)最大化的问题了,也就是一个RL问题。这时候完全使用有监督学习的话显然建模起来是有缺陷的,难以从整个对话session(一次完整的对话过程)的层次上去优化每轮对话的决策。

啰嗦一下举个栗子,比如这里有个邮件助手,它的action空间中有很多操作,比如add邮件、delete邮件和edit邮件等,那么显然误决策到edit的代价要远远小于误决策到delete的代价,毕竟错误的选择编辑邮件不会直接导致对话终止,而是用户会给你纠错的机会,但是误决策把邮件删了,用户就肯定骂街了。因此通过RL学习到的任务完成型对话系统可以aware到“谨慎”决策,会关注长远的对话目标的实现;而使用有监督学习的话,这些actions是同等权重的,网络只会满足于当下模式匹配。

当然啦,RL并不只是作用于任务完成型对话问题上,在其他对话问题如问答型( 信息获取)[36,41]和闲聊型对话[20]上依然是适用的。总之做对话的话,是很难完全离开RL的,不管有没有全局目标,毕竟多轮对话和用户交互问题就放在这里呢╮( ̄▽ ̄””)╭

不过我们可以先说点轻松愉快的,先来简单了解一下构建对话数据集的问题。

4.1 难做的对话数据集

显然要使用数据驱动的方式建模任务完成型对话的话,不仅要有文本,而且还要给每一轮对话标记好对话进行到此时的状态(对,就是小夕的小本本)以及可能需要发出的动作(比如小夕打开百度地图app去找椰子鸡),显然任务完成型对话的数据集要难做很多。

ps:对做数据集这方面有兴趣的同学可以看小夕前不久写的这篇《如何打造高质量的数据集》。

显然对于任务完成型对话来说,直接目的是完成任务。而要完成任务,在对话的过程中就很容易遇到需要查询甚至更新数据库等超出文本理解和生成范畴的操作,而这些“融会贯通”的操作在互联网上是很难自然产生的,自然也没地方可爬取。这也是为什么任务型对话的数据集大多是人工生成的(注意不仅仅是人工标注,而是样本本身都要比较刻意的人工撰写)。当然了,为了降低造数据集的成本,也出现了一些人与机器协同造数据集[10],甚至让机器与机器对话[11,12],不过考虑到本文的长度,这里就不展开写了,感兴趣的同学从[19]刷起。

假如我们不用考虑数据集怎么造的问题,那么我们就可以考虑一下系统本身怎么设计了。

很多文章开篇就会告诉你一个任务型对话系统包括NLU、DST、DP、NLG等模块。而实际上这种pipeline的方式虽然经典而常用,但是带来的问题也很多,都9012年了,自然诞生了很多joint training甚至end2end的方法将pipeline中的各个模块连接起来。下面慢慢来看一下。

4.2 对话动作

首先来考虑一下前面提到的寻找餐厅并预定餐位的例子。对基础概念已经很了解对小伙伴可快速跳过本节。

显然这里作为系统的小夕寻找椰子鸡、拿小本本记下等操作并不是自然语言,我们将这一类操作称为“对话动作(dialogue action)“,由于这里的小夕是机器人,因此更细化的说法是系统动作system action。同样,如果用户通过按钮或自然语言的方式发出了类似的指令,比如”小夕,帮我找下附近的椰子鸡“,那么用户发出的这个蕴含在自然语言中的命令就称为用户动作user action。显然用户动作就可以看作是用户输入的语义表示。因此,将用户动作从用户的自然语言文本甚至语音信号中解析出来的过程就称为自然语音理解(NLU)或口语理解(SLU)。

那么这里的对话动作在计算机中怎么表示呢?一种简单的想法就是把每个action表示成全局唯一的id。然而action与action之间经常存在很深的耦合关系,比如”预定附近的椰子鸡“与”预定椰子鸡“之间是上下位关系,”预定西二旗附近的椰子鸡“与”预定西三旗附近的椰子鸡“有一个共同的”父action“——预定椰子鸡,显然将所有的action打平成平级的表示肯定是不合理的,但是要完全建模也非常不容易,因此一种折中的方式就是表示成“意图+槽位”,即使用意图来表示一个模糊的目标,使用该意图预定义的一系列槽位来限制这个模糊目标,使得目标具体化。

例如,预定西二旗附近的椰子鸡就可以表示成

{
 意图: 订餐
 槽位: {
       地点: 西二旗,
       餐厅名: 椰子鸡
 }
}

“订餐”就是PM小姐姐预定义的一堆意图中的一个,这个意图下小姐姐预定义了一堆槽位及其可能的取值,其中“地点”和“餐厅名”就是预定义的槽位之二,当然啦,很可能还预定义了其他槽位,比如“就餐人数”,“菜系”,“联系方式”等,只不过在这个case中的取值统统都是None了。

而完成这个自然语言输入到用户动作这种结构化语义表示(frame)的过程就称为自然语言理解(NLU)。当然啦,实际产品中的意图和槽位设计可能要复杂的多,比如有的槽位是不可枚举的(比如时间),槽位有冲突,甚至槽位内要嵌套槽位等,这些就要具体情况具体分析了,有兴趣进一步了解的小伙伴可以看一个PM写的这篇文章[13]。

显然,如果我们能事先把所有的用户动作都做成按钮,让用户戳戳戳,那这个“对话系统”就不需要NLP了╮( ̄▽ ̄””)╭不过这当然是不可能的,我们可以通过几个按钮预定义几个高频用户动作,节省用户口舌,不过大部分的用户动作还是需要从用户的自然语言输入中解析出来的。所以先来看看如何理解用户的自然语言输入吧~

4.3 理解用户输入

理论上用户的一句话可以包含多个意图,比如“我想一会儿去食宝街吃好吃的然后去看电影”,然而实际现在的人工智障发展水平来说,还是很难有闲情逸致处理这种情况的╮( ̄▽ ̄””)╭所以就默认大部分用户还是脑回路比较直白的,一句话最多包含一个意图,因此NLU任务中的意图识别其实可以简单的看作一个文本分类任务。

当对话系统cover的领域很多时,可能意图会多达成百上千,这时候意图识别模型的决策空间变得过大,且各个意图共享同一个模型,每增加一个新意图就要重训模型,导致其他意图也受影响了。不仅导致意图识别模型难训,而且会导致系统变得难以维护。因此一种更好的办法是在意图识别之前再加一级领域分类(domain classification)。

显然意图往往是繁多而且极其不均衡的(在百度地图的小度助手,用户唤醒它导航的意图要远远多于让它播放音乐),而且对于大部分意图来说,也很难从互联网上找到现成的标注语料。因此意图识别虽然是个分类问题,但是同时也是一个小样本学习问题。因此近年来也有不少focus意图识别的小样本学习的工作[38,39]。

虽然对于一个对话session,用户的意图往往只有一个,但是如前所述,一个意图中往往包含很多个槽位。因此可以很自然的将槽位解析任务建模为序列标注任务[14, 15]或者干脆简化为文本多标签分类任务(一个slot-value pair是一个类别)。

而意图识别跟槽位解析任务又明显息息相关的,甚至说高度互相依赖的,因此这两个任务joint training再自然不过了。事实上这个想法在2013年就已经被实践过了,并且确实work[14]。上图。

对话系统的设计艺术(完结)_数据集_03

当时deep learning刚兴起不久,大家对深度学习的认知普遍还是CNN、RNN甚至RBM、DBN之类的,所以这里没有太复杂的网络结构,也没有什么深度可言,这里仅使用一层卷积来encoding文本,得到文本中每个词位的编码了局部上下文信息的特征向量(卷积得到的特征图),而后便可以接CRF来进行序列标注从而得到每个位置的槽位信息(没有槽位信息的词位就标为O,有槽位信息的就标上相应的槽位tag)而后接全局池化来得到整个句子的特征向量。当然这篇论文还使用了Tri-CRF作为槽位解析的输出层,来进一步增强槽位和意图之间的交互。

之后由于LSTM的流行和attention的兴起,joint training的backbone自然也要升级换代了,有兴趣的小伙伴看这篇paper[15]。

如今的意图识别与槽位解析的SOTA方法应该就是百度对话团队的DGU[9]了,基于ERNIE2.0+处理多轮对话的精巧tricks刷爆了绝大多数对话任务,包括前面提到的对话匹配问题和后面将要讲的DST和DP问题。

4.4 记录对话状态

显然我们要完成帮用户订餐这个目标的话,很难通过一轮对话就把所有的必选槽位填满(想象一下用户一句话填满所有槽位的场景:“小夕你好,我想预定本周二晚上6点的北京市海淀区中关村大街上的椰子鸡的4人桌并且预订人的名字是小多以及我的联系电话是10086”。

因此为了摸清楚用户的具体意图(把该填的槽填上,该解决的取值冲突解决掉),往往要像上一章末尾那样有个小本本来记下对话的关键信息,以供上一节提到的对话策略模块使用,帮助进行每一轮的决策。这里的这个小本本就称为对话状态(dialogue state),完成这个小本本更新的过程就称为对话状态追踪(dialogue state tracking,DST,也叫belief tracking)。

这里的关键信息主要就是意图和对应的槽位取值,当然啦,如果你的对话策略模块还要依赖更多从稀奇古怪的地方搜集到的当前时刻的状态(比如监测到了用户位置),你也可以丢进DST模块进行追踪。总之,DST是个小本本,负责记录整个对话全过程积累下来的重要信息。

那么这个对话状态该怎么描述呢?显然最容易想到的就是前面”理解用户输入“小节中提到的用这种frame的方式来描述。而这种结构化的表示并不是在对话记录中显式存在的,很难通过大规模数据驱动的方法来学习记录对话状态的DST模型。

规则方法

不过,既然我们已经有了NLU模块,那么很容易想到一种策略就是直接把NLU的输出结果(意图、槽位的概率分布)离散化一下(比如直接取概率最高的意图、槽值作为本轮的用户动作),然后更新到DST模块里面(本轮的解析结果有新的意图或新的槽位取值的话)。这种简单的基于规则的方法很适合用来进行DST的冷启动。

显而易见的是,这种直接离散化最高概率的规则方法实现的DST会高度依赖NLU的准确性,而且简单的规则只能处理简单的情况,在进行DST更新时完全忽略了已经积累的对话状态,显然这种过于简单粗暴的假设无法建模复杂的状态转移关系。虽然也出现了一些更加复杂的规则方法[16],但是规则系统都非常难以应对输入中的噪声,而无论是语音识别(ASR)系统还是语义理解NLU模块,都非常容易被不规范的输入攻陷(比如小夕的文风),两者级联后噪声更是被进一步放大。因此使用统计方法来建模ASR和NLU输出的不确定性是非常有必要的。

统计方法

虽然人工造DST数据集代价昂贵,但是总有舍得花钱的

os:舍得为小夕花钱的人在哪里呢!!

假如历经千辛万苦,DST数据集造好了,那么一系列有监督学习的操作就可以进行了。比如传统的定义特征然后+最大熵/SVM等分类器。再比如Willaim等人将DST问题建模为有监督的序列标注问题[10],让模型根据NLU若干轮输出的slot-value概率分布来预测当前轮的真实slot-value。

但是这样明显有一个问题。显然DST学习到的函数映射是基于SLU输出的概率分布的,因此一旦我们为了各种花式的借口更新了NLU(比如一个攻城狮说NLU的准确率上升了0.1个点,墙裂要求更新NLU!),那么就会导致DST所熟悉的输入分布发生巨大改变,导致性能大打折扣(相当于你买了一台中英翻译机,但是突然有一天你开始跟它说法语,那翻译机当然就不干了)。

因此很自然的想法是让NLU和DST从pipeline结构变成端到端结构,即让用户自然语言输入直接连接到对话状态上,因此就可以将DST问题建模成“多轮分类”问题啦。

DGU解决DST问题也是根据这种多轮分类的思路来做的,将问题建模成多轮分类问题,然后直接刷爆相关benchmark。总之只要能把DST问题建模成有监督学习问题,一切都好说,后续各种花式无脑炼丹的工作就不展开讲了。

当然了,后续也有工作摆脱了将DST建模成有监督学习的做法,快来往下看~~

另外近期会放出一篇文章进一步探讨DST的建模方法哦,感兴趣的小伙伴记得盯紧订阅号动态( ̄∇ ̄)

4.5 多轮决策完成对话目标

接下来,系统可以根据当前轮NLU模块解析出来的用户动作和小本本(积累的对话状态)来完成相应的“推理”(完成这个过程的模块被称为对话策略模块,Dialogue Policy,DP),决定下一步是去澄清意图,say goodbye还是其他什么动作,并且后续NLG模块(自然语言生成)也会根据DP模块输出的对话决策(系统动作)来决定回复内容(即结构->文本)。

如本章开头所述,要完成对话目标,有监督学习对话策略是不靠谱的,所以对话策略的学习离不开RL。先来简单介绍下RL吧,已经对RL很熟悉的小伙伴可快速跳过。

RL模型是建立在马尔科夫决策过程(MDP)之上的,MDP可以表示成一个五元组(S,A,P,γ,R),这里S是状态(state,比如我们这里的对话状态),A是动作(actions,比如我们这里的对话动作),P定义的是状态转移概率,R是reward,γ是个先忽略的因子。而强化学习的目标就是让策略模型找到最优的策略π,使得累积的奖励最大化。而MDP是假设系统状态S是完全可观测的,因此显然任务型对话不能直接建模成MDP问题,因为DST无论再怎么做都不可能做到100%的准确率,无法为我们的DP模块提供完全观测的对话状态。

那怎么办呢?不要急,虽然任务型对话无法满足马尔可夫性,但是至少我们能够通过DST观测到系统状态的概率分布,也就是模糊的看到了系统的状态(模模糊糊看到的不一定准确,但是总是可以一定程度上反映真实状态的),这时就叫做部分观测。因此我们可以将对话策略的学习问题转化为POMDP问题。

事实上,在很早很早以前便有人提出基于POMDP建模对话策略的学习[21]。如图

对话系统的设计艺术(完结)_生成式_04

这里的Dialogue Model就是DST模块,与对话策略模块(这里的Policy Model)一起称为Dialogue Manager(对话管理模块),即负责记录对话状态并作出对话决策。

这里假设NLU和NLG模块是现成可用的,并且我们定义了一个合理的reward function(比如成功完成用户订餐目标;用户情感极性为正向等)来表示我们所期望达到的对话目标,那么我们就可以通过planning under uncertainty, value iteration, Monte Carlo opti- mization, least squares policy iteration (LSPI), natural actor–critic乃至Q-learning等方式进行学习了。

显然这种交互式学习的方法依赖于与真实用户的大量交互,非常非常的costly。因此就有了用户模拟器的概念,反正对话系统与用户模拟器都不会累,就让他俩一直在那里嗨吧╮(╯▽╰)╭对用户模拟器这个小方向感兴趣的小伙伴可以看阿里小蜜团队写的这篇用户模拟器的科普[30],这里同样不展开讲啦。

然而都已经9012年了,我们怎么能还满足于这么古老的方法呢!看到Q-learning,当然就要想到2015年的DQN(Deep Q Networks)。对话的状态空间可是不小的,直接用原始的Q-learning想想也知道很捉急,不过升级到Q-learning就能把问题解决了吗?对Q-learning和DQN还不了解的小伙伴可以看今天订阅号推送的另一篇文章《扫盲贴:从Q-learning到DQN》

虽然2015年时DQN在Atari游戏上的表现惊艳了我们这些吃瓜群众,单纯的Q-learning要维护一张Q表,使得状态数不能太大,因此就有了DQN来极大的解放了生产力(神经网络来代替Q表)。不过要知道对于Atari游戏来说,每个时刻的状态也是可以完全观测的(原论文用4帧画面描述状态,可以充分表征小球所在位置、速度、角度等信息了)。但是我们前面说了,每一轮的对话状态只能部分观测,显然直接上DQN建模对话也是有明显有问题的。

于是就有了很多对DQN进行魔改的工作,一些可以比较好的适配任务型对话场景。其中一个很重要的变体就是DRQN[40],使用RNN来代替DQN中的全连接层,使得网络可以通过RNN模糊的表示历史信息,不再需要完全观测的状态了,也就是说,可以处理POMDP问题了。

除此之外,我们再来考虑一个问题。虽然如上一节所说,DST可以通过有监督学习的方式来解决。但是,为训练DST而标注数据是代价高昂的,那么能不能将DST也用DRQN一起训练呢?让DST也能在跟用户的交互中成长,摆脱数据标注问题。

即,将下图的虚线部分统一的建模。

对话系统的设计艺术(完结)_数据集_05

当然可以咯,[34]就是使用DRQN建模POMDP-based任务型对话系统,并且使用一个LSTM来作为DST的模型,将LSTM的输出作为DST输出的对话状态(从自然语言直接到对话状态)。该输出被送到策略网络中(一个MLP)去计算对话动作(近似Q值函数),同时送到另外S个策略网络(S为槽位数)中去update槽位信息,如图。

对话系统的设计艺术(完结)_建模_06

当然啦,毕竟这是2016年的文章,所以网络结构比较简单╮( ̄▽ ̄””)╭

沿着DQN这条线走,还能走出来很多文章[35,42],就不展开讲了。不过毕竟都是value-based RL,其实小夕也没有研究的很深入,一些细节没搞懂,有兴趣的小伙伴可以自行往下钻研。

比起基于价值(Value based)的RL方法,显然基于策略(Policy based)RL方法在现代RL里更为主流。

对value based、policy based这些RL大地图中的概念还不了解的小伙伴快去看今天推送的另一篇文章《强化学习扫盲贴:从Q-learning到DQN》,简单、全面而易懂

Policy based dialogue system的代表性工作就是[41],如图

对话系统的设计艺术(完结)_数据集_07

这是一篇非常经典的工作,不仅使用policy based RL建模了对话问题,而且完成了用户自然语言输入、系统自然语言输出、数据库交互的端到端建模。

首先,左上角称为意图网络(intent network),通过一个lstm来完成用户当前轮自然语言输入到表征用户意图的隐状态的映射,网络的输出为中间的policy network提供用户意图的特征表示;

左下角是一个在当时比较复杂的belief tracker,如图

对话系统的设计艺术(完结)_数据集_08

这个看起来比较花哨的tracker有一个花哨的名字“Tied Jordan-type RNN belief tracker with delexicalised CNN feature extractor”。底下是一个Delexicalised CNN网络,通过几层卷积+最终的最大池化来得到句子的向量表示,作为当前轮用户输入的特征;上面是一个jordan-type RNN网络,

所谓jordan type其实就是每一轮的输出层连接到下一轮的隐层(我们平常用的RNN大多是上一轮的隐层到下一轮的隐层

以每一轮的Delexicalised CNN抽取出的特征向量来作为RNN网络输入,建模turn-level的状态转移关系,RNN的输出一方面可以为policy network提供表征对话状态的特征向量,另一方面对输出进行离散化转换,得到结构化的数据库sql查询语句,从而完成与数据库的交互。从数据库中取出的信息同样可以输入policy network中,从而完成诸如前面说的“小夕打开百度地图搜索椰子鸡”等类似的结构化操作,查询到的结果(椰子鸡的位置)可以帮助完成对话目标。

看,这样策略网络policy network就有了3个维度的观测信息,当前轮的用户意图、当前的对话状态和外部数据库提供的信息,这里通过简单的三通道矩阵变换来完成策略的计算:

对话系统的设计艺术(完结)_建模_09

其中zt,pt和xt分别代表intent network、belief tracker和数据库的输出。

最后,策略网络的输出就代表了当前轮决策(system action)的特征向量(称为action vector),将该向量作为condition来丢进基于语言模型(比如lstm语言模型)的NLG模块,从而得到最终的自然语言输出。看,这样就完成了完整的端到端建模。

那么怎么训练呢?

前面说了,DST可以轻松的使用有监督的方法来学习,因此这里虽然端到端的建模了任务型对话,但是并不妨碍先将DST模块用监督数据训练一把,来进行初始化,这样可以减少端到端训练时的波动,也能清晰的定义belief tracker所完成的功能,而不是完全随机初始化后学习到其他什么奇奇怪怪的东西。

DST完成预训练后,fix住它的参数,然后网络中剩下的部分就可以通过末端语言模型输出与ground truth计算交叉熵来得到loss,由于整个网络的连接处都是连续可导的,因此梯度信号可以顺利的传到各个部分的参数上,于是就完成了整个系统的训练。

这篇文章后,出现了大量的policy based端到端任务型对话建模方法,感兴趣的同学可以顺着这篇文章继续往下挖,各种花式方法这里就不展开叙述啦,写不动了QAQ

最后挤出一丝力气来单独的说说NLG问题(虽然像前面这种端到端policy-based method已经将NLG模块也一起丢进去学习了),不过工业界大多还是pipeline的结构,独立的NLG模块研究在短时间内还是很有研究价值的(而且NLG并不仅仅适用于对话场景)。

4.6 NLG:最后一公里

假如我们的pipeline系统终于可以作出合理决策(action)了。比如用户说,“好的,谢谢”,那么我们的系统经过语义理解、对话状态查询和作出决策,得出了“说再见”的系统动作,于是就要有一个模块将系统动作(即结构化语义表示)来翻译成自然语言输出“不客气哦,下次再见啦~”,完成这个结构->文本的模块就是自然语言生成(NLG)模块。

在对话动作的控制下,给用户的回复可以说非常丰富,比如引导式询问、确认、澄清、对话结束语等等。最简单的方法当然还是基于规则生成╮( ̄▽ ̄””)╭,比如在上面的订餐场景中,当前系统动作是询问就餐人数query(num_persons),匹配到了就餐人数的规则,那么系统就会给出“请问您是几位就餐呢”的回复。应对这种简单情况,只需要事先为actions定义好规则和话术模版就好了。毕竟是人为设计好的回复,所以流畅度是可以保证的~

显然,当对话动作很多时,可能打死PM也无法为每一种action都编写出合适的话术模板,这就需要更佳“智能”的文本生成方法了。而本身这里的NLG问题就是一个结构->文本的“翻译”问题,显然seq2seq的方法是肯定少不了的,比如[37]就用了一种很fancy的方法远程监督的方法构造了一个NLG数据集,然后使用seq2seq的方法作为baseline跑出了还不错的结果。

不过,很显然,如果DP的结果是错误的,那这种无论规则控制还是seq2seq的NLG模块都会错的很彻底了(´・ω・`)所以一来可以为NLG模块引入更多的condition(比如对话状态),二来还是沿着end2end的roadmap来走比较有所期待。

好了,大家的小夕已经由于本文写死台前了,默哀