「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战」
sequence-to-sequence 模型
sequence-to-sequence
简称Seq2Seq
,是一种序列转换模型,广泛地应用于机器翻译、语音识别等领域中,一般采用Encoder-Decoder结构。encoder(编码器)神经网络逐符号处理一个句子,并将其压缩为一个向量表示;然后,一个decoder(解码器)神经网络根据encoder状态逐符号输出预测值,并将之前预测的符号作为每一步的输入,如下图所示👇
Encoder的输入是原始序列,hidden state即语义编码。Decoder是一个生成式语言模型,初始化是Encoder的hidden state。<bos>
是开始符,作为第一个输入,结合hidden state,预测第一个单词。接下来的输入是目标序列,结合前一个神经元的hidden state,预测下一个单词,直到<eos>
(结束符)。最后和目标序列作比较,计算损失函数。
传统的机器翻译基本都是基于Seq2Seq
模型来做的,由于翻译输出是单词序列而不是单个单词。 输出序列的长度可能与源序列的长度不同,该模型分为encoder层与decoder层,并均为RNN或RNN的变体构成,encoder将输入序列转化成语义编码,decoder再将语义编码转化成输出序列。
翻译原理流程如下图所示:
在encode阶段,第一个节点输入一个词,下一个节点输入的则是下一个词与前一个节点的hidden state,最终encoder会输出一个context,这个context又作为decoder的输入,每经过一个decoder的节点就输出一个翻译后的词,并把decoder的hidden state作为下一层的输入。该模型对于短文本的翻译来说效果很好,但是其也存在一定的缺点,如果文本稍长一些,就很容易丢失文本的一些信息。为了解决这个问题,Attention应运而生。
Attention
注意力机制在decode阶段,会选择最适合当前节点的context作为输入。Attention与传统的Seq2Seq
模型主要有以下两点不同。
1)encoder提供了更多的数据给到decoder,encoder会把所有的节点的hidden state提供给decoder,而不仅仅只是encoder最后一个节点的hidden state。
2)decoder并不是直接把所有encoder提供的hidden state作为输入,而是采取一种选择机制,把最符合当前位置的hidden state选出来,具体的步骤如下
-
确定哪一个hidden state与当前节点关系最为密切
-
计算每一个hidden state的分数值
-
对每个分数值做一个softmax的计算,这能让相关性高的hidden state的分数值更大,相关性低的hidden state的分数值更低
Attention模型并不只是盲目地将输出的第一个单词与输入的第一个词对齐。Attention函数的本质可以被描述为一个查询(query)到一系列(键key-值value)对的映射。
在计算attention时主要分为三步:
- 将query和每个key进行相似度计算得到权重,常用的相似度函数有点积、拼接、感知机等;
- 使用一个softmax函数对这些权重进行归一化;
- 将权重和相应的键值value进行加权求和得到最后的attention。
目前在NLP研究中,key和value常常都是同一个,即key=value。
Transformer模型
Transformer总体结构
和Attention模型一样,Transformer模型中也采用了 encoder-decoder 架构。但其结构相比于Attention更加复杂,encoder层和decoder层由6个encoder堆叠在一起。👇
每一个encoder和decoder的内部简版结构如下图👇
对于encoder,包含两层:
- self-attention层:能帮助当前节点不仅仅只关注当前的词,从而能获取到上下文的语义
- 前馈神经网络
如图所示:
decoder部分其实和encoder部分大同小异,不过在最下面额外多了一个masked mutil-head attetion,帮助当前节点获取到当前需要关注的重点内容。
encoder流程:
-
模型需要对输入的数据进行一个embedding操作
-
输入到encoder层,self-attention处理数据
-
把数据送给前馈神经网络,前馈神经网络的计算可以并行
-
前馈神经网络的计算得到的输出会输入到下一个encoder。
Self-Attention
self-attention是Transformer用来将其他相关单词的“理解”转换成我们正常理解的单词的一种思路,我们看个例子:
The animal didn't cross the street because it was too tired.
这里的it
到底代表的是animal
还是street
呢,对于我们来说能很简单的判断出来,但是对于机器来说是很难判断的,self-attention就能够让机器把it和animal联系起来。
Multi-Headed Attention
不仅仅只初始化一组Query、Key、Value的矩阵,而是初始化多组,transformer是使用了8组,所以最后得到的结果是8个矩阵。
多头attention(Multi-head attention)整个过程可以简述为:
- Query,Key,Value经过一个线性变换
- 输入到放缩点积attention(注意这里要做h次,其实也就是所谓的多头,每一次算一个头,而且每次Q,K,V进行线性变换的参数W是不一样的)
- 将h次的放缩点积attention结果进行拼接
- 进行一次线性变换得到的值作为多头attention的结果
可以看到,google提出来的多头attention的不同之处在于进行了h次计算而不仅仅算一次,这样的好处是可以允许模型在不同的表示子空间里学习到相关的信息
那么在整个模型中,是如何使用attention的呢?
如下图:
首先在编码器到解码器的地方使用了多头attention进行连接,Key、Value、Query分别是编码器的层输出(这里K=V)和解码器中多头attention的输入。其实就和主流的机器翻译模型中的attention一样,利用解码器和编码器attention来进行翻译对齐。
Self-attention即K=V=Q,例如输入一个句子,那么里面的每个词都要和该句子中的所有词进行attention计算。目的是学习句子内部的词依赖关系,捕获句子的内部结构。
Positional Encoding
到目前为止,transformer模型中还缺少一种解释输入序列中单词顺序的方法。为了处理这个问题,transformer给encoder层和decoder层的输入添加了一个额外的向量Positional Encoding
,维度和embedding的维度一样,这个向量采用了一种很独特的方法来让模型学习到这个值,这个向量能决定当前词的位置,捕捉到单词的顺序信息(简单点说就是在一个句子中不同的词之间的距离)。transformer位置编码向量不需要训练,它有规则的产生方式,具体计算方法有很多种。
Layer normalization
在transformer中,每一个子层(self-attetion,ffnn)之后都会接一个残差模块,并且有一个Layer normalization
。
Normalization有很多种,但是它们都有一个共同的目的,那就是把输入转化成均值为0方差为1的数据。我们在把数据送入激活函数之前进行normalization(归一化),是因为我们不希望输入数据落在激活函数的饱和区。
Mask
mask 表示掩码,它对某些值进行掩盖,使其在参数更新时不产生效果。Transformer 模型里面涉及两种 mask,分别是 padding mask 和 sequence mask。
其中,padding mask 在所有的 scaled dot-product attention 里面都需要用到,而 sequence mask 只有在 decoder 的 self-attention 里面用到。
Padding Mask
由于每个批次输入序列长度是不一样的,所以我们要对输入序列进行对齐。具体来说,就是给在较短的序列后面填充 0。但是如果输入的序列太长,则是截取左边的内容,把多余的直接舍弃。因为这些填充的位置,其实是没什么意义的,所以我们的attention机制不应该把注意力放在这些位置上,所以我们需要进行一些处理。
具体的做法是,把这些位置的值加上一个非常大的负数(负无穷),这样的话,经过 softmax,这些位置的概率就会接近0!而我们的 padding mask 实际上是一个张量,每个值都是一个Boolean,值为 false 的地方就是我们要进行处理的地方。
sequence mask
sequence mask 是为了使得 decoder 不能看见未来的信息。也就是对于一个序列,在 time_step 为 t 的时刻,我们的解码输出应该只能依赖于 t 时刻之前的输出,而不能依赖 t 之后的输出。因此我们需要想一个办法,把 t 之后的信息给隐藏起来。
那么具体怎么做呢?也很简单:产生一个上三角矩阵,上三角的值全为0。把这个矩阵作用在每一个序列上,就可以达到我们的目的。
- 对于 decoder 的 self-attention,里面使用到的 scaled dot-product attention,同时需要padding mask 和 sequence mask 作为 attn_mask,具体实现就是两个mask相加作为attn_mask。
- 其他情况,attn_mask 一律等于 padding mask。
别慌,看图说话😎
首先编码器通过处理输入序列启动,然后将顶部编码器的输出转换为一组注意向量K和V。每个解码器将在其“encoder-decoder attention”层中使用这些注意向量,这有助于解码器将注意力集中在输入序列中的适当位置。
完成编码阶段后,我们开始解码阶段。解码阶段的每个步骤从输出序列输出一个元素。以下步骤重复此过程,一直到达到表示解码器已完成输出的符号。每一步的输出在下一个时间步被送入底部解码器,解码器就像我们对编码器输入所做操作那样,我们将位置编码嵌入并添加到这些解码器输入中,以表示每个字的位置。
输出层
当decoder层全部执行完毕后,怎么把得到的向量映射为我们需要的词呢?
很简单,只需要在结尾再添加一个全连接层和softmax
层。假如我们的词典是1w个词,那最终softmax
会输入1w个词的概率,概率值最大的对应的词就是我们最终的结果。
欢迎巨佬指导~
参考资料