这部分也被称为Belief Tracking。
首先要思考为什么需要DST?
这个问题是因为我们需要一种对话状态,或者至少我们觉得对话流程有一种状态性的东西比较合适。
1. 什么是DST
对话状态追踪(Dialogue State Tracker),对DST来说包括只读部分和可变部分。DST模块以当前的用户动作、前轮对话状态和相应的系统动作作为输入,输出是DST模块判定得到的当前对话状态。
对话状态的表示(DST-State Representation)
通常由以下3部分构成:
- 本轮对话过程中的用户动作。
- 目前为止的槽位填充情况。
- 对话历史。
1.1 什么是对DST只读的
- 例如用户行为是用户决定的,系统应该不改变(但是可以增强或修正),所以对DST来说就是只读的。
- 例如系统行为是DPL发出的,是系统做过的决策,这个自然也是只读的。
1.2 什么是对DST可变的
- 对于基于帧(Frame)的对话系统来说,可变部分基本上已经定义了帧中的内容。
- 例如我们设计这样的一个对话帧,用来实现返回天气预报。我们假设这个系统只能回答某一天某个城市的天气,那么就有两个必要变量:城市和日期。
那么我们设计语义帧可以这么做:
{
"city": null,
"date": null
}
也就是说当用户inform了系统的所有必要条件,这里是city和date之后,并且DST也填充更新了语义帧,那么DPL应该就能给出对应的天气回答。
其中槽位填充情况通常是最重要的状态表示指标。
2. 槽位设计
我们知道,由于语音识别不准确或是自然语言本身存在歧义性等原因,NLU模块的识别结果往往与真实情况存在一定的误差。所以,NLU模块的输出往往是带概率的,即每一个可能的结果有一个相应的置信程度。由此,DST在判断当前的对话状态时就有了两种选择,这两种选择分别对应了两种不同的处理方式,一种是1-Best方式
,另一种则是N-Best方式
。
-
1-Best方式
指DST判断当前对话状态时只考虑置信程度最高的情况,因此维护对话状态的表示时,只需要等同于槽位数量的空间,如图:
-
N-Best方式
指DST判断当前对话状态时会综合考虑所有槽位的所有置信程度,因此每一个槽位的N-Best
结果都需要考虑和维护,并且最终还需要维护一个槽位组合在一起(overall)的整体之心程度,将其作为最终的对话状态判断依据,如图:
3. 举例说明
整个问答类似这样:
- 用户:我要查天气
- 系统:好的,要查哪个城市的?
- 用户:广州
- 系统:查哪天的?
- 用户:明天
- 系统:明天广州天气是xxx
在这个过程中我们可以认为有三轮交互(用户到系统),如果写成用户行为和系统行为那么是这样的:
- User:requestWeather()
- Sys:request(city)
- User:inform(city=广州)
- Sys:request(date)
- User:inform(date=明天)
- Sys:informWeather(city=广州,date=明天)
我们分为3轮,看看语义帧的变化:
- User:requestWeather()
- State Before DST:
- user_action_t = null
- sys_action_t-1 = null
- city = null
- date = null
- State After DST:
- user_action_t = requestWeather()
- sys_action_t-1 = null
- city = null
- date = null
- Sys:request(city)
在通过DST之前,我们可以认为系统是一篇空白,都是null(空)。
在DST之后我们可以认为DST更新了user_action, 而用户行为也可以认为是自动更新的而不是DST的功劳。
- User:inform(city=广州)
- State Before DST:
- user_action_t = requestWeather()
- sys_action_t-1 = request(city)
- city = null
- date = null
- State After DST:
- user_action_t = inform(city=广州)
- sys_action_t-1 = request(city)
- city = 广州
- date = null
- Sys:request(date)
在通过DST之前,State和前一轮通过DST之后是一致的。
然后因为提供了“北京”这个信息,所以DST更新了city这个项。所以本质上DST在这里的作用只有一个,决定某个语义帧的一项,要不要更新。
我们例子比较简单,但是DST是很多不能更新的时候,例如用户输入的系统没有理解,或者理解的概率很低,那么DST就不应该被更新。当然此时应该有其他的语义帧来标识这种状态。
- User:inform(date=明天)
- State Before DST:
- user_action_t = inform(city=广州)
- sys_action_t-1 = request(date)
- city = 广州
- date = null
- State After DST:
- user_action_t = inform(date=明天)
- sys_action_t-1 = request(date)
- city = 广州
- date = 明天
- Sys:informWeather(city=北京,date=明天)
这一轮的对话基本同上。
我们假设一种情况,用户会说错,或者是一些重要选项其实可能是需要用户确认的情况。例如用户如果买票,但是要么语音识别出错,要么NLU出错,把“北京市”识别成“北海市”(广西的一个市),例如用户说:“我想去北京看北海,请问天气怎么样”,但是错误的被NLU理解成了想去北海市,或者NLU同时识别了北京市和北海市,或者这两者的置信度都比较低(NLU不确定用户想要什么),那么就应该做出一个让用户确认的操作
,一般这个操作被称为confirm。我们看一下假设对话行为和状态包含了confirm会如何。
{
"city": null,
"city_confirmed": false,
"date": null,
"date_confirmed": false
}
- 用户:我要查天气
- 系统:好的,要查哪个城市的?
- 用户:我想去北京看北海
- 系统:请问是北京市吗?
- 用户:是的
- 系统:那么查哪天的?
- 用户:明天
- 系统:明天北京的天气是xxx
在这个过程中我们可以认为有三轮交互(用户到系统),如果写成用户行为和系统行为那么是这样的:
- User:requestWeather()
- Sys:request(city)
- User:inform(city=北京), inform(city=北海) # 这两个行为是并列的,但是置信度不同
- Sys:confirm(city=北京)
- User:confirm
- Sys:request(date)
- User:inform(date=明天)
- Sys:informWeather(city=北京, date=明天)
主要变化的是下面这一步:
- User:inform(city=北京), inform(city=北海) # 这两个行为是并列的,但是置信度不同
- Sys:confirm(city=北京)
- User:confirm
我们可以认为逻辑是这样的:假设NLU输出的结果,出现了太多不确定的内容,或者不确定性大于某个阈值,系统就可以反问用户来确认答案。
当然从系统设计上来说,系统反问次数越多,系统状态正确的可能性越大,但是系统反问越多,系统的可用性就越低
,因为太长的对话本身会导致用户体验的下降
。
4. 是否可以无状态?
首先答案,基本上是可以的。
实际上在一些研究上的End-to-End系统中,DST本身只是以一种向量分布
或者类似完全记忆化的形式存储在神经网络中,而不需要直接的定义。这不能算无状态,但是也可以算隐藏状态
。
但是缺点也是显而易见的,因为并不知道状态,至少不知道状态的实际意义(因为它可能只是一个分布),所以假设它输入DPL导致错误的系统行为,我们也很难调试
。
5. 实现方式
实现DST模块的方法主要有:基于条件随机场模型的序列跟踪模型、基于RNN和LSTM的序列跟踪模型等。
参考文献
- 自然语言处理与实践