2017 年,Google 在论文 Attention is All you need 中提出了 Transformer 模型,其使用 Self-Attention 结构取代了在 NLP 任务中常用的 RNN 网络结构。相比 RNN网络结构,其最大的优点是可以并行计算,此后在此基础上又出现了GPT、Bert等优秀模型,这些优秀模型都是在Transformer的基础上衍生出来的。要想了解Transformer,就必须先了解"self-attention"。
Self-attention是早于transformer的,但是它是由于transformer才火起来。它可以应用在transformer和Bert等应用上,那么为什么要用到self-attention呢?它能解决什么问题呢?
在某些应用场景,比如机器翻译,语音辨识,或者是语音翻译,在这些场景中,我们都是输入一段序列(计算机中用向量表示),再输出一段序列,这就是seq2seq任务,如果我们去实现,该如何操作。
我们可以将自注意力机制看成一层能够接受所有向量的输入,然后输出向量的数目跟输入向量的数目的是一样的,可以认为它的每个输出都是考虑了整个向量集之后的结果,之后再将这些输出每个单独放入一个全连接网络中来得到输出,这样可以显著的降低全连接网络的参数量。并且这个也可以多次使用,自注意力后FC再自注意力再FC等等。
那么接下来的问题就是这个机制如何接受输入并考虑整个向量集之后而作出相应的输出,以第一个向量对应的第一个输出为例:怎样产生b1向量?
注:并不一定用soft-max,其他也行,比如relu,可以试试哪个效果更好
接下来,我们用矩阵乘法的角度再看看上述过程是怎样得到的:
- 输入序列中每个单词之间的相关性得分,就是用Q中每一个向量与K中每一个向量计算点积,具体到矩阵的形式:a= Q ⋅ K^T
- 对于输入序列中每个单词之间的相关性得分进行归一化,归一化的目的主要是为了训练时梯度能够稳定,防止其结果过大。即(Q ⋅ K^T)/dk,dk就是K的维度
- 通过softmax函数,将每个单词之间的得分向量转换成[0,1]之间的概率分布,得到a’=softmax((Q ⋅ K^T)/dk)
- 根据每个单词之间的概率分布,然后乘上对应的Values值,α与V进行点积,attention=a’⋅V
在self- attention中我们使用q去寻找相关的k,但是这种相关并不是只有一种形式和定义,也许我们不能只有一个q,可能有多个q去负责不同种类的相关性。如何去操作呢?(以2个head为例)
在前面的自注意力过程中,我们并没有关注到各个输入向量之间的位置信息!这在一些场景里面是很关键的一部分信息,但是我们只是计算了各自的相关性没有考虑到位置信息,因此需要加上Position embedding:每个位置有一个专属的位置向量;
事实上,CNN是受限制的Self-attention,也就是Self-attention的特例,Self-attention可以通过某些设计和限制就可以变成CNN,完成和CNN同样的任务
RNN也是处理输入是向量序列问题的算法,其具体过程就是一开始有一个memory,它和第一个输入向量一起输入到RNN中将会输出一个向量,该向量一方面放入全连接网络中得到一个输出,另一方面和下一个输入向量一起作为下一个RNN的输入,以此类推。
它们的区别在于:
Transformer 其实就是一个Seq2seq的模型,输出的长度是由模型决定的,那么一般的seq2seq模型它里面会分成两块分别是Encoder和Decoder,将输入的向量给Encoder进行处理,处理后的结果交给Decoder,由Decoder来决定应该输出一个什么样的向量,原论文的transformer结构图如下:
Transformer的结构图,拆解开来,主要分为图上4个部分,其中最重要的就是2和3Encoder-Decoder部分,下面是对其四部分的讲解。
首先输入inputs embedding后需要给每个word的词向量添加位置编码positional encoding,因为在Transformer中需要明确各个向量之间的位置关系,但在输入的时候有可能这个位置关系已经丢失了,那么就通过这个模块来告诉模型这些向量彼此之间的顺序关系
从图中我们可以看出一个encoder由Multi-Head Attention 和 全连接神经网络Feed Forward Network构成
关于Encoder内部结构是什么样的:
实际上一个Encoder里面有很多个Block(一个Block不止有一层layer),而每一个Block实现的功能都是输入一排向量然后也是输出一排向量,而我们从图的右边可以看到每一个Block内部的实现,就是一排向量先经过自注意力机制后得到一排处理过的向量,那么再逐个放于全连接的全向网络之中,最终的输出也是一排向量。
但其实在Transformer中关于block的实现会更复杂一点:
其中不同点我已经圈出来了,其具体的流程为:
一排向量经过Self-attention之后的输出,需要和对应的输入向量一一相加起来,如图中的a需要和它对应的输入b相加起来,这种网络架构(将输出与输入相加)称为residual connection。 得到真正的输出之后,需要经过一次Layer normolization,其特别的地方在于是直接对向量进行标准化(减去均值除以标准差),因为向量中的每个元素是属于不同的维度的,属于不同的特征的。 将经过norm之后的向量作为全连接的前向网络的输入,然后输出需要再与输入相加,并且再一次经过norm,才能够真正作为这一层block的输出。
为什么会退化,为什么使用LN?
为什么深度神经网络会发生退化?
举个例子:假如某个神经网络的最优网络层数是18层,但是我们在设计的时候并不知道到底多少层是最优解,本着层数越深越好的理念,我们设计了32层,那么32层神经网络中有14层其实是多余地,我们要想达到18层神经网络的最优效果,必须保证这多出来的14层网络必须进行恒等映射,恒等映射的意思就是说,输入什么,输出就是什么,可以理解成F(x)=x这样的函数,因为只有进行了这样的恒等映射咱们才能保证这多出来的14层神经网络不会影响我们最优的效果。
但现实是神经网络的参数都是训练出来地,要想保证训练出来地参数能够很精确的完成F(x)=x的恒等映射其实是很困难地。多余的层数较少还好,对效果不会有很大影响,但多余的层数一多,可能结果就不是很理想了。这个时候大神们就提出了ResNet 残差神经网络来解决神经网络退化的问题。
为什么添加了残差块能防止神经网络退化问题呢?
咱们再来看看添加了残差块后,咱们之前说的要完成恒等映射的函数变成什么样子了。是不是就变成h(X)=F(X)+X,我们要让h(X)=X,那么是不是相当于只需要让F(X)=0就可以了,这里就巧妙了!神经网络通过训练变成0是比变成X容易很多地,因为大家都知道咱们一般初始化神经网络的参数的时候就是设置的[0,1]之间的随机数嘛。所以经过网络变换后很容易接近于0
这样当网络自行决定了哪些层为冗余层后,通过学习残差F(x)=0来让该层网络恒等映射上一层的输入,使得有了这些冗余层的网络效果与没有这些冗余层的网络效果相同,这样很大程度上解决了网络的退化问题。
为什么使用Layer Normalization(LN)而不使用Batch Normalization(BN)呢?
先看图,LN是在同一个样本中不同神经元之间进行归一化,而BN是在同一个batch中不同样本之间的同一位置的神经元之间进行归一化。
BN是对于相同的维度进行归一化,但是咱们NLP中输入的都是词向量,一个300维的词向量,单独去分析它的每一维是没有意义地,在每一维上进行归一化也是适合地,因此这里选用的是LN。
更好理解之间的区别还可以举例就是有b句话,每句话有len个词,每个词由d个特征表示,BN是对所有句子所有词的某一个特征做归一化,LN是对某一句话的所有词所有特征做归一化
一个decoder由Masked Multi-Head Attention,Multi-Head Attention 和 全连接神经网络FNN构成。比Encoder多了一个Masked Multi-Head Attention,其他的结构与encoder相同,那么咱们就先来看看这个Masked Multi-Head Attention。
Decoder的输入分为两类:
Masked Multi-Head Attention与Encoder的Multi-Head Attention计算原理一样,只是多加了一个mask码。mask 表示掩码,它对某些值进行掩盖,使其在参数更新时不产生效果。Transformer 模型里面涉及两种 mask,分别是 padding mask 和 sequence mask
Output如图中所示,首先经过一次线性变换,然后Softmax得到输出的概率分布,然后通过词典,输出概率最大的对应的单词作为我们的预测输出
Encoder如何将其信息传递到Decoder:
实际上它们之间信息的传递就是用到下图中框框中的模块,该模块称为Cross attention,可以看到该模块接受Encoder两个输入,接受Decoder一个输入:
大致流程如下:
另外一个值得注意的问题是Encoder有很多层,Decoder也有很多层,而在原始的论文中Decoder中的每一层的Crossattention都是用Encoder最后一层的输出
Transformer如何进行训练
我们的输出BEGIN后得到的输出是一个分布,其中代表着取到每一个汉字的概率,而我们希望它输出的正确答案为一个One-hat-vector,那么损失函数就是这个分布和正确答案的向量之间的交叉熵,我们希望它们越接近越好,因此应该最小化它们之间的交叉熵
而在多个向量的时候也是同样的道理,我们希望每一个输出都能够和正确答案对应的向量之间的交叉熵足够小。但这边需要注意的是在训练的时候我们给Decoder看的是正确答案,例如上图给Decoder输入的是BEGIN、机器学习等都是正确的One-hat-vector,这种让机器在学习的时候看到正确答案的方法称为Teacher-Forcing,但在测试集的时候就不会给正确答案。