深度学习 - 15 Transformer 架构(0)

发布时间:2026/9/24 17:01:02
深度学习 - 15 Transformer 架构(0) Transformer 架构深度技术文档定位说明:本文档既适合初学者彻底理解 Transformer,也适合工程人员直接实现它。全文以数据流为主线,追踪每一步的 tensor shape 变化,解释每一个设计决策背后的"为什么"。目录Transformer 为什么出现Transformer 总体架构Embedding 与位置编码Self-Attention 深度讲解Multi-Head AttentionResidual + LayerNormFFN(前馈网络)Decoder完整数据流追踪训练阶段推理阶段与 KV CacheTransformer 的局限性现代改进概览1. Transformer 为什么出现要理解 Transformer 的价值,必须先理解它的前辈们都有什么问题。1.1 RNN/LSTM 的根本缺陷RNN(循环神经网络)的计算范式是:h_t = f(h_{t-1}, x_t)即每一步的隐状态h_t依赖于上一步的h_{t-1}和当前输入x_t。这个设计导致了两个根本性问题:问题一:时序依赖,无法并行由于h_t依赖h_{t-1},计算必须按时间步顺序执行。一个长度为 512 的句子,必须顺序执行 512 次 RNN 计算,无法利用 GPU 的并行计算能力。问题二:长距离梯度消失当句子很长时(例如 100 个 token),第 1 个 token 的信息需要经过 100 次非线性变换才能影响到第 100 个 token 的梯度。每次 tanh 激活函数的导数范围在 (0, 1),梯度在反向传播中会指数级衰减:∂L/∂h_1 = (∂h_100/∂h_1) · (∂L/∂h_100) ≈ (tanh'(..) × W)^99 · (∂L/∂h_100) → 接近 0(梯度消失)LSTM 通过"门控"机制缓解了这一问题,但并未从根本上解决——序列过长时,远处的信息仍然难以被准确保留。1.2 CNN 序列模型的限制CNN(卷积神经网络)可以并行处理序列(每个位置同时卷积),这解决了 RNN 的并行问题。但 CNN 的感受野是局部的:一个 kernel size=3 的卷积,每次只能看到相邻 3 个 token要捕获距离为k kk的依赖,需要堆叠O ( k ) O(k)O(k)层卷积(或使用扩张卷积)对于自然语言中的长距离依赖(例如"那个在黑板上写字的男孩是我的朋友","那个…男孩"和"是"之间的主谓关系),CNN 模型需要很深的网络才能捕获。1.3 为什么需要 AttentionAttention 机制的核心思想是:让每个位置都可以直接"看到"序列中所有其他位置,不管距离远近。这样,捕获任意距离的依赖只需要O ( 1 ) O(1)O(1)层操作(而不是 RNN 的O ( n ) O(n)O(n)步或 CNN 的O ( n / k ) O(n/k)O(n/k)层)。Attention 最早用于 seq2seq 模型中的 decoder 对 encoder 的注意力,让 decoder 在生成每个词时,动态关注 encoder 输出序列的不同部分。1.4 为什么 Self-Attention 很重要Self-Attention是 Attention 的进一步演化:序列中每个位置都可以对自身序列内的所有位置做 Attention。这意味着:编码"猫"这个词时,可以同时参考句中所有词“银行”(bank)这个词,在金融和地理两种语境下,可以通过 Self-Attention 捕获不同上下文,学到不同表示句法结构、语义关系、共指消解——都可以通过 Self-Attention 在单一层中建模Self-Attention 的复杂度:对长度为n nn的序列,Self-Attention 的计算复杂度为O ( n 2 ⋅ d ) O(n^2 \cdot d)O(n2⋅d),其中d dd是维度。这既是优点(所有位置对直接建模),也是局限(序列很长时代价高昂)。2. Transformer 总体架构2.1 Encoder-Decoder 结构原始 Transformer(“Attention Is All You Need”,Vaswani et al., 2017)是一个用于序列到序列翻译的模型:输入序列(源语言) ↓ [Encoder] ← 由 N 个 Encoder Block 叠加(原文 N=6) ↓ encoder_output ← shape: [batch, src_len, d_model] ↓ [Decoder] ← 由 N 个 Decoder Block 叠加,同时接收 encoder_output ↓ decoder_output ← shape: [batch, tgt_len, d_model] ↓ [Linear + Softmax] ↓ 概率分布 over 词表 ← shape: [batch, tgt_len, vocab_size]为什么 Encoder 和 Decoder 要分开?这不是唯一的设计选择,而是针对翻译任务的自然划分:Encoder的职责:读懂整个源语言句子,生成每个 token 的上下文相关表示(可以双向看)Decoder的职责:一次生成一个目标语言 token,生成当前 token 时只能看到已生成的历史(单向)现代大型语言模型(GPT 系列)只使用 Decoder,因为它们只做语言建模(预测下一个词);BERT 只使用 Encoder,用于理解任务。2.2 每个 Block 的结构Encoder Block(重复 N 次):输入 x │ ├──→ LayerNorm → Multi-Head Self-Attention → (+x) [Residual] │ └──→ LayerNorm → FFN → (+x) [Residual] │ 输出 x'Decoder Block(重复 N 次):输入 x(目标序列已生成部分) │ ├──→ LayerNorm → Masked Multi-Head Self-Attention → (+x) [Residual] │ ├──→ LayerNorm → Multi-Head Cross-Attention(K,V 来自 encoder_output)→ (+x) [Residual] │ └──→ LayerNorm → FFN → (+x) [Residual] │ 输出 x'3. Embedding 与位置编码3.1 Token Embedding输入是一个整数序列(token ids),例如:["The", "cat", "sat"] → [264, 3857, 9839] (假设 vocab 中的 index)Embedding 层:一个可学习的矩阵E ∈ R^{vocab_size × d_model},将每个 token id 映射为一个d m o d e l d_{model}dmodel​维的向量。输入:[batch, seq_len] (整数) ↓ E[token_id](查表操作) 输出:[batch, seq_len, d_model] (浮点向量)原文中d m o d e l = 512 d_{model} = 512dmodel​=512,现代模型如 GPT-3 使用d m o d e l = 12288 d_{model} = 12288dmodel​=12288。为什么需要 Embedding?整数 token id 是离散的、无结构的——id=264 和 id=265 没有任何语义关联。Embedding 将 token 映射到连续的向量空间,使得语义相近的词(如 “cat” 和 “kitten”)的向量在空间中接近。3.2 为什么需要位置编码Self-Attention 是一个对位置不敏感的操作:Attention("The cat sat") = Attention("cat The sat") = Attention("sat cat The")三个序列的 attention 计算结果相同,因为 attention 只关心 token 之间的关系(通过 Q/K 点积),不关心顺序。但语言的意义高度依赖顺序——“猫吃鱼” 和 “鱼吃猫” 完全不同。因此,必须显式注入位置信息。3.3 Sinusoidal 位置编码原始 Transformer 使用固定的正弦/余弦函数:P E ( p o s , 2 i ) = sin ⁡ ( p o s 10000 2 i / d m o d e l ) PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{model}}}\right)PE(pos,2i)​=sin(100002i/dmodel​pos​)P E ( p o s , 2 i + 1 ) = cos ⁡ ( p o s 10000 2 i / d m o d e l ) PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{model}}}\right)PE(pos,2i+1)​=cos(100002i/dmodel​pos​)其中p o s pospos是 token 的位置(0, 1, 2, …),i ii是向量的维度索引(0, 1, …,d m o d e l / 2 − 1 d_{model}/2 - 1dmodel​/2−1)。直观理解:把位置向量的每两个维度看作一个"时钟":维度i ii周期λ \lambdaλ含义0, 12 π ≈ 6.3 2\pi \approx 6.32π≈6.3每 6 个 token 完成一个完整振荡,编码精细位置2, 32 π × 10000 2 / 512 2\pi \times 10000^{2/512}2π×100002/512更长的周期,编码更粗粒度的位置………510, 5112 π × 10000 ≈ 62832 2\pi \times 10000 \approx 628322π×10000≈62832极长周期,在几乎所有常见序列中单调变化不同维度有不同的"频率",合在一起,每个位置得到一个独特的向量。为什么 sin/cos 可以外推到更长序列?关键性质:位置p o s + k pos + kpos+k的编码可以由位置p o s pospos的编码通过线性变换得到:P E ( p o s + k ) = M k ⋅ P E ( p o s ) PE(pos + k) = M_k \cdot PE(pos)PE(pos+k)=Mk​⋅PE(pos)其中M k M_kMk​是一个只依赖k kk的旋转矩阵。这意味着模型在训练时学到的"位置关系"(相对偏移k kk),在推理时对更长序列仍然适用——只要相对距离关系类似即可。Shape 变化:PE 矩阵:[max_seq_len, d_model] (预先计算好,固定) 输入 embedding:[batch, seq_len, d_model] 相加后:[batch, seq_len, d_model] (shape 不变)实现:importnumpyasnpdefsinusoidal_pe(max_len,d_model):pe=np.zeros((max_len,d_model))pos=np.arange(max_len)[:,None]# [max_len, 1]i=np.arange(0,d_model,2)[None,:]# [1, d_model/2]div_term=10000**(i/d_model)# [1, d_model/2]pe[:,0::2]=np.sin(pos/div_term)# 偶数维pe[:,1::2]=np.cos(pos/div_term)# 奇数维returnpe# [max_len, d_model]3.4 可学习位置编码(Learnable)另一种方案是直接把位置编码设为可学习的参数(与 token embedding 类似):self.pos_embedding=nn.Embedding(max_seq_len,d_model)# 使用时:pos_ids=torch.arange(seq_len).unsqueeze(0)# [1, seq_len]pos_emb=self.pos_embedding(pos_ids)# [1, seq_len, d_model]对比:SinusoidalLearnable参数量0(固定)m a x _ l e n × d m o d e l max\_len \times d_{model}max_len×dmodel​外推能力较好(有数学规律)差(训练长度外的位置没有梯度)灵活性固定,无法适应数据可以从数据中学习位置特征现代使用RoPE/ALiBi 替代GPT-2/BERT 等早期模型使用4. Self-Attention 深度讲解Self-Attention 是 Transformer 最核心的组件。本章从第一性原理出发,完整推导其计算过程。4.1 Q/K/V 的设计动机假设你有一个序列中的词 “bank”。在翻译时,你需要判断它是"银行"还是"河岸"——这取决于上下文中其他词(如 “money” 或 “river”)。Self-Attention 的设计思想:Query(Q):代表"我想查询什么信息"——当前 token 主动发出的查询请求Key(K):代表"我能提供什么信息"——序列中每个 token 对外展示的"索引"Value(V):代表"我实际携带的信息"——当信息被检索时,真正传递出去的内容为什么需要三套不同的矩阵?如果 Q = K = V = X(直接用原始表示),那么一个 token 对自己的注意力得分永远最高(因为x ⋅ x x \cdot xx⋅x总是最大的),这会主导 attention 分布,失去学习其他词关系的能力。三套独立的线性变换(W Q , W K , W V W_Q, W_K, W_VWQ​,WK​,WV​)让模型可以:学习"如何提问"(W Q W_QWQ​)和"如何回答"(W K W_KWK​)是解耦的“检索相关性”(Q·K)和"实际传递信息"(V)是分离的,增加了表达灵活性不同的头可以学习完全不同的 Q/K/V 投影,实现多角度建模4.2 完整公式与 Shape 推导设:输入序列长度为n nn(例如 5 个 token)模型维度d m o d e l = 512 d_{model} = 512dmodel​=512每个 head 的维度d k = d v = 64 d_k = d_v = 64dk​=dv​=64(d m o d e l / h d_{model} / hd