想象你走进一家手工巧克力工坊。师傅们接到一袋杂乱的原料——可可豆、糖、奶油,每样都带着自己的“原始性格”。工坊的操作间里,六道工序依次排开:第一道让原料两两碰面、寻找搭配,第二道从更多角度审视每样原料的风味,第三道把每粒原料单独深加工……每道工序之后,都有一位品控师把口味拉回标准区间,而师傅还会特地从上一道工序保留一份“原味样本”叠回去,防止加工过度。六道工序走完,原本零散的原料已经融成一整块“懂彼此”的巧克力——这就是 Transformer 编码器做的事:把一句话里每个词,从孤立的词向量一步步炼成蕴含整句话上下文的特征表示。你不需要任何 AI 基础,只需要跟着这条流水线走一遍,就能明白 BERT 这类模型的“心脏”是怎样工作的。
理解编码器结构是掌握 BERT 等预训练模型的基石。对 AI 从业者来说,它能帮你读懂模型如何提取语义特征、理解深层网络训练中的梯度问题,也为微调、部署与模型改进提供底层直觉。
可视化演示
展示Transformer编码器六层堆叠架构、自注意力QKV矩阵计算与注意力热力图、残差连接与LayerNorm位置对比三大核心概念。
本节配有交互动效(进入视口后自动加载,约 3KB)。
六层编码器堆叠架构 每层含自注意力(SA)+前馈网络(FFN)两个子层 原始论文6层,BERT-Base 12层,BERT-Large 24层 Encoder = Stack(L1...LN), N=6 多层堆叠逐步提取更高层语义特征
编码器总览
巧克力工坊的流水线入口,一袋袋原料被贴好标签,送进操作间。整条流水线由六个几乎相同的操作间串联而成,每个操作间里做两件事:先让原料两两碰面、寻找搭配,再把每粒原料单独深加工。没人规定六个操作间必须一模一样,但工坊主发现“重复同样工序”反而最有效——这就是编码器“六层堆叠”的设计哲学。
在 Transformer 里,输入的一句话先被切成一个个词,再通过嵌入层(Embedding)把每个词变成一串数字组成的“词向量”,相当于给每粒原料贴标签。因为自注意力本身没有先后顺序的概念,还需要加上位置编码(Positional Encoding)来标注每个词在句子中的位置,好比在原料上写上“第 1 号”“第 2 号”。
编码器的核心就是六层完全同构的编码器层。每一层包含两个子层:多头自注意力子层和前馈网络子层,每个子层外面都套着残差连接与层归一化。前一层输出就是后一层的输入,逐层传递:
六道工序结构相同,但每一道都在前一道的基础上加深理解。当词向量走出最后一层时,它们已经不再是孤立的“词”,而是“在特定语境中的词”——这正是编码器存在的意义。
- 输入嵌入把句子里的每个词映射为一个稠密词向量,得到 X_embed,相当于为每种原料建立一张“风味档案卡”。
- 注入位置信息把位置编码 X_pos 逐元素加到词向量上,让模型知道词的先后顺序。
- 逐层套用变换每层都是一个编码器层,对上一层的输出做相同类型的加工。
- 输出语义表示经过 l 层迭代,h_6 就是最终输出,每个词向量都已包含整句话的信息。
自注意力机制
工坊里有位“搭配师傅”,他的任务很特别:每一粒原料都要望着屋子里其他所有原料,判断“我和谁在一起最合适”。可可豆会倾向于和奶油搭配,而不太会搭理包装纸。搭配师傅做完判断后,会把其他原料的“精华”按亲疏比例抽一点到自己身上——于是在这间屋子里,每一粒原料都吸收了一点全场的气息。
在 Transformer 中,这位搭配师傅就是自注意力机制(Self-Attention)。每个词向量 X 会同时扮演三个角色:查询 Q(Query,“我在找谁”)、键 K(Key,“我有什么特征”)和值 V(Value,“我能提供什么信息”)。它们分别由 X 乘以三个不同的权重矩阵得到:
经过这五步,“我”这个词已经不再是孤立的“我”,而是“处在整句话语境中的我”。搭配师傅让每个词都拥有了“全场视野”。
- 生成 Q、K、V把输入 X 分别乘上 W_Q、W_K、W_V,得到查询、键、值三组向量。这相当于搭配师傅先给每粒原料填三张不同的卡片。
- 点积打分用 Q 与所有 K 做点积,得到词与词之间的相关分数。分数越高,说明两粒原料越“投缘”。
- 缩放防溢出除以 √d_k,把分数拉回到一个温和的数值范围,避免点积太大让 softmax 进入饱和区。
- softmax 归一化把分数变成一组和为 1 的权重,相当于把“投缘程度”换算成“抽取精华的比例”。
- 加权聚合用权重对 V 做加权求和。每个词最终得到的是全场词信息的混合体,这就是“上下文向量”。
多头注意力
一位搭配师傅的眼光再准,也总会有盲区——他可能擅长看口味搭配,却不擅长看口感层次。工坊主于是请了多位师傅:一位管口味、一位管香气、一位管色泽。每位师傅都独立地做判断,最后把大家的意见汇总在一起,形成一份更全面的搭配方案。
这就是多头注意力(Multi-Head Attention)。与其用一组 Q、K、V 做一次注意力,不如把输入拆成 h 组,每组用自己独立的权重投影到不同的“子空间”,并行做 h 次自注意力。每个“头”关注不同的关系模式——有的关注语法依存,有的关注指代关系,有的关注语义相似性。最后把所有头的输出拼起来,再经过一个线性层融合:
多头机制让模型不再“只从一个角度理解关系”,而是像一支专家评审团那样,从多个视角捕捉文本中丰富而微妙的关联。
- 分头投影第 i 个头用独立的权重矩阵把 Q、K、V 投影到低维子空间,等于每位师傅戴上自己的“滤镜”观察原料。
- 并行计算h 个头完全并行,互不干扰,各自完成一次自注意力。
- 拼接结果把所有头的输出沿特征维度拼接成一个长向量,汇总多位师傅的意见。
- 线性融合乘以 W^O 把拼接后的向量投影回 d_model 维,形成多头注意力的最终输出。
前馈网络
搭配师傅让每粒原料都吸收了全场气息,但这还不够——每粒原料自身还需要被“深加工”:可可豆要研磨、糖要焦化、奶油要打发。于是工坊里还有一位“烹饪师傅”,他不管别的原料,只专心处理手头这一粒,把它加工成更精细的形态。
在编码器层里,这位烹饪师傅就是前馈网络(Feed-Forward Network,FFN)。它作用于每一个词向量,且对每个词独立操作、互不干扰。FFN 由两层线性变换组成,中间夹着一个 ReLU 激活函数:先升维再降维,让模型有足够的容量对每个位置的特征做非线性变换。
因为 FFN 对每个词独立处理,它不会在词之间传递信息——那是注意力子层的职责。两者分工明确:注意力负责“词与词对话”,前馈网络负责“每个词自我提升”。
- 第一层线性变换把输入 x 映射到一个更高维的空间(通常是 d_model 的 4 倍),给“深加工”提供更大操作台。
- ReLU 激活把负数全部置零,引入非线性。负值被“剪掉”,相当于把没用的味道丢弃,只保留正向的风味特征。
- 第二层线性变换把高维特征投影回 d_model 维度,作为本子层的输出,便于和残差连接相加。
残差连接
工坊有条不成文的规定:每道工序开工前,师傅都会先留一份“原味样本”。无论工序怎么折腾,最后都要把加工后的成品和原味样本混在一起。这样一来,万一加工过度、味道全变了,原本的底味还在,整体不至于跑偏。
这就是残差连接(Residual Connection),也叫跳跃连接。在编码器里,每个子层的输出不会直接传给下一层,而是先与子层的输入相加:
残差连接的意义远超“防止味道跑偏”。在反向传播时,梯度可以沿着这条捷径“抄近道”直达底层,避免乘以多层矩阵后梯度消失。可以说,没有残差连接,十几层的 Transformer 几乎无法训练。
- 子层处理Sublayer(x) 代表自注意力或前馈网络对输入 x 的加工结果。
- 逐元素相加把原始输入 x 与加工结果相加,形状完全一致才能相加,因此所有子层都保持 d_model 维。
- 跨层传递这个“x + 变化量”的结果成为下一个子层的输入,梯度可以沿着 x 这条直路畅通无阻地回传。
层归一化
每道工序结束,品控师都会登场。他先测量当下成品的“平均味道”和“味道波动幅度”,然后通过一番调整,把整体味道拉回到一个标准区间——既不太浓,也不太淡。这样下一道工序面对的永远是“味道正常”的原料,不会因为上一道太极端而失控。
品控师的绝活就是层归一化(Layer Normalization,LayerNorm)。对一个词向量 x(一个 d_model 维向量),LayerNorm 在特征维度上计算均值和方差,把向量标准化为均值为 0、方差为 1 的分布,再用可学习的缩放参数 γ 和平移参数 β 恢复表达能力。
在原始 Transformer 中,归一化放在残差相加之后,称为 Post-LN;后来的工作发现把它放到子层之前(Pre-LN)能让深层模型训练更稳定。无论哪种位置,品控师都在源源不断地把“走偏的数值”拉回正轨。
- 求均值对每个向量在特征维度上求平均值,代表这个词向量目前的“整体能量水平”。
- 求方差计算各维特征偏离均值的程度,代表“各维之间的差异有多大”。
- 标准化减去均值除以标准差,加上极小量 ε 防止除零。这一步让所有词向量的尺度对齐。
- 缩放平移乘以 γ 再加上 β,γ 和 β 都是可学习参数。恢复模型需要的表达能力,不至于被“标准化”削掉所有个性。
六层堆叠
六道工序全部走完后,巧克力会是什么样子?第一道工序里,原料只是勉强混了个脸熟;第二道、第三道开始有了默契;到第四、第五道时,各种风味已经交织在一起;最后一道工序结束,你尝到的每一口都同时带着可可的苦、糖的甜和奶油的醇——它们再也分不开了。
编码器的六层堆叠正是这样一个渐进抽象的过程。研究表明,底层编码器更关注词法和局部搭配(比如“窗”和“玻璃”相邻出现),中层开始捕捉句法结构(主语、谓语、宾语的关系),高层则逐渐提炼出全局语义(整个句子的主题与情感倾向)。六层不是随意定的数字,而是“够用且不过度”的经验选择——BERT 用了 12 层,GPT 用了 12 层,层数是可以调节的超参数。
每一层的完整变换可以写成复合函数:
所以,“六层堆叠”的本质是让信息在深度上逐级精炼。底层看到的是“词”,高层看到的是“意义”。当这六层全部跑完,每个词向量都携带着整句话的灵魂——这正是下游任务真正需要的特征。
- 函数复合符号 ∘ 表示“先做后者的变换,再做前者的变换”,即把六层编码器按顺序套在一起。
- 逐层传递h_0 先经过第一层得到 h_1,h_1 再经过第二层得到 h_2,如此一直推进到第 6 层。
- 深层输出最终 h_6 就是编码器输出的特征表示,每个向量都被注入了从局部到全局的多层语义。
AI 应用场景
BERT 预训练:用编码器堆出语言理解模型
阶段:预训练
解决的问题:传统词向量无法区分“苹果很好吃”和“苹果手机很贵”中“苹果”的语义区别。BERT 用多层 Transformer 编码器做双向语言建模,通过“掩码语言模型”(随机遮住 15% 的词,让模型预测)和“下一句预测”来学习上下文相关表示。
具体案例:Google 发布 BERT-Base 时,堆叠了 12 层编码器(每层 12 个注意力头、隐藏维度 768),在 SQuAD 问答任务上取得 93.2% 的 F1 分数,一举超过人类基线。如今 HuggingFace 上的 bert-base-uncased 被全球开发者下载数亿次,成为 NLP 领域的“荔枝核”模型。
语义检索与句向量
阶段:特征提取与向量化
解决的问题:关键词搜索无法理解同义改写。将编码器输出的 [CLS] 向量(或对所有 token 向量做池化)作为句向量,语义相近的句子在向量空间中距离更近,从而实现语义检索。
具体案例:Sentence-BERT 用孪生编码器把句子编码为 768 维向量,在 STS-B 语义相似度基准上达到 86% 以上的 Spearman 相关度。电商客服系统用它把用户问题与 FAQ 库匹配,准确率比 BM25 提升约 20 个百分点。
命名实体识别与序列标注
阶段:微调
解决的问题:实体识别需要同时理解上下文。传统 BiLSTM-CRF 对长距离依赖捕捉有限,而编码器输出的每个 token 向量已融合整句信息,直接接一个线性分类器就能识别人名、地名、机构名。
具体案例:在医疗病历文本上,用中文 BERT 编码器微调 NER 模型,对“阿莫西林胶囊”这种多词实体的 F1 值从 82% 提升到 91%,因为编码器能让“阿莫西林”与“胶囊”通过注意力建立强关联,模型更容易判断这是一个药物实体。
Python 演示
import numpy as np def softmax(x): e = np.exp(x - np.max(x, axis=-1, keepdims=True)) return e / np.sum(e, axis=-1, keepdims=True) def layer_norm(x, eps=1e-6): mu = np.mean(x, axis=-1, keepdims=True) var = np.var(x, axis=-1, keepdims=True) gamma = np.ones_like(x) beta = np.zeros_like(x) return (x - mu) / np.sqrt(var + eps) * gamma + beta class SelfAttention: def __init__(self, d_model, d_k): self.d_k = d_k self.W_Q = np.random.randn(d_model, d_k) * 0.1 self.W_K = np.random.randn(d_model, d_k) * 0.1 self.W_V = np.random.randn(d_model, d_k) * 0.1 def forward(self, X): Q = X @ self.W_Q K = X @ self.W_K V = X @ self.W_V scores = Q @ K.T / np.sqrt(self.d_k) attn = softmax(scores) return attn @ V class FeedForward: def __init__(self, d_model, d_ff): self.W1 = np.random.randn(d_model, d_ff) * 0.1 self.b1 = np.zeros(d_ff) self.W2 = np.random.randn(d_ff, d_model) * 0.1 self.b2 = np.zeros(d_model) def forward(self, x): return np.maximum(0, x @ self.W1 + self.b1) @ self.W2 + self.b2 class EncoderLayer: def __init__(self, d_model, d_k, d_ff): self.attn = SelfAttention(d_model, d_k) self.ffn = FeedForward(d_model, d_ff) def forward(self, x): h = layer_norm(x + self.attn.forward(x)) return layer_norm(h + self.ffn.forward(h)) class TransformerEncoder: def __init__(self, num_layers, d_model, d_k, d_ff): self.num_layers = num_layers self.layers = [EncoderLayer(d_model, d_k, d_ff) for _ in range(num_layers)] def encode(self, x): for layer in self.layers: x = layer.forward(x) return x if __name__ == "__main__": np.random.seed(42) seq_len, d_model = 4, 8 encoder = TransformerEncoder(num_layers=6, d_model=d_model, d_k=4, d_ff=16) x = np.random.randn(seq_len, d_model) out = encoder.encode(x) print("输入形状:", x.shape) print("输出形状:", out.shape)
scores = Q @ K.T / np.sqrt(self.d_k)计算词与词之间的相关分数并除以 √d_k,防止点积过大导致 softmax 梯度消失。attn = softmax(scores)把分数转成和为 1 的注意力权重,体现“抽取其他词精华的比例”。return attn @ V按权重聚合所有词的信息,得到融合上下文的输出。return np.maximum(0, x @ self.W1 + self.b1) @ self.W2 + self.b2前馈网络先升维、ReLU 截断、再降维,对每个词做独立深加工。h = layer_norm(x + self.attn.forward(x))残差连接先让原始输入和子层输出相加,再做层归一化,稳定训练。for layer in self.layers: x = layer.forward(x)六层逐层堆叠,前一层输出作为后一层输入。
常见误区
误区我一直以为多头注意力是把一个注意力模块重复跑六次,第一次的输出作为第二次的输入,就像流水线上几个人排队检查一样。
正解多头注意力是并行执行的。h 个头各自用独立的权重矩阵投影 Q、K、V,在同一个输入上同时计算,最后拼接再线性融合。就像多个品鉴师同时从不同角度审视同一块巧克力,而不是轮流看。如果串行,“头”之间就会产生依赖,无法各自专注不同的关系模式。
为何常见:因为日常经验里“多方审查”常被描述成逐级流程,而并行计算不符合直觉,容易把“多头”误解为“多轮”。
误区残差连接不就是把输入和输出加一下吗?就算去掉它,模型应该也能训练,只是可能慢一点。
正解残差连接是深层 Transformer 能训练起来的关键。没有残差,梯度要从最后一层一路乘回第一层,经过几十个矩阵乘法后几乎衰减为零,底层参数根本得不到有效更新。残差提供了梯度“高速公路”,让深层网络第一次变得可以训练。它不是优化选项,而是必要组件。
为何常见:单看加法运算觉得简单,容易低估它对梯度流动的深远影响;只有真正训练深层网络时才会发现没有它就崩。
误区层归一化应该是对一句话里所有词做归一化吧?就像把整锅汤的咸淡调匀一样。
正解LayerNorm 是对每个词向量内部的特征维做归一化,也就是对每个 token 的 d_model 维度求均值方差。一句话里有 4 个词,就会有 4 组独立的 μ 和 σ。与之相对,BatchNorm 才是沿批次维做归一化。这个区别直接影响模型在不同序列长度下的表现稳定性。
为何常见:“层”这个词让人联想到“一整层网络”或“整句话”,加上 BatchNorm 的类比习惯,很容易把归一化范围搞混。
误区既然文章标题叫“六层堆叠”,那 Transformer 编码器是不是必须正好六层?BERT 也是六层吗?
正解层数是超参数,不是硬规定。BERT-Base 用了 12 层,BERT-Large 用了 24 层,GPT 系列甚至堆了 12 到 48 层。六层只是论文里的基准配置。“六层堆叠”强调的是“多层重复同构子层”这个设计模式,而非固定数字。层数越多容量越大,但训练成本和过拟合风险也越高。
为何常见:初学者倾向于把论文中的默认配置当成标准答案,忽略了“超参数可调”这一基本事实。
面试题
Q1. 在 Transformer 编码器中,自注意力机制的 Q、K、V 是如何得到的?
- A.从输入序列中随机采样三组向量
- B.将输入 X 分别乘以 W_Q、W_K、W_V 矩阵← 正确答案
- C.将输入 X 原样复制三份直接使用
- D.用三个不同的激活函数从 X 生成
答案:B。Q、K、V 是输入 X 分别与三个可学习权重矩阵相乘得到的线性投影。类比搭配师傅填的三张卡片,每张卡片由同一份原料档案导出但用途不同。A 是随机无意义,C 少了投影多样性,D 混淆了激活函数与线性投影的概念。
Q2. 自注意力打分后除以 √d_k 的主要目的是什么?
- A.防止点积过大导致 softmax 梯度消失← 正确答案
- B.减少自注意力的计算开销
- C.让每个词只关注邻近词汇
- D.把注意力权重变成对称矩阵
答案:A。当维度 d_k 较大时,点积的方差会随之增大,softmax 容易落入饱和区,梯度趋近于零。除以 √d_k 可以把数值范围拉回温和区间。这就像把说话音量调回适中,让“品鉴师”能听清每句话,而不是被震耳欲聋的声音吓到失聪。
Q3. 前馈网络(FFN)中的激活函数通常使用哪一个?
- A.sigmoid
- B.tanh
- C.ReLU← 正确答案
- D.softmax
答案:C。Transformer 论文中的 FFN 使用 ReLU 作为中间层激活函数,即 max(0, x)。ReLU 计算简单且能缓解梯度消失。sigmoid 和 tanh 在深层网络中容易饱和,softmax 是归一化函数而非激活函数,一般不用在 FFN 中。
Q4. 残差连接在深层编码器中最核心的贡献是什么?
- A.增加可学习的参数数
- B.给梯度提供直达通道防退化← 正确答案
- C.让自注意力机制变成多头
- D.对隐藏特征做归一化处理
答案:B。残差连接让梯度可以从深层沿“捷径”直接传到浅层,缓解深层网络中的梯度消失与性能退化问题。它不引入新参数,也和多头、归一化无关。就像工坊保留原味样本,保证加工过程再长也不丢底味。
Q5. 层归一化(LayerNorm)归一化的是哪个维度?
- A.整个批次中的全部样本
- B.单个词向量内部的特征维← 正确答案
- C.序列中词与词之间的维度
- D.六个编码器层之间的维度
答案:B。LayerNorm 对每个 token 的 d_model 特征维求均值和方差,独立做标准化。一句话有 N 个词,就有 N 组 μ 和 σ。选项 A 是 BatchNorm 的做法,C 和 D 混淆了归一化的作用维度。
Q6. 多头注意力中,多个“头”之间的关系是什么?
- A.串行执行,一个头接一个头处理
- B.并行独立,学习不同的特征子空间← 正确答案
- C.多个头完全共享相同的权重
- D.每个头只处理片段而非全序列
答案:B。每个头有独立的 W_Q、W_K、W_V 投影矩阵,在相同输入上并行计算,各自关注不同的关系模式(语法、指代、语义等)。串行会让头之间互相影响,共享权重则退化成单头,只处理片段则会丢失全局视野。
Q7. 标准的 Transformer 编码器能够看到输入序列的哪个部分?
- A.只能看到当前词左侧的词汇
- B.只能看到当前词右侧的词汇
- C.同一层内所有词相互可见← 正确答案
- D.只能看到相邻的两个词汇
答案:C。编码器中的自注意力没有掩码,每个位置的 Q 都能与所有位置的 K 计算分数,因此是双向的。这区别于解码器中的 masked self-attention。就像搭配师傅放眼整个屋子,而不是只看左边或右边的邻居。
Q8. 以下关于 Transformer 编码器层的说法,正确的有哪些?
- A.每层包含多头自注意力子层← 正确答案
- B.每层包含前馈网络子层← 正确答案
- C.每层只包含一个线性变换
- D.每个子层均有残差与层归一化← 正确答案
答案:ABD。每个编码器层由多头自注意力和前馈网络两个子层组成,且每个子层都包裹在残差连接和层归一化中。C 错在编码器层包含多个线性变换(Q/K/V 投影、FFN 的两层线性、输出投影),远不止一个。
Q9. 关于层归一化位置(Post-LN 与 Pre-LN)的说法正确的是?
- A.后置归一化在残差相加之后进行← 正确答案
- B.前置归一化深层训练通常更稳定← 正确答案
- C.两种方案在浅层效果完全相同
- D.两种方案在深层完全等价
答案:AB。Post-LN 是原始 Transformer 做法,把 LayerNorm 放在 Add 之后;Pre-LN 先归一化再进子层,残差分支保持恒等,深层训练更稳定。C 和 D 的说法都过于绝对:浅层表现可能接近但不完全相同,深层更不可能完全等价。
Q10. 关于深层编码器的训练稳定性,下列说法正确的有哪些?
- A.残差连接帮助梯度直达底层← 正确答案
- B.前置归一化比后置更深更稳← 正确答案
- C.层归一化的位置会影响梯度流← 正确答案
- D.深度加深必然导致梯度消失
答案:ABC。残差连接为梯度提供直达通道,Pre-LN 在子层前归一化可让残差分支保持恒等映射,从而让深层 Transformer 训练更稳定。归一化位置直接影响梯度流动路径。D 错在“必然”——残差和层归一化的设计目标正是克服深度加深带来的梯度消失。