点击上方,选择星标或置顶,每天给你送干货!
阅读大概需要26分钟
跟随小博主,每天进步一丢丢
作者: 龚俊民(昵称: 除夕)
学校: 新南威尔士大学
单位:Vivo AI LAB 算法实习生
方向: 自然语言处理和可解释学习
知乎: https://www.zhihu.com/people/gong-jun-min-74
前言:今天要说的内容是关于非自回归方法的序列生成。在作业1中,我们做了语音识别。它可以看作是一种条件序列生成。输入是一段语音,输出的一段文本序列要求对应到这段输入的语音。除了语音识别外,条件序列生成还可以拓展到更多的任务。比如看图说话,给定一副图片,要求生成的文本序列能够描述图片中的内容。类似的,机器翻译也是,给定一个源语言的文本序列,要求生成的文本序列是还原源语言语义且语言是另一种指定语言。
![](https://i-blog.csdnimg.cn/blog_migrate/9f1fec26413169bcdd8bde676cb10738.png)
一般自回归模型用的是 RNN,序列的 token 是一个个输入给模型。在解码时,每次生成出的 tokens 都会用来作为限制下一个 token 生成的条件。这样的方法有一个缺点在,当我们要解码生成的句子很长时,要花的时间就和解码的长度成正比。但我们有了 transformer 之后,我们编码时就不需要像 RNN 一样,需要吃前一个时间点的 token 后,才能编码下一个时间点的东西。所以它的速度是可以并行加速的。但在解码时,还是会遇到和 RNN 一样的问题。
![](https://i-blog.csdnimg.cn/blog_migrate/f96abfc921accad31aa8dad99c7dd49f.png)
既然 transformer 都能够并行运算了,为何不让它在解码的时候也一口气把整句话直接生成呢?这样的话,时间就能减少很多。但有一个问题是,我们要如何确定一口气生成的句子到底有多长?我们可以先让解码器预测出一个长度,再利用这个长度去得到位置嵌入。但这样去做又会遇到一个很大的问题。
![](https://i-blog.csdnimg.cn/blog_migrate/20e5e712b6c9009c63de574e5702a053.png)
在理解这个问题之前,可以先看一下在图像处理中的例子。假如我们不用 GAN 的方法而是纯粹监督学习的方法去训练一个文本到图像的模型。这个模型的输入是一个文字比如说(火车),输出是一张图片。损失就用带有火车的图片和生成的图片算L2距离。这个方法的缺陷在,生成的图像都会很糊。因为它会是每一张图片的平均。假如我们有三张朝向不同的火车。那么生成的火车则倾向于是它们三个朝向的平均。
![](https://i-blog.csdnimg.cn/blog_migrate/b47eeb5b15619b15238aa21b68d9c2b9.png)
为什么我们会遇到这个问题?这是因为图像每个位置的像素都是独立输出的,没有考虑到彼此的依赖关系。如果我们用自回归模型去做,就可以避开这类问题。因为在输出第二个位置的像素时候,可以看到第一个位置的像素。另一种方法是用 GAN 来解这个问题。判别器能够给整张图片评分。然后把这个分数反馈给生成器去更新参数。这个分数得出是参照了整个图片所有的结构,考虑了不同位置的像素与像素之间的依赖关系。所以 GAN 是一种可以让生成器输出完整结构的方法。除了没有考虑不同输出的依赖问题外,这类依赖监督信息的模型也没有任何的随机机制,没有一个隐变量去决定输出的火车朝向左边还是右边。虽然这个模型在训练的过程中,已经知道有 70% 左右火车朝左。但由于它内部没有随机的机制,所以即使到了最后一层输出,它还是会输出两种朝向火车的叠加状态。为什么自回归模型不会有这类问题呢?这是因为它对每个时间点解码就会有个采样的过程。它会挑一个几率比较大的,把其它可能性都排除掉。对于 GAN 而言,不管有没有条件生成,输入给生成器的都会加一段随机的噪音。这段噪音实际上就是让模型实现决定好生成的方向。所以自回归模型 和 GAN 都会避开这两个问题。但是我们的非自回归序列生成模型,就会遇到这两个问题。
![](https://i-blog.csdnimg.cn/blog_migrate/fc6723fc6ebe7bb726129666947b3b04.png)
说完图像的例子,现在我们来回到文字的例子中。我们有一句英文,想要把它翻译成中文。可是中文可以有很多不同的翻译方法。比如,Hello 可以翻译成你好,或者是嗨。假如我们有一半的概率是你好,一半的概率是哈喽。那么 Transformer 解码时,第一个字 "哈" 和 "你" 的概率是差不多大的。第二个字是 "喽" 和 "好" 的概率是差不多大的。所以 Transformer 会生成出错误的翻译,比如 "你喽" 或者是 "哈好"。这便是 multi-modality 问题。同一个输出,可能对应到很多不同模式的输出。如果是非自回归,它会把各种可能的模式叠加在一起,作为输出。所以这个 multi-modality 是我们关键要解决的问题。
![](https://i-blog.csdnimg.cn/blog_migrate/4d52512a8c6c4700e20dd74390197986.png)
这里总结一下。加入我们要用传统算L2距离的方法去训练图像生成,它会输出很多张图片的平均。对应在文本就是,我们用非自回归模型去生成文本,它也是各种可能的输出叠加状态。如果我们用自回归方法在图谱上,就能生成出品质很好的图片。对应到文本上,就是用 RNN 自回归的方式去解码生成。GAN 可以生成很高品质的图片。我们在做 GAN 的时候,可以用传统的模型架构,从而可以避开自回归模型的架构。但 GAN 在文字上却显得很不可行。因为离散的文字用 GAN 来做,目前还不成熟。因此目前 GAN 在文本的研究上都还是偏自回归多一点。由此我们只有一条路走,如何针对非自回归模型改良?
![](https://i-blog.csdnimg.cn/blog_migrate/db31544f7b0c673c7b6dca676ef321b8.png)
ICLR 18 出了第一篇用非自回归模型做翻译的模型。非自回归翻译简称叫 NAT。这篇文章的概念还挺简单的。它提出了几种改良方法。第一种方法是,我们在 transformer 的编码器这里,每个位置去预测一个数字。这个数字代表的意义是,这个输入的字,对应输出的几个字。我们预测完这个数字后。就把输入给解码器的token 嵌入来拷贝该位置输出的数字份。图中 Hello 的位置编码器输出2,解码器的输入就有两个 Hello。为什么我们要预测这个数值呢?其实它代表了解码器对这个输出的规划。
![](https://i-blog.csdnimg.cn/blog_migrate/0bedeffd4a00f2e6a28268d623f2bdac.png)
假如编码器输出的是 2121,Hello 复制两次,那它就可能输出一个 Hello 的 token 对应着 "你好" 两个中文 tokens。但如果编码器输出的是 1121,则 Hello 只有一次,它就只可能对应着中文的 "嗨" 一个token。除此以外,我们也可以直接去训练一个序列到序列的模型,然后去看它的注意力权重是怎样分布的。在语音处理中,对应的就是哪段语音信号对应的哪段文字。这里做的事情是一样的。但如果我们只是从外部得到答案,来端对端训练这个 Fertility 预测的话,它和最后模型要预测一句不错的句子目标是不一致的。所以我们在模型收敛以后,会做一个微调的步骤。就是用强化学习的损失加在 fertility 分类器上面,去优化。
![](https://i-blog.csdnimg.cn/blog_migrate/523145ec927c26061323593228c7fcd0.png)
之前的 Fertility 是一种技巧。我们也可以基于知识蒸馏来做。假如我们要训练一个小模型,想让它和大模型一样好。那我们可以把小模型当作学生,向大模型学习。小模型在训练时,先把输入丢给大模型,让它预测出概率分布,作为小模型的监督信息计算损失。这样小模型就会学的比较好。
![](https://i-blog.csdnimg.cn/blog_migrate/c6fdbb648f536d900eedf87393200250.png)
类似的,我们可以让自回归模型作为老师,非自回归模型作为学生。但后面的事情有一小点不一样,我们不用自回归模型的预测分布作为监督信息。而是让自回归模型的贪心解码输出作为新的训练语料。用它来作为学生的训练目标。为什么这种方式可以解决 multi-modality 问题呢?因为输出之间没有考虑依赖,所以错误的翻译概率会很大。但如果我们的数据集有先给自回归模型做解码,它在生成出第二个字的时候,其概率分布依赖于之前生成的字。比如第一个字预测的是 "你",那第二个字预测出是 "喽" 的概率就会变小。这样一来,正确答案里面就不会有"哈喽"这个可能,只会留下 "你好" 这个可能。这样做完后,数据集就会变干净,更容易让自回归模型去学习。
![](https://i-blog.csdnimg.cn/blog_migrate/82a8cb3951243c3e742556f68d90fbf0.png)
还有一招叫作 Noisy Parallel Decoding (NPD)。它的概念是,我们先训练好非自回归模型后,解码时,其实是可以采样不同的 fertilities。这样就能确保随机性,输出一大堆不同的句子。这些句子再交给自回归模型来评分,选择一个最好的句子当成答案。
![](https://i-blog.csdnimg.cn/blog_migrate/3cc05cd576c6ac72e1edce0e32694927.png)
这样可能有点奇怪,我们不就使用了自回归模型了吗,不就变慢了吗?但其实我们让自回归模型去为一个已经生成出的句子打分,是只需要一步就能完成。比如我们用 Transformer,把当前 token 的后面用 MASK 遮住,它就不能看到后面的字。这样一个 MASK 矩阵与 Transformer 解码器乘起来,就是整个句子的概率。
![](https://i-blog.csdnimg.cn/blog_migrate/9588d05d7f379690b13ff371a97ef759.png)
所以我们就有三种方法可以加强非自回归模型。从结果来看,b 代表的是 beam search size。在解码推断上,NAT 速度会显著快于自回归模型,但表现低于它。我们一步步把三个 trick 加上去,会发现表现越来越好,直到与自回归模型相当。即便如此,速度也是自回归模型的至少两倍。
![](https://i-blog.csdnimg.cn/blog_migrate/88aac9a1e5014c8675ac48c987507930.png)
在消融实验中,可以看出,加上 fertility 之后,BLEU 分数可以进步到 18.87,加上蒸馏之后,分数一下子进步到25.20,。再加上微调之后,BLUE 略微提升了1个点左右。
![](https://i-blog.csdnimg.cn/blog_migrate/8ea4cf3513a10a73607c06e298361037.png)
自从这篇 NAT 出来之后,之后几乎每个月都会不止一个新模型出现。第一个出来的变种是 Iterative Refinement 的思路是,如果我们让模型一口气输出整段话可能会太勉强,我们能不能把解码出的文本再重新丢回到解码器,重复多次让它来修正之前的错误。除此之外,还有一种方法是 Insertion-based,它类似于前面的变种,不过它迭代的方式是通过插入的方法。对于非自回归模型输出的比较差的句子,重新丢回解码器判断每两个字之间要不要插入新的词。因为原本的迭代优化的方法,句子长度是固定的。第一次解码出的句子多长,迭代优化后的句子也会是这个长度。但是用基于插入的方法,迭代优化的句子长度就可以改变。但这样还是会比较慢。比如我们想要生成 ABCDEFG,第一步可能会先生成中间的D,然后是 BDF,最后才是全部的tokens。最快是 O(logN) 的实践。所以基于插入的迭代优化法,比较像是介于自回归和非自回归模型中间的一个产物。有了插入之后,我们又会想到它插入了一个错字,它就不能再修改了。所以我们会自然地想到既可以插入又可以删除地迭代优化方法。最后一个就是 CTC-based 方法,这个和语音识别中的 CTC 是一致的。待会我们会详细介绍它要怎样应用。
![](https://i-blog.csdnimg.cn/blog_migrate/d7b51c249d86d9eb7543386cf0634531.png)
这就是第一种迭代优化的变体。它一开始也是一个编码器,去预测目标token的长度。然后把每个 token 按照长度去复制,输入给解码器1生成出 Y0 做第一次迭代。接下来我们把 Y0 再输入给解码器1输出Y1,做第二次迭代。以此类推。我们要怎样训练这第二个解码器呢?我们除了可以直接拿Y1和正确答案算损失外,我们也可以随机加一些噪音丢给解码器2,让它去学去噪的过程。加噪音可以直接把某个 token 重复两次,或者直接把某个 token 换成随机的 token,再或者是把两个 token 交换。接着我们让模型去学习两种不同的损失,前面那项是第一个解码器的损失,负责计算把根据长度复制的X‘输入给解码器后得到Y0,与正确答案的交叉熵,后面那项是迭代优化去噪的交叉熵。
![](https://i-blog.csdnimg.cn/blog_migrate/b1030e107b8bdab0581ec3c87650b21d.png)
结果表明它的效果和最初的 NAT 那篇论文相当。
![](https://i-blog.csdnimg.cn/blog_migrate/324ff14454fa254680f41190f1d33fa8.png)
另一篇在 BERT 出来之后做的。它的思路是把 BERT 当作纠错的解码器。BERT 可以把一句话中被替换成 MASK 的 token,还原出来。我们把 BERT 加上一个编码器,再用 BERT 当解码器来用。它在做 MaskedLM 的时候,可以看到翻译之前的句子。这样基于条件的语言模型训练完后,要怎样使用呢?首先,英文 tokens 输入给编码器后会在 CLS 这个 token 的位置预测一个长度,比如是6。接着,它就会把 MASK 这个 token 复制六次,让 BERT 去预测出第一版的翻译。这个翻译可能会是比较乱的。于是我们会把这第一版中生成几率比较低的 token 重新 MASK 掉,再作为输入丢给 MaskedLM,让他生成出第二版。
![](https://i-blog.csdnimg.cn/blog_migrate/f96e8b1a3a0d6a7982b81fa15ccc2061.png)
我们要怎样决定要 MASK 掉多少 tokens 呢?一开始我们希望MASK多一些,到后面随着迭代优化得越来越好,则 MASK 越少。
![](https://i-blog.csdnimg.cn/blog_migrate/c739a068169582bd6435af7daab9cf25.png)
我们可以看一下个例。在t=0代表第一次的时候,模型一口气吐出了所有文字,发现有明显的语法错误。涂黄色的区域表示要 MASK 掉的 tokens。t=1,迭代一次后,错误明显减少。再迭代一次后,则翻译的很通顺,没有明显错误。
![](https://i-blog.csdnimg.cn/blog_migrate/2bbb9734d5ff19df9c97c0216bd5acd1.png)
从结果上看,这种 MASK-predict 的方法显著好于过去非自回归的生成,甚至好到要接近自回归模型的生成。
![](https://i-blog.csdnimg.cn/blog_migrate/57dda9bc23fb38bc1d6eb5ed2a97fa00.png)
接下来要讲的是基于插入的方法。它也是一种部分自回归模型。insertion transformer 的概念是,将残缺的句子输入给模型,模型要预测每两个句子之间要不要插入一个字,如果要,则插入什么样的字。假如我们输入有6个 token,输出也会是6个向量。我们把相邻的两个向量都接起来,这样每两个字中间就会有一个表征。我们把这个表征接上 MLP 去预测它要插入的字。如果两个字之间不需要预测插入的字,就预测 end。
![](https://i-blog.csdnimg.cn/blog_migrate/8fb7825fac55573f19a4528149bb5345.png)
这样的模型我们要怎样训练呢?我们要怎样创造让模型去学的个例?假如我们数据集中有一段10个字的话,我们先把这10个字做顺序打乱,然后从0-10中随机挑k个数字,比如k为5,我们就只取前5个字。后五个字就是被 drop 掉的字。然后我们把这五个句子还原回来,被 drop 掉的字就会形成一个槽位,表示要插入字的位置。
![](https://i-blog.csdnimg.cn/blog_migrate/a577953f6af91b6446531acec095411b.png)
接着,我们用这样处理出的训练数据集取训练一个 insertion decoder。它是一个 transformer 架构。输入是前面方法处理后的个例,解码器目标是还原出 drop 掉的字。如果 drop 掉了连续两个字要怎么办呢?我们要两个y都去算损失。假如这两个字中间本来就没有字的话,就预测 end。
![](https://i-blog.csdnimg.cn/blog_migrate/45f1037de1baeb5a037724d88b54c59a.png)
假如一个槽位有很多个字的话,我们就要把全部的损失都计算。原本我们这些字的所有的损失相加求平均。但我们可以让模型倾向于生成出槽位靠中间的字。为什么要这样做呢?因为在迭代的过程中,先生成出中间的字,可以方便下一次迭代生成补全。这样迭代的次数就会尽可能的少。所以我们可以针对中间的字算损失的时候,乘上一个比较大的权重,让模型更倾向于先把中间的字生成出来。
![](https://i-blog.csdnimg.cn/blog_migrate/8adc0d2e7c81116918921a1f79bebfe9.png)
这边我们可以看一个例子。模型在解码的时候,会先生成出最中间的字,然后一步步像二叉树遍历一样,生成左右两边中间的字。迭代次数显著少于不对中间字生成损失加权重的方式。
![](https://i-blog.csdnimg.cn/blog_migrate/667e76aaa295d7c2c84c05230a1972f3.png)
这种方式除了可以并行解码之外,我们也可以每次选一个几率最大的,一步步做解码。但相比用二叉树生成的方法解码会慢很多。一个要操作 n 次,另一个操作 log2n 次就行。
![](https://i-blog.csdnimg.cn/blog_migrate/7ed002ba6a28c878c4c342df4f17a5f6.png)
从表现结果上去看,一个个生成和二叉树生成 BLEU 得分是差不多的,都于从左到右生成的自回归模型相当。但从速度上看,二叉树生成的方法会显著快于过往的方法。尤其是在要生成的序列很长的时候。
![](https://i-blog.csdnimg.cn/blog_migrate/46f175c7105f9eea08dc9bd0abfa7a53.png)
Insertion transformer 出来以后,又出了一篇叫 KERMIT 的论文。这个也是芝麻街的人物。为了凑名字,原本是 C 开头的 contextual 变成了德文的 Kontextuell。这个 KERMIT 的概念和 insertion transformer 的概念是一模一样的。唯一不同在, insertion transformer 有编码器和解码器。而 KERMIT 把编码器和解码器混合在同一个模型中了,就像 BERT 一样。比如我们要中文翻译英文,我们把中文和英文的序列接在一起,然后在英文的那半边做插入的操作就好了。
![](https://i-blog.csdnimg.cn/blog_migrate/6cd812955074b60f60371019a334feb9.png)
这样做是有个好处的。英文翻译中文的话,我们就可以把英文接中文。然后在中文的那边做插入的操作。它可以做到中英互译。我们还可以训练中文和英文的联合概率 P(x,y)。我们把中文和英文接在一起,中文 drop 掉部分字,英文也 drop 掉部分字。英文要预测插入的字的时候,它就可以看一下中文有什么信息。同理,中文要预测插入字的时候,也可以参照英文的信息来生成结果。当然,我们也可以只放中文或只放英文,去做插入的训练。这样一个模型就能学到 5 件事情。
![](https://i-blog.csdnimg.cn/blog_migrate/3429f15b98be0af2b7f1a1e89f53dfef.png)
同时学 5 件事有什么好处呢?如果我们分别训练两个中译英,英译中的模型,作为表现基准。单纯地让模型同时学中译英,英译中 (KERMIT 的方式),结果表现是往下掉的。但如果我们为 5种任务在一个模型中去建模,最后再做一个微调,效果又会升回来,甚至超过单P(y|x)或P(x|y)的基准。
![](https://i-blog.csdnimg.cn/blog_migrate/2361b8fdaf499730dcb3375d5175702c.png)
KERMIT 之所以用芝麻街中的人物命名是因为,它可以像GPT 和 BERT 一样作为预训练语言模型。KERMIT 的方法在 GLUE 上跑出来的效果,竟然和 BERT 不相上下。而比BERT厉害的是,KERMIT 又可以做生成,又可以和 BERT 一样做其它任务。
![](https://i-blog.csdnimg.cn/blog_migrate/af3d9ed1dc23764164aed36d0c8dd5ba.png)
在 ClozeQA上做零样本学习,BERT 不能很好地判断空格要填入几个 tokens。但是 KERMIT 可以很自然地插入正确地字。同理在 SQuAD 上做零样本学习,KERMIT 也显著好过 BERT 和 GPT-2。
![](https://i-blog.csdnimg.cn/blog_migrate/9c34fea112be89dab6e26b2d49036842.png)
KERMIT 往后还有一个新版本叫作 Multi-KERMIT。它把各种语言的数据都训练在同一个模型上面,是一篇值得一看的 paper。
上面说到 KERMIT 在问答任务的零样本学习上表现要比 BERT 和 GPT2 好很多,因为它为五个任务建模。这一期我们来说一下非自回归序列生成未讲完的两个模型。一种是加入了删除操作的插入迭代法,另一种是借鉴了语音识别任务 CTC 的方法。
![](https://i-blog.csdnimg.cn/blog_migrate/97fcc367d411d8eda96d863a1650fefa.png)
Levenshtein Transformer 相比于之前插入迭代的方法,它还增加了删除操作。解码器会生成三个部分。首先我们会有一个翻译到一半的未完整片段。我们把它丢进解码器后,再接一个分类器用来决定哪些生成的 token 不合适需要删掉。我们再把这个句子丢到解码器中,又接一个分类器用来决定哪些 token 之间需要插入新的token。最后我们把这个句子丢到解码器,再用一个分类器去决定要插入token的位置,应该插入什么新 token。我们要怎么去训练一个这样的模型呢?它用的是模仿学习的方法。
![](https://i-blog.csdnimg.cn/blog_migrate/5d32169702e31bbace7da633dbd82c62.png)
既然是模仿学习,我们就需要有一个模仿对象。这个模仿对象的算法就是 Levenshtein 的 python 算法包。它可以算两个序列的编辑距离。它也可以算出我们要怎样把第一个序列编辑成第二个序列的方案。我们把替换操作拆解成先删除后插入,这样会比较好操作。
![](https://i-blog.csdnimg.cn/blog_migrate/17cf11286891914b15d507cfbc4d2f6b.png)
接下来要怎么训练呢?首先,我们要在数据集中制造需要被删除一些字的句子,然后我们插入一些错的字进去。同时,我们也会制造一些需要被插入一些字的句子,然后我们把一些字drop 掉。比如 "This is is a pen .",这句话中 "is" 是重复的,需要被删除。我们把这句话和原本的正确句子 "This is pen",丢到 Levenshtein 算法中,它就会得出,哪一个字需要被删除的01向量。这个向量会作为删除分类器的监督信息。再比如 "This pen .",这句话中 "is" 是缺失的,需要被插入。我们把这句话和原本正确的句子 "This is pen",也丢到Levenshtein 算法中,它就会得出,哪一个位置需要被插入字数的向量。这个向量会作为插入分类器的监督信息。最后,我们把原本这句话,插入 placeholder 之后,去看一下,原本这些 placeholders 会对应到怎样的字。它会作为监督信息给 token 分类器。
![](https://i-blog.csdnimg.cn/blog_migrate/2020f085d5b9ad74a559d8678cc22651.png)
我们再来详细地看一个例子。它的句子序列是日文。第一次迭代时,模型能一口气生成出很多文字,但由于上一期讲到的 multi-modality 问题,某些位置上的 token 会生成的不好,比如存在重复冗余的tokens。于是在第二次迭代的时候,它会把其中一些 tokens 删掉,再插入一些让句子更顺畅的 tokens。于是在短短三步之内,它就能生成出一句完整的话。
![](https://i-blog.csdnimg.cn/blog_migrate/aea7f9db164f69ee8e3b86a251eab053.png)
从结果上去看,Levenshtein Transformer 的非自回归方法,是要和自回归的 Transformer BLEU 分数相当,但速度显著变快。如果再加上知识蒸馏的方法,它还能获得更好的表现。
![](https://i-blog.csdnimg.cn/blog_migrate/53c7a0447b2015942b8c5029de07c40f.png)
CTC 也是一个非自回归模型。对于语音数据,通常不存在很严重的 multi-modality 问题。这是因为语音中,前一个时段的发音对后一个时段的发音影响依赖很小。通常一段语音就是搭配一种可能的说法。之前语音识别的笔记中有讲到,编码器可以直接一口气生成一个序列。对于每个 frame 就输出一个 token。这个 token 可以包含空白的 blank token。所以最终模型输出了如图那句话后,可以把 blank tokens 拿掉,把重复的字缩成一个字。最后就会生成出 "你好棒" 这个序列。CTC 有几个小缺点。虽然它的表现一直还不错,但是它还是会输给 序列到序列的 LAS。另一方面,CTC 虽然是个非自回归序列生成模型。但它没办法做迭代优化的动作。因为输入是语音,而不是文字。文字不能再拿来重新当作输入喂给模型。假如我们输出了一个错字。这个错字是没有机会被修正的。
![](https://i-blog.csdnimg.cn/blog_migrate/89ffd1b43913e085199ad7d822fef495.png)
所以为了改良 CTC,就有了 Imputer。它的概念就是 CTC + Mask-Predict。这是一篇非常新的 paper。它的模型和 CTC 很像,不同在它输入的地方,会同时把文字的token序列也加进来。在 t=0 时刻,这个序列会全是 MASK tokens。就和在做 Mask-Predict 一样。输出需要模型把部分 MASK 换成真的字,也可以换成是 blank token。
![](https://i-blog.csdnimg.cn/blog_migrate/39b02c0256b5b3f614c0fa3fb6d6e731.png)
在 t=1 的时候,它会把刚刚生成的序列重新放回输入,再和语音信号同时丢给编码器,继续预测。
![](https://i-blog.csdnimg.cn/blog_migrate/1d784004b9eda3582aa90187ec137097.png)
这边还会用一个叫 Block Decoding 的技巧。我们会让输入的特征有一个 Block。假如 Block 的大小是 3。那至少在一个 Block 里面,我们限制,至少一个 MASK 要被换成是真的字。这样做之后,模型在3步之内一定可以把全是 MASK 的序列变成最终生成的序列。
![](https://i-blog.csdnimg.cn/blog_migrate/97b63111270670e2f6ee5a25b0e45147.png)
这个例子中,Block size 是8,所以它每一个 Block 中就有8个声学特征和 MASK。在第一步,会把一个 Block 中的其中一个 MASK 换成真 token。每一步都会如此,到第8步,刚好每个 Block 中的 MASK 都完成了解码转换。
![](https://i-blog.csdnimg.cn/blog_migrate/c1eceeb8c1ebe7394e74ef8e99b284c7.png)
从结果上去看。前面的都是自回归的 Seq2Seq,其 word Error Rate 可以被压到非常低。但如果如果用 CTC 的话,没办法压到那么低。但用了 Imputer 之后,是可以赢过 CTC 的。
![](https://i-blog.csdnimg.cn/blog_migrate/0b5c00da37a9e4ee12d04301f17218e0.png)
这种 CTC/Imputer 的方法有没有办法用在文字生成上呢?其实有两篇论文都是在做这件事情。我们想要做一个翻译的任务,可能是中译英。输入句子在经过编码后,会做一个上采样的操作,每个位置的嵌入会被分裂成两个小向量。这样我们的句子就变长了,有点像语音信号那样的序列。我们再对这个变长的序列,去训练一个 CTC。这样就可以用 CTC 来做翻译。Imputer 也是一样。它左边部分原本是要放语音信号的。但是我们现在换成源序列句子,对其嵌入上采样后,把变长的序列当作原版的 Imputer 中的语音信号来处理。
![](https://i-blog.csdnimg.cn/blog_migrate/ab10304089a849dcf7ee60c944c18897.png)
这个方法做纯文字的翻译,效果很好。我们可以看到它和其它所有非自回归生成的方法比较,CTC就可以胜过之前所有的模型。而 Imputer 又可以赢过 CTC,做到略胜一筹的表现。
![](https://i-blog.csdnimg.cn/blog_migrate/f42ee9f33bd30106f94f6b16e1bd43f6.png)
我们再拿它和自回归模型做比较。我们会发现,Imputer 在多迭代几次的情况下,已经可以赢过自回归模型了。这个方法其实还蛮强大的。概念也很简单。
![](https://i-blog.csdnimg.cn/blog_migrate/4af78ca8370e197e87b7a80309bc30ee.png)
最后再提一下知识蒸馏在NAT中为什么有用。这篇2020的论文做了一个实验。任务是把英文翻译成西班牙文、法文、或德文。数据对随机匹配,模型不知道它要把英文翻译成哪种语言。我们让自回归模型去学这个任务的话,模型每次都分不清要翻译成什么语言。只要一吐出德文,它句子中所有字都会是德文的词汇。图中的每一个点就是它输出的一句话中,德文、西班牙文和法语的比例。要么就整句话都是德文,要么就整句话都是西班牙文或法文。就很难出现同一句话有三种不同的文字的句子。这也非常符合真实的数据分布。但如果我们用 NAT 非自回归序列生成模型去学,就发现它会出现一句话中出现不同语言文字的情况。如果我们把数据集中,存在一句英文对应到三种翻译的句子,随机删除掉英文到另外两种语言的数据对。我们互发现,之前的问题就会减轻。可是我们发现更有效的是,我们直接让 NAT 模型去学自回归语言模型的输出,即做知识蒸馏。这个状况可以减轻得更多。所以说知识蒸馏在 NAT 中的作用很大。
![](https://i-blog.csdnimg.cn/blog_migrate/cd2afae7e486bc0c2810c1d8f8fbdb36.png)
作者后面还做了一个分析。我们在不同参数大小的 teacher model 下去做蒸馏。不管和参数多大的自回归语言模型学,它都会比纯粹的 NAT 好。最开始的那篇 NAT 模型,和小模型学是最好的。但如果是迭代优化的 NAT 模型,则和中等大小的模型学比较好。如果是 Mask-Predict 或 Levenshtein ,和大模型学比较好。所以每种模型都有适合它的 teacher model 的参数大小。Levenshtein 的表现已经可以和自回归模型表现差不多了。
![](https://i-blog.csdnimg.cn/blog_migrate/7bd32b0e203680fd36c6a489e7105d2a.png)
值得一提的是,非自回归序列生成模型,初版的 NAT,Levenshtein 和最后那篇知识蒸馏的效果,都是由 Facebook 的同一个人做的。
![](https://i-blog.csdnimg.cn/blog_migrate/4af3c5c7d6a7c23b81357c48da2efec9.png)
对于插入迭代优化法,KERMIT和其改进,Imputer 和 Imputer + NAT 都是由这一位谷歌大佬做的。他还是著名的 LAS 算法的一作。
![](https://i-blog.csdnimg.cn/blog_migrate/0aa0b17b88940ca43740d28c6a54c5fd.png)
最后是剩下的四篇论文来源。
视频见(需要梯子):
https://www.youtube.com/watch?v=jvyKmU4OM3c&feature=youtu.be
Reference
• 李宏毅 《人类语言处理 2020》Non-Autoregressive Generation
添加个人微信,备注:昵称-学校(公司)-方向,即可获得
1. 快速学习深度学习五件套资料
2. 进入高手如云DL&NLP交流群
记得备注呦