题目:Task-Oriented Dialogue as Dataflow Synthesis
来源:TACL2020
原文链接: https://www.mitpressjournals.org/doi/full/10.1162/tacl_a_00333
代码: https://www.microsoft.com/en-us/research/project/dataflow-based-dialogue-semantic-machines.
转载请注明出处:学习ML的皮皮虾
一、当前对话系统存在的主要问题和挑战
跟小王预约半小时的会议
1)短短一句话包含了多个指示:查询用户的日程表和小王的日程表;在工作时间内找到两人同时有空的半个小时;创建会议;向小王发送邀请。
2)人类语言的长尾性,大部分语言无法被有限的高频意图所覆盖:例如「预约会议」可视作高频意图,但「在某会议后预约另一个会议」却并不高频。对每个意图编写触发条件和行为代码,开发维护成本昂贵,同时数据短缺。
3)多轮对话,引用和修改时,会使得意图爆炸:
用户:今天北京天气怎么样?
机器:8月1日,北京地区晴,气温27-35摄氏度
用户:那明天呢?
机器:8月2日,北京地区多云,气温25-32摄氏度
第二轮意图识别为「查询天气-修改日期」,通过这个特定的意图,系统得知用户想查询天气情况,日期槽来自本轮,而其他槽来自前一轮的拷贝,当存在大量多轮引用情况时,大大增加了代码量和数据收集的难度。进一步考虑第三轮对话:
用户:空气质量怎么样?
机器:8月2日,北京地区空气质量为优
系统需要从第一轮对话中提取地点,从第二轮对话中提取时间,再从第三轮对话提取具体的问题。为此,开发者不但要添加更多或更复杂的意图,而且语义理解的难度也随着对话的长度而不断上升。
4)同一句话跨多个领域时,难以处理:
用户:我和小周约在几点吃饭?
机器:上午12:30
用户:开车过去需要多久?
机器:从当前地点驾车到福临门餐厅需要20分钟
在第二轮对话中,系统需执行以下操作:
- 提取第一轮对话提到的事件
- 在日历中查找该事件的地点(福临门餐厅)
- 调用GPS服务查找当前地点
- 调用导航服务查询两个地点之间的驾驶时间
由于同时调用了日历、GPS、导航三个领域的服务,我们很难对该意图进行分类。如果日历和导航分属两个团队,很难说哪个团队该对这个意图负责。在现实中,跨领域的需求往往是长尾需求,实现难度大且维护困难,因此经常被放弃,然而它们对用户便利性的提升是非常大的。
二、程序合成
1. 优势
用户:明天泰山日出的时候会冷吗?
驱动上述回答的是这样一段程序:
val x0 = tomorrow()
describe(weatherForecast(
dateTime = DateTime(
date = x0,
time = sunriseTime(date = x0)
),
place = searchPlace("泰山")
).temperature < ColdThreshold)
上述程序调用了4个外部API: tomorrow (返回明天的日期),sunriseTime (返回日出时间),searchPlace (返回地点),weatherForecast (返回天气预报),并利用 "temperature < ColdThreshold" 来刻画"冷"这个概念。最后,describe 函数将程序的运行结果转化为机器的回复。
程序合成的优势:它善于对碎片化的函数和API进行组合,完成任意复杂的操作,从而大大提升了语义理解能力的上限,打破意图识别的局限。面对长尾需求,开发者不需编写新代码,只需有针对性地收集数据,训练语义理解模型,使它能合成出正确的程序即可。
2. 难点
程序合成难以标注和解析,①其要求标注人员拥有编程经验,且程序标注出错率较传统【意图-槽位】标注要高很多;②其包含所有可能的程序,解空间较后者也要大很多,自然语句和正确的程序往往并不存在对应关系(多轮对话常见)。
对程序合成而言,标注的困难和解析的困难,最终都归结于同一个原因,即自然语言和机器语言的分歧。
我们有了一个自然的改进思路:设计一种对话系统专用的机器语言,通过提高和自然语言间的相似性来降低标注与解析的难度。
三、Abstract
本文描述了一种将对话状态表示为数据流图的面向任务对话方法。对话代理将每个用户的话语映射到扩展此图的程序。程序包括用于引用和修订的元计算操作符,可重用以前回合的数据流片段。我们基于图形的状态允许表达和操作复杂的用户意图,而显式的元计算使已学习的模型更容易预测这些意图。我们引入了一个新的数据集SMCalFlow,它具有关于事件、天气、地点和人的复杂对话。实验表明,数据流图和元映射极大地提高了这些自然对话的可表征性和可预测性。在MultiWOZ数据集上的其他实验表明,我们的数据流表示可以使现有的序列对序列模型与现有的最佳任务特定状态跟踪模型相匹配。
主流的任务型对话系统(NLU+DM+NLG):一般将用户语言,进行“领域识别→意图识别→槽位标注”处理,通过意图预设逻辑决定接下来系统动作,理论上,只要开发者枚举所有可能的意图,并训练足够精确的映射模型,那么系统就能处理任意复杂的对话。但在现实中,这个假设并不总是成立的。
“端到端”对话系统原则上可以自由地学习任意上下文相关的响应,但仅仅灵活地使用词汇是不够的,因为对话还需要灵活地执行动作。部署系统还需要可控性和真实性,这在非结构化系统中是具有挑战性的。
本文介绍了一个对话建模的新框架,旨在结合Pipline和S2S两种方法的优点:足够的结构使有效的学习,但足够灵活,支持开放组合的用户目标,支持多任务、跨域交互、可以解决复杂的回指和异常处理问题。
Overview
任务:属于交互编程任务,用户和代理(人机对话),其中对话状态用数据流图表示。在每一轮中,代理的目标是将用户话语翻译成一个程序,预测的程序非破坏性地扩展数据流图,构造新请求值或真实的影响,并最终向用户描述结果(简单来说,就是让机器人在理解自然语言的基础上自动编写程序解决各式各样的问题)。
我们的方法与传统的对话系统管道模型有很大的不同,后者有独立的模块用于语言理解、对话状态跟踪和对话策略。相反,本文是由一个单一的学习模型直接预测可执行代理动作,并以图形对话状态记录它们。
每个对话轮次都用实现用户请求的数据流程序进行注释,该程序具有API调用、函数组成和由字符串、数字、日期和时间构建的复杂约束。
四、方法实现
为处理机器语言和自然语言的分歧问题,引入了“辅助函数”和“规范语句”;为解决程序对上下文的依赖性问题,引入了“引用机制”和“修改机制”,灵活运用「引用」和「修改」可以大大增强机器语言和自然语言的相似性,从而降低标注和解析的难度。
- 辅助函数:
// 输入日期,返回那一天日出的日期和时间
def dateTimeAtSunrise(date: Date): DateTime = {
DateTime(
date = date,
time = sunriseTime(date)
)
}
// 输入天气预报,返回气温是否寒冷的布尔值
def isCold(weatherForecast: WeatherForecast): Boolean = {
weatherForecast.temperature < ColdThreshold
}
那么前一节的程序即可简化为:
用户:明天泰山日出的时候会冷吗?
describe(isCold(weatherForecast(
dateTime = dateTimeAtSunrise(tomorrow()),
place = searchPlace("泰山")
)))
2. 规范语句:每条“规范语句”都有与之对应的“程序”,则只要将用户所说的话(通过机器翻译)转化为与之对应的规范语句,就能得到执行程序。
为每个函数书写一条中文生成规则:
describe($1) => 描述$1
isCold($1) => $1是否冷
weatherForecast($1, $2) => $1$2的天气
dateTimeAtSunrise($1) => $1日出时
tomorrow() => 明天
searchPlace($1) => $1
我们即可为上述程序生成一段规范语句:
描述明天日出时泰山的天气是否冷
3. 数据流图
系统不仅要理解"现在该做什么",还必须记住"曾经发生了什么",这份历史记录称作对话状态(Dialogue state),在这里作者用有向数据流图来表示对话状态。
用户:明天泰山日出的时候会冷吗?
### 初始程序
describe(isCold(weatherForecast(
dateTime = dateTimeAtSunrise(tomorrow()),
place = searchPlace("泰山")
)))
### 程序库
// 输入日期,返回那一天日出的日期和时间
def dateTimeAtSunrise(date: Date): DateTime = {
DateTime(
date = date,
time = sunriseTime(date)
)
}
// 输入天气预报,返回气温是否寒冷的布尔值
def isCold(weatherForecast: WeatherForecast): Boolean = {
weatherForecast.temperature < ColdThreshold
}
作者用一幅有向图表示了完整的执行过程,这幅图叫做数据流图(Dataflow Graph),其中节点代表数值或函数(灰色节点代表初始程序,而蓝色节点代表程序的执行结果),实线从参数指向使用它的函数,虚线从函数指向它的结果。数据流图记录了所提到的实体及其之间的关系。数据流图中的所有节点都可以被后续的话语引用。有时,函数的执行结果本身也是一段程序。例如 isCold 的执行结果是 "weatherForecast.temperate < ColdThreshold",而最终结果是 False。
4. 引用机制
- 显式的引用:用户使用单词“then”(What’s the weather going to be like then?)引用数据流图中较早的节点;如“她”或“您提到的第二次会议”,也可以表示引用前面对话中所提到的值或实体的请求。
- 隐式的引用:“What’s the weather going to be like?”通常指的是近期的天气,但如果是在提及未来的一个活动后问这样的问题,很可能是在询问该活动期间活动地点的天气。
弄清楚如何区分这些用法(更不用说对这个问题的其他解释)是一个具有挑战性的机器学习问题。本质上“天气会是什么样的呢?”在这两种情况下,意思是相同的——用户想知道与会话上下文最相关的时间和地点的天气。
在我们的方法中,这种推理是显式的:当在上下文中解释用户输入时,我们的对话代理显式地预测了涉及到现有计算片段的程序,包括从对话开始就隐式可用的片段,比如“here”和“now”。对于上面的两个例子,如下所示:
在两个对话场景中,对话代理将用同样的方式解释“天气将会是什么样子?”。它预测相同的数据流图片段,调用refer(Time)和refer(Place),但是这个片段的解释会根据前面的上下文发生变化。
——————————————————————————————————————
用户:明天泰山日出的时候会冷吗?
机器:不会。8月2日早5:12,泰山地区天气晴,气温15摄氏度。
用户:能见度怎么样?
我们为第二轮对话生成如下的规范语句:
描述明天日出时泰山的天气的能见度
"能见度怎么样?"包含了一个隐性的引用:"(那个天气)的能见度怎么样?"。当规范语句中出现带括号的短语,如"(那个天气)"时,就意味着程序调用了「引用」机制——复用前几轮程序中出现过的某个"天气"。利用「引用」机制,我们可以把上述程序重写为:
描述(那个天气)的能见度
这段程序排除了上下文信息,因此它的标注和解析就变得非常简单了。
具体操作:根据字面意思把(引用)解释成某种约束(Constraint),再调用「解析」函数把符合约束的程序从数据流中找出来,「解析」函数定义如下:
// 接受任意的约束条件,并返回数据流中满足该条件的节点
def resolve(constraint: Constraint): Node
「解析」函数的实现非常简单:它只需遍历数据流,并找出符合约束的节点即可。如果有多个节点同时满足约束,它会选择和当前语境最相关的那个。在一般情况下,出现越晚的节点和当前语境越相关。因此「解析」函数只需对数据流进行逆序遍历并返回第一个符合约束的节点。
用户:能见度怎么样?
利用解析函数,我们可以把这一轮的程序改写为:
describe(
resolve(
TypeConstraint[WeatherForecast]() // 类型约束,它意味着返回值必须满足 WeatherForecast 类型
).visibility
)
这幅图中,resolve 的解析结果为第一轮的 weatherForecast 节点。解析函数的复用效果是全方位的。系统不但复用了前一轮的计算结果,而且在生成回复时复用了前一轮的时间、地点等信息。
添加以下生成规则:
WeatherForecast => 天气
$1.visibility => $1的能见度
TypeConstraint[$1]() => $1
resolve($1) => (那个$1)
第二轮的规范语句则记为:
描述(那个天气)的能见度
除类型约束外,解析函数还接受很多其他种类的约束。下面列举几种常用的约束:
// 对节点的角色进行约束,它必须作为某个函数的argName参数存在
RoleConstraint(argName: String)
// 对节点的某个属性进行约束
PropertyConstraint(property: String, constraint: Constraint)
// 节点的值必须等于reference
EqualsConstraint(reference: Any)
// 节点必须违反constraint
NegationConstraint(constraint: Constraint)
运用这些约束,我们可以解决自然语言中的各种「引用」现象,例如:
"它"
resolve(
NegationConstraint(
TypeConstraint[Person]()
)
)
"开始的时间"
resolve(
RoleConstraint(start) &&
TypeConstraint[Time]()
)
"小李"
resolve(
TypeConstraint[Person]() &&
PropertyConstraint(
lastName,
EqualsConstraint("李")
)
)
可以看到,从文字到程序的转译是完全上下文无关的。
5. 修改机制
当用户先询问了“What’s the weather going to be like during my coffee with Megan?”,而后又询问“What about during the company retreat?”;第一次事件搜索的约束(针对包含Megan的名为coffee的事件)被替换为新的约束(指定名为company retreat的事件)用户:明天泰山日出的时候会冷吗?
机器:不会。8月2日早5:12,泰山地区天气晴,气温15摄氏度。
用户:能见度怎么样?
机器:8月2日早5:12,泰山地区能见度20千米。
用户:那后天呢?
对于用户提出的第三个问题,对应的程序应记为:
描述后天日出时泰山的天气的能见度
这是一个典型的「修改」——把第二轮程序中的日期修改为"后天"。在修改后的程序中,"后天"来自当前语句,"泰山"和"天气"来自第一轮,而"能见度"来自第二轮。不幸的是,我们再次失去了程序语言和自然语言间的对应关系。
在规范语句中 ,「修改」机制对应一个固定的模板:"把[X]修改为[Y]并重新执行[Z]"。这里,[X]是待修改位置,[Y]是新的程序片段,而[Z]是修改后的待执行目标。利用这个模板,我们把上述程序改写为:
把(那个日期)修改为后天并重新执行(那个结果)
这里,"(那个日期)"和"(那个结果)"都是「引用」,分别代表此前对话中出现过的某个日期和程序的最终结果。系统的执行引擎负责解析它们,取得修改位置和执行目标,并填入"后天"这个程序片段、运行新生成的程序。新的规范语句是原语句"那后天呢?"的同义改写,因此我们再次建立起了机器语言和自然语言间的上下文无关的对应关系。
应对这类语言现象,我们引入另一个重要的数据流依赖函数(「修改」函数):
// 把value嫁接到满足slotConstraint的位置,并返回修改后的root
def revise(
root: Node,
slotConstraint: Constraint,
value: Node
): Node
具体来说,它要做下面三件事:
- 找到 root 代表的程序,记为根程序
- 通过「引用」机制找到根程序上游满足 slotConstraint 约束的节点,记为修改位置
- 把 value 代表的程序嫁接到修改位置,并返回修改后的根程序。
——————————————————————————————————————
示例1:
用户:开车去上班要多久?
// 描述从(那个地点)到工作地点的驾车的通勤时间
describe(transitTime(
from = resolve(TypeConstraint[Place]()),
to = workPlace(),
mode = Driving
))
机器:从当前位置驾车到微软公司需20分钟
用户:坐公交呢?
// 描述把(那个通勤模式)修改为公共交通并重新执行(那个结果)
describe(revise(
root = resolve(RoleConstraint(result)) // 根程序,对节点的角色进行约束,它必须作为某个函数的argName参数存在
slotConstraint = TypeConstraint[TransitMode]() //对[通勤模式]类型约束修改
value = PublicTransportation // [通勤模式]的新值
))
机器:从当前位置乘公交到微软公司需35分钟
上述两段程序使用了两次解析和一次修改。执行它们可以得到了如下的数据流:
第一轮的「解析」函数解析了一个地点(Place)。由于缺乏上下文,它给出了默认值:"here()" —— 调用GPS服务获得当前的位置。
第二轮的「解析」函数返回了根程序,即前一轮的 transitTime 节点。接下来,「修改」函数找到了修改位置,即前一轮的 Driving 节点,并把 PublicTransportation 节点嫁接过去。修改后的程序拥有了新的根节点(蓝色的 transitTime)并得到新的通勤时间:35分钟。如果我们把虚线相连的 revise 和 transitTime 当做同一个节点,那么第二轮 describe 所描述的计算图和第一轮是完全同构的。这解释了为什么第二轮生成了与第一轮类似的回复。
最后我们写出规范语句。添加如下规则:
TransitMode => 通勤模式
Driving => 驾车
PublicTransportation => 公共交通
result => 结果
workPlace() => 工作地点
transitTime($1,$2,$3) => 从$1到$2的$3的通勤时间
RoleConstraint($1) => $1
revise($1,$2,$3) => 把$2修改为$3并重新执行$1
「引用」和「修改」机制是普适的。在它们的帮助下,上下文无关的程序可以完成原本必须依赖上下文的各种任务。
——————————————————————————————————————
示例2:
用户:我想订一张去上海的机票
描述目的地等于"上海"的航班搜索
机器:好的,请问出发地是?
在这轮对话中,由于"出发地"和"出发日期"的缺失,航班搜索函数抛出了一个包含失败原因的异常(Exception),而 describe 通过描述这个异常生成了询问出发地的回复。接下来,系统将根据用户提供的信息不断修改程序:
用户:从北京出发
描述把(那个出发地)修改为"北京"并重新执行(那个航班搜索)
机器:好的,请问出发日期是?
第二轮对话补充了"出发地信息",但"出发日期"仍然缺失。系统继续提问,但用户可能并不会立刻回答:
用户:我的演讲是在哪天?
描述标题匹配"演讲"的事件的日期
机器:您的"复旦大学演讲"是在2020年8月10日。
用户:那订前一天的票吧
描述把(那个出发日期)修改为(那个日期)的前1天并重新执行(那个航班搜索)
机器:2020年8月9日,从北京到上海有55个航班:(展示航班列表)
经过四轮对话,系统终于集齐了执行搜索所需的信息。这有点类似于传统的意图识别系统通过多轮对话填充多个「槽」的过程,但又与之不同:在第三轮中,用户并未直接回答系统的询问,而是转向另一个话题,并利用获得的信息在第四轮作出了回答。这类「打断」现象在人类的交流中非常普遍。传统的多轮对话大多由预设的"路径"驱动。一旦用户的行为偏离路径,系统就会失去对上下文的掌控。而在添加了「引用」和「修改」机制后,语义理解模型只需关注当前的语义,因而能轻松地应对类似上文的语境切换。
6. 错误处理
在任何复杂的对话中,都有许多意想不到的事情发生的方式。“预定与Megan的会议”的请求可能会失败,因为用户的联系人列表中没有人名为Megan;因为很多人都叫Megan;因为没有空闲时间开会;或者甚至因为internet已断开,对话代理无法与服务器联系。每种情况都需要不同的响应,而现有的对话系统通常使用复杂的硬编码逻辑来从错误中恢复。
本文通过从数据流图的某个节点抛出异常来处理所有这些故障(异常本质上只是求值返回的特殊结果(可能是结构化值))。我们的对话代理对这个“故障结果”作出反应,为用户生成一个适当的警告或问题。用户可以自由地做出回应,也许是通过纠正问题,例如“I meant Megan Bowen”,这种方法允许系统和用户在错误出现时灵活地、根据上下文、模块化地和协作地处理错误。
示例:
User : What do I have on February 30 ?
findEvent(EventSpec(
start = DateTimeSpec(month = feb, day = 30)))
Agent: There is no 30th of February. Did you mean some other date ?
此时,从异常中恢复与其他任何修正步骤一样:用户提供一个新值,代理只需将其修正到数据流图中的正确位置,如下:
User: I meat February 28.
revise(rootLoc=RoleConstraint(output), // 需要修正的根程序
oldLoc=Constraint[DateTimeSpec](), // 对[DateTimeSpec]约束修改
new=DateTimeSpec(month=feb, day=28)) // 新值
User: I meat March.
revise(rootLoc=RoleConstraint(output),
oldLoc=Constraint[Month],
new=mar)
用户可以修订先前的所说内容,也可以尝试其它话语“List all my meetings in February”/“Never mind, let’s schedule a vacation”。
我们的异常处理机制适用于许多传统上不被视为异常的情况。例如,一个交互式的填槽工作流可以通过一系列未指定的构造函数来实现,每个构造函数都会触发一个异常并引发用户修改:
User:Create a meeting.
creatEvent()
--->UnderconstrainedException(name)
Agent: What should it be called ?
User: Planning meeting.
// 代理预测用户打算修改“遗落的名称”,因为在上一个回合的对话历史中出现了一个涉及名称路径的异常。
revise(rootLoc=RoleConstraint(output),
oldLoc = RoleConstraint(name),
new = "Planning meeting")
--->UnderconstrainedException!(start)
Agent: What should it start?
通过构建数据流图(从自然语言进行程序合成的工作)和评估其节点之间的阶段分离,实现恢复行为。数据流图总是包含用户当前目标的记录,即使目标不能被成功地评估。这个目标在变换对话轮次时仍然存在,并且仍然可以被引用,因此可以使用与用户发起的修订相同的元编译操作交互地细化和澄清。异常处理影响对话的进程,而不需要传统的手写或学习的“对话策略”来解释完整的对话状态。我们的策略只需要生成一种语言,这种语言能够对最近的用户程序的计算中的任何一个或多个异常做出适当的反应,就像它在计算成功的情况下对普通的返回值做出反应一样。
7. 机器回复的生成
以"明天泰山日出的时候会冷吗?"为例,用户期待的并不是简单的「是」或「否」的答案。一个高质量的回答,如:
机器:不会。8月2日早5:12,泰山地区天气晴,气温15摄氏度。
还包括了很多额外的信息。这类似于人类的交流习惯:人类在汇报任务时一般不只描述任务的结果,还会适当地讲述其过程。这样的汇报往往更有价值,也更容易取得用户的信任。为此,回复模型必须能完整地看到当前正在执行的程序,并使用它的某些中间结果来充实机器回复的细节。
上下文理解和机器回复的生成对系统设计提出了同一个要求,即系统不仅要理解"现在该做什么",还必须记住"曾经发生了什么",我们把这份历史记录称作对话状态(Dialogue state),在这里作者用有向图来表示对话状态。
用户:明天泰山日出的时候会冷吗?
### 初始程序
describe(isCold(weatherForecast(
dateTime = dateTimeAtSunrise(tomorrow()),
place = searchPlace("泰山")
)))
### 程序库
// 输入日期,返回那一天日出的日期和时间
def dateTimeAtSunrise(date: Date): DateTime = {
DateTime(
date = date,
time = sunriseTime(date)
)
}
// 输入天气预报,返回气温是否寒冷的布尔值
def isCold(weatherForecast: WeatherForecast): Boolean = {
weatherForecast.temperature < ColdThreshold
}
数据流图记录了所提到的实体及其之间的关系,数据流图中的所有节点都可以被后续的话语引用。数据流的存在允许我们定义一些非常有用的新函数,之前讨论过的函数都是上下文无关函数,即它们的执行结果只取决于参数。有了数据流后,我们可以定义「数据流依赖函数」,使函数的执行结果不仅取决于参数,还取决于当前的数据流状态。上图中,describe 函数就是一个典型的数据流依赖函数,它利用了包括时间、地点、天气预报在内的多种数据流信息来生成机器的回复,涉及到图结构的匹配的和自然语言的生成。
语言的产生依赖于对话语境
大多数现有的对话方法要么是硬编码生成规则(导致听起来像机器人一样的输出,不会因不同的上下文而改变),要么是非结构化的神经语言模型(有时不能说出真相)。
在作者的方法中,语言生成被建模为一个神经引导的合成程序转换过程,在这个过程中代理轮流扩展数据流图。代理可以讨论图表中显示的任何内容,而不仅仅是它计算的最后结果。它甚至可以添加新的计算和结果到图表中,用户可以在以后的回合中自由参考:
SMCalFlow数据集
SMCalFlow是一个新收集的约40K英文大规模、跨领域、多轮次的任务型对话数据集,在日历、通信录、天气、地点的交叉领域内标注了4.1万段对话,包括总计15.6万轮交互和338种不同的函数调用。SMCalFlow规模是MultiWOZ 数据集的四倍,语言的复杂度和多样性方面也远超MultiWOZ。
标注过程:标注者扮演助理。他拥有一份事先编写的“标注手册”,讲述了助理在面对不同要求时的服务细则以及程序语言的使用方法。在看到用户语句后,标注者根据手册的指导并在辅助工具的帮助下写出对应的程序。标注完成后,系统会运行程序并把机器回复发还给用户。而用户则继续下一轮对话直到满意为止。
典型对话:
机器:您好!请问我能怎样帮您?
用户:提醒我明天早上8点去机场。
机器:以下安排可以吗?(展示事件卡片: 时间=8:00AM,标题="去机场")
用户:地点设为拉瓜迪亚机场。
机器:这样可以吗?(展示更新后的事件卡片:地点="拉瓜迪亚机场")
用户:天气怎么样?
机器:明天6:00AM - 12:00PM, 纽约拉瓜迪亚机场多云,最高气温77华氏度,最低气温67华氏度。
用户:会堵车吗?
机器:对不起,我目前还无法回答关于通勤的问题。
用户:那算了。就这么定吧。
机器:好的,已为您加入日程表(展示确认后的事件卡片)。
用户:对了,把它抄送给我的下属。
机器:您要把"去机场"日程抄送Jake Cobb和Jennifer Watts吗?(展示事件卡片)
用户:是的
机器:好的,已为您抄送Jake和Jennifer。
User:"What time is my appointment with Jerri Skinner on Friday?
Agent:I didn't find any events with Jerri Skinner.
(Yield
:output (
:start (singleton (
:results (FindEventWrapperWithDefaults
:constraint (EventOnDate
:date (NextDOW
:dow #(DayOfWeek \"FRIDAY\"))
:event (Constraint[Event]
:attendees (AttendeeListHasRecipientConstraint
:recipientConstraint (RecipientWithNameLike
:constraint (Constraint[Recipient])
:name #(PersonName \"Jerri Skinner\"))))))))))
实验
// "Joint Goal"是对话状态匹配精确值;
// "Dialogue"是对话级别的平均精确匹配;
// "Prefix"是一个错误预测之前的平均轮次;
// "Underprediction"从代理程序中删除步骤;
// "Entity linking"从用户话语中提取实体时出现错误;
// "Fencing"将用户请求分类为范围之外;
// "ambiguity"具有多种可能解释的用户话语
总结
整体流程:用户所说的话——>(机器翻译)转化为与之对应的规范语句——>生成执行程序(数据流图)——引用机制&修改机制&错误处理(扩展数据流图)——>自然语言回复。
基于程序合成的对话系统,比传统的对话系统适用性更强,尤其是工业前景更强。本文利用“规范语句”“引用机制”“修改机制”等,帮助解决程序合成最大问题——标注和解析,但基于程序合成的对话系统仍属于探索阶段,语义解析、回复生成等问题仍待解决。
Reference:
论文原文:https://www.mitpressjournals.org/doi/full/10.1162/tacl_a_00333
深入解读:张雨辰:解读微软最新的智能对话技术:用数据流合成实现任务型对话
对比模型:TRADE
来源:oschina
链接:https://my.oschina.net/u/4346770/blog/4656912