ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Transformer前置知识系统梳理:从RNN、CNN到注意力机制

2026/8/29 15:54:34 拓冰建站 浏览量
Transformer前置知识系统梳理:从RNN、CNN到注意力机制 很多朋友在学 Transformer 时习惯一上来就啃源码、跑模型结果被torch.nn.MultiheadAttention源码里的 reshape 操作、mask 矩阵、位置编码公式弄得一头雾水。我第一次看 Transformer 源码时也有同感并不是代码本身有多难而是它背后聚合了太多前置概念——RNN 的序列建模思路、CNN 的并行计算思想、注意力机制的查询匹配逻辑、词嵌入的表示方式以及位置编码的注入方法。这些概念如果不提前理清读源码就会变成“每个函数都认识连起来不知道在干什么”。这篇文章就来系统梳理 Transformer 的前置概念。内容定位是“复习”不是“精讲”假定你学过深度学习基础但可能对某些细节记忆模糊。我会按神经网络训练基础、序列模型、注意力机制、表示学习这条线把 Transformer 之前的关键知识点逐一串起来。学完之后再去看“手撕 Transformer”一类文章会轻松很多。1. Transformer 解决了什么问题在展开前置概念之前先把 Transformer 本身定位清楚。Transformer 是 2017 年 Google 团队在论文《Attention Is All You Need》中提出的序列建模架构。它最初用于机器翻译任务后来逐渐扩展为自然语言处理、计算机视觉、语音识别等多个领域的通用骨干网络。Swin Transformer、Vision Transformer、BERT、GPT 等模型都建立在 Transformer 基础之上。从问题域来看Transformer 解决的核心问题有三个长距离依赖文本中相距很远的词之间可能存在语义关联模型需要有能力捕捉这种跨位置的依赖关系。并行计算传统循环神经网络RNN必须按时间步逐个处理序列难以大规模并行训练效率受限。全局建模卷积神经网络CNN受限于卷积核大小只能看到局部区域需要堆叠多层才能扩大感受野而 Transformer 的自注意力机制通过一次计算就让序列中任意两个位置直接交互。Transformer 之所以能“一战成名”在于它用注意力机制取代了循环结构实现了全序列范围的并行建模。但它的核心组件并不是凭空产生的注意力机制、嵌入表示、残差连接、层归一化等思想都能在更早的工作中找到影子。所以把前置概念复习扎实是理解 Transformer 最有效率的路径。2. 复习神经网络训练的基础闭环Transformer 本身是一个庞大的神经网络它的训练流程和你熟悉的任意深度学习模型并无区别。如果在理解 Transformer 时忽略了训练环节可能看到损失函数下降时只觉得神奇却不清楚背后的梯度是怎么流动的。2.1 前向传播与损失函数我们可以把一个神经网络看作一个复杂的复合函数输入 - 线性变换 - 非线性激活 - 线性变换 - 非线性激活 - ... - 输出在训练阶段模型接收一批输入数据经过层层计算后得到预测结果y_pred然后和真实标签y_true计算损失值。这个损失值衡量的是模型当前预测得有多差。以分类任务为例最常用的损失函数是交叉熵损失。下面是一个简化的计算过程import numpy as np def softmax(logits): # 为了数值稳定性减去最大值 exp_logits np.exp(logits - np.max(logits, axis-1, keepdimsTrue)) return exp_logits / np.sum(exp_logits, axis-1, keepdimsTrue) def cross_entropy_loss(logits, labels): probs softmax(logits) batch_size logits.shape[0] # 取每个样本真实标签对应的概率值 correct_log_probs -np.log(probs[range(batch_size), labels] 1e-10) return np.mean(correct_log_probs)这里softmax把模型输出的原始得分logits转换成概率分布cross_entropy_loss再计算预测概率与真实类别之间的差异。Transformer 在分类任务如文本分类中的输出层同样使用这个逻辑。理解这一点很重要Transformer 不是某种特殊的黑盒训练机制它依然依赖损失函数来衡量“当前网络输出的质量”并据此更新参数。2.2 反向传播与梯度下降有了损失值接下来就要回答一个问题模型参数应该往哪个方向调整才能让下一次的损失更小答案就是反向传播Back Propagation。通过链式法则从输出层开始逐层计算损失对每个参数的梯度。梯度指示了损失函数上升最快的方向那么参数更新时应该沿着梯度的反方向前进param param - learning_rate * gradient这个公式是梯度下降的最基本形式。实际训练中我们通常使用随机梯度下降SGD及其改进版本如 Adam、AdamW。Transformer 的训练最常用的是 Adam 系列优化器因为它能自适应地调整每个参数的学习率在模型较大时收敛更稳定。下面是一段手动实现梯度下降的示意代码目的是展示完整的参数更新闭环import numpy as np # 演示数据y 2x 1 x np.array([1, 2, 3, 4, 5], dtypenp.float32) y np.array([3, 5, 7, 9, 11], dtypenp.float32) # 初始化参数 w 0.0 b 0.0 learning_rate 0.01 # 手动梯度下降 for epoch in range(500): y_pred w * x b loss np.mean((y_pred - y) ** 2) # 梯度d loss / d w, d loss / d b dw np.mean(2 * (y_pred - y) * x) db np.mean(2 * (y_pred - y)) # 参数更新 w - learning_rate * dw b - learning_rate * db print(f训练后 w {w:.4f}, b {b:.4f})在 Transformer 中反向传播的链条远比这个复杂但基本思想完全一致——计算损失对每一层参数的梯度然后通过优化器更新参数。2.3 归一化与残差连接的作用Transformer 中频繁使用的是层归一化Layer Normalization和残差连接Residual Connection。如果不理解它们的前置概念很难读懂 Transformer 结构图中的Add Norm模块。残差连接把输入直接加到层的输出上即output layer(x) x。这样做的好处是即使深层网络的变换部分效果不好梯度仍然可以通过恒等分支直接回传避免梯度消失。层归一化对每个样本的隐藏层特征做归一化使数据分布保持稳定。和 Batch Normalization 不同层归一化不依赖 batch 大小因此特别适合序列模型。Transformer 解码时是逐个 token 生成的batch 可能很小甚至为 1层归一化在这种情况下依然稳定。这两个设计在 Transformer 中无处不在理解它们能帮助你读懂很多源码中看似多余的 x和norm(x)操作。3. 序列建模的经典方案RNN 与 LSTMTransformer 火起来以后很多人以为循环神经网络已经被完全取代。但 Transformer 中“位置编码”“序列关系建模”等设计其实是在解决 RNN 最擅长解决的问题。所以复习 RNN 和 LSTM能帮你理解 Transformer 为什么在这些地方做出了不同选择。3.1 RNN 的核心思想循环神经网络Recurrent Neural Network的核心是一个共享的循环单元。它按时间步迭代每一步接收当前输入x_t并结合上一步的隐藏状态h_{t-1}更新当前隐藏状态h_t tanh(W_ih * x_t W_hh * h_{t-1} b)从代码角度看一个最简单的 RNN 前向过程可以写成import numpy as np class SimpleRNN: def __init__(self, input_size, hidden_size): # 简化初始化实际会用随机分布 self.W_ih np.random.randn(input_size, hidden_size) * 0.1 self.W_hh np.random.randn(hidden_size, hidden_size) * 0.1 self.b np.zeros((hidden_size,)) def forward(self, inputs): # inputs: 序列shape (seq_len, input_size) h np.zeros((self.W_hh.shape[0],)) hidden_states [] for x in inputs: h np.tanh(x self.W_ih h self.W_hh self.b) hidden_states.append(h) return np.stack(hidden_states, axis0)这样一个结构天然适合处理变长序列。但它的缺点是信息要沿着时间步逐步传递如果序列很长早期位置的信息会在传递过程中不断衰减导致模型很难学习到长距离依赖关系。3.2 LSTM 如何缓解长距离依赖问题长短期记忆网络Long Short-Term Memory通过引入门控机制让信息可以选择性地写入、遗忘和输出。它不要求每个时间步都完全覆盖旧信息而是通过“记忆细胞”维护一条独立的信息高速公路。LSTM 的核心公式有三扇门遗忘门决定上一时刻的记忆细胞有多少应该被遗忘。输入门决定当前输入有多少应该写入记忆细胞。输出门决定当前记忆细胞有多少应该输出到隐藏状态。理解 LSTM 不需要死记公式关键是理解“门”的含义——门就是一个小型 Sigmoid 层输出范围在 0 到 1 之间表示“允许通过的比例”。RNN 没有这种选择性遗忘机制所以会随着时间步累积信息衰减问题。3.3 为什么 RNN 难以并行RNN 的另一个问题是串行计算。每一个时间步的隐藏状态h_t都依赖于h_{t-1}这意味着无法在一个批次内同时对序列中的所有位置进行独立计算。即使你有 8 块 GPU处理长度为 100 的序列时依然要按顺序执行 100 个时间步的计算。这个问题直接促使 Transformer 走向并行架构。Transformer 的核心思路是不再把序列逐个时间步喂给模型而是把整个序列作为一个矩阵输入通过矩阵运算一次处理所有位置。自注意力机制在这个过程中替代了 RNN 的信息传递功能。当你理解了 RNN 的优点和缺点就能理解为什 Transformer 中要引入位置编码。因为 Transformer 不再按顺序处理序列它必须通过额外的方式告诉模型“哪些 token 在前哪些 token 在后”。4. CNN 与序列特征提取的另一种思路提到 Transformer 的前置概念很多人会忽略 CNN。但 CNN 在序列建模中同样扮演过重要角色尤其是一维卷积1D Convolution和 TextCNN 这类模型。4.1 一维卷积如何理解句子一维卷积在自然语言处理中的思路很直观用一个固定大小的卷积核例如宽度为 3 的窗口在词向量序列上滑动提取 n-gram 级别的局部特征。这个操作和在图像上做卷积很像只是把二维卷积变成了沿时间维度滑动的一维操作。例如对一句话“我 / 爱 / 自然 / 语言 / 处理”宽度为 3 的卷积核会依次覆盖“我爱自然”“爱自然语言”“自然语言处理”等多个局部片段从而提取局部语义信息。TextCNN 就是利用这种思路做文本分类的经典模型。它通过多个不同宽度的卷积核捕捉不同范围的 n-gram 特征再通过池化操作得到整个句子的向量表示。4.2 从 CNN 到 TCN 的尝试传统 CNN 在序列建模中的局限是感受野受限。如果两个距离很远的词存在依赖关系需要堆叠很多卷积层或者使用很大的卷积核才能让它们“看见”彼此。时域卷积网络Temporal Convolutional NetworkTCN通过空洞卷积Dilated Convolution扩大感受野。空洞卷积允许卷积核在输入上按照指定间隔跳着取值这样即使卷积核宽度不变也能覆盖更大范围的输入区域。近年来TCN 加 Transformer 的组合在股票预测、时间序列预测等领域很受欢迎因为 TCN 能高效提取局部时序特征Transformer 能捕捉全局依赖关系。不过CNN 也好TCN 也好它们都属于“局部到全局”的层层抽象思路需要堆叠多层才能实现全局交互。而 Transformer 的自注意力一步到位让所有位置之间直接建立联系。这也是“为什么最后是 Transformer”的一个关键答案。从序列处理的发展线索看整个演进过程是RNN - 逐步传递信息串行长距离依赖弱 LSTM/GRU - 门控机制缓解长距离衰减但仍是串行 CNN/TCN - 并行计算但需要多层堆叠扩大感受野 Transformer - 自注意力一步建立全局依赖且可并行理清这条线再看 Transformer 结构图就不会觉得它横空出世了。5. 注意力机制从 Seq2Seq 到 Transformer 的核心如果说 RNN 是 Transformer 要打败的对手那么注意力机制就是 Transformer 最核心的武器。注意力机制的诞生早于 Transformer最早应用在 Seq2Seq 机器翻译模型中。5.1 传统 Seq2Seq 模型的瓶颈Seq2Seq 模型由编码器Encoder和解码器Decoder组成编码器把输入序列压缩成一个固定长度的向量解码器根据这个向量逐步生成输出。这种做法的缺点是明显的当输入序列很长时把所有信息都压缩到一个向量里早期输入的信息很容易在压缩过程中丢失。就像要求一个人听完一整本书后只用一个词总结全书内容然后要求他基于这个词回答书中任意细节——信息损失太大了。5.2 Attention 的直觉聚焦关键信息注意力机制的引入解决了这个问题。它不再是“只看那一个固定向量”而是在解码的每一步重新查看编码器所有位置的隐藏状态并计算它们对当前解码位置的“重要程度”。这个“重要程度”就是注意力权重。数学上它通常通过计算查询向量Query和键向量Key的相似度来得到score Q * K^T attention_weight softmax(score) context attention_weight * VQuery当前需要“查找什么信息”在解码器一侧。Key被查找方的“索引标签”在编码器一侧。Value被查找方真正提供的“内容”。举个直觉的例子当你在图书馆想找一本关于深度学习的书时你的需求向量是 Query书名或索书号是 Key书籍内容是 Value。你先把 Query 和所有 Key 做匹配得到每本书的分数然后通过 softmax 变成权重最后按权重汇总各本书的内容得到一个和你需求最匹配的综合信息。5.3 缩放点积注意力的代码实现Transformer 使用的是缩放点积注意力Scaled Dot-Product Attention比加性注意力计算更高效。它的公式是Attention(Q, K, V) softmax(Q * K^T / sqrt(d_k)) * V除以sqrt(d_k)是为了防止点积结果过大导致 softmax 进入梯度很小的饱和区。下面用 NumPy 实现一个简单的缩放点积注意力import numpy as np def scaled_dot_product_attention(Q, K, V, maskNone): Q, K, V: shape (batch_size, seq_len, d_k) d_k K.shape[-1] # 计算 Q 和 K 的点积 scores np.matmul(Q, K.transpose(0, 2, 1)) # (batch, seq_len, seq_len) # 缩放 scores scores / np.sqrt(d_k) # 如果需要 mask把不需要的位置替换为极小值 if mask is not None: scores np.where(mask, scores, -1e9) # softmax 转换为权重 weights np.exp(scores - np.max(scores, axis-1, keepdimsTrue)) weights weights / np.sum(weights, axis-1, keepdimsTrue) # 加权求和 output np.matmul(weights, V) return output, weights这段代码里最关键的是scores矩阵的解读scores[i][j]表示序列中第i个位置对第j个位置的注意力得分。得分越高说明i越应当关注j。Transformer 中的多头注意力Multi-Head Attention就是把 Q、K、V 分别投影到多个子空间然后并行执行多次注意力计算最后把结果拼接起来。这样做的好处是每个注意力头可以关注不同的关系模式——有的头关注语法依赖有的头关注指代关系有的头关注位置邻近关系。5.4 Self-Attention 与 Cross-Attention 的区别注意力机制根据 Q、K、V 的来源可以分为两种Self-Attention自注意力Q、K、V 都来自同一个序列。也就是说序列中的每个位置都关注序列中的所有位置包括自己。这是 Transformer 编码器的核心操作用来建模输入序列内部的依赖关系。Cross-Attention交叉注意力Q 来自解码器K 和 V 来自编码器。也就是说解码器在生成每个词时去关注输入序列的哪些部分。这是机器翻译等 Seq2Seq 任务中的核心操作。理解这两者的区别是阅读 Transformer 源码的基本功。很多初学者看到MultiheadAttention调用时如果不清楚 Q、K、V 各自的来源就会对维度变换一头雾水。6. 表示学习与位置编码除了注意力机制Transformer 还依赖两个重要的表示学习概念词嵌入Embedding和位置编码Positional Encoding。它们回答了同一个问题的两个侧面“模型如何理解一个词”和“模型如何理解词的位置”。6.1 词嵌入从 One-Hot 到分布式表示在深度学习出现之前文本建模常用 One-Hot 编码。One-Hot 的做法是如果一个词表有 10000 个词每个词就表示成一个长度为 10000 的向量只在对应词的位置上取值为 1其余位置为 0。One-Hot 的问题是向量维度高、稀疏而且任意两个词的向量内积都是 0完全无法表示词之间的语义相似性。词嵌入Word Embedding的思路是把每个词映射到一个低维稠密向量例如 128 维、256 维或 768 维。通过训练语义相近的词在向量空间中的距离也更近。“国王”和“王后”的距离会比“国王”和“苹果”更近。早期有 Word2Vec、GloVe 这类独立的词向量训练工具现在 Transformer 模型则直接把嵌入层作为网络的一部分在训练过程中端到端地学习。PyTorch 中对应的层是torch.nn.Embeddingimport torch import torch.nn as nn vocab_size 10000 embedding_dim 256 embedding nn.Embedding(num_embeddingsvocab_size, embedding_dimembedding_dim) # 假设一个 batch 中有两个句子每个句子 5 个 token input_ids torch.tensor([[1, 5, 23, 78, 12], [3, 8, 45, 6, 99]]) embedded embedding(input_ids) # shape (2, 5, 256) print(embedded.shape)这里的input_ids是文本经过分词后每个 token 在词表中的索引。嵌入层把这些索引映射成稠密向量。6.2 为什么需要位置编码RNN 通过逐个时间步处理序列天然知道词的先后顺序。CNN 靠卷积核在时间维度上的滑动也隐式包含了相对位置信息。但 Transformer 的自注意力计算是“不分先后”的——它同时对整个序列做矩阵运算即使把句子中的词顺序打乱计算出的注意力分数也不会改变。为了让模型感知顺序信息Transformer 在输入嵌入向量上叠加了位置编码。位置编码的作用是给每个位置生成一个独一无二的向量加到 token 的嵌入向量上这样模型既能知道“这个位置是什么词”也能知道“这个词在句子的什么位置”。Transformer 原文中使用的是正余弦函数形式的位置编码PE(pos, 2i) sin(pos / 10000^(2i/d_model)) PE(pos, 2i1) cos(pos / 10000^(2i/d_model))其中pos表示位置d_model表示嵌入维度2i和2i1表示维度的索引。这种形式的编码有几个好处一是值域稳定在 [-1, 1]不会随位置增大而溢出二是可以通过三角函数公式让模型容易学习到相对位置关系。下面给出位置编码的生成代码import numpy as np def positional_encoding(seq_len, d_model): pe np.zeros((seq_len, d_model)) position np.arange(seq_len).reshape(-1, 1) # (seq_len, 1) div_term np.exp(np.arange(0, d_model, 2) * (-np.log(10000.0) / d_model)) pe[:, 0::2] np.sin(position * div_term) # 偶数维度用 sin pe[:, 1::2] np.cos(position * div_term) # 奇数维度用 cos return pe # 生成序列长度为 20嵌入维度为 64 的位置编码 pe positional_encoding(20, 64) print(pe.shape) # (20, 64)除了正余弦位置编码后来也出现了可学习位置编码Learned Positional Embedding也就是说位置向量也作为可训练参数由模型自己学习。BERT 就是采用这种方式。两种方式各有优劣正余弦编码可以外推到比训练时更长的序列可学习编码在训练长度范围内通常表现更稳定。6.3 输入表示的整体过程在 Transformer 中一个 token 进入模型之前会经历如下步骤分词把文本拆分成 token 序列。查嵌入表把每个 token 映射成向量得到token_embedding。生成位置编码根据每个 token 的位置生成位置向量。相加final_embedding token_embedding positional_encoding。如果是 BERT 这类预训练模型还会额外加上 Segment Embedding区分不同句子。最终得到的向量序列输入到 Transformer 编码器中。7. 把这些概念串联成 Transformer前置概念复习到这里你已经具备了理解 Transformer 原文结构图的全部知识。下面把各个概念组合起来看它们如何在 Transformer 中协作。Transformer 的整体架构是编码器-解码器结构。以机器翻译为例编码器读取源语言句子解码器逐步生成目标语言句子。编码器由多个完全相同的层堆叠而成每一层包含两个子层多头自注意力子层让序列内部任意两个位置间建立关联。前馈神经网络子层对每个位置的表示做非线性变换。每个子层后面都跟着残差连接和层归一化。解码器同样由多层堆叠而成每一层包含三个子层掩码多头自注意力子层和编码器类似但每个位置只能关注当前位置及之前的位置不能“偷看”未来。多头交叉注意力子层Q 来自解码器K、V 来自编码器让解码器从源语言中提取信息。前馈神经网络子层。从上到下梳理一遍你会发现词嵌入和位置编码负责把离散 token 变成带位置信息的向量序列。自注意力负责建模序列内部的关系替代 RNN 的信息传递。多头机制让模型能同时关注多种关系模式是特征表达的扩展。前馈网络为每个位置提供非线性变换能力。残差连接和层归一化保证深层网络的训练稳定性。为什么最终是 Transformer 成为主流核心原因是它在长距离建模能力和并行效率之间找到了最佳平衡点。RNN 因串行计算受限CNN 因感受野受限注意力机制虽然计算复杂度高一些但在 GPU 上可以通过大规模矩阵运算来加速。随着算力不断增强这个权衡的天平自然倒向了 Transformer。从“手撕 Transformer”的角度看理解本文前置概念后你的初始目标可以定在“用 PyTorch 从零实现一个 mini Transformer”并不依赖torch.nn.Transformer封装而是自己写多头注意力、前馈网络、位置编码、层归一化和训练循环。这个练习做完你对 Transformer 的理解会远超只会调 API 的人。8. 常见概念混淆与学习建议在学习 Transformer 前置概念的过程中有几组概念特别容易混淆。这里整理成表格方便对照混淆点正确理解说明Transformer 与 Attention注意力机制是组件Transformer 用到了注意力机制Seq2Seq Attention 早于 TransformerSelf-Attention 与 Cross-AttentionSelf 中 QKV 同源Cross 中 Q 与 KV 异源编码器用 Self解码器中间层用 Cross位置编码与词嵌入词嵌入表示语义位置编码表示顺序两者相加得到最终输入LayerNorm 与 BatchNormLayerNorm 对每个样本归一化BatchNorm 对每个特征跨样本归一化Transformer 用 LayerNorm多头注意力与多通道卷积多头是多种关系子空间不是多通道特征图每个头独立计算后拼接RNN 与 Transformer 并行性RNN 串行Transformer 并行这是 Transformer 的核心优势之一学习路线上我建议按照下面的递归方式推进先用 PyTorch 实现单头缩放点积注意力。再加入多头机制实现 Multi-Head Attention。加入残差连接和层归一化搭出一个编码器层。加入掩码机制实现解码器层。叠层后训练一个简单的机器翻译模型比如英语到法语的小规模数据集。每一步都对应本文复习过的一个前置概念。这样做的好处是每行代码都能对应到具体的设计意图而不是照抄源码。我还想补充一点学习心态。Transformer 相关的论文、源码、专栏文章非常多信息过载是这个领域最典型的问题。我的建议是不要试图一次读完所有材料而是定住一条主线——注意力机制的核心计算、多头注意力的维度变化、残差与归一化为什么必要——沿着这条线反复精读。遇到不懂的中间概念再回来复习这样比来回跳读效率高得多。如果你手头有原文 PDF可以重点精读其中第 3.2 节Attention 部分和第 3.3 节位置编码前向过程配合本文的代码示例一起看理解速度会快很多。看完之后再打开 PyTorch 的nn.Transformer源码你会发现自己能看懂大部分实现了。9. 最后说几句Transformer 本身并不复杂它是由若干个你已经学过的概念组合而成的。真正让它显得“难”的是概念密度高、前置依赖多以及大多数资料默认读者已经掌握了 RNN、CNN、注意力机制和词嵌入。把这门前置课补上后续阅读源码、跑实验、做改进都会顺畅很多。我推荐你找一个最短的 Transformer 实现把它从头到尾敲一遍遇到不理解的模块就回到本文对应章节复习。动手敲代码是检验理解的唯一标准光看不写很难真正掌握。如果这篇文章对你准备“手撕 Transformer”有帮助可以收藏备用。接下来你可以继续阅读“Transformer 代码拆解”或“从零实现多头注意力机制”方向的实战文章把前置概念落实到每一行代码里。