序言
以一周前的一条微博作为开始。一周前我讲:相对的,自然语言解析技术已经逐渐不再成为各家广义智能助理产品的核心竞争力,识别用户意图之后所提供的服务开始成为对话机器人差异化的核心。
对于一个对话系统而言,我微博中所指的『后续服务』,就是上图中的 DST(对话状态维护)以及 Policy(动作候选排序),或者统一的称其为 DM(Dialogue Mannagement,对话管理)。也即,当接收到 NLU 模块的输出、其他场景及用户特征信息之后,判断系统应该跳转到什么状态,以及执行什么样的动作。
从产品的角度,DM 是对话机器人封闭域多轮对话体验的核心,正是一次次 DST + Policy 形成了人机间的多轮对话体验。(我个人倾向于将识别用户意图之后,为了获取必要信息,与用户进行的有目的的多轮对话称为封闭域多轮对话,区别于识别用户意图之前,为了利用上文信息,所采用的『上下文替换』、『主体补全』等技术,aka 开放域多轮对话。下文提到的『多轮对话』,均指封闭域多轮对话。)
既然多轮对话在对话机器人类产品体验中扮演着如此重要的角色,我便倾向于开始思考:一个架构完备的多轮对话体系应该是什么样的。也即多轮对话系统中需要至少包含哪些模块,才能为用户提供一种与人人对话相去不远的人机对话体验。
多轮对话定义
我有个习惯,就是在构造一个复杂系统之前,先从纷繁的细节之中跳出,尝试抽象的描述整个系统,及系统中的各个模块,也即为它们『下定义』。这能帮助你在多种可行方案中做出选择,也即帮你明确:什么该做,什么不该做,什么该谁做。
基于以上思想,我尝试先给出几个我个人对于多轮对话体系定义问题的回答:
基本定义:什么是多轮对话?
(封闭域)多轮对话是一种,在人机对话中,初步明确用户意图之后,获取必要信息以最终得到明确用户指令的方式。多轮对话与一件事情的处理相对应。
补充说明:所谓『必要信息』一定要通过与用户的对话获取吗?
不一定,即便是人与人之间的交流,对话本身所包含的信息也只占总传递信息量的一小部分,更多信息来源于说话人的身份、当前的时间/地点等一系列场景信息。所以多轮对话的信息获取方式也不应当只局限于用户所说的话。
补充说明:多轮对话一定在形式上表现为与用户的多次对话交互吗?
不一定,如果用户的话语中已经提供了充足的信息,或者其它来源的补充信息已经足够将用户的初步意图转化为一条明确的用户指令,那就不会存在与用户的多次对话交互。
以上是针对多轮对话整体定义问题的回答,每个模块的相关定义会在下文各个模块的讲解中尝试给出。
槽
基本定义:什么是槽?
槽是多轮对话过程中将初步用户意图转化为明确用户指令所需要补全的信息。一个槽与一件事情的处理中所需要获取的一种信息相对应。
补充说明:多轮对话中的所有的槽位都需要被填充完整吗?
不一定,以如下对话为例:
我:『去萧山机场多少钱』
出租车司机:『70』
对话中的『70』,应当被理解为70元人民币,而不必再去追问:『你说的是人民币、美元、日元还是港币?』。这类信息应当以默认值的形式存在,也即槽有必填与非必填之分,与上文所说的『信息未必需要通过与用户的对话获取』相对应。
词槽与接口槽
上文反复的提到,对话内容并不是获取信息的唯一方式,用户身份以及当前场景也包含着大量值得被利用的隐含信息。所以,与此相对的,一个完备的多轮对话体系应当同时具备从用户话里以及话外获取信息的能力。
我个人将利用用户话中关键词填写的槽叫做词槽,利用用户画像以及其他场景信息填写的槽叫做接口槽。
举个例子,我讲『我明天要坐火车去上海』。其中,分别将『明天』、『上海』填入名为『出发时间』、『目的地』的词槽中,而我当前所在的位置,则填入到了名为『出发地』的接口槽中。
槽组与槽位
我个人将利用用户话中关键词填写的槽叫做词槽,利用用户画像以及其他场景信息填写的槽叫做接口槽。
举个例子,我讲『我后天要坐火车去上海』。其中,分别将『后天』、『上海』填入名为『出发时间』、『目的地』的词槽中,而我当前所在的位置,则填入到了名为『出发地』的接口槽中。
不知道上文错的如此离谱的结论有没有引起你的注意 :)
仔细读一遍上面举的例子,就会发现一个很严重的矛盾点:难道『出发地』这个槽不能由用户指定?用户完全可以说『我后天要坐火车从北京去上海』,那它是词槽还是接口槽?而且更进一步的,难道只能用『我当前所在的位置』来填入『出发地』这个槽中?比如,如果能读到我的日程表,发现我明天会去杭州,那是不是就应该用『杭州』而不是『我现在所在的位置』来填『出发地』这个槽了?
从中我们能发现什么呢?同一个槽,可能会存在多种填槽方式。
我将可能包含多种填槽方式的槽称为槽组,槽组下面可能存在任意多个槽位,也即任意多种填槽方式,而每个槽位又都对应着『词槽』与『接口槽』两种槽位类型之一。
本质上来讲,槽组(也即上文中提到的『槽』),对应着一种信息,而几乎不会有哪种信息的获取方式只有一种。所以一个『槽』会同时对应多种填槽方式也就是自然而然的了。
依照上文,同一种信息会有多种获取方式,也即同一个槽组会对应多种填槽方式(槽位)。那不同填槽方式之间必然会存在优先级的概念。
就如同上文『订票』的例子,『出发地』槽包含三种填写方式,一种词槽、两种接口槽,自然的,词槽的优先级最高,『日程表中隐含的出发地』次之,『我当前所在的位置』再次。
如果将其与前文提到过的必填/非必填结合起来,其填槽过程应当遵循以下步骤:
- 尝试填写词槽
- 若失败,尝试填写第一接口槽『用户日程表中隐含的出发地』
- 若失败,尝试填写第二接口槽『用户当前所在位置』
- 若失败,判断是否该槽必填
- 若必填,反问用户,重填词槽
*若非必填,则针对该槽组的填槽过程结束
我们需要知道,必填/非必填在逻辑上与槽组而不是槽位平级,只有信息才会分为必要/非必要,填槽方式不做这种区分。而且是否必填实际上与接口槽无关,只取决于是否需要与用户进行交互。
澄清话术
与槽组也即与一种信息平级的概念还有一个,叫做澄清话术。
澄清话术是对话机器人希望获取某种信息时所使用的问句。比如『目的地』对应的澄清话术就是『您想从哪出发呢?』,『出发时间』对应的澄清话术就是『您想什么时间出发呢?』。
显而易见的,澄清话术与槽组而不是槽位平级。
槽的填写
上文讲到,一个槽组可能会有多个槽位,槽位存在词槽与接口槽之分。
先说词槽。
词槽信息的抽取其实还是有些麻烦的,不过这属于解析的问题,不在本文探讨的范围内,这里只是简单提一下,举两个例子:
- 用户表达『不』,可能会有『不行』、『不是』、『算了』、『没有』等一系列说法。
- 用户话中有多个符合条件的关键词,我们整套多轮对话中有多个槽,每个槽填一个还是多个值?哪个槽与哪个词对应?
同义词典、规则、双向LSTM+CRF,各有各的方法。
再说接口槽。
接口槽与词槽相比,额外存在一个问题,就是:接口返回的结果就是用户需要的结果吗?
这里需要分成两种情况来讨论,一种是:我们明确知道接口的返回值可以直接填入槽位(不是槽/槽组)中,不需要向用户确认。
特别的,这里还要明确一点,即便是上述情况,也并不意味着当前槽/槽组只有该特定接口槽这一个槽位。有两种情况存在:一种是该槽组下只有这一个槽位,该接口的返回值直接填入槽位中,也相当于填入了槽/槽组中;或者该槽位下有多个槽位,接口槽的填入值并不一定最终作为槽/槽组的填入值。
另一种是:我们知道接口的返回值只能作为参考,需要用户的协助才能进行槽位的填写。
这种情况下,需要提供选项,让用户最终决定该槽位的填入值,与词槽一样,这里同样需要处理单值/多值的问题。单值/多值在逻辑上与槽组平级。
此外,这里还要注意一个否认选项的问题,比如我对阿里小蜜说,我忘记密码了,它会通过接口拿到我的当前账号,然后将其提供选项给我,问『你是忘记了哪个账号的密码?』,不过,除了我当前账号之外,还有一个选项也被提供出来了,就是『不,不是这个账号』。
这代表了一类问题的存在,用户的意图并不一定包含在接口的全部返回值之中。所以就必然会有这样一种类似『不要/不是/不』的选项,我将其叫做否认选项。
用户选择否认选项后,即意味着该槽位的填写失败了,需要填入一个特殊值代表失败。用户选择否认选项的失败,可以与接口调用失败等其它意外情况合并处理,因为这都意味着该槽位填写失败,意味着该种信息获取方式未能成功获取信息。
如果该槽组下只有这一个槽位,这个特殊的失败表征值就应当作为整个槽组的填入值,如果还有其他槽位值,则根据槽位间优先级最终确定槽组填入值。
平级槽和依赖槽
上面说到底都在讲一个槽组的填写,也即一种信息的获取,但多轮对话的目的是将初步用户意图转化为明确用户指令,这其中所需要的信息通常都不只有一种。
谈完了槽组与槽位之间的关系,接下来谈一下槽组与槽组之间的关系,也即信息与信息之间的关系。
为了便于理解,我先举两个例子来代表两种多轮对话中所包含的极端情况。
第一种:订车票,你需要知道用户出发的时间、地点、目的地、座位种类。这四个槽组之间,没有任何依赖关系。换言之,你只需要确定好这四个槽组中必填槽组之间的澄清顺序,接收到用户问句后,对还未填充完成的必填槽组依次进行澄清即可。我将这四个槽组之间的关系称为平级槽关系。
另一种,不知道读者玩没玩过橙光,或者其它多结局的剧情类游戏。它们的特点是什么呢?每一个选择都会有影响到后续剧情发展也即 每个槽组的填写结果会影响其它槽组的填写。换言之,部分槽组依赖前序槽组的填写结果,在其依赖的前序槽组填写完成之前,该槽组都无法进行填写。我将槽组间的这种关系称为依赖槽关系。
这种情况下,整个多轮对话过程就形成了一棵树,极端情况下,这棵树是满的。树上的每个节点放置着一个会对后续对话走向产生影响的槽组。
槽关系的选择要根据实际业务场景来确定。
如果错将平级槽采用依赖槽关系来管理,就会出现信息的丢失。比如 A、B、C,三者本为平级槽关系,但却将其用 A->B->C 的依赖槽关系来管理,那即便用户问句中包含填写 B、C 槽组的信息,也可能会由于 A 槽组的未填写而造成 B、C 槽组的填写失败。
如果错将依赖槽采用平级槽的关系来管理,就会出现信息的冗余,比如 A、B、C三者的关系为 A、A1->B、A2->C,那即便用户将值 A1 填入槽组 A 后,却仍然需要向用户询问本不需要的 C 槽组的填写信息。
上述两种情况属于全平级槽关系与全依赖槽关系的特殊情况,在实际的业务场景中,这两种关系会是同时存在的,不同槽组间,既有平级槽关系,又有依赖槽关系。
实际业务场景中,完整的多轮对话过程通常会以树的形式存在,每个节点存在一个或多个槽组,用于获取一种或多种信息,节点间的槽组为依赖关系,节点内的槽组为平级关系。
上文将多轮对话定义为一件事情的处理,槽组/槽定义为一种信息的获取,槽位定义为信息的一种获取方式。这里我倾向于将多轮对话树结构中的一个节点定义为处理事情的一个步骤。
一件事情的处理包含多个步骤,每个步骤中需要补全一种或多种信息,每种信息存在一种或多种获取方式。
上述定义和组里算法大佬的定义有些分歧,不过谁让这是我的文章呢 :) 就按我的来。
填槽意义
结合上文,我们需要了解到,填槽的意义有两个:作条件分支多轮对话、作信息补全用户意图。换言之,填槽不仅是补全用户意图的方式,而且前序槽位的填写还会起到指导后续信息补全走向的作用。
准入条件
上文我们讲到,完整的多轮对话过程通常会以树的形式存在,树中包含多个节点,代表处理这件事情的一个步骤。
而每个节点,都应当有其特别的准入条件。树的根节点往往需要限制 NLU 模块的输出,也即明确什么样的用户意图将会由该棵多轮对话树来处理;树的中间及叶子节点往往需要根据前序槽组的填槽结果以及其他背景信息进行条件限制。(如果将所有信息,比如 NLU 模块输出,或是其他背景信息都看做前序槽组的填写结果,那就能得到统一的槽组-条件-槽组-条件······形式,槽组用于获取信息,条件用于信息限制)
我尝试从两个角度来描述一套完备的准入条件体系。
一个是多条件的组织形式,准入条件在逻辑上应该支持条件间的与或非,百度的 UNIT 平台提供了一种相对成熟的组织形式,将准入条件整体划分为条件和条件组,条件包含在条件组中,组内条件间是且关系,条件组之间是或关系(当然这里的且与或可以根据自身业务情况对调),条件本身支持非关系。
一个是单条件的限制能力,准入条件应当同时支持对前序槽组填写值、填写方式、填写状态进行限制。也即需要有针对值的条件、针对类型的条件和针对状态的条件。简单的讲,状态就是『填了吗』,类型就是『谁填的』,值就是『填了什么』。
不同业务场景下我们会需要不同角度的限制条件。比如,上文中提到填槽的意义包含两种:作条件分支多轮对话、作信息补全用户意图,如果仅仅作信息,那我们通常就只关心『填了吗』,只要填写完成就进行后续步骤,并不关系『谁填的』以及『填了什么』;但是如果槽组内的填入值会影响后续多轮对话走向,那我们就倾向于通过槽组的填入方式或填入值来作多轮对话的分支。
答案系统
先明确一个观点,多轮对话树的节点属于对话节点而不是答案节点,同一份答案可能会出现在多个对话节点中。
答案系统和多轮过程应当是解耦的,答案系统中的每份答案都应当设置好自己的触发条件。举个例子,若存在 ABC 三个槽,A=A1、B=B3、C=C1 提供答案一,A=A2、B=B1、C=C2 或 A=A3、B=B2、C=C1 提供答案二。
另外,答案的种类也不应仅局限于文本,富文本、接口、话题切换,都可以视为合理的答案形式。
话题切换
话题切换指用户与用户的对话从一个多轮过程切换至另一个多轮过程,话题切换有主动切换和被动切换之分。
上文提到的作为答案的话题切换,就可以理解为主动的话题切换。
被动的话题切换是指,系统发现无法从用户的问句中抽取信息以继续当前的多轮对话,只好将其作为一条全新的问句重新进行解析和话题识别。
话题切换,尤其是主动的话题切换会涉及到一个新问题:槽继承。
举个例子:
我:『我明天要坐高铁从杭州到北京』
我:『算了,还是坐飞机吧』
这种情况下,机器人不应当重复询问『出发地』、『出发时间』和『目的地』。
除了槽继承,还有一个与之相对的问题叫做槽记忆,这通常适用在被动式的话题切换中。由于解析失误,或者其他原因,使得用户跳出了原话题,当用户在一定时间内重新回到原话题时,不应让用户重复进行填槽,该技术已被用于阿里小蜜,不过他们似乎称之为『多轮状态记忆』。
举个例子:
我:帮我订张从杭州到北京的机票。
VPA:请问您希望哪天出发呢?
我:明天杭州下雨吗?
VPA:明天杭州有雷阵雨。
我:后天呢?
VPA:后天杭州天气晴。
我:机票订后天的。
VPA:好的,已帮你预定后天从杭州到北京的机票。
状态切换
我们还需要思考这样一个问题,既然话题可以切换,也即一个多轮过程可以切换到另一个多轮过程,那多轮过程中的对话状态是否可以切换?
我举两个例子:
第一个:
我:帮我订张机票,从杭州出发。
VPA:请问你想去哪呢?
我:(发现明天杭州有雷阵雨)换出发地。
VPA:请问你想从哪出发呢?
我:上海。
多轮对话应当允许回到前序节点。
第二个:
我:我想买个杯子。
VPA:以下是为您推荐的杯子。(展示结果一)
我:换一换。
VPA:以下是为您推荐的杯子。(展示结果二)
多轮对话应当允许重复进入同一节点。
结语
就先这么多吧 :)