seq2seq

joker ... 2022-4-7 大约 15 分钟

# seq2seq

台大李宏毅深度学习——seq2seq | 碎碎念 (samaelchen.github.io) (opens new window)

9.7. 序列到序列学习(seq2seq) — 动手学深度学习 2.0.0-beta0 documentation (d2l.ai) (opens new window)

https://blog.csdn.net/Mr_Meng__NLP/article/details/122033759

# 1. 简介

论文地址 (opens new window)

什么是seq2seq

seq2seq是一种NLP常见的框架——这种框架不要求输入和输出序列是维度是一样的。

一种广泛用于机器翻译和不等长序列网络的方法,最早是由google工程师在2014年Sequence to Sequence Learning with Neural Networks论文中提出。seq2seq框架大多包含encoder和decoder。

img

原则上encoder,decoder可以由CNN,RNN,Transformer三种结构中的任意一种组合。但实际的应用过程中,encoder,decnoder的结构选择基本是一样的(即encoder选择CNN,decoder也选择CNN,如facebook的conv2conv)。因此本文我们也就介绍encoder,decoder是同种结构的三种模型,并对比其内部结构在编码和解码的不同之处。

解决的问题

许多NLP task的输入输出维度不统一,比如机器翻译、图像的图注生成、摘要生成、自动问答等。

解决不等长序列的问题

Attention机制有什么关系

Attention机制只是一种思想——即,人在理解一件事物并作出判断的时候,并不是概览了整个事物的全貌再作出判断,而是只着重关注了其中一部分。比如在机器翻译领域,每个英文单词翻译成中文词汇的时候,并不是这句英文里所有单词都做了平等的贡献,只是某一个或某几个单词作出了突出贡献。Attention机制就是要找到一种发现事物重要信息,然后被模型着重去学习的思想。 Attention机制并不局限于某个特定框架,它在不同的框架下有不同的践行方案、

# 1.1 什么叫不定长序列

# N vs N

上图是RNN 模型的一种 N vs N 结构,包含 N 个输入x1,x2,,xnx_1,x_2,\cdots,x_n,和 N 个输出 y1,y2,,yny_1,y_2,\cdots,y_n。N vs N 的结构中,输入和输出序列的长度是相等的,通常适合用于以下任务:

  • 词性标注
  • 训练语言模型,使用之前的词预测下一个词等

# 1 vs N

在 1 vs N 结构中,我们只有一个输入 xx,和NN个输出 y1,y2,...,yNy1, y2, ..., yN。可以有两种方式使用 1 vs N,

第一种只将输入 xx 传入第一个RNN神经元,

第二种是将输入$ x$ 传入所有的RNN神经元。1 vs N 结构适合用于以下任务:

  • 图像生成文字,输入 x 就是一张图片,输出就是一段图片的描述文字。
  • 根据音乐类别,生成对应的音乐。
  • 根据小说类别,生成相应的小说。

# N vs 1

在Nvs1结构中,我们有N个输入 x1,x2,,xnx_1,x_2,\cdots,x_n,和一个输出yy

Nvs1结构适合用于以下任务:

  • 序列分类任务,一段语音、一段文字的类别,句子的情感分析

# 2. 框架讲解

这个是encode-decode的过程。之前写的LSTM做文档分类是限定了输入的长度。超出规定长度的句子我们是截断,没达到长度的我们是padding。但是用seq2seq可以接受不定长的输入和不定长的输出。

实际上seq2seq是有两个循环神经网络,一个处理输入序列,另一个处理输出序列。处理输入序列的叫编码器,处理输出序列的叫解码器。流程上如下图:

img

一半会有以下几种形式

第二红模型

这三种 Seq2Seq 模型的主要区别在于 Decoder,他们的 Encoder 都是一样的。

# 2.1 编码器

编码器是将一个不定长的输入序列变换成一个定长的背景向量cc。根据不一样的任务,编码器可以是不一样的网络。例如在对话系统或者机器翻译的场景下,我们用的编码器可以是LSTM,如果在caption的场景下,CNN就是编码器。

现在假设我们做一个机器翻译的任务,那么有一句话可以拆成x1,,xtx1,…,x_t个词的序列。下一个时刻的隐藏状态可以表示为ht=f(xt,ht1)ht=f(x_t,h_{t−1})ff是循环网络隐藏层的变换函数。

然后我们定义一个函数qq将每个时间步的隐藏状态变成背景向量:c=q(h1,,ht)c=q(h_1,…,h_t)

下图是 Encoder 部分,Encoder 的 RNN 接受输入 x,最终输出一个编码所有信息的上下文向量 c,中间的神经元没有输出。Decoder 主要传入的是上下文向量 c,然后解码出需要的信息。

# 2.2 解码器

之前的编码器将整个输入序列的信息编码成了背景向量cc,

而解码器就是根据背景信息输出序列y1,y2,,yty_1,y_2,\cdots,y_t,

解码器每一步的输出要基于上一步的输出和背景向量,所以表示为P(yty1,y2,,yt1,c)P(y_t|y_1,y_2,\cdots,y_{t-1},c).

像机器翻译的时候,我们的解码器也会是一个循环网络.我们用gg表示这个循环网络的函数,那么当前步的隐藏状态st=g(yt1,c,st1)s_t=g(y_{t-1},c,s_{t-1})

然后我就可以自定义一个输出层来计算输出序列的概率分布,比如我们可以使用输出层和softmax操作来计算在时间步tt时刻输出yty_t的条件概率分布P(yty1,,yt1,c)P(y_t|y_1,\cdots,y_{t-1},c)

# 2.3 注意力机制

在 Seq2Seq 模型,Encoder 总是将源句子的所有信息编码到一个固定长度的上下文向量cc中,然后在 Decoder 解码的过程中向量cc都是不变的。这存在着不少缺陷:

  • 对于比较长的句子,很难用一个定长的向量cc完全表示其意义。

  • RNN 存在长序列梯度消失的问题,只使用最后一个神经元得到的向量cc效果不理想。

  • 与人类的注意力方式不同,即人类在阅读文章的时候,会把注意力放在当前的句子上。

上述的seq2seq解码器设计中,输出序列的各个时间步使用了相同的背景变量。如果解码器的不同时间步可以使用不同的背景变量呢?

image-20220319174436268

其实所谓的关注点,如果用数据来表示也就是权重大小,关注度越高权重越高。如下图:

img

我们在输出背景向量的时候做一个softmax,然后每一个state给一个权重,作为tt^\prime时刻的输入,这样jointly训练就可以学出一个attention的形式。

那么这里的α\alpha是这样计算出来的:

img

# 3. 模型的预测

接下来,还需要一个问题。

在准备训练数据集时,我们通常会在样本的输⼊序列和输出序列后面分别附上⼀个特殊符号“<eos><eos>”表⽰序列的终⽌。我们在接下来的讨论中也将沿⽤上⼀节的全部数学符号。

为了便于讨论,假设解码器的输出是⼀段⽂本序列。大小为YY,输出序列的最大长度为TT,所有可能的输出序列一共是O(yT)O(y^T)种。这些输出序列中所有特殊符号“<eos><eos>”后⾯的⼦序列将被舍弃。

# 3.1 贪婪搜索

贪婪搜索(greedy search)。对于输出序列任⼀时间步tt^\prime,我们从Y|Y|个词中搜索出条件概率最⼤的词:

yt=argmaxyYP(yy1,,yt1,c)y_t=argmax_{y\in Y}P(y|y_1,\cdots,y_{t-1},c)

作为输出。⼀旦搜索出“<eos><eos>”符号,或者输出序列⻓度已经达到了最⼤⻓度TT^\prime,便完成输出。我们在描述解码器时提到,基于输⼊序列⽣成输出序列的条件概率是t=1TP(yty1,,yt1,c)\prod_{t=1}^TP(y_t|y_1,\cdots,y_{t-1},c)。我们将该条件概率最⼤的输出序列称为最优输出序列。而贪婪搜索的主要问题是不能保证得到最优输出序列。

下⾯来看⼀个例⼦。假设输出词典⾥⾯有“A”“B”“C”和“<eos><eos>”这4个词。

下图中每个时间步 图中的4个数字分别代表了该时间步⽣成“A”“B”“C”和“<eos><eos>”这4个词的条件概率。在每个时间步,贪婪搜索选取条件概率最⼤的词。因此,图10.9中将⽣成输出序列“A”“B”“C”“<eos><eos>”。该输出序列的条件概率是0.5 × 0.4 × 0.4 × 0.6 = 0.048。

img

接下来,观察下面演⽰的例⼦。与上图中不同,在时间步2中选取了条件概率第⼆⼤的词“C” 。由于时间步3所基于的时间步1和2的输出⼦序列由上图中的“A”“B”变为了下图中的“A”“C”,下图中时间步3⽣成各个词的条件概率发⽣了变化。我们选取条件概率最⼤的词“B”。此时时间步4所基于的前3个时间步的输出⼦序列为“A”“C”“B”,与上图中的“A”“B”“C”不同。因此,下图中时间步4⽣成各个词的条件概率也与上图中的不同。我们发现,此时的输出序列“A”“C”“B”“<eos><eos>”的条件概率是0.5 × 0.3 × 0.6 × 0.6 = 0.054,⼤于贪婪搜索得到的输出序列的条件概率。因此,贪婪搜索得到的输出序列“A”“B”“C”“<eos><eos>”并⾮最优输出序列。

img

# 3.2 穷举搜索

如果⽬标是得到最优输出序列,我们可以考虑穷举搜索(exhaustive search):穷举所有可能的输出序列,输出条件概率最⼤的序列。

虽然穷举搜索可以得到最优输出序列,但它的计算开销O(yT)O(y^T)很容易过大

beam search 方法不用于训练的过程,而是用在测试的。在每一个神经元中,我们都选取当前输出概率值最大的 top k个输出传递到下一个神经元。下一个神经元分别用这kk个输出,计算出LL个单词的概率 (LL为词汇表大小),然后在kLkL个结果中得到topktop k个最大的输出,重复这一步骤。

束搜索(beam search)是对贪婪搜索的⼀个改进算法。它有⼀个束宽(beam size)超参数。我们将它设为 k。在时间步 1 时,选取当前时间步条件概率最⼤的 k 个词,分别组成 k 个候选输出序列的⾸词。在之后的每个时间步,基于上个时间步的 k 个候选输出序列,从 k |Y| 个可能的输出序列中选取条件概率最⼤的 k 个,作为该时间步的候选输出序列。最终,我们从各个时间步的候选输出序列中筛选出包含特殊符号“<eos><eos>”的序列,并将它们中所有特殊符号“<eos><eos>”后⾯的⼦序列舍弃,得到最终候选输出序列的集合。

img

束宽为2,输出序列最⼤⻓度为3。候选输出序列有A、C、AB、CE、ABD和CED。我们将根据这6个序列得出最终候选输出序列的集合。在最终候选输出序列的集合中,我们取以下分数最⾼的序列作为输出序列:

1LlogP(y1,,yL)=1Lαt=1TlogP(yty1,yt1,c)\frac{1}{L}logP(y_1,\cdots,y_L)=\frac{1}{L^\alpha}\sum_{t=1}^TlogP(y_t|y_1\cdots,y_{t-1},c)

其中 L 为最终候选序列⻓度,α ⼀般可选为0.75。分⺟上的 Lα 是为了惩罚较⻓序列在以上分数中较多的对数相加项。分析可知,束搜索的计算开销为O(kyT)O(k|y|T′)。这介于贪婪搜索和穷举搜索的计算开销之间。此外,贪婪搜索可看作是束宽为 1 的束搜索。束搜索通过灵活的束宽 k 来权衡计算开销和搜索质量。

# 4. 代码

farizrahman4u/seq2seq: Sequence to Sequence Learning with Keras (github.com) (opens new window)

# 5. 小结

评价机器翻译结果通常使⽤BLEU(Bilingual Evaluation Understudy)(双语评估替补)。对于模型预测序列中任意的⼦序列,BLEU考察这个⼦序列是否出现在标签序列中。

  • 根据“编码器-解码器”架构的设计, 我们可以使用两个循环神经网络来设计一个序列到序列学习的模型。
  • 在实现编码器和解码器时,我们可以使用多层循环神经网络。
  • 我们可以使用遮蔽来过滤不相关的计算,例如在计算损失时。
  • 在“编码器-解码器”训练中,强制教学方法将原始输出序列(而非预测结果)输入解码器。
  • BLEU是一种常用的评估方法,它通过测量预测序列和标签序列之间的nn元语法的匹配度来评估预测。

# 常见问题

a. 自注意力与注意力的区别?

attention一般指的是encoder和decoder之间的Attention score的计算。其中,K和V来自encoder,先计算来自decoder的隐状态,即查询矩阵Q和对应encoder每个位置的隐状态键矩阵K之间的Attention值(这里是使用了矩阵的点乘),然后将结果经缩放后再softmax归一化,结果乘以K对应的值矩阵V,得到Attention结果 但self-attention,指的是encoder内部或decoder内部,自己和自己的Attention score的计算。如果说Attention机制计算的是decoder某时刻的输出与encoder所有位置的相关度的话,self-attention就是计算的encoder或decoder内部,比如一个句子中,某时刻的状态与其他时刻位置的相关度。因为self-attention都是一对一对计算的,所以它更善于捕获长距离的关系,如句法特征(一个动词和主语宾语介词的关系)、语义特征(its的指代)。 Attention的query来自外部序列,而self-attention的query就来自于其本身

b. 为什么要进行残差连接?

随着层数的加深,没有残差连接可能使position embedding信息弥散。

加上残差连接,可以缓解这样的问题

c. 为什么要设置多头注意力 ?

多头注意力,可以让模型学习到多个不同方向的表示,在不同的子空间中进行学习。

如果只有single head,那么学到的相当于是多头的平均,这会削弱多头学习多个不同方向内容的能力

d. 一个自注意力层计算的复杂度是多少,为什么?

O(n**2*d).因为每次计算一个self-Attention时,都需要计算t时刻与每个时刻的Attention score,

这就说明有两次循环,每次循环都遍历所有时刻状态,序列长度为n,所以是n^2。而每次计算都要进行d维向量的乘积,所以还有d之后因数

e. 为什么要进行mask?

这个机制存在于decoder中,为了防止decoder在输出时考虑到t时刻以后的内容,

所以将t+1及其以后的信息,在反向序列的输入中mask掉

f. 位置嵌入除了文中的这种形式还有哪些?

g. 比较一下CNN和self-attention?

CNN可以通过卷积核的卷积运算和pooling运算获得局部的信息,但卷积核大小是有限的,距离特别远的信息,self-attention可以一步搞定,而CNN可能要通过卷积-池化而计算多次;

CNN对比self-attention有个好出,那就是CNN可以同时考虑多个通道的输入。

这个观点引入self-attention,就是“multi head self-attention”,同样,通过引入多个head,我们计算不同方向的内容表示,再将这些内容表示一同考虑

那么,multi head的多头是如何实现的?

(如下图)其实,就是将QKV矩阵分别进行不同的线性变换,得到不同的QKV矩阵,再进行self-attention的计算。所以多头主要在于对QKV不同的线性映射方式。计算好self-attention权重后,再将结果concat在一起。最后再乘以Wo矩阵,其shape为(n_head*d_model, d_model),目的是将n和头拼接的,维度扩展了n倍的矩阵变换回d_model的shape

h. 为何模型选择了self-attention?

  • 可以并行计算

  • 可以捕获长距离的内容

i. 为何模型在计算Attention值的时候,对Q和K.T的矩阵除以和dk(1/2)d_k^{(1/2)}

可以防止Q和K.T点乘结果变得太大。

因为二者结果要进行softmax归一化,如果结果太大,会导致其落在softmax函数的饱和区。