下面的文章,是我在做BOT产品上时最近的一些思考,其中并不代表现在做BOT的通用思维或方法论,我更想通过自己的思考和总结,去尝试推演出在做BOT产品时,到底要解决的是什么问题,而这个问题对我们来说的本质是什么,边界是在哪里。
1.BOT配置架构
这个架构流程,是我所理解的,去系统搭建一个场景的模块与流程,其中每个模块都是去解决一个子问题,整个对话系统由他们构成。
2.NLU语义解析框架
建立NLU语义解析框架是做BOT的基础与核心,现在绝大多数的做法,都是通过场景(即Domain)去建立场景内的用户意图和槽位信息。
这个框架之所以非常重要,是因为一方面在一句Query进来之后,要通过以这个框架为基础的去触发意图,解析信息;另一方面又连接起解析完成后服务的请求,以及给用户的话术回复的配置。
在我个人看来,产品在这个模块工作本质是为“NLU语义”、“用户语音交互需求”和“服务或内容”这三个维度找到交集,并据此建立解析框架。
对NLU技术团队来说,理论上是可以把任何形式话语或词语设置成意图句式和槽位信息去处理,但这样的技术实现没有任何现实意义,解析出来的信息必须是去寻找用户的意图和其中的关键信息,但两个集合的交集还不够。
语音交互本身只是一种信息传递形式,最终目的是通过这个传递方式,帮助用户获取满足其需求的服务价值,那也就必须考虑到服务本身,反过来对BOT上的限制,或者“参考”可能更加合理,因为最终的目的是去通过语义解析的参数,去精准映射服务的不同信息,并且根据不同信息的组合,去差异化配置回复话术
这个服务方的“参考”非常重要,因为任何形式的服务,无论是像听音乐听相声这样的内容服务,还是查天气或订票这样的生活服务(可能闲聊除外),服务价值本身是先于“语音交互”这个形式而存在的,服务方已经和用户经过长久的互动和连接,彼此早就形成了互相认同信息框架。比如对于内容播报这个领域来说,“信息传递框架”可提供的就像是“资源名称、创作者、表演者、风格、年代、适合播放场景”这样的信息;对于天气服务来说,可提供的就是“城市、经纬度和日期”这样的参数。
也就是说,用户Query经过BOT后,必须能够获得完美映射服务的不同参数,以获取不同的信息,才能返回给用户他们所需要的服务,这样才算是一个完整的以语音交互形式,从需求到价值满足的闭环
所以,搭建NLU语义解析框架这个问题的本质,是基于以上这三个维度,去寻找一个“最优解集合”的问题
3.意图句式配置
3.1意图要素
在搭建起某个场景的NLU语义解析框架后(包含意图和槽位),面临最重要的问题就是什么样的Query可以触发意图(这是需要产品去定义和配置的),即把与意图不相关的Query挡在外面,把与意图相关的Query尽可能使其触发对应的意图。那么在本质上,这就成了一个“边界判定”的问题,即什么样的Query可以进入到意图边界之内,什么样的Query要在意图边界之外。
那么先暂时抛开意图和句式这些东西不谈,我尝试从逻辑上论证并得出这个“边界问题”的解,然后关于“意图和句式”的问题可能自然就解开了。
假设一个元素A,它之所以能够成为或者能被定义为元素A,而不是元素B、C或D,那么它一定含有只有它才有的要素,是其他元素“B、C、D”所没有的,或者说即时有,也因为构成要素之间的排序差别,导致不同。因为如果元素A与元素B拥有的要素全部相同且顺序相同,那么也就不存在“A与B”的区别。也就是说,元素A之所以能够成为A,是因为它具备符合成为A的所有要素条件。
举个栗子,假设一台“台式电脑”(A)能够成为电脑的必要要素是“屏幕”和“键盘”与“主机”(注意这里是假设,而不是事实),那么因为一台笔记本电脑(元素B)虽然同样具备“屏幕”和“键盘”,但并不具备“主机”,他们在“主机”这个要素上有差异,所以笔记本电脑不是台式电脑
那么我们可以用演绎法将这个结论放在我们“意图触发边界”这个问题上,一个“意图A”之所以能够成为意图“A”,一定有符合意图A的全部必要要素才可以。换句话说,只有符合该意图的所有必要要素条件,这个Query句式才能够触发该意图。那么,关于意图的边界判定的问题,就成了一个“构造意图必要要素”的问题,只有符合该意图的所有必要要素才能触发我们配置的意图,不符合的就不能触发。
那么,我将一句话的信息按照“信息类型”和“必要要素”两个维度,去划分为四个类别:
必要槽位信息
必要意图信息
非必要槽位信息
非必要意图信息
通过这样的划分,基本问题的解就出来了。在确定一个意图后,要找到得就是表格中的“意图必要要素”,它又有两种信息类型构成即“必要槽位信息”和“必要意图信息”,只有符合“意图必要要素”,那么这个句式才可以触发对应的意图。
先举个栗子,假如你的一个Intent是“查天气”,那么以下句式都是可以触发该意图:
A:天气怎么样
B:今天天气怎么样
C:北京今天天气怎么样
D:播一下天气预报
我们来简单分析一下,在句式A,它的必要要素有两个“天气”和“怎么样”;在B,必要要素为“天气”和“怎么样”,非必要要素为“今天”;在C,必要要素为“天气”和“怎么样”,非必要要素为“今天”和“北京”;在D,必要要素为“播”和“天气”,非必要要素为“一下”和“预报”
3.2“意图必要”与“意图必填”
个问题需要解释,“北京”和“今天”明明是必填槽位啊,怎么会在这里被划分为“非必要要素”(也即非必要槽位信息)呢?
这里就要澄清一下“必要”和“必填”的概念,他们之间所处的BOT流的模块和要解决的问题在本质上是不一样的。“意图必要槽位信息”是用于判定用户的某句Query能否触发某个意图,它的本质是“边界判定”,所处的位置是在一句Query进来后意图分类和句式匹配的阶段;而“意图必填槽位”,是在句式已经确认完全符合并触发某个配置好的明确意图之后,去请求后续服务而言是否“必填选项”。
还是以天气为例子,在“天气怎么样”这个句式中,【天气】和【怎么样】即上表中“意图必要信息”,他们不是某个槽位,但确实是出发这个意图的“必要要素”,用户说出这句话,是一定可以触发意图的。但触发意图之后,要请求“天气服务”则必须有“城市”和“日期”两个信息才能完成服务,所以这时候“城市”与“日期”会成为意图的两个“必填槽位”。
所以可以看出,在配置意图句式的模块,对于能否触发意图这个边界而言,“必要要素”是可以不含有解析框架中的任何槽位的。这在我一开始接触BOT的时候给我造成了很大的困惑,因为翻看现在的一些信息,都是突出了槽位相对于已经出发意图后,在请求对应服务上的“必填性”,而没有强调在触发意图边界上的“必要性”,但实际上他们是两个概念。
对于某个句式能否触发某个意图而言,句式中必要要素是可以不含有任何“槽位信息”的(比如上文中不一定含有“北京”和“今天”就能触发查询天气意图),但是却必须有“必要意图信息”(比如上文的“天气”和“怎么样”)
这就为我们提供意图句式配置的一个边界标准,一个句式能否触发某个意图,一定要含有这个意图句式的“必要要素”,而这个必要要素由两部分构成分别为“必要槽位信息”和“必要意图信息”,其中“必要槽位信息”是非必须存在的,而必要“必要意图信息”是必须存在的,这是我在配置意图句式要素的一个核心思路
3.3意图表达类型句式
接下来,我们再看看另外一个现象,比较一下“A”和“D”的共性和区别。按照我刚才的说法,A中含有的“必要意图信息”分别为“天气”和“怎么样”;而D中含有的“必要意图信息”分别为“播”和“天气”(注意这两个句式,都不含有“必要槽位信息”,符合我刚才的理论)。他们之间共有的必要要素为“天气”,而差异的要素为“怎么样”和“播”,但是他们可以触发的是同一个Intent(查询天气)
这里就再引申我自己给定义的一个概念,即“意图表达类型句式”,即同样的一个意图,会包含有不同的表达类型句式,但是这些表达类型不同的句式都可以触发这一个意图。而不同的“意图表达类型句式”,所包含的意图必要要素又是不一样的。
比如刚才的“句式A”和“句式D”本质上就是触发同一个查询天气意图的不同“意图类型表达句式”,一个是“天气怎么样”,另一个是“播一下天气”,在这两个句式的必要要素中,差异的就是“怎么样”和“播”,那么他们就属于不同的“意图类型表达句式”。
那么到这里,结合上面的“意图要素理论”,可以得出一个自己在意图句式配置中,建立意图边界的核心思路:
根据NLU语义解析框架进行意图分类后,按照“意图必要要素”去划分出同一个意图的不同表达类型句式,然后将必要要素,可能是槽位,也可能是意图信息,按照顺序排列好,这就可以构建“一个明确意图、一个明确表达句式”的基础边界句式,只要符合这个边界的句式,全都可以触发这个意图。
后面的工作就是在这个基础上,把一些“非必要要素”配置上去以覆盖更多句式。这里就不展开说了,比如说在必要要素为“天气”和“怎么样”这两个要素配置好后,可以放的“非必要要素”是槽位信息“城市”和“日期”,组成多样的句式如:
{城市}天气怎么样
{日期}天气怎么样
{城市}{日期}天气怎么样
{日期}{城市}天气怎么样
至于后面的包括非必要要素的配置、排列组合和词典等,本质上都是基于这个基础句式去服务,以符合这个边界的更多意图句式的。
4.多轮对话
多伦对话现在普遍的划分方式,分为“线性多伦”和“非线性”多伦。从功能层面上来讲,线性多伦是在必填槽位缺失,通过系统主动发起追问的方式,去获得缺失的槽位;非线性多伦是解决需要联系上文才能获得用户完整意图的问题。虽然都被称之为“多伦”,但这两个模块本质上在整个系统里所要解决的问题,以及问题的边界是不同的。
4.1线性多轮定位与边界
首先先看线性多轮所解决问题的边界,他是为了在某一个意图的对话中,由于用户发起了某项意图,但是意图中有缺失的必填槽位,而发起的一种追问方式以获取该槽位的信息。而该意图及其“必填槽位信息”是已经事先被定义好的,换句话说,该意图请求服务所需要的“完整意图信息”是已经事先被我们所圈定好的,解决的问题只是在缺失时怎么补上就可以了。
再看它在整个对话流中的定位,是在一句Query符合“意图触发必要要素”,可以触发场景中的某个意图,但是触发之后,却缺失了“意图请求服务必要要素”(即某些必填槽位信息)。线性多轮的存在位置,就是为了弥补上两者之间的GAP(如下图)
这里还想区分一下“意图触发必要要素”和“意图请求服务必要要素”之间的概念区别,前者是为了判断是否能够触发并进入某个意图的边界,而后者则是能否具备完整请求后续服务的边界。
4.2非线性多轮
为什么说“非线性多伦”本质上与“线性多伦”并不是一回事呢?我们从三个维度上去看
·“用户完整意图”定义
从目标上,虽然线性多伦与非线性多伦都是为了获取用户的完整意图信息,但在“完整”的定义上,线性多伦是已经被设定死的。而非线性多伦中,用户的每一个意图是否“完整”,都是不可知的
·问题边界
再看解决的问题,因为人说话的习惯,往往会把前一两句说过的信息默认忽略,即假定你已经知道上面的信息。而计算机处理的方式,却只能以一句完整Query意图的方式去处理。比如你第一句“我想听刘若英的后来”,第二句“换张智霖的”,如果光看第二句,系统并不知道你的完整意图是“我想听张智霖唱的后来”。
所以非线性多轮这个模块的本质,是为了解决“人基于上下文说话习惯”与“计算机处理意图信息”方式之间的GAP。
那么为了弥补上这个GAP,第一个要解决的问题,还是边界问题,即“什么时候要联系上文”,这是在非线性多伦找模块中,找到得第一个边界。
接下来的讨论,还是只限定在一个场景内,既然是在场景内,按照指定的语义解析框架,要分为“意图内”和“跨意图”的非线性多伦。他们两个又是在第一个边界内,解决两类问题。
4.3意图内非线性多轮边界问题
在用户发起第一轮的对话后,第二轮的对话是可以任意的,但我们却没必要对所有的Query进行理解与回复。比如用户第一轮“我想听歌”,第二轮如果是“换周杰伦的”,那么系统就有必要基于上文去理解并获取用户完整意图。如果第二轮是“那明天呢”,那么这样的Query是可以置之不理的。
这样就引出“意图内非线性多轮”的一个边界:需要联系上文,且涉及到在相同意图内槽位信息的变动,才能获得用户完整意图信息。找到这个边界,之后要做得就是在配置好的意图及句式中,看看哪些槽位是可以改变的,被改变的意图有哪些,然后按照“意图要素”去配置句式,为“非线性多轮意图”划定可以触发的边界
4.4跨意图非线性多伦边界问题
“跨意图非线性多伦”问题的边界,我这就给出自己的理解:需要联系上文,且两个意图之间的“意图服务必要要素”(即必填槽位)必须完全相同,才能获得用户完整意图信息。
因为触发某个意图,是可以不需要任何“槽位信息”的,但请求服务的时候,几乎是必须要含有“槽位信息”的。而用户在第二轮触发“新意图”的时候,可以通过不含有任何“槽位信息”的句式触发新意图,但系统如果要在触发意图之后去请求服务,则必须保证“意图服务必要要素”的完整,但这时候是不可能强制让用户在Query里体现“槽位信息”。
那么问题就变成了,在第二轮用户发起的对话中,必须保证在不含有任何“槽位信息”的情况下,Query是可以触发某个意图。触发这个意图后又不能去通过“线性多轮”这个模块去获取触发意图后的“服务必要要素”,那么只能通过另一种形式去获取“必填槽位”,即上一轮的槽位继承。
这也就限定了“跨意图非线性多轮”所能解决的问题的边界,只有在两个意图的“服务必要要素”(必填槽位)完全相同的情况下,才可以保证在用户在不提供任何槽位信息的Query中,你是可以通过槽位继承的方式,满足此意图的“服务必要要素”的槽位信息。
否则第二轮中“触发意图”和“意图服务”之间的GAP,在不借用“线性多轮”能力的情况下,是没有办法完美弥补上的。可一旦你借用了“线性多轮”追问补槽的能力,那么这个问题也就不再是一个“多轮对话”的问题,因为它已经超出了“需要联系上文才能获得完整意图”这个边界。
所以,在“跨意图非线性多伦”这个问题边界内,需要去人为定义哪些意图之间是可以进行跨意图多伦对话的。现在普遍的实现方式,是去配置一个意图的“输入前置语境”和“输出语境”,来限定某个意图在第二轮的触发,即时句式完全相同,也必须在符合配置好的“输入前置语境”条件下,才能触发。
确定好一个“跨意图非线性多伦”意图可触发的前置条件后,下面的工作就和“配置意图句式”模块流程相似了,配置这个意图的可能“意图表达类型”和其中的“意图触发必要要素”
比如在“查天气”这个场景中,两个intent分别为“查温度”和“查空气质量”。那么在“查空气质量”这个“跨意图非线性多伦”的配置中,前置输入语境条件就是“查温度”这个Intent,触发“查空气质量”的这个意图必要要素就是“空气质量”这四个字(不含任何槽位信息),比如“那空气质量呢”。完整过程如下:
QA:北京今天温度多少 (获取【城市】与【日期】必填槽位)
AN:气温XX度
QA(二轮):那空气质量呢
AN:今天PM2.5XXXXX.....
可以看到,在第一轮的句式Query里,如果用户仅仅说“那空气质量呢”要么是不会触发意图,或即时可以触发也需要“线性多伦”或其他方式去补槽。而“查空气质量”这个意图,通过“跨意图非线性多伦”的配置,“那空气质量呢”这个句式,在符合前置语境条件的情况下,是可以匹配上“查空气质量”这个意图,并且后续通过槽位继承的方式形成“完整用户意图”去请求后续的服务
4.5多轮槽位解决方式
在边界问题之后,就是如何获取用户完整意图的问题。这个在反而是比较简单的了,操作的方式无非是槽位的“继承、替换和新增”上,并且这样的操作方式是可以直接在逻辑上得以证明的。
当然,还有一些比较特殊的Query进行槽位的特殊处理。比如用户第一轮“我想听爱我别走”,系统随机放了一首张震岳唱的爱我别走,用户听着听着第二轮“换别人唱的”。这种情况下用户的完整意图是“我不想听张震岳版本的爱我别走”(原谅我是个周杰伦粉....),但你会惊人地发现,两轮Query的槽位只有一个“爱我别走”(歌曲名称槽位),但用户的完整意图是“我想听不是张震岳唱的爱我别走”,你怎么破!
这样的特殊情况,也是有一套通用的方法,是可以通过特殊句式匹配和槽位处理方式,以及后续的搜索手段去解决的。
在“非线性多伦”这个模块,我更多是提出了自己的问题,而没有在解决思路和方法论的层面上做过的的阐述,因为就像我在最开始说的,我更希望通过这篇文章,去思考在整个BOT对话中,在每一模块,我们要解决的是什么问题。
到这里,我大概论述了我对整个BOT产品的框架的认知,其中涉及到最多的关键字,就是“边界”。随着了解和思考的深入,越来越体会到边界对BOT产品极为重要,因为每一个模块要么是为了解决一个边界问题,要么是在某个边界内去解决某类问题。当然,以上仅是基于个人的一些思考和想法,也并没有在上面过多提及方法论层面上的东西,也希望之后会能分享出来。