参考文章:
Transformer 模型详解_transformer模型-CSDN博客
【Transformer系列】深入浅出理解Positional Encoding位置编码-CSDN博客
Self-Attention和Transformer | machine-learning-notes (gitbook.io)
10.7. Transformer — 动手学深度学习 2.0.0 documentation (d2l.ai)
大模型面试准备(十二):深入剖析Transformer - 残差连接和层归一化_残差层和归一化层-CSDN博客
【手撕Transformer】Transformer输入输出细节以及代码实现(pytorch)-CSDN博客
一、transformer架构
主要由编码器和解码器两部分组成。但在介绍之前,还要考虑一个概念
1.位置编码
自注意力因为并行计算而放弃了顺序操作,为了使用序列的顺序信息,通过在输入表示中添加位置编码(positional encoding)来注入绝对的或相对的位置信息。位置编码可以通过学习得到也可以直接固定得到。
位置编码就是将位置信息添加(嵌入)到词向量中,让Transformer保留词向量的位置信息。一般分为绝对位置编码(Learned Positional Embedding)、相对位置编码(Sinusoidal Positional Encoding
和 Learned Positional Encoding
)和其他位置编码(Complex embedding
)。
Sinusoidal Positional Encoding是使用sin和cos计算得到位置向量:
其中,pos 表示token在序列中位置,i 表示维度(矩阵第pos行、第i列上的元素),PE 是token的位置向量,PE(pos,2i) 表示这个位置向量里的第i个元素,i 表示奇数维度,2 i 表示偶数维度;表示token的维度(通常为512)
利用高中的正弦余弦公式,可以证明 pos+k
可以被 pos
线性表示。
如何给输入编码:
一般来说,可以使用向量拼接或者相加的方式,将位置向量和词向量相结合。
input = input_embedding + positional_encoding
(关于位置编码的其他内容主要参考第二篇文章讲解的很清楚)
二、编码器 Encoder
输入是单词的Embedding,再加上位置编码,然后进入一个统一的结构。
这个结构由多个相同的层叠加而成的,每个层都有两个子层(子层表示为sublayer)。第一个子层是多头自注意力(multi-head self-attention)汇聚;第二个子层是基于位置的前馈网络(positionwise feed-forward network,缩写为 FFN);两个子层之后都还有一个残差连接(residual connection)和层规范化(layer normalization)。
1.第一层:多头自注意力(multi-head self-attention)
先了解自注意力的计算机制:
已知输入的单词embedding,即x1和x2,想转换成z1和z2
首先,先把x1转换成三个不一样的向量,分别叫做q1、k1、v1,
之后,用v1和v2两个向量的线性组合,来得到z1和z2,即
而权重系数为:
接着,讲多头自注意力:
具体做法:首先,用独立学习得到的ℎ组不同的 线性投影(linear projections)来变换查询Q、键K和值V。 然后,这ℎ组变换后的查询Q、键K和值V将并行地送到注意力汇聚中。 最后,将这ℎ个注意力汇聚的输出拼接在一起, 并且通过另一个可以学习的线性投影进行变换, 以产生最终输出。
每一个注意力汇聚都被称作一个头(head)。
1)每个头计算过程可表示为:(也就是自注意力的计算过程)
其中,查询Q、键K和值V这三个向量是由输入X 乘以 、、3 个矩阵相乘得到,而这 3 个矩阵通过训练获得。
为键K 向量的维度,除以它主要是为了防止其结果过大,目的是在反向传播时,求梯度更加稳定。
还有一个概念:注意力分数,是查询Q和键K的点积。
2)将每个头拼接起来,再和一个权重矩阵相乘,这就是多头自注意力最终的输出矩阵。
暂时将其叫做 Z,之后将这个矩阵Z会输入到 FFN层
2.第二层:基于位置的前馈网络(FFN)
基于位置的前馈网络对序列中的所有位置的表示进行变换时使用的是同一个多层感知机(MLP),这就是称前馈网络是基于位置的(positionwise)的原因。
它由两个线性变换组成,即两个全连接层组成,第一个全连接层的激活函数为 ReLU 激活函数,第二全连接层是一个线性激活函数。可以表示为:
作用是考虑到注意力机制可能对复杂过程的拟合程度不够,通过增加两层网络来进一步提取和融合不同位置的特征信息,以增强模型的非线性处理能力和表达能力。
3. 残差连接和层规范化
多头自注意力和基于位置的前馈网络之后都还需进行残差连接和层规范化操作
a。层规范化:就是先计算这一层所有激活值的均值μ
和方差σ²
,然后使用这些统计量对h
进行分布调整。
作用:通过对单个样本的激活值进行归一化,提升训练稳定性和加速收敛。b。残差连接:本质上类似一种兜底策略,目的是当模型的深度已经达到最优解,后面再增加冗余层也至少不会导致之前的效果下降。它的做法是将上一层的输出直接连接到下一层的输出,及上一层的输出直接和下一层的原始输出对应位置相加形成最终输出。
作用:通过在层与层之间添加跳跃连接,缓解深层网络的退化问题,提升训练效率和模型性能。
三、解码器 Decode(AT)
Decoder的初始输入是训练集的标签Y,并且需要整体右移(Shifted Right)一位,之后的就是上一次产出的Embedding,加入位置编码,然后进入一个可以重复很多次的结构。
Shifted Right的原因:T-1时刻需要预测T时刻的输出,所以Decoder的输入需要整体后移一位
这个结构也是由多个相同的层组成,每个层包含了三个子层:自注意力(self-attention)、“编码器‐解码器”注意力(encoder-decoder attention)和基于位置的前馈网络(FFNN)。
1.第一层 :掩码自注意力(Masked-MultiHead-attention)
mask表示,它对某些值进行掩盖,使其在参数更新时不产生效果。Transformer模型里面涉及两种mask,分别是 padding mask和sequence mask。 其中,padding mask在所有的scaled dot-product attention 里面都需要用到,而sequence mask只有在Decoder的Self-Attention里面用到。
a。Padding Mask
每个批次输入序列长度是不一样的,但我们要对输入序列进行对齐。具体来说,就是给在较短的序列后面填充0。但是如果输入的序列太长,则是截取左边的内容,把多余的直接舍弃。因为这些填充的位置,其实是没什么意义的,所以我们的Attention机制不应该把注意力放在这些位置上,所以我们需要进行一些处理。
具体的做法是,把这些位置的值加上一个非常大的负数(负无穷),这样的话,经过softmax,这些位置的概率就会接近0。
b。Sequence mask
sequence mask是为了使得Decoder看不见未来的信息。也就是对于一个序列,在time_step为t的时刻,我们的解码输出应该只能依赖于t时刻之前的输出,而不能依赖t之后的输出。因此我们需要想一个办法,把t之后的信息给隐藏起来。
具体的做法是:产生一个上三角矩阵,上三角的值全为0。把这个矩阵作用在每一个序列上。
总结:对于 Decoder 的 Self-Attention,里面使用到的 scaled dot-product attention,同时需要 Padding Mask 和 Sequence Mask,具体实现就是两个 Mask 相加。其他情况下,只需要 Padding Mask。
Masked-MultiHead-attention的其它部分计算流程实际上与Encoder中的计算过程一致,区别只是在计算出scores矩阵时对其沿对角线上部分进行mask掩码。其主要在训练阶段屏蔽t时刻之后的输入生效,而在预测阶段其实并没有真实作用。
Transformer推理时是一个一个词预测,而训练时会把所有的结果一次性给到Transformer,但效果等同于一个一个词给,而之所以可以达到该效果,就是因为对target进行了掩码,防止其看到后面的信息,也就是不要让前面的字具备后面字的上下文信息。
2.第二层 :Encode-Decode注意力层(类似于自注意力层)
在这一层(Decoder中的第二个注意力层),输入不仅有前一层的输出x,还有来自Encoder的输出Z,后把Encoder产生的向量Z作为Decoder的key和value,Decoder的x作为query,然后进行Self-Attention。相当于是,Encoder告诉我key和value是什么,我现在要做的就是产生query。即有
3.第三层:基于位置的前馈网络(FFN )
和解码器的FFN相类似。
4.最后一层:linear+softmax层
Liner和Softmax层将解码器最终输出的实数向量变成一个word,线性变换层(linear)是全连接神经网络,将解码器产生的向量投影到一个比它大得多的、被称作对数几率(logits)的向量里。假设模型从训练集中学习一万个不同的word,则对数几率向量为一万个单元格长度的向量,每个单元格对应某一个单词的分数。softmax层将分数变成概率,概率最高的单元格对应的单词被作为该时间的输出。
注意:本文主要介绍AT的Decoder,还有另外一种类型,是NAT的Decoder,具体可看李宏毅老师视频中提及的一个视频链接:https://youtu.be/jvyKmU40M3c 。下图是两者之间简单比较。
四、模型的训练与预测
具体的例子如下:
样本:“我/爱/机器/学习”和 “i/ love /machine/ learning”
训练:(把正确的结果输入到decoder)
把“我/爱/机器/学习”embedding后输入到encoder里去,最后一层的encoder最终输出的outputs [10, 512](假设我们采用的embedding长度为512,而且batch size = 1),此outputs 乘以新的参数矩阵,可以作为decoder里每一层用到的K和V;
将<bos>作为decoder的初始输入,将decoder的最大概率输出词 A1和‘i’做cross entropy计算error。
将<bos>,“i” 作为decoder的输入,将decoder的最大概率输出词 A2 和‘love’做cross entropy计算error。
将<bos>,“i”,“love” 作为decoder的输入,将decoder的最大概率输出词A3和’machine’ 做cross entropy计算error。
将<bos>,“i”,"love ",“machine” 作为decoder的输入,将decoder最大概率输出词A4和‘learning’做cross entropy计算error。
将<bos>,“i”,"love ",“machine”,“learning” 作为decoder的输入,将decoder最大概率输出词A5和终止符做cross entropy计算error。
测试
训练好模型, 测试的时候,比如用 '机器学习很有趣’当作测试样本,得到其英语翻译。
这一句经过encoder后得到输出tensor,送入到decoder(并不是当作decoder的直接输入):
然后用起始符<bos>当作decoder的 输入,得到输出 machine
用<bos> + machine 当作输入得到输出 learning
用 <bos> + machine + learning 当作输入得到is
用<bos> + machine + learning + is 当作输入得到interesting
用<bos> + machine + learning + is + interesting 当作输入得到 结束符号<eos>
得到了完整的翻译 ‘machine learning is interesting’
可以看到,在测试过程中,只能一个单词一个单词的进行输出,是串行进行的。
五、训练小技巧:
1、Label Smoothing(标签平滑)
注:K表示多分类的类别总数,ϵ是一个较小的超参数。
2、Noam Learning Rate Schedule
学习率不按照这样可能就得不到一个好的Transformer。
3.Dropout
很多时候,训练时的效果非常好,但预测时效果却一般,这种现象一般称为过拟合现象或退化现象,也称为过度学习,主要原因是因为在训练过程中学习了过多不必要的特征信息,使网络的泛化能力大大降低。
Dropout主要思想是以概率 𝑝中断一部分神经元的连接,使一些神经元关闭,使得一部分不重要的特征不能进行与后续网络连接,从而达到使模型泛化能力增强。
对编码器和解码器的每个子层的输出使用 Dropout 操作,是在进行残差连接和层归一化之前。词嵌入向量和位置编码向量执行相加操作后,执行 Dropout 操作。