☰
Transformer架构深度解析:从自注意力到多头机制
2026/9/30 6:32:57 网站建设 项目流程

1. 从序列建模的痛点说起

1.1 为什么Transformer之前“慢半拍”

在Transformer出现之前,主流的序列建模工具是RNN、LSTM和GRU,尤其是LSTM在机器翻译、语音识别等任务上统治了很多年。它们的核心思路是“按时间步逐个处理”:第t个词进来,隐藏状态 h_t 由 h_{t-1} 和当前输入共同计算。这种循环结构天然适合变长序列,但副作用也很明显——不能并行。训练的时候必须等前一个时间步算完,才能算下一个时间步,GPU利用率上不去,模型规模一扩大,训练成本直线上升。

另一个痛点是长距离依赖。虽然LSTM设计了门控机制来缓解梯度消失,但序列长度超过几百甚至几千后,早期信息很容易被“稀释”。你可以把LSTM想象成一个人逐字转述一句话,传到后面除非专门做记忆强化,否则开头说了什么早就含糊了。对于需要跨长距离建模的任务,比如文档级情感分析、长句翻译,这就很吃力。

还有一点,RNN系的模型在编码双向语义时往往要用两个方向拼接,增加复杂度。当时整个社区都在期待一种更直接、更适合并行、能长距离建模的新架构,Transformer正是在这种需求下登场的。

1.2 Attention机制:从“软对齐”到“全部注意力”

Attention思想其实在2014年的Bahdanau Attention中已经出现,那时候主要用作RNN的辅助模块:解码每一步时,通过比对当前隐状态和所有源端隐状态,算出一个权重分布,告诉模型“该重点关注源句的哪个词”。这种方式本质上是建立一个软对齐,让模型不依赖单点信息,而是动态加权所有输入。

Transformer把这个思想彻底推到底——不走循环,直接把“注意力”当主角。论文《Attention Is All You Need》提出一个简洁的编码器-解码器架构,编码器是N层自注意力加前馈网络,解码器也类似,但靠掩码自注意力保证自回归。整个网络不再有循环单元,输入的所有词在计算时是同时参与的,因此可以大规模并行训练。这不仅是性能提升,更是一种建模哲学的转变:序列中的每个元素不是按顺序“继承”前文信息,而是直接与所有元素交互,由模型自己学出该注意谁。

这个设计带来的直接好处有两个。第一,任意两个位置的交互路径长度为1,长距离依赖不再是问题,至少在理论上如此;第二,所有位置共享计算逻辑,优化难度大大下降,再配合硬件张量并行,训练效率成倍提升。后来大家发现这套架构在几乎所有序列任务上都能碾压RNN系模型,陆续延伸出BERT、GPT、ViT等一大批模型。读完Transformer再回头看之前的LSTM,你会觉得很多繁琐的调参技巧其实都是在和结构缺陷赛跑。

2. Transformer整体架构拆解

2.1 从宏观到微观:一张架构图背后的逻辑

Transformer的标准结构是“编码器-解码器”,N=6层堆叠,每层里有两个核心子层:多头自注意力子层和位置逐列前馈网络子层。每个子层外面都套了残差连接和层归一化。解码器比编码器多一个交叉注意力子层,同时第一个自注意力子层要加因果掩码。

如果只看宏观,编码器负责把源序列编码成连续的语义表示,解码器在生成目标序列时逐步从这个表示里“取信息”。这个框架并非新概念,但内部实现完全变革了。编码器中每一层输出的维度是 d_model,论文里取512。你输入一批形状为 [batch_size, seq_len] 的词索引,经过embedding层变成 [batch_size, seq_len, d_model],然后加上位置编码,送入堆叠的Transformer块,输出还是同样形状。解码器的输入是目标序列(训练时整体输入),生成阶段则是逐步自回归。

“层”里面具体做了什么,我建议这样记:自注意力负责“交换信息”,让每个位置的向量融合整个句子中其他位置的信息;前馈网络再对每个位置独立做一次非线性变换,相当于在全局交互之后做局部加工。残差连接保证深层梯度畅通,层归一化让训练更稳定。理解了这条主线,Transformer在你眼里就不再是一团黑盒。

2.2 自注意力机制:让每个词看到整个句子

自注意力是Transformer最重要的组件。输入一个序列,每个位置生成三个向量:Query、Key、Value。这三个向量是输入向量分别乘上三个可学习矩阵 W_Q、W_K、W_V 得到的。你可以把它们类比成检索系统:Query是你想查的内容,Key是每个文件的内容标签,Value是文件正文。注意力分数就是Query和所有Key做点积,越大说明越相关;对这些分数做softmax归一化,得到权重;最后用权重对Value做加权求和,就是当前位置在这个句子中的“聚合语义表示”。

具体到第 i 个位置的输出公式:

Attention(Q, K, V) = softmax(Q K^T / √d_k) V

这里 Q 的第 i 行代表第 i 个词的查询向量,K 的第 j 行代表第 j 个词的键向量,Q_i 与 K_j 的点积就是词 i 对词 j 的关注程度。除以 √d_k 是为了防止点积过大导致softmax进入饱和区。d_k 是每个头的维度,论文取64。如果 d_k 是64,√d_k=8;如果维度是512,不缩放的话,点积数值容易很大,softmax算出来近似one-hot,梯度很小,学习效率会明显下降。这个缩放不是玄学,是一个实打实的让训练稳定的手段。

用生活类比再说一次:你要在一堆文档里找“如何训练模型”的资料,Query是你输入的关键词,Key是每篇文章的标题,Value是文章内容。你先算关键词与每个标题的匹配度,再做归一化,最后按匹配度的比例从各篇文章提取信息。自注意力就是做了一堆这样的检索,只不过Q、K、V都是从输入自己生成的,所以叫“自”注意力。

2.3 多头注意力:为什么要切成多个头

单一注意力虽然能建模所有位置两两之间的关系,但“只抓一种关系”其实不够。比如一句话里,动词和主语有语法关系,代词和指代对象有语义关系,如果只用一组Q/K/V,模型很难同时兼顾多种关系模式。多头注意力把 d_model 维向量分成 h 个头(论文 h=8),每个头在 d_k = d_model / h 维度上独立做注意力,再拼接起来做一次线性变换。

每个头相当于让模型从不同子空间里去观察序列关系。有的头可能倾向于捕捉邻近词之间的局部依赖,有的头可能学到远距离指代关系。实验里这种分工不一定规范,但经验表明多头确实比单头效果更好。从训练角度看,多头还相当于给模型提供了多个“特征视图”,比单头在相同参数下更丰富。

注意这里有个很容易混淆的细节:多头注意力输入还是同一个序列,每个头有自己的 W_Q、W_K、W_V,但它们不增加太多参数量,因为每个头的维度是原来的 1/h,总维度不变。计算上其实相当于把大矩阵分块并行算,前向速度甚至可以更快。我用过单头四层的Transformer做小规模翻译任务,效果明显比多头差,当时只靠经验确定是注意力表达能力不足。

3. 关键公式与实现细节

3.1 从公式到张量形状

如果你要手写Transformer,最先要搞明白的就是形状。假设输入 X 是 [batch_size, seq_len, d_model](其实embedding后是 [batch_size, seq_len] 查表得到),把 X 分别乘上 [d_model, d_model] 的 W_Q、W_K、W_V,得到 Q、K、V,三个矩阵的形状都是 [batch_size, seq_len, d_model]。多头切分时,把最后一维切成 h 份,变成 [batch_size, seq_len, h, d_k],然后交换维度成 [batch_size, h, seq_len, d_k]。之后对每个头计算注意力分数:

scores = Q @ K.transpose(-2, -1) # [batch_size, h, seq_len, seq_len]

再除以 √d_k,做softmax,最后 @ V,得到 [batch_size, h, seq_len, d_k],concat到 [batch_size, seq_len, d_model] 再经过输出投影。

这段逻辑如果你只在概念上理解,听懂了;真动手时经常会报维度不匹配。我当时在PyTorch里写这个步骤,踩过一个bug:transpose之后没有用contiguous()再view,导致内存布局不对,结果张量值全乱了。这个坑在写自定义Transformer时会遇到,直接用框架自带的 nn.MultiheadAttention 很少暴露,但理解这些细节对你调参很有帮助。

3.2 位置编码:给Transformer“编号”

自注意力机制本身是置换等变的——把输入顺序打乱,输出的集合不变,只是顺序跟着动。语言是顺序敏感的,“我爱你”和“你爱我”完全不是一回事,所以必须给每个位置注入顺序信息。Transformer用的是正弦余弦位置编码:

PE(pos, 2i) = sin(pos / 10000^(2i/d_model)) PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))

这种编码有几个特性。第一,不同位置的编码向量是唯一的;第二,相邻位置编码差异相对平滑;第三,它能让模型很容易通过线性变换学习到相对位置关系,因为对于固定偏移k,PE(pos+k) 可以表示为 PE(pos) 的线性函数。这一设计在当时非常巧妙,不用额外参数就能处理变长序列。

如果你用BERT类预训练模型,会发现它们用的其实是可学习位置嵌入,不是正弦编码。两者区别在于:正弦编码是固定函数,好处是可以外推到训练时没见过的更长序列;可学习嵌入在固定max_len内更灵活,但超出长度就要另外处理。如今很多模型也采用RoPE(旋转位置编码)等新式位置编码,更利于外推。理论篇里要把基础搞清楚,后面理解RoPE、ALiBi这些变体就容易多了。

3.3 残差连接、层归一化和前馈网络

每个子层输出都要做LayerNorm(x + Sublayer(x)),这就是Post-LN结构。残差连接让深层梯度直接回传,这是深层Transformer能够训练的关键。如果去掉残差,18层以上的Transformer大概率会出现训练不稳定甚至发散。层归一化则对不同特征维度做归一化,它和BatchNorm不同,BN是对每个特征通道跨batch归一化,LN是对每个样本内部的特征归一化,所以LN不受batch size影响,更适配变长序列的NLP任务。

前馈网络(FFN)在每个位置独立运作,是一个两层的MLP:

FFN(x) = max(0, x W1 + b1) W2 + b2

中间维度 d_ff 论文取2048,是 d_model 的4倍。ReLU激活这里引入了非线性,让模型能够学到更复杂的特征交互。从参数数量看,FFN占了Transformer总参数的2/3,远多于注意力部分。很多“大模型关掉FFN层会怎样”的消融实验发现,FFN可以看作隐式存储知识的地方,注意力则负责信息路由。理论篇先记住这个分工。

3.4 解码器中的掩码细节

解码器有两个特殊设计。第一个是因果掩码(causal mask),也叫自回归掩码。解码器在预测第t个词时,不能看到第t个词之后的目标序列,否则就是作弊。实现上是在注意力分数矩阵的上三角(不包括对角线)填充一个很大的负数(比如-1e9),softmax之后权重就近乎0。自己训练GPT类模型时一定要加这个掩码,不然损失会“泄露”。

第二个是交叉注意力。解码器第二层子层的Q来自解码器自身,K和V来自编码器最后一层输出。这样解码器能动态地从编码好的源信息中提取生成所需内容。比如翻译时,生成英文单词时,模型会通过交叉注意力去对齐源文本中对应的中文词语。训练时解码器输入是完整的目标序列(shifted right),预测每个位置的词;推理时则用已经生成的词作为输入,一步步往下走,直到输出结束符。

4. 训练机制与核心参数

4.1 训练目标:预测下一个词

Transformer最典型的使用方式是自回归语言建模:给定前t个词,预测第t+1个词的概率分布。损失函数是交叉熵。对于机器翻译,输入是源语言序列,输出是目标语言序列;模型在训练时看到完整的目标序列,计算每个位置的预测误差。推理时,用beam search或贪心搜索逐步生成。

这里必须理解训练和推理行为的差异。训练时是“teacher forcing”,当前预测不依赖自己的输出,直接用真实token作为下一步输入,效率高、收敛稳。推理时模型自己的预测可能偏离训练分布,一个错误会逐步累积,这叫exposure bias。这也是很多翻译系统要加“课程学习”或“序列级强化学习”的原因。理论篇你可以先记住这个差异,以后跑推理做生成时就会意识到为什么有时候训练loss很低、生成效果却一般。

4.2 优化器与学习率:warmup不是玄学

Transformer训练有一个非常经典的学习率方案:先用warmup把学习率从0线性增加到峰值,然后按倒数平方根衰减。论文给的公式是:

lrate = d_model^(-0.5) * min(step^(-0.5), step * warmup_steps^(-1.5))

warmup_steps 论文取4000。这样做的意义是:训练初期参数还没平稳,过大的学习率容易让训练发散;先warmup让梯度统计量稳定下来,再逐渐降低学习率帮模型收敛到更优的局部极小。很多人不用warmup直接训练大Transformer,结果loss震荡严重,不一定是因为模型写错了,可能只是学习率策略不对。

实际训练中,Adam优化器的beta2也要注意,论文用0.98,比常见默认值0.999更小,因为beta2太大会让梯度的指数移动平均值对早期梯度变化反应太慢,配合warmup更容易不稳。这些数值都是论文里被验证过有效的设定,我在复现时直接用这些默认值,很少翻车。

4.3 正则化手段:Dropout与Label Smoothing

Transformer训练还有一个容易忽略的细节——正则化。每个子层输出后、残差相加前,要施加Dropout,论文rate取0.1。这个位置是关键:如果放在残差之后,信息流可能会被打断,放在残差之前更符合原始实现。embedding和位置编码相加之后也建议做一次Dropout,帮助位置信息不被过拟合。

标签平滑也是paper里明确提到的训练技巧,label_smoothing默认0.1。它会避免模型对训练集过自信,降低logits输出极端值,对泛化有帮助。我自己用1.2亿参数的GPT类小模型时,开了label smoothing后生成句子的多样性有明显改善,困惑度也略有下降。但要注意,如果后续要做蒸馏或者严格概率输出,label smoothing会略微扭曲概率分布,需自行取舍。

5. 理论进阶与常见误区

5.1 复杂度分析:为什么长文本那么“贵”

自注意力的时间和空间复杂度是 O(n^2),n是序列长度。每个位置要和其他所有位置算注意力,所以n个位置共 n^2 个分数。这对于短序列还好,序列拉到4096、8192甚至更长后,显存和计算量都急剧上升。这也是为什么后来涌现了Sparse Attention、Longformer、FlashAttention等优化方案。你需要记住:Transformer的“强”和“贵”都源于同一件事——全局两两交互。

处理长文本时,常见的工程优化包括:FlashAttention用分块和在线softmax把中间矩阵写回显存的量降下来;滑动窗口注意力只在局部做注意力;或者是用稀疏模式。理解了原始复杂度,你看到这些变体就不会觉得突兀。理论篇只需抓住这个复杂度瓶颈,后续学优化方案就有了出发点。

5.2 位置编码真的是“绝对”的吗

严格说,原始的sinusoidal位置编码是绝对位置编码,即每个绝对位置有一个对应向量。但由于位置编码与词向量相加后进入注意力层,注意力分数的计算里会包含位置之间的点积项,模型可以因此学习到相对位置关系。后来的RoPE直接做了相对位置编码,效果更好,泛化到长文本的能力也更强。不少人以为Transformer只有绝对位置编码,其实它是靠绝对位置编码“间接”表达了相对位置。

位置编码还有一个容易混淆的点:它和embedding到底怎么融合?两种常见方式是相加和拼接。原始Transformer是相加,因为它们维度可以相同;但如果你希望保留更独立的语义,可以选择拼接。大多数论文和实战都采用相加,这样可以不增加维度,计算代价更小。可学习位置嵌入等价于把位置信息也当作可训练参数,模型自己会权衡如何组合。

5.3 解码器到底比编码器难在哪

很多人跑BERT很熟,但第一次做自回归生成时会发现明显更难。核心原因就是解码器要自回归预测,错误会累积,而且训练和推理行为不一致。编码器可以一次看到整个输入,做双向attention;解码器只能看到左侧上文。因为信息量受限,模型很难充分捕获“右侧”的全局信息。这也是为什么做生成任务要专门引入“采样温度”、“top-p”这些解码策略,本质上都是和概率分布打交道。

从理论角度看,解码器内部的交叉注意力在编码-解码模型中天然起到“翻译”或“信息抽取”作用。对于纯自回归GPT类模型,没有交叉注意力,而是堆叠多层masked self-attention。后者更简单,学习难度也更大——所有信息都来自左侧。理解了这种结构差异,你看到“prefix LM”、“encoder-decoder”等不同架构时就不会一头雾水了。

5.4 常见理论误区速查

我整理一下自己在面试学生和看论文时反复见到的几个误区:

  • 误区一:Q、K、V来自不同输入。其实自注意力里Q、K、V都来自同一个输入X;只有交叉注意力中,K、V来自编码器,Q来自解码器。
  • 误区二:残差连接是x + sublayer(x)之后再做LayerNorm。注意力子层和FFN子层各自包裹了残差和LayerNorm,顺序是x -> attn -> dropout -> + x -> LN,不是LN在attn之前。
  • 误区三:位置编码加在每层输入上。实际上是在最底层的embedding后加一次,然后后续层共享这个位置信息(因为有残差,信息会传下去),而不是每层都加。
  • 误区四:多头注意力中每个头独立输出一个d_model向量。其实是每个头输出d_k维,拼接后才是d_model,总参数量与单头差不多。

这些细节在初学者中非常普遍,我在复现时也踩过,尤其是LayerNorm的位置,放错后收敛性能差异明显。

6. 为什么Transformer这么能打

6.1 并行计算带来的宏观优势

Transformer能够统治NLP,一个决定性因素是它的训练效率——所有token同时参与计算,彻底抛弃了循环迭代。这种“并行化友好”让模型能够充分利用现代GPU的张量核心,batch规模可以拉大,训练步数可以缩短。对比LSTM,同等算力下Transformer可以在更短时间内达到更好效果,这是工程上最直接的胜利。

并行能力还带来一个连锁反应:模型规模可以不停放大。从1亿参数到千亿参数,Transformer都能稳定扩展;RNN系模型在扩展时很难利用大规模并行算力。可以说,没有Transformer的并行特性,就不会有大语言模型时代。同时,这种架构天然的“通用计算”能力也让它跨领域适应良好——后来ViT把图像切块作为序列,也一样取得了效果。

6.2 注意力机制的通用性

自注意力本质上是对集合元素之间交互的建模。它的假设是“所有位置之间都可能有关系”,而不是像CNN那样假设局域性或者RNN那样假设时间延续。这种通用性让Transformer可以处理文本、图像、音频、点云甚至推荐系统里的行为序列。你不需要像CNN那样设计复杂的卷积核或感受野,只需要把数据切成token,就能套用同一个框架。

这种“少人工归纳偏置”的特点是一把双刃剑。好处是很强的表达能力和灵活性,模型可以从数据里学出适合自身任务的结构;坏处是需要很大数据量来训练,因为缺少内建的结构先验。这也是为什么早期Transformer在一些小数据任务上反而打不过CNN。理论篇里要看到:Transformer更像是一种通用可编程的“记忆检索机”,而先验信息要靠数据和训练策略引导。

6.3 从NLP到多模态的扩展

Transformer的出现统一了NLP领域繁杂的模型,同时向视觉、语音、多模态等领域快速扩展。ViT把图像划分成16x16的patch,每个patch拉直成一个token,然后跑标准的Transformer编码器,效果可以和CNN/SOTA模型持平甚至更好。Whisper里也用Transformer做语音识别。大模型时代,CLIP、GPT-4V这类多模态模型也都是在Transformer基础上融合不同模态的token序列。掌握Transformer理论,实际上就拿到了理解这些模型的一把“万能钥匙”。

理论篇讲的这些概念,比如注意力、位置编码、掩码、残差结构,会以各种变体出现在所有后续模型中。如果你能熟练推导自注意力的形状变化、说清各子层的作用,那么面对新模型时,很容易把它的核心组件映射回Transformer的框架里,学习曲线会平缓很多。

7. 写在最后的一些体会

第一次读“Attention Is All You Need”的时候,我并没有完全理解为什么要用那么多头、为什么位置编码选正弦函数,甚至觉得论文有些地方写得过于简洁。直到自己动手实现并训练了一个小翻译模型,才意识到每一步设计背后都有它要解决的问题——缩放点积是防止softmax饱和,多头是给模型多个关系子空间,残差和LayerNorm则是为深层网络的稳定优化兜底。纸上得来终觉浅,对Transformer这种结构极其规整模型而言尤为如此。

对新人我有个建议:不要只满足于会调用nn.Transformer或transformers库。试着从零实现一个极小的两层Transformer,在玩具数据集上训练,你会深入掌握每个张量形状的变化和每个子层的意义。之后再看GPT、BERT、ViT的代码,会有一种豁然开朗的感觉。一些库的默认实现其实做了很多优化,比如FlashAttention,它会让你忽略原始的注意力计算,但理解原始版本永远是理解优化版本的前置条件。

最后再分享一个我在调试中常用的小技巧:把注意力分数矩阵可视化,看看到底哪些token之间分数高。你会发现很多有趣的现象,比如句子的句法结构会隐含在注意力模式里,甚至能看到某些头专门负责“捕捉相邻词”。多花点时间观察这些可视化结果,比单纯盯着loss曲线能帮你建立更直观的模型直觉。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询