Transformer架构深度解析:从自注意力到编码器-解码器实战

发布时间:2026/8/6 11:52:37
Transformer架构深度解析:从自注意力到编码器-解码器实战 1. 项目概述为什么Transformer值得你花时间深挖如果你正在学习深度学习尤其是自然语言处理或者计算机视觉那么“Transformer”这个词你一定绕不过去。我第一次系统学习Transformer就是跟着李宏毅老师的课程和笔记。当时的感觉是这个模型结构清晰但细节极多从自注意力机制到位置编码从编码器-解码器架构到各种变体每一个环节都藏着设计者的巧思。很多人觉得Transformer难是因为它不像CNN或RNN那样有直观的“局部感受野”或“时间步”概念。但恰恰是这种摆脱了传统归纳偏见的架构让它成为了一个强大的通用特征提取器从文本翻译到图像分类再到今天的多模态大模型几乎无处不在。这份笔记的核心价值在于它不仅仅是公式的罗列而是试图串联起“为什么这么设计”的逻辑链条。对于初学者它能帮你建立对Transformer的整体认知理解自注意力如何实现全局依赖建模对于有一定基础的学习者它能带你深入理解层归一化、残差连接、前馈网络等子模块的设计精妙之处以及它们如何共同作用来稳定深层网络的训练。无论是想看懂论文里的模型图还是想亲手实现一个简易的Transformer这份笔记都能提供一个扎实的起点。接下来我会结合李老师的讲解框架和我自己的实践体会带你拆解Transformer的每一个核心部件。2. 核心架构全景与设计哲学拆解2.1 从序列建模的困境到自注意力的破局在Transformer出现之前处理序列数据如句子、时间序列的主流是循环神经网络RNN及其变体LSTM、GRU。RNN的核心问题是顺序计算带来的低效——你必须等前一个时间步计算完才能计算下一个这无法利用现代GPU的大规模并行计算能力。此外尽管LSTM通过门控机制缓解了长程依赖问题但对于非常长的序列信息在一步步传递中仍然会衰减或爆炸。Transformer的答案非常直接抛弃循环完全依赖注意力机制特别是自注意力来建立序列中所有元素两两之间的关系。你可以把它想象成在一个会议室里开会。RNN的模式是每个人只能听前一个人说完再轮到自己发言。而Transformer的模式是会议一开始每个人同时将自己想说的Value写下来同时也会准备一个关于“我是谁”的自我介绍Query和一个关于“我想听谁”的意向表Key。然后每个人通过对比自己的Query和所有人的Key来决定应该从每个人的发言Value中听取多少信息。这个过程是同时发生的完美并行。这种设计的直接优势是极高的并行度序列中所有位置的Query-Key-Value计算可以同时进行。全局视野每个输出位置都能直接“看到”输入序列的所有位置一步到位建立长程依赖避免了信息传递的衰减。可解释性注意力权重矩阵可以直观地展示模型在处理一个词时更“注意”句子中的哪些其他词。2.2 Encoder-Decoder框架的Transformer实现Transformer整体采用了经典的编码器-解码器架构但内部全部由自注意力层和前馈神经网络层堆叠而成。编码器的作用是将输入序列如一个英文句子映射为一个蕴含了上下文信息的连续表示序列。一个编码器层由两个子层构成多头自注意力层让序列中的每个词都能与其他所有词交互信息。前馈神经网络层一个独立作用于每个位置的全连接网络通常是两层MLP中间有一个ReLU激活用于进行特征变换。每个子层外面都套着一个“残差连接”和“层归一化”即LayerNorm(x Sublayer(x))。这是稳定深层网络训练的关键我们后面会详细讲。解码器的作用是基于编码器的输出和已生成的部分输出序列来生成下一个词或标记。它比编码器多了一层注意力机制带掩码的多头自注意力层确保在预测第i个位置时只能“看到”第1到i-1个位置的信息防止信息泄露这是自回归生成的关键。多头交叉注意力层它的Query来自解码器上一层的输出而Key和Value来自编码器的最终输出。这让解码器在生成每一个词时都能有选择地聚焦于输入序列的相关部分。前馈神经网络层与编码器中的相同。编码器和解码器都可以堆叠N层原论文中N6通过层层抽象提取越来越复杂的特征和关系。注意很多初学者容易混淆“自注意力”和“交叉注意力”。简单记法自注意力的Query, Key, Value都来自同一序列要么全是输入要么全是已生成的输出交叉注意力的Query来自一个序列如解码器Key和Value来自另一个序列如编码器输出。3. 核心组件深度解析与实操要点3.1 自注意力机制从Scaled Dot-Product说起自注意力是Transformer的灵魂。它的计算过程可以分解为以下几步我们以一个序列长度为n特征维度为d_model的输入X为例线性变换生成Q, K, V这是第一步也是决定模型容量的一步。# 假设 X 的形状为 (n, d_model) # 定义三个权重矩阵 W_Q, W_K, W_V形状均为 (d_model, d_k) 或 (d_model, d_v) # 通常 d_k d_v d_model / h其中h是注意力头数 Q X W_Q # 形状 (n, d_k) K X W_K # 形状 (n, d_k) V X W_V # 形状 (n, d_v)这里W_Q,W_K,W_V是可学习的参数矩阵。它们的作用是将输入投影到不同的“语义空间”Query空间用来询问、Key空间用来被匹配、Value空间承载实际信息。计算注意力分数Score计算每个Query对所有Key的匹配程度。# 计算点积分数矩阵形状为 (n, n) scores Q K.T # (n, d_k) (d_k, n) - (n, n)点积越大表示Query和Key的向量方向越相似关联度越高。缩放Scale这是一个非常关键但容易被忽略的步骤。scaled_scores scores / sqrt(d_k)为什么需要缩放点积的结果会随着维度d_k的增大而增大。假设Q和K的分量是独立随机变量均值为0方差为1那么点积结果的方差就是d_k。方差过大会导致Softmax函数的梯度变得非常小因为Softmax会将大部分概率集中到极少数值上这不利于模型训练。除以sqrt(d_k)可以将方差拉回1左右稳定梯度流。应用Softmax获取权重# 对每一行每个Query对应的所有分数做Softmax使其和为1 attention_weights softmax(scaled_scores, dim-1) # 形状 (n, n)现在attention_weights[i, j]就表示第i个位置在生成输出时应该“注意”第j个位置的程度。加权求和得到输出output attention_weights V # (n, n) (n, d_v) - (n, d_v)最终每个位置的输出都是所有位置Value向量的加权和权重由该位置与所有位置的匹配度决定。实操心得多头注意力原论文中并没有使用一个大的d_model维度的单头注意力而是将其“拆分”成h个头。具体操作是将d_model维的Q、K、V分别通过h个不同的线性变换投影到d_k,d_k,d_v维度通常d_k d_v d_model / h然后在每个头上独立进行上述的自注意力计算得到h个形状为(n, d_v)的输出。最后将这h个输出在特征维度上拼接起来再通过一个线性层W_O映射回d_model维。为什么需要多头这类似于CNN中的多个滤波器。不同的注意力头可以学习到不同类型的依赖关系。例如在语言模型中有的头可能更关注语法结构如主谓一致有的头可能更关注指代关系如代词指向哪个名词有的头可能更关注固定搭配。多头机制让模型拥有更强的表征能力。3.2 位置编码赋予序列顺序信息自注意力机制本身是“排列不变”的它不考虑词与词之间的相对或绝对位置。打乱输入序列的顺序得到的注意力权重矩阵也会相应打乱但计算模式不变。这对于理解语言是致命的因为“猫追老鼠”和“老鼠追猫”的意思完全不同。因此Transformer必须显式地注入位置信息。原论文使用的是正弦余弦位置编码PE(pos, 2i) sin(pos / 10000^(2i/d_model)) PE(pos, 2i1) cos(pos / 10000^(2i/d_model))其中pos是位置索引0, 1, 2...i是维度索引。对于每个位置pos其编码是一个d_model维的向量每一维对应一个不同频率的正弦或余弦波。这种编码方式的优点相对位置关系可学习对于固定的偏移量kPE(posk)可以表示为PE(pos)的线性函数这意味着模型可以轻易地学会关注相对位置。能处理比训练时更长的序列由于是确定性函数生成即使遇到训练时未见过的更长序列也能计算出其位置编码。注意事项位置编码直接与词向量相加而不是拼接。相加操作可以理解为在同一个语义空间中为词向量附加了一个位置“偏置”。现在也有很多研究使用可学习的位置编码即一个位置嵌入矩阵这在某些任务上可能表现更好但失去了正弦编码的外推性。对于初学者理解并实现正弦版本是更重要的。在视觉Transformer中位置编码同样关键。对于图像需要将二维坐标映射为一维序列的位置或者使用二维的正弦编码。3.3 残差连接与层归一化训练深度网络的稳定器Transformer的编码器和解码器每一层都有两个核心操作Add和Norm。这指的是残差连接和层归一化。它们的顺序通常是LayerNorm(x Sublayer(x))也被称为“Pre-Norm”结构后来也有“Post-Norm”的变体。残差连接就是将子层自注意力或前馈网络的输入x和输出Sublayer(x)直接相加。它的核心作用是缓解深度神经网络中的梯度消失/爆炸问题。梯度在反向传播时除了要经过子层内部的权重矩阵还多了一条“捷径”——恒等映射。这确保了即使子层的梯度很小信号也能直接传回底层使得深层网络得以训练。层归一化是对单个样本在特征维度上进行归一化。对于一个特征向量x计算其均值和方差然后进行标准化最后应用缩放和平移参数gamma和beta。LN(x) gamma * (x - mean) / sqrt(var eps) beta为什么用层归一化而不是批归一化批归一化BN在同一个批次内对每个特征通道跨样本进行归一化这在序列长度可变、批次大小可能较小的NLP任务中效果不稳定。层归一化LN只依赖单个样本自身与批次大小无关更适合RNN、Transformer这类变长序列模型。它能稳定每层输入的分布加速收敛。实操心得Pre-Norm vs Post-Norm原论文Post-Normx Sublayer(LayerNorm(x))。这种结构有时在非常深的网络中训练不稳定。现在主流Pre-NormLayerNorm(x Sublayer(x))。梯度流更顺畅训练更稳定已成为大多数Transformer变体的默认选择。在实现时务必注意这个顺序。4. 前馈网络、输出层与训练推理细节4.1 前馈神经网络位置级感知机在自注意力层之后Transformer还有一个前馈神经网络。它是一个独立应用于每个位置序列中的每个词的两层全连接网络。FFN(x) max(0, xW1 b1)W2 b2其中W1将维度从d_model映射到d_ff通常d_ff 4 * d_modelW2再映射回d_model。中间使用ReLU激活。它的作用是什么自注意力层的作用是“混合”不同位置的信息可以看作一种广义的“卷积”。而前馈网络则负责对每个位置混合后的特征进行非线性变换和升维/降维增强模型的表达能力。你可以把它理解为每个词在经过全局信息交互后自己再进行一次深度思考。4.2 输出层从连续向量到离散词汇解码器的最后一层输出是一个d_model维的向量序列。要把它变成我们想要的词需要经过一个线性层和一个Softmax层。线性层一个形状为(d_model, vocab_size)的权重矩阵W_out将每个位置的d_model维向量映射到词汇表大小的维度上。这个操作可以理解为计算该位置对应词汇表中每个词的“得分”。logits decoder_output W_out.T # 形状 (seq_len, vocab_size)Softmax层对logits在最后一个维度词汇表维度上应用Softmax将其转换为概率分布。probs softmax(logits, dim-1) # 形状 (seq_len, vocab_size)这样probs[i, j]就表示在第i个时间步生成词汇表中第j个词的概率。训练时我们使用交叉熵损失比较模型预测的概率分布和真实的“one-hot”标签即目标词。同时一个重要的技巧是标签平滑即将真实标签的1稍微调低如0.9将剩余的0.1均匀分给其他词这可以防止模型对预测过于自信提升泛化能力。推理时如机器翻译、文本生成我们使用自回归的方式。从起始符sos开始将当前已生成的序列输入解码器得到下一个词的概率分布然后根据策略选取下一个词如贪婪搜索取概率最大的或束搜索保留多个候选将其追加到序列后再输入模型循环往复直到生成结束符eos。4.3 掩码训练与推理的关键约束Transformer中有两种重要的掩码填充掩码由于批次中的序列长度不一我们需要用特殊符号pad将短序列填充到统一长度。在计算注意力时这些填充位置不应该参与。具体做法是在计算Softmax之前将这些位置对应的注意力分数设置为一个极大的负数如-1e9这样经过Softmax后这些位置的权重就几乎为0。# attention_scores 形状 (batch_size, num_heads, seq_len, seq_len) # padding_mask 形状 (batch_size, 1, 1, seq_len) 在pad位置为True attention_scores attention_scores.masked_fill(padding_mask, -1e9)序列掩码因果掩码仅用于解码器的自注意力层。为了保证自回归属性在预测第t个位置时模型只能“看到”第1到t-1个位置。这通过一个下三角矩阵主对角线及以上为-inf以下为0来实现。# 生成一个下三角布尔矩阵包含对角线 seq_len query.size(-2) causal_mask torch.tril(torch.ones(seq_len, seq_len)).bool() # 取反使得未来位置为True以便填充 -inf causal_mask ~causal_mask attention_scores attention_scores.masked_fill(causal_mask, -1e9)5. 动手实现一个简易Transformer核心代码块理解的最好方式是实践。下面我们用PyTorch勾勒出Transformer几个核心组件的关键代码。这不是一个完整的、可运行的模型但包含了最核心的逻辑帮助你建立代码层面的直觉。import torch import torch.nn as nn import math class MultiHeadAttention(nn.Module): def __init__(self, d_model, num_heads): super().__init__() assert d_model % num_heads 0 self.d_model d_model self.num_heads num_heads self.d_k d_model // num_heads # 定义线性变换层生成Q, K, V self.W_q nn.Linear(d_model, d_model) # 实际实现中会拆分成num_heads个这里为清晰起见 self.W_k nn.Linear(d_model, d_model) self.W_v nn.Linear(d_model, d_model) # 输出投影层 self.W_o nn.Linear(d_model, d_model) def forward(self, query, key, value, maskNone): batch_size query.size(0) # 1. 线性投影并分头 Q self.W_q(query).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2) K self.W_k(key).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2) V self.W_v(value).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2) # 2. 计算缩放点积注意力 # scores shape: (batch_size, num_heads, seq_len, seq_len) scores torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k) # 3. 应用掩码如果提供 if mask is not None: scores scores.masked_fill(mask 0, -1e9) # 4. Softmax得到注意力权重 attention_weights torch.softmax(scores, dim-1) # 5. 加权求和 # context shape: (batch_size, num_heads, seq_len, d_k) context torch.matmul(attention_weights, V) # 6. 合并多头 context context.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model) # 7. 输出投影 output self.W_o(context) return output, attention_weights class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len5000): super().__init__() pe torch.zeros(max_len, d_model) position torch.arange(0, max_len).unsqueeze(1).float() div_term torch.exp(torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model)) pe[:, 0::2] torch.sin(position * div_term) pe[:, 1::2] torch.cos(position * div_term) pe pe.unsqueeze(0) # shape: (1, max_len, d_model) self.register_buffer(pe, pe) # 这不是可学习参数但会随模型保存/加载 def forward(self, x): # x shape: (batch_size, seq_len, d_model) return x self.pe[:, :x.size(1), :] class TransformerEncoderLayer(nn.Module): def __init__(self, d_model, num_heads, d_ff, dropout0.1): super().__init__() self.self_attn MultiHeadAttention(d_model, num_heads) self.feed_forward nn.Sequential( nn.Linear(d_model, d_ff), nn.ReLU(), nn.Linear(d_ff, d_model) ) self.norm1 nn.LayerNorm(d_model) self.norm2 nn.LayerNorm(d_model) self.dropout nn.Dropout(dropout) def forward(self, src, src_maskNone): # Pre-Norm 结构 # 1. 多头自注意力子层 attn_output, _ self.self_attn(src, src, src, src_mask) src src self.dropout(attn_output) # 残差连接 src self.norm1(src) # 层归一化 # 2. 前馈网络子层 ff_output self.feed_forward(src) src src self.dropout(ff_output) # 残差连接 src self.norm2(src) # 层归一化 return src代码解读与注意事项分头操作在MultiHeadAttention中我们通过.view()和.transpose()操作将(batch_size, seq_len, d_model)的张量转换为(batch_size, num_heads, seq_len, d_k)这样每个头就能独立计算注意力。掩码应用时机在计算完注意力分数scores之后Softmax之前应用掩码。将需要屏蔽的位置填充一个极大的负值-1e9Softmax后这些位置的权重会趋近于0。位置编码的注册PositionalEncoding中的pe是一个缓冲区使用register_buffer注册。这意味着它不是模型的可学习参数但会成为模型状态字典的一部分随模型一起保存和加载。Pre-Norm结构在TransformerEncoderLayer中我们实现了现在更流行的Pre-Norm结构即先进行层归一化再进入子层最后残差连接。注意残差连接后一般会跟一个Dropout层用于正则化。Dropout的应用在原论文中Dropout被广泛应用在词嵌入后、注意力权重后、前馈网络ReLU激活后等。这是防止过拟合的重要手段。6. 常见问题、调参心得与扩展方向6.1 训练Transformer的典型问题与排查损失不下降或震荡检查学习率Transformer通常需要配合热身策略。使用一个较小的初始学习率在训练初期线性或余弦增加到设定值然后再衰减。Adam优化器默认的lr1e-3可能偏大尝试5e-4或1e-4并配合热身。检查梯度使用torch.nn.utils.clip_grad_norm_进行梯度裁剪防止梯度爆炸。范数阈值通常设置在0.5到5.0之间。检查数据确保输入输出序列的对应关系正确特别是掩码应用是否正确。一个错误的填充掩码可能导致模型学习不到有效信息。检查初始化权重初始化很重要。Transformer通常使用Xavier均匀初始化或He初始化。验证集性能差过拟合增加Dropout适当增大注意力Dropout和前馈网络Dropout的比例原论文用0.1。增加标签平滑这被证明是非常有效的正则化手段。使用更激进的数据增强对于NLP任务可以是随机替换、删除、交换词语对于CV任务则是经典的图像增强。训练速度慢检查批次大小在GPU内存允许的情况下尽量使用大的批次大小能更好地利用GPU并行能力。使用混合精度训练使用torch.cuda.amp进行自动混合精度训练可以显著减少显存占用并加速计算。优化注意力计算对于超长序列标准的注意力计算复杂度是O(n^2)显存和计算都无法承受。可以研究稀疏注意力、局部注意力或线性注意力等变体。6.2 关键超参数的经验之谈超参数典型值/范围作用与影响调参建议d_model (模型维度)512, 768, 1024表征向量的维度直接影响模型容量。资源允许下越大越好但需与层数平衡。768是BERT-base的常用值。num_layers (层数)6, 12, 24编码器/解码器的堆叠层数。层数增加能提升模型能力但也更难训练。通常从6或12层开始。num_heads (头数)8, 12, 16多头注意力的头数。通常设为d_model能被整除的数。头数多能捕捉更多样关系但单头维度会变小。d_ff (前馈网络维度)2048, 3072, 4096前馈网络中间层的维度。通常是d_model的4倍。是模型参数的主要组成部分影响较大。dropout0.1用于防止过拟合的丢弃率。一个可靠的起点是0.1。数据量小可适当增加模型很大或数据很多可适当减少。学习率1e-4优化器的步长。必须配合热身。Adam优化器下1e-4是常见起点使用线性热身到该值。标签平滑0.1软化目标分布正则化。0.1是一个广泛使用的有效值能稳定提升模型泛化性能。6.3 超越原版重要的Transformer变体与演进Transformer本身也在不断进化。了解这些变体有助于你理解当前的研究和应用前沿BERT (Encoder-only)仅使用Transformer编码器通过“掩码语言模型”和“下一句预测”任务进行预训练。它生成的词向量是上下文相关的极大地推动了NLP发展。GPT (Decoder-only)仅使用Transformer解码器带掩码的自注意力通过“自回归语言建模”任务预训练。它擅长文本生成是当今大语言模型LLM的基石。T5 (Encoder-Decoder)将所有NLP任务都重构为“文本到文本”的格式统一使用标准的编码器-解码器架构处理体现了Transformer的通用性。Vision Transformer将图像分割成固定大小的图块线性投影为序列然后输入标准的Transformer编码器。它证明了自注意力在纯视觉任务上也能超越CNN。Efficient Transformers针对O(n^2)复杂度问题提出的改进如Linformer低秩近似、Reformer局部敏感哈希、Performer随机特征映射等旨在处理更长序列。架构改进如Pre-LN将层归一化放在残差块之前训练更稳定、DeepNorm一种新的初始化与残差连接方式用于训练极深模型等。学习Transformer从理解原论文和经典教程如李宏毅老师的讲解开始然后动手实现一个迷你版最后在具体任务如机器翻译、文本分类上调试应用是条扎实的路径。这个模型的设计之美在于其模块化和简洁性每个部分都有明确的功能。当你理解了这些基础构件再看各种眼花缭乱的变体时就能一眼看出它们是在哪个环节做了怎样的修改是为了解决什么问题。这才是举一反三的关键。