©作者 | 林亿
单位 | 科大讯飞AI研究院
研究方向 | 对话系统
背景
对话系统中,DST(对话状态跟踪)可以说是直接决定多轮对话效果的一大模块,其主要作用就根据对话历史信息推断当前对话状态和各类属性值,多轮对话中一些不完整表述则可能对 DST 中的意图、槽位等产生影响,而对话改写就是解决方案之一,如关键词补全、指代消解、实体消歧等;如下面的对话:
代码语言:javascript复制M:亲需要些什么?
C:有戴森的吹风机吗?
M:没有哦亲亲
C:那有松下的吗?
如果按照单轮对话来做,对话系统接收到“那松下的呢?”这句话是直接懵圈的,实际上我们更期待用户的问题是“那有松下的吹风机吗?”,这就是多轮对话改写任务 [1](排名)。
前言
逛 openreview 看到的一篇将对比学习应用到多轮对话任务中,并超越当前 SOTA——RUN 的文章,《Utterance Rewriting with Contrastive Learning in Multi-turn Dialogue》[2]
全文只在 RUN 的基础上加了一些工程性 trick 使提升为 SOTA,没有创新性工作,如多任务 对比学习损失。
进入正题
先说一下当前的多轮对话改写任务——RUN,EMNLP 2020 的一篇文章,链接:Incomplete Utterance Rewriting as Semantic Segmentation [3],其主要解决了多轮对话中的关键词补全与指代消解任务。关键词补全就如背景中的例子,对“那有松下的吗”进行“吹风机”补全;指代消解如下例:
代码语言:javascript复制C:播放周杰伦的歌
M:好的,已经播放
C:不听他的了,换张学友的
期待改写成“不听周杰伦的了,换张学友的”;
RUN 是怎么做的?非常简单,其假设关键词补全的关键词,与指代消解的原词,均来自于历史对话,如上文的补全的“吹风机”,与“他”指代的“周杰伦”均来自于上文,所以只要找到当前对话中需要插入关键词的位置、需要替换的指代词位置,以及上文中对应的关键词与原指代即可;
听起来好像很复杂,看一下实际怎么操作的,假设有这么一组对话:
训练脚本:M:需要什么 C:有戴森吹风机吗 M:没有 C:那松下的呢 标签: C:那松下的吹风机呢
首先把训练脚本中的所有字符连接到一起,即“需要什么 [SEP] 有戴森吹风机吗 [SEP] 没有 [SEP]”,这是一个长度为 M=16 的文本,和改写前的当前 N=6 的会话“那松下的呢 [END]”,构造一个 M*N 的全 0 矩阵;[SEP] 仅用来分割不同对话,[END] 符号第 2 点说。
然后定义三种操作 {'none': 0, 'replace': 1, 'insert': 2},在 M*N 矩阵中,需要对当前矩阵进行插入关键词操作的位置改写成 2,需要进行替换操作的,改写成 1;而 1 中在句子末尾加上 [END] 符号的原因就是也有可能会在句末进行插入。按这个逻辑,上面的标签最终转化的 M*N 矩阵就是一个 16*6 的矩阵,也是任务最终训练的目标:
▲ 图一 关键词补全,将上文出现的”吹风机“补到当前会话”呢“字前
这个图就一目了然了,如果是有指代消解任务,就假设是需要将“松下”替换成“戴森”,如下图:
▲ 图二 关键词补全 指代消解 这里假设用松下指代戴森,实际对话场景种通常是”他她它那个这个“指代某某
任务到这里就一目了然了,基本就当成 CV 里面的语义分割来做就行了,不管你用什么样的网路结构去搭积木,最终输出目标就是这样;在 RUN 原文中用的结构是 BERT->LSTM->Unet-> 交叉熵。
主角出场——RUN
RUN 其实是我自己给这篇文章取的名字,因为这篇文章全文没给自己取名字,只说了自己是在 RUN 的基础上加了对比学习和多任务,刷新了 RUN 的表现,成为 SOTA;为了方便描述,下面都会称这篇文章 RUN 。
RUN 的损失函数一:RUN 的原损失函数, 就是上面的标签, 就是模型预测输出,CE 是交叉熵。
RUN 的损失函数二:关键词检测,把多轮对话(包括当前句),有操作的字是 1,其它是 0,如图二,输入“需要什么 [SEP] 有戴森吹风机吗 [SEP] 没有 [SEP] 那松下的呢 [END]”,标签就是“0000001111100000011010”,其中“戴森”(替换当前句中松下)、“吹风机”(插入当前句中呢字前插入),“松下”(被历史对话中戴森替换),“呢”(被历史对话中吹风机插入),
其中, 是一个 M*N*2 大小的矩阵,2 代表标签 0 和 1;
RUN 损失函数三:意图一致性检测,用完整的上下文表述的语义应该和标签句保持一致语义,然后用当前句原句(即未改写的真实句)在每轮训练时随机删除一些字,当做其自身负例,如图所示:
损失函数,希望拉近完整的上下文和标签句,即这是一对正例对,、推远完整的上下文和其它上下文、推远完整的上下文和其自身负例、推远完整的上下文和其它上下文负例,公式表述如下:
RUN 损失函数四:对比损失,注意在损失函数一和二中的 、,同样的一组输入,像 SimCSE 那样输入两次,经过两次 dropout 的结果,应该互相靠近,利用 KL 散度拉近,(其实是论文 R-Drop 的做法),如下所示:
上面的 和 不是,C 和 、C 和 的意思啊,是 {CQ} 第一次编码后 dropout 和第二次编码后的 dropout;
到此为止,RUN 的多任务损失函数就整理出来了,如下
即等于:
① Loss(forward): 和 ,原始 RUN 的输出矩阵两次 dropout 的结果; 和 ,关键字检测两次输出的结果; ——有监督。
② Loss(icon):意图一致性损失(个人觉得应该叫语义一致性更合适)——无监督。
③ Loss(pcon):对比损失;——无监督。
表现
可以看到本文一系列操作之后,指标确实上去了,并且只是在训练时候新增了多个优化目标,实际推理的时候和 RUN 没什么区别,所以推理速度不会增加。
代码
工程部分最关键的无非就根据多轮对话构建标签矩阵以及根据标签矩阵输出改写内容,这部分写了个类无依赖环境可以直接运行,代码:GitHub - ZeroE04/run_encrypt_decrypt: Incomplete Utterance Rewriting as Semantic Segmentation [4],剩下的结构偏置、调参过程、数据使用策略,仁者见仁智者见智。