拿下Transformer

本篇作者邮箱:jgang_d@163.com

本文将会系统的介绍自然语言处理和图像领域的Transformer内容,篇幅较长,请耐心阅读(小白大佬均可)。


Transformer 的直观认识

Transformer就是一个encoder和decoder结构。只是encoder和decoder有很多层,encoder的输出还会作为下一个encoder的输入,通过N个encoder(大部分论文中是六个)编码后,将第六个encoder的输出传给decoder,并在decoder中重复数次。

Encoder

Encoder即为把自然语言序列或图像的每一个patch映射为隐藏层的数学表达的过程。Encoder部分将会按照绿框所示的五个部分分别展开:

在Transformer中,是以字或者patch为单元进行编码的。假设我们输入堆个句子,该句子的维度为[batchsize, sequence_length],通过Input Embedding后,就会将输入维度映射到三维,变为[batchsize, sequence_length, embedding_dimension]。

1. Position Encoding(位置编码)

之所以会用到位置编码,是因为Transformer 没有用到循环神经网络,而循环神经网络是天然的有时间序列在其中的,即有顺序的。而encoder和decoder中没有用到任何的循环神经网络内容,如果直接Embedding后直接进入到下一步操作则会丧失一个句子中词或图像中每个patch之间的位置关系。在Transformer 中Embedding操作的权值是通过训练得到的,而Position Encoding是不用训练的。对每一个词或者patch来说,其Position Encoding的维度需要和经过Embedding后得到向量的维度保持一致,否则就不能相加。下面为位置编码的数学公式:

上式中,pos指一句话中某个字或者一张图片中某个patch的位置,取值范围是[0, sequence_length],sequence_length即有多少个字或者有多少个patch。i 指的是字向量的维度序号,也就是下图中的列的索引,取值范围为[0, embedding_dimension/2]。

为什么是embedding_dimension/2呢?比如pos = 0,i = 0 和 i = 1表示了第一个字或第一个patch所有的位置编码,即一个i要对应两个位置。

或者这样说:

此外,公式中d_model指的就是embedding_dimension的值。得到位置编码后,将所有的位置编码和词相加即可。为了更能说明这一点,我们可以通过代码自行打印一下这些位置编码: 

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import math
 
def get_positional_encoding(max_seq_len, embed_dim):
    # 初始化一个positional encoding
    # embed_dim: 字嵌入的维度
    # max_seq_len: 最大的序列长度
    positional_encoding = np.array([
        [pos / np.power(10000, 2 * i / embed_dim) for i in range(embed_dim)]
        if pos != 0 else np.zeros(embed_dim) for pos in range(max_seq_len)])
    
    positional_encoding[1:, 0::2] = np.sin(positional_encoding[1:, 0::2])  # dim 2i 偶数
    positional_encoding[1:, 1::2] = np.cos(positional_encoding[1:, 1::2])  # dim 2i+1 奇数
    return positional_encoding
 
positional_encoding = get_positional_encoding(max_seq_len=100, embed_dim=16)
plt.figure(figsize=(10,10))
sns.heatmap(positional_encoding)
plt.title("Sinusoidal Function")
plt.xlabel("hidden dimension")
plt.ylabel("sequence length")

 

如上图:纵向表示一句话中有多少个字或一张图被分成了多少个patch,横向表示的是经过embedding_dimension后的维度。可以看到,每个字或patch都是用16维的向量来编码的,在第0维的时,每个字或patch的位置编码变化非常明显,第一维时变化有所平滑,随后更加平滑,到第五维时已经没有什么变化了。因此,不同字或patch的相同维度上的位置编码的数值随着维度的增大变化趋于平缓。每一个位置在 embedding_dimension​维度上都会得到不同周期的 sin 和 cos 函数的取值组合,从而产生独一的纹理位置信息,最终使得模型学到图像patch之间的依赖关系和自然语言的时序特性。至于位置编码为什么用上述的公式,这里不再赘述,网上有很多说明。

2. Self attention Mechanism

通过自编码(embedding)和位置编码(position encoding)后,网络就会得到一个图像或者句子的真正编码,如第一个词所对应的X1,第二个词所对应的X2等等,X1或X2所对应的维度为[batchsize, embedding_dimension],即将一个batchsize的词经过编码后的一堆向量。

对于每一个x向量,网络又可以通过乘WQ,WK,WV衍生出三个向量Q1,K1,V1向量。对于所有的Xi来说,其乘的WQ,WK,WV都是一模一样的(即权重是共享的)。而这个乘WQ,WK,WV的操作就是做了一个线性变换,即nn.Linear函数()。将Q1,K1,V1三个向量分别命名为查询向量,键向量和值向量。

Self attention接下来就要求句子中第一个字与其他字的相关程度了,其结果为下图所示的a1向量。具体做法即取第一个字的查询向量,分别和其他字的k相乘(线性代数中求两个向量之间的距离就是向量的内积即相乘哦)。

对于a1而言:

 求出a1后,为了让相关程度变为一个个的权重,还需要做Softmax操作,让其相加总和变为1。随后再依次求第二个字、第三个字,···与其他字的相关程度:

 随后,网络要根据这个相关程度重新编码x1,x2等这些通过自编码(embedding)原始输入得到的向量,从而得到Context vector(上下文向量)。具体做法为:用a1向量(就是刚才得到的一大堆和为1的权重)乘所有的v向量,并求和。公式和图解图下:

上述就是全部的Self attention所作的内容,总结一下就是一个包含三个参数矩阵WQ,WK,WV的模块,输出为m个Context vector,并且每一个Context vector和原先的x维度是一致的。

 下面是Self attention的计算动图,大家可以截图下来一步一步看:

上述计算过程是通过循环的形式实现的,在实际编码中,通常是用矩阵的方式实现的:

该例的句子一共有两个字,X矩阵的每一行即为该词通过自编码(embedding)和位置编码(position encoding)后得到的向量,Q矩阵的每一行即为经过计算得到的q向量。通过矩阵乘法就可以代替循环一次性把每个字的Q,K,V得到,只不过他们都拼在了一起。随后二者经过转置相乘再除以根号dk,经过softmax后再乘以V得到输出。(除以根号dk是论文中提到的一个训练过程中使用的trick,大家可以自行在网上查看)。

 

3. Multi-Head Attention

Multi-Head Attention 的概念其实很简单,前面定义的一组 Q,K,V 可以让一个词 attend to 相关的词,我们可以定义多组 Q,K,V,让它们分别关注不同的上下文。计算 Q,K,V 的过程还是一样,对于输入矩阵 X,每一组 Q、K 和 V 都可以得到一个输出矩阵 Z。有几个头,就将一模一样的操作做几次。

引入原因:不同的注意力机制,描述会有差异。就像CNN中一个卷积核只能够得到一个特征,但随机初始化N个卷积核后,经过训练,就可以得到N个特征。
具体实现:同样一件事(获取不同patch相关信息),分给多个人(Head)做,最后将工作拼接(concat拼接)到一起,最终整合成一份完整的工作(进一步融合并降维) 。

  • 将通过自编码(embedding)和位置编码(position encoding)后得到的Q,K,V向量按照head的个数进行均分,并对均分后的数据编号。
  • 将编号相同的放入一个Head中。
  • 对每一个Head执行Self-attention。
  • 将每一个Head得到的结果按照之前的编号进行Concat拼接。
  • 通过矩阵Wo对拼接后的数据进一步融合。为了保证输出和输入的维度一致,矩阵一般为正方形。

 3. 残差连接和 Layer Normalization在得到了Context vector后,会和原先的embedding加起来做残差连接:

这么做的目的:用于解决深层网络的退化现象,可以让网络只关注当前差异的部分。

Encoder中的norm 指Layer Normalization。网络的学习过程可以理解为传话,一个人传给另一个人会越传越偏。Transformer堆叠了很多层,Layer Normalization可以防止层内的数值变化过大造成梯度消失或梯度爆炸,通过Norm中的w和b可以产生一个新的数据分布,让数据分布稳定下来。当网络训练过程中数据分布产生偏移,通过Norm可以校准数据,使数据重新标准化,便于训练。

此外,关于Encoder block的第五部分,也就是也就是 FeedForward,其实就是两层线性映射并用激活函数激活。Fully-connection中,先将数据映射到高维空间再映射到低维空间的过程这样一个操作,可以学习到更加抽象的特征。至于ReLU激活函数,在Multi-Head Attention的内部结构中,主要操作都是矩阵乘法,即都是线性变换。而线性变换的学习能力是不如非线性变化的强的,所以Multi-Head Attention的输出尽管利用了Attention机制,学习到了每个token的新representation表达,但是这种representation的表达能力可能并不强,所以我们仍然希望可以通过激活函数的方式,来强化representation的表达能力。

Transformer Decoder 整体结构

 

先从 HighLevel 的角度观察一下 Decoder ,直观上Decoder 比Encoder 要长一点从下到上依次是:

  • Masked Multi-Head Self-Attention
  • Multi-Head Encoder-Decoder Attention
  • FeedForward Network

很多同学不知道Decoder的输入到底是什么,也不知道Decoder到底是不是并行计算,还有Encoder和Decoder之间的交互也不是很清晰,这里博主做一个系统的归纳。

Decoder的输入:在train模式下和在test模式下Decoder的输入是不同的,在train模式下Decoder的输入是Ground Truth,也就是不管输出是什么,会将正确答案当做输入,这种模式叫做teacher-forcing。但是在test模式下根本没有Ground Truth去teach,那只能将已经出现的词的输出当做下一次Decoder计算的输入,也即图中shifted right的意思(“一直右移”)。

Decoder的过程:用Encoder给的Values和Keys先decode出第一个单词。接着将这第一个单词作为输入,经过positional-encoding,加上self-attention,输出第二个Context vector,随后再与Encoder所提供的一模一样的Values和Keys一起输入中间的“encoder-decoder multi-head attention” layer,然后decode出第三个单词 ... 直到遇到结束标志。举个栗子:

        一句话:我爱中国。Encoder将其输入后得到输出:Encoder Embedding。Decoder的执行步骤如下:

Step 1   初始输入: 起始符</s> + Positional Encoding(位置编码)+ Masked Multi-Head Attention。 中间输入:Encoder Embedding   最终输出:“I”
Step 2   初始输入: 起始符</s> + “I” + Positional Encoding(位置编码)+ Masked Multi-Head Attention。 中间输入:Encoder Embedding   最终输出:“love”
Step 2   初始输入: 起始符</s> + “I” + “love” + Positional Encoding(位置编码)+ Masked Multi-Head Attention。 中间输入:Encoder Embedding   最终输出:“China”
Encoder-decoder attention: 在蓝框所示的attention结构中,和之前的attention有略微不同,蓝框中K和V的数据内容是相同的,他们均为Encoder的输出,Q来自Decoder自身Mask Attention模块的输出。

Masked Multi-Head Self-Attention:虽然这在图像领域应用较少,但是通过理解其的原理可以帮助我们更好的理解图像领域中的其他机制。具体而言,传统 Seq2Seq 中 decoder 使用的是 RNN 模型,因此在训练过程中输入 t 时刻的词,模型无论如何也看不到未来时刻的词,因为循环神经网络是时间驱动的,只有当 t 时刻运算结束了,才能看到 t+1 时刻的词。而 Transformer Decoder 抛弃了 RNN,改为 Self-Attention,由此就产生了一个问题,在训练过程中,Decoder 的输入为整个 ground truth,这显然是不对的,我们需要对 Decoder 的输入进行一些处理,该处理被称为 Mask。

比如decoder的当前输入为<start> 我 爱 你 <end>,原始Multi-Head Self-Attention会将输入的每一个词计算Q,K,V。但实际上此时只需要的是<start>这个起始标志位。当我们输入 "我" 时,模型目前仅知道包括 "我" 在内之前所有字的信息,即 "<start>" 和 "我" 的信息,不应该让其知道 "我" 之后词的信息。道理很简单,我们做预测的时候是按照顺序一个字一个字的预测,怎么能这个字都没预测完,就已经知道后面字的信息了呢?具有Masked的Multi-Head Self-Attention原理图如下所示:

Scaled Scores为每一个词经过自编码(embedding)和位置编码(position encoding)后所得到特征向量的相关程度矩阵(没有经过softmax处理),但矩阵的左上部分是不能看得到的,因此就需要通过加上一个负无穷的数给屏蔽掉(就是在self-attention之前把future positions的Q*K都换成极小的数,即让当前的词不要关注未来的词,这样在做self-attention的时候就不会把未来的词也考虑进去了,否则就是你已经知道了下一个词的信息的情况下去预测下一个词,就属于cheating prediction)。负无穷是因为其带入softmax后值为0。因此,图中白色虚线框即为右边的Masked Scores矩阵,随后通过softmax后的值如下:

到此为止,Transformer 的内容差不多分析完了,最后,我们用一张图展示其完整结构(以N = 6为例):

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值