结合源码的Transformer最全面、最深度的解析

0. 模型架构

在这里插入图片描述
举例:中文输入为“我爱你”,通过 Transformer 翻译为 “I Love You”。

1. Inputs和Outputs(shifted right)整块部分

1.1 Embedding

我们不直接给 Transformer 输入简单的one-hot vector,原因包括这种表达方式的结果非常稀疏,非常大,且不能表达 word 与 word 之间的特征。所以这里对词进行 embedding,用较短的向量表达这个 word 的属性。一般在 Pytorch 中,我们都是用 nn.Embedding 来做,或者直接用 one-hot vector 与权重矩阵 W 相乘得到。

nn.Embedding 包含一个权重矩阵 W,对应的 shape 为 ( num_embeddings,embedding_dim )。num_embeddings 指的是词汇量,即想要翻译的 vocabulary 的长度。embedding_dim 指的是想用多长的 vector 来表达一个词,可以任意选择,比如64,128,256,512等。在 Transformer 论文中选择的是512(即 d_model =512)

其实可以形象地将 nn.Embedding 理解成一个 lookup table,里面对每一个 word 都存了向量 vector 。给任意一个 word,都可以从表中查出对应的结果。

处理 nn.Embedding 权重矩阵有两种选择:

  1. 使用 pre-trained 的 embeddings 并固化,这种情况下实际就是一个 lookup table。
  2. 对其进行随机初始化(当然也可以选择 pre-trained 的结果),但设为 trainable。这样在 training 过程中不断地对 embeddings 进行改进。(Transformer √)

在这里插入图片描述
d_model = 512,具体实现时,embedding的输出除了经过Pytorch本身的nn.Embedding层,同时还与根号d_model 相乘,最后输出embedding。

1.2 Positional Encoding

我们对每一个 word 进行 embedding 作为 input 表达。但是还有问题,embedding 本身不包含在句子中的相对位置信息

那 RNN 为什么在任何地方都可以对同一个 word 使用同样的向量呢?因为 RNN 是按顺序对句子进行处理的,一次一个 word。但是在 Transformer 中,输入句子的所有 word 是同时处理的,没有考虑词的排序和位置信息。

对此,Transformer 的作者提出了加入 ”positional encoding“ 的方法来解决这个问题。”positional encoding“ 使得 Transformer 可以衡量 word 位置有关的信息。

positional encoding 与 word embedding 相加就得到 embedding with position。
在这里插入图片描述
那么具体 ”positional encoding“ 怎么做?为什么能表达位置信息呢?作者探索了两种创建 positional encoding 的方法:

  1. 通过训练学习 positional encoding 向量。
  2. 使用公式来计算 positional encoding向量。(Transformer √)

试验后发现两种选择的结果是相似的,所以采用了第2种方法,优点是不需要训练参数,而且即使在训练集中没有出现过的 句子长度 上 也能用

计算 positional encoding 的公式为:
在这里插入图片描述
那么如何理解Transformer论文中的positional encoding,和三角函数有什么关系?

  1. 首先,需要明确的是,建模位置信息(无论是绝对位置还是相对位置)并不是必须用到三角函数,作者在这里使用正余弦函数,只是根据归纳偏置和一些经验作出的选择罢了。
  2. 在这里插入图片描述
  3. 在这里插入图片描述
  4. 在这里插入图片描述
  5. 在这里插入图片描述
  6. 为什么官方代码tensor2tensor的最初版本只是简单地分了两段,却没有什么性能差异呢?因为 s i n / c o s sin/cos 的交替使用只是为了使编码更「丰富」,在哪些维度上使用 s i n sin ,哪些使用 c o s cos ,不是很重要,都是模型可以调整适应的。
  7. 至少现在看来:1. 这个函数形式很可能是基于经验得到的,并且应该有不少可以替代的方法;2. 谷歌后期的作品BERT已经换用位置嵌入(positional embedding)了,这可能说明编码的方案有一定的问题(猜测)

再回头来看这个公式:
在这里插入图片描述

  1. pos 指的是这个 word 在这个句子中的位置。
  2. i指的是 embedding 维度。比如选择 d_model=512,那么i就从1数到512,只不过偶数维和奇数维使用不同的函数。

在这里插入图片描述
先创建全为0的 p e pe ,然后表示出公式里的 p o s pos (position)和公式中的分母(div_term),再使用 s i n / c o s sin/cos 即可,最后将原来的x和positional encoding相加经过dropout输出。

1.3 Outputs(shifted right)详细解析

我们带着问题来进行分析
Transformer模型中,decoder的第一个输入是什么?

  1. 一般而言,训练阶段的Transformer的Decoder的第一次输入为起始符 + Positional Encoding,也可能是其他特殊的Token,目的是为了预测目标序列的第一个单词是什么。
  2. 在这里插入图片描述
  3. 可能还是看的不太清楚,我们结合例子具体描述下步骤。
  4. 在这里插入图片描述

这里由于还没有讲解后面的Encoder和Decoder部分,所以新手可能不太懂,但对于已经对Transformer有一定了解的同学来说,这个部分的理解是非常关键的。新手可以先跳过此部分,最后再回头来看这个部分。

1.4 小结

在这里插入图片描述
对于Outputs(shifted right)部分,也存在word embedding和positional encoding相加的过程,刚刚上述对此的分析已经很详细了,一定要知道预测/训练过程中,Outputs(shifted right)是如何参与进来的,文章的最后会给出更多预测/训练过程的细节解释。
在这里插入图片描述
在这里插入图片描述

2. Encoder

Encoder 相对 Decoder 会稍微麻烦一些。 Encoder 由 6 个相乘的 Layer 堆叠而成(6并不是固定的,可以基于实际情况修改),看起来像这样:
在这里插入图片描述
每个Encoder Layer 包含 2 个 sub-layer:
在这里插入图片描述
在这里插入图片描述
第一个是 multi-head self-attention mechanism(多头自注意力)
第二个是 simple,position-wise fully connected feed-forward network

在这里插入图片描述

  1. class “Encoder” 将 Encoderlayer 堆叠N次。是 class “EncoderLayer” 的实例。
  2. “EncoderLayer” 初始化需要指定size, self_attn , feed_forward , dropout:
    size 对应 d_model,论文中为512
    self_attn 是 class MultiHeadedAttention 的实例,对应sub-layer 1
    feed_forward 是 class PositionwiseFeedForward 的实例,对应sub-layer 2
    dropout 对应 dropout rate。

2.1 Encoder Sub-layer 1: Multi-Head Attention Mechanism

理解 Multi-Head Attention 机制对于理解 Transformer 特别重要,并且在 Encoder 和 Decoder 中都有用到。

概述:
我们把该机制的输入定义为 x,x 在 Encoder 的不同位置,含义有所不同。在最开始的输入处(图中的in),x 的含义是final representation。在 EncoderLayer 的各层中间,x 代表前一层 EncoderLayer 的输出。

详细解释: Annotated Transformer 中 Multi-Headed attention 的实现为
在这里插入图片描述

  1. 这个 class 进行实例化时需要指定:
    h = 8,即 “heads” 的数目。在 Transformer 的 base model 中有8 heads
    d_model = 512
    dropout = dropout rate = 0.1
  2. 维度 d_k 是基于 d_model / / h 计算来的。在上面的例子中 d_k = 512 / 8 = 64。

下面分3步详细介绍一下 MultiHeadedAttention 的 forward() 函数
forward 的 input 包括:query,key,values和mask。这里先暂时忽略 mask。 query,key和value 是哪来的? 实际上他们是 “x” 重复了三次得来的。x 是初始的final representation或前一个 EncoderLayer 的输出,见 EncoderLayer 的代码红色框部分,self.self_atttn 是 MultiHeadedAttention 的一个实例化:
在这里插入图片描述
这里对MultiHeadedAttention实例化后的self_attn()输入三个同样x。

这里的x的shape为[nbatches, L, 512],那么query、key、value同样也是这样的shape。nbatches 对应 batch size,L 对应 sequence length ,512 对应 d_model。

Step 1)(对着MultiHeadedAttention实现代码来看步骤)

  1. “query”,“key”和“value”经过线性层,他们的 shape 依然是[nbatches, L, 512]。
  2. 对其通过 view() 进行 reshape,shape 变成 [nbatches, L, 8, 64]。这里的h=8对应 heads 的数目,d_k=64 是 key 的维度。
  3. transpose 交换 dimension1和2,shape 变成 [nbatches, 8, L, 64]。

Step 2)
attention函数具体为:在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. query 和 key.transpose(-2,-1) 相乘,两者分别对应的 shape 为 [nbatches, 8, L 64] 和 [nbatches, 8, 64, L]。这样相乘得到的结果 scores 的 shape为[nbatches, 8, L, L]。
  2. 对 scores 进行 softmax,所以 p_attn 的 shape 为 [nbatches, 8, L, L]。values的 shape 为 [nbatches, 8, L, 64]。所以最后 p_attn 与 values 相乘输出的 shape 为 [nbatches, 8, L, 64]。
  3. 在我们的输入与输出中,有8个 heads 即 Tensor 中的 dimension 1,[ nbatches, 8, L, 64 ]。8个 heads 都进行了不同的矩阵乘法,这样就得到了不同的 “representation subspace”。这就是 multi-headed attention 的意义。

Step 3)
看完了Attention源码实现,我们回到MultiHeadedAttention实现的源码中

此时x的shape为 [ nbatches, 8, L, 64 ],x.transpose(1,2) 得到 [ nbatches, L, 8, 64 ]。然后使用 view 进行 reshape 得到 [ nbatches, L, 512 ]。可以理解为8个heads结果的 concatenate 。 最后使用 linear layer 进行转换。shape仍为 [ nbatches, L, 512 ]。也就是说,Encoder Sub-layer 1: Multi-Head Attention Mechanism的输入和输出的维度是一致的,输入的shape为[ nbatches, L, 512 ],各种变换后的输出的shape也为[ nbatches, L, 512 ]。
在这里插入图片描述
左图是Attention的计算过程(名为Scaled Dot-Product Attention),右图为整体多头自注意力机制的运算过程,看看就行,知道有这个图即可,这个图只是方便新手理解,从源码的角度来看,才更清晰。

2.2 Encoder Sub-layer 2: Position-Wise fully connected feed-forward network

在这里插入图片描述
从公式可以看出,x经过relu和线性层输出为FFN(x)。
而具体实现中,x经过线性层w_1,再经过relu,再经过dropout,最后再经过线性层w_2,输入输出的维度均没有发生变化。
理论和具体代码实现还是有差别的。

2.3 小结

Encoder 总共包含6个 EncoderLayers 。每一个 EncoderLayer 包含2个 SubLayer:

SubLayer-1 做 Multi-Headed Attention
SubLayer-2 做 feedforward neural network

3. Decoder

在这里插入图片描述
Encoder 与 Decoder 的交互方式可以理解为:
在这里插入图片描述
Decoder 也是N层堆叠的结构。被分为3个 SubLayer,可以看出 Encoder 与 Decoder 三大主要的不同:

  1. Diff_1:Decoder SubLayer-1 使用的是 “masked” Multi-Headed Attention 机制,防止为了模型看到要预测的数据,防止泄露。
  2. Diff_2:Decoder SubLayer-2 是一个 encoder-decoder multi-head attention
  3. Diff_3:LinearLayer 和 SoftmaxLayer 作用于 SubLayer-3 的输出后面,来预测对应的 word 的 probabilities 。(注意本文所说的Decoder是包括输出层的!)

3.1 Diff_1 : “masked” Multi-Headed Attention

mask 的目标在于防止 decoder “seeing the future”,就像防止考生偷看考试答案一样。mask包含1和0:
在这里插入图片描述
在这里插入图片描述

3.2 Diff_2 : encoder-decoder multi-head attention

在这里插入图片描述
self_attn(“masked” Multi-Headed Attention), src_attn(encoder-decoder multi-head attention) 均是MultiHeadedAttention的实例,只不过第一个有mask,第二个没有mask。

src_attn中的输入为x,m,m,其中x为上一个self_attn的输出,m来自Encoder的输出,即query是自己的(上一个self_attn的输出),key和value是Encoder输出给的。

3.3 Diff_3 : Linear and Softmax to Produce Output Probabilities

最后的 linear layer 将 decoder 的输出扩展到与 vocabulary size 一样的维度上。经过 softmax 后,选择概率最高的一个 word 作为预测结果。

假设我们有一个已经训练好的网络,在做预测时,步骤如下:

  1. 给 decoder 输入 encoder 的输出(从整体来看的) 和一个特殊的开始符号 < s > <s> 。decoder 将产生预测,在我们的例子中应该是 ”I”。
  2. 给 decoder 输入 encoder 的输出和 “ < s > <s> I”,在这一步 decoder 应该产生预测 “Love”。
  3. 给 decoder 输入 encoder 的输出和 “ < s > <s> I Love”,在这一步 decoder 应该产生预测 “You”。
  4. 给 decoder 输入 encoder 的输出和 “ < s > <s> I Love You”, decoder应该生成句子结尾的标记,decoder 应该输出 ” < e o s > <eos> ”。
    然后 decoder 生成了 < e o s > <eos> ,翻译完成。

在这里插入图片描述

但是在训练过程中,decoder 没那么好时,预测产生的词很可能不是我们想要的。这个时候如果再把错误的数据再输给 decoder,就会越跑越偏:
(注意上面是预测过程,这里是训练过程)
在这里插入图片描述
这里在训练过程中要使用到 “teacher forcing”。利用我们知道他实际应该预测的 word 是什么,在这个时候喂给他一个正确的结果作为输入。

相对于选择最高的词 (greedy search)(正常的预测方式),还有其他选择,比如 “beam search”,其可以保留多个预测的 word。 Beam Search 方法不再是只得到一个输出放到下一步去训练了,我们可以设定一个值,拿多个值放到下一步去训练,这条路径的概率等于每一步输出的概率的乘积:
在这里插入图片描述
在这里插入图片描述
或者也可以使用“Scheduled Sampling”:一开始我们只用真实的句子序列进行训练,而随着训练过程的进行,我们开始慢慢加入模型的输出作为训练的输入这一过程。
在这里插入图片描述

3.4 小结

在这里插入图片描述

4. 细节补充

4.1 Teacher Forcing

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
其实不止在Transformer中使用到了Teacher Forcing,早在RNN模型中已有这种东西了。
在这里插入图片描述
在这里插入图片描述

4.2 Transformer中的Add & Norm

在这里插入图片描述

4.2.1 残差连接

残差连接就是上一个输出的结果与该块的输入相加,如下图:
在这里插入图片描述
为什么要用残差连接呢?

首先大家已经形成了一个通识,在一定程度上,网络越深表达能力越强,性能越好。不过,好是好了,随着网络深度的增加,带来了许多问题,梯度消散,梯度爆炸;更好的优化方法,更好的初始化策略,BN层,Relu等各种激活函数,都被用过了,但是仍然不够,改善问题的能力有限,直到残差连接被广泛使用

研究直接表明训练深度神经网络失败的原因并不是梯度消失,而是权重矩阵的退化,也就是每个层中只有少量的隐藏单元对不同的输入改变它们的激活值,而大部分隐藏单元对不同的输入都是相同的反应,此时整个权重矩阵的秩不高。并且随着网络层数的增加,连乘后使得整个秩变的更低。

这也是我们常说的网络退化问题,虽然是一个很高维的矩阵,但是大部分维度却没有信息,表达能力没有看起来那么强大。

总的来说一句话,残差连接打破了网络的对称性,提升了网络的表征能力。

4.2.2 layer normalization

会单独写一篇文章来对相关的batch normalization等进行总结,这里暂时不多说,知道其大概过程即可。

Transformer KO~

发布了71 篇原创文章 · 获赞 20 · 访问量 4823

猜你喜欢

转载自blog.csdn.net/qq_22795223/article/details/105676186