研读 Transformer 论文- ai 时代爆发的前提
深度学习模型的发展
RNN:循环神经网络

Encoder,Decoder架构

初始的注意力机制

尽管注意力模型的表现已经足够优秀,但所有基于RNN的模型都面临着同样一个问题:RNN本轮的输入状态取决于上一轮的输出状态,这使RNN的计算必须串行执行。因此,RNN的训练通常比较缓慢。
在这一背景下,抛弃RNN,只使用注意力机制的Transformer横空出世了。
注意力机制的理解
注意力
向量化,查询Q为向量,标签K为向量,内容V为向量,都建模为向量(或向量的矩阵)
通过点积计算来获取相似度(transformer中使用点积,还有其他方式比如加性注意力通过单层神经网络计算,但是那个略慢)
QK^T
以此计算出的相似度作为加权的权重,再与V点乘可得到一个加权平均值
(QK^T) V
一次做多组查询,将q化为矩阵
1 | |
softmax是将得分转换为比率的函数
dk就是query和key向量的长度,使用它的开方来放缩QK^T,避免点乘的值过大,因为softmax在绝对值较大的区域梯度较小,梯度下降的速度比较慢。
自注意力机制
自注意力模块的目的是为每一个输入token生成一个向量表示A,该表示不仅能反映token本身的性质,还能反映token在句子里特有的性质。
注意力其实就是全局信息查询。而自注意力就是注意力的一种应用:通过让一句话中的每个单词去向其他单词查询信息,我们能为每一个单词生成一个更有意义的向量表示。
自注意力中,每一个单词的query, key, value应该只和该单词本身有关。
因此,这三个向量都应该由单词的词嵌入得到。
另外,每个单词的query, key, value不应该是人工指定的,而应该是可学习的。
因此用可学习的参数来描述从词嵌入到query, key, value的变换过程。综上自注意力的输入Q,K,V用下面这个公式计算
1 | |
E是词嵌入矩阵,由句子的所有n个词向量组成。由于每个模型向量的长度是固定的d_model,所以E的形状是[n,d_model]
WQ,WK的长度是d_model * d_k,WV的长度是 d_model * d_v
通过这些计算出的QKV带入上面的注意力公式就能得到自注意力了。
多头注意力
用多组生成多组自注意力结果的话每个单词的自注意力表示会更丰富一点。这种机制就叫做多头注意力。把多头注意力用在自注意力上的公式为:
1 | |
也就是将多个注意力通过concat()拼接后乘以另一个参数矩阵W0得到结果
W0的形状是[h * d v, d model]
在transfromer论文中的默认配置如下:
实际上,多头注意力机制不仅仅可以用在计算自注意力上。推广一下,如果把多头自注意力的输入E拆成三个矩阵Q,K,V,则多头注意力的公式为
1 | |
Transformer架构

残差连接&层归一化(Add & Norm)
Transformer使用和ResNet类似的残差连接,即设模块本身的映射为F(x),则模块输出为
1 | |
但是和ResNet不同,Transformer使用的归一化方法是LayerNorm。
另外要注意的是,残差连接有一个要求:输入和输出的维度必须等长。在Transformer中,包括所有词嵌入在内的向量长度都是d model。
前馈网络 (Feed forward)
架构图中的前馈网络(Feed Forward)其实就是一个全连接网络。具体来说,这个子网络由两个线性层组成,中间用ReLU作为激活函数。
1 | |
中间的隐藏层的维度数记作d ff。默认d ff = 2048。
一个单词的向量x,经过前馈网络时,先由512升维至2048,在经过激活函数后,向量内为负数的值置0,然后降维至512维
架构和掩码多头注意力
用英文翻译为法文的一次推理为例
- **Encoder **的流水线(两道工序):
- 多头自注意力 (Self-Attention):句子内部的单词互相“看”,搞清楚彼此的关系,更新自己的理解。(“内部大讨论”)
- 前馈网络 (Feed-Forward Network):每个单词对自己刚刚更新的理解进行一次“深度思考”。
- 这个过程重复 **N **次,理解越来越深刻。
- Decoder 的流水线(三道工序):
- 掩码多头自注意力 (Masked Self-Attention):已生成的法文单词互相“看”,但带了眼罩,只能看前面的,不能看后面的。(“回顾已写内容”)
- 编码器-解码器注意力 (Encoder-Decoder Attention):正在生成的法文单词去“请教”英文句子的“含义笔记
z”,看看应该重点关注哪个英文单词。(“咨询英语专家”) - 前馈网络 (Feed-Forward Network):结合了前两步的信息后,进行一次“深度思考”。
- 这个过程也重复 N 次。
关键点:**K, V **来自 z,Q 来自上一层的输出。
z(Encoder的输出) 是信息源,是“被查询”的对象,所以它提供键(K)和值(V)。- Decoder 上一层的输出是查询者,它想知道“我应该关注
z里的什么信息?”,所以它提供查询(Q)。 - 这完美地模拟了翻译过程:拿着你正在写的法文草稿(Q),去查阅英文原文的笔记(K, V),找到最相关的信息来帮助你写下一个词。
嵌入层

和其他大多数序列转换任务一样,Transformer主干结构的输入输出都是词嵌入序列。
词嵌入,其实就是一个把one-hot向量转换成有意义的向量的转换矩阵。
one-hot向量类似于一个大位图,来唯一标志这个词,缺点是太稀疏以及不能做近似语义,通过词嵌入相对应查字典,查到对应的词嵌入向量。
词嵌入向量能够捕捉近似语义,而且更短,更稠
因此嵌入层相当于一个巨大的字典或者转换矩阵
在Transformer中,Decoder的嵌入层和输出线性层是共享权重的:
输出线性层表示的线性变换是嵌入层的逆变换,其目的是把网络输出的嵌入再转换回one-hot向量。
如,嵌入层的转换矩阵是[词典 , 512],则输出线性层的转换矩阵是[512 , 词典],那么就可以使用同一个转换矩阵来作为他们的权重矩阵
如果某任务的输入和输出是同一种语言,那么Encoder的嵌入层和解码器的嵌入层也可以共享权重。
位置编码
嵌入层的输出是一个向量数组,即词嵌入向量的序列。设数组的位置叫pos,向量的某一维叫i。我们为每一个向量里的每一个数添加一个实数编码,这种编码方式要满足以下性质:
- 对于同一个pos不同的i,即对于一个词嵌入向量的不同元素,它们的编码要各不相同。
- 对于向量的同一个维度处,不同pos的编码不同。且pos间要满足相对关系,即f(pos+1) - f(pos) = f(pos) - f(pos - 1) 。
Transformer使用三角函数来决定这个位置编码,公式为:
1 | |
位置编码小结
- 问题:Transformer 的自注意力机制是“顺序脸盲”。
- 解决方案:在词嵌入上加上 (Add) 一个位置编码向量。
- 为什么用三角函数:1. 有界且唯一:
sin和cos的值域在[-1, 1],且不同频率的组合为每个位置提供了唯一的编码。- 蕴含相对位置信息:由于三角函数的性质,任意两个位置
pos和pos+k的编码之间存在一个固定的线性关系(旋转),这使得模型极易学习到相对位置的概念。 - 超强泛化能力:公式可以生成任意长度序列的位置编码,不受训练数据长度的限制。
- 蕴含相对位置信息:由于三角函数的性质,任意两个位置
为何用自注意力?
在论文的第四章,作者用自注意力层对比了循环层和卷积层,探讨了自注意力的一些优点。
自注意力层是一种和循环层和卷积层等效的计算单元。它们的目的都是把一个向量序列映射成另一个向量序列,比如说编码器把映射成中间表示。论文比较了三个指标:每一层的计算复杂度、串行操作的复杂度、最大路径长度。

我们可以从这三个指标分别探讨自注意力的好处。首先看序列操作的复杂度。如引言所写,循环层最大的问题是不能并行训练,序列计算复杂度是。而自注意力层和卷积一样可以完全并行。
再看每一层的复杂度。设是序列长度,是词嵌入向量长度。其他架构的复杂度有,而自注意力是。一般模型的会大于,自注意力的计算复杂度也会低一些。
最后是最大路径长度。注意力本来就是全局查询操作,可以在的时间里完成所有元素间信息的传递。它的信息传递速度远胜卷积层和循环层。
为了降低每层的计算复杂度,可以改进自注意力层的查询方式,让每个元素查询最近的个元素。本文仅提出了这一想法,并没有做相关实验。
大模型相关概念
参数
模型能学习的量,具体体现在transformer里的各个权重矩阵和偏置向量,比如chagpt3有1750亿参数,也就是说它能在各层的权重矩阵和偏置向量里学习的总和为1750亿。
Token
模型不理解人类语言,所以词句都是先通过分词器转化为token再给嵌入层变为向量的,使用大模型时,一次使用消耗了多少token具体指的是,输入的token数与输出的token数之和。
推理和训练
Transformer在推理时,Encoder是并行的,它并行处理完后,它的输出将作为Decoder的输入。
而Decoder在推理时是串行的,一个一个词生成,不过得益于自注意力机制,它在生成时参考之前已生成的目标序列的所有token,并且每步都会参考Encoder的输出,也就是说Decoder有一个动态增长的输入(目标序列)和一个固定的输入(Endcoder的输出)
而Transformer在训练时,Encoder和Decoder都是并行的,Encoder本来就是并行的,不过在单次运行中,它依然是运行一次把输出给decoder。不过虽然信息流上Encoder必须先于Decoder,但是训练时,计算上Encoder和Decoder是并行计算的,这和GPU的调度计算有关系,并不是阻塞等待。
Decoder也是自己练自己的,由于transformer有掩码多头注意力,所以可以直接把整个答案作为它的目标序列,不需要动态增长了。然后并行预测,但是通过掩码机制使得每个并行的预测不会看到它不应该看到的答案部分。
上下文窗口
chat型大模型是将聊天的历史记录+这次聊天作为输入的,而这个输入的长度正是transformer论文里的d_model,论文里默认是512维,对一个比较长的聊天肯定是不够的,而如果直接扩充这个值,由于transformer的计算复杂度中dmodel是平方值,所以会导致计算复杂度指数级上升。
所以就会有一些优化,比如使用滑动窗口来让token只注意附近的r个token,这样就不是n2复杂度,而是n * r
大致有对注意力的优化,对GPU的优化,对IO方式的优化以及更改模型架构比如引入rnn的transformer -xl以及使用RAG来取得一些关键信息放入上下文而不是全丢进上下文等优化
目前最有效的优化FlashAttention是通过优化GPU显存的IO方式实现的。