Attention 机制 02 - Add&Norm 残差机制

残差连接与 LayerNorm 数值推导:接续 bank 词向量的完整计算过程


步骤 0 --- 衔接上一步

上一步自注意力结束后,bank 产生了两个不同的输出向量(句子 A/B)。现在这两个向量要经过残差连接 再经过 LayerNorm,才算完成编码器一层的第一个子层。

论文公式写法(每个子层都长这样):

scss 复制代码
输出 = LayerNorm( x + Sublayer(x) )

其中 x 是子层的输入,Sublayer(x) 是子层(这里是自注意力)的计算结果。

我们手头的三个向量(d_model = 4)

d₀ d₁ d₂ d₃ 说明
x(输入) 0.80 0.40 0.60 -0.10 ← bank 的原始 embedding
Attn_A 0.44 0.12 0.12 0.31 ← 句子 A 注意力输出(river)
Attn_B 0.32 0.19 0.28 0.23 ← 句子 B 注意力输出(street)

两句 bank 的注意力输出已经不同了。 但还没做残差和 Norm。接下来两步才把这些向量真正"定型"。


步骤 1 --- 残差连接:x + Sublayer(x)

做法极简:把注意力输出原始输入逐元素相加。

ini 复制代码
residual = x + Attn(x)

句子 A 的计算

ini 复制代码
x       = [0.80, 0.40, 0.60, -0.10]
Attn_A  = [0.44, 0.12, 0.12,  0.31]
        ─────────────────────────────
res_A   = [1.24, 0.52, 0.72,  0.21]  ← 残差结果

句子 B 的计算

ini 复制代码
x       = [0.80, 0.40, 0.60, -0.10]
Attn_B  = [0.32, 0.19, 0.28,  0.23]
        ─────────────────────────────
res_B   = [1.12, 0.59, 0.88,  0.13]

注意: 残差加法之后,两个向量的差距缩小了------因为两句共享了同一个 x。这不是坏事,LayerNorm 之后的输出还是会保留足够的差异用于消歧。残差的核心价值在下一步讲。


步骤 2 --- 为什么需要残差连接?

这是一个设计选择,不是数学必须。但它解决了一个训练层数越多越容易失败的根本问题。

问题:梯度消失

神经网络靠反向传播来学习:计算损失对每个参数的梯度,然后更新参数。梯度从最后一层往前传,每经过一层都要乘以那一层的导数。

如果导数普遍小于 1,乘 6 次之后梯度就会变得极小------趋近于 0。参数几乎不更新,网络学不动了。这叫梯度消失

没有残差 vs. 有残差的梯度对比

没有残差连接

r 复制代码
输出 = F(x)
∂Loss/∂x = ∂Loss/∂F · ∂F/∂x

梯度只能走"穿过 F"这一条路。如果 F 的导数小,梯度就衰减。6 层叠下来,第 1 层收到的梯度可能已经乘了六次小数 → 接近 0。

梯度保留比例
层 6 1.000
层 5 0.600
层 4 0.360
层 3 0.216
层 2 0.130
层 1 0.078

第 1 层梯度只剩 8% --- 几乎学不动

有残差连接

scss 复制代码
输出 = x + F(x)
∂Loss/∂x = ∂Loss/∂输出 · (1 + ∂F/∂x)

导数里多了一个 +1。即使 ∂F/∂x 趋近于 0,整个导数还剩 1------梯度可以"原封不动"地穿过这一层继续往前传。

梯度保留比例
层 6 1.000
层 5 0.970
层 4 0.941
层 3 0.913
层 2 0.885
层 1 0.858

第 1 层梯度还有 86% --- 正常学习
另一个角度理解残差: 每一层不用"从头学一个变换",只需要学"原来的基础上改哪里"------即学习残差(residual = 差量) 。从 0 出发学一个复杂变换很难;在已有结果上学一个小修正容易得多。这也是"residual"这个名字的来历。

残差连接由 ResNet(2015,图像识别)引入,Transformer 直接借用了这个设计。它是让网络能堆到 6 层、甚至后来几百层的关键。


步骤 3 --- Layer Normalization

残差之后,向量要过 LayerNorm 。它把向量重新拉到均值 0、方差 1 附近,然后用两个可学习参数微调。

scss 复制代码
LayerNorm(z) = γ · (z − μ) / (σ + ε) + β

符号含义

符号 含义
z 输入向量(这里是残差结果)
μ z 各维度的均值
σ z 各维度的标准差
ε 极小数(防止除以 0,取 1e-6)
γ 可学习缩放参数(初始化为 1)
β 可学习偏移参数(初始化为 0)

对句子 A 的 res_A = [1.24, 0.52, 0.72, 0.21] 完整计算

第一步:算均值 μ

ini 复制代码
μ = (1.24 + 0.52 + 0.72 + 0.21) / 4
  = 2.69 / 4
  = 0.6725

第二步:算方差 σ²,再开方得 σ

ini 复制代码
各分量减均值:  [0.568, -0.153, 0.048, -0.463]
各分量平方:    [0.323,  0.023,  0.002,  0.214]

方差 σ² = (0.323 + 0.023 + 0.002 + 0.214) / 4 = 0.1405
标准差 σ = √0.1405 ≈ 0.3748

第三步:归一化 (z − μ) / (σ + ε)

ini 复制代码
z_norm = [0.568, -0.153, 0.048, -0.463] / 0.3748
       ≈ [1.516, -0.408, 0.128, -1.235]

第四步:乘 γ 加 β(初始 γ=1,β=0,训练后会变)

ini 复制代码
LN_A = 1 × [1.516, -0.408, 0.128, -1.235] + 0
     = [1.516, -0.408, 0.128, -1.235]

可以拖动滑块感受 γ 和 β 的效果(原 HTML 中有交互滑块,γ 范围 0.13,β 范围 -22)

当 γ=1.0,β=0.0 时:LN 输出 = [1.516, -0.408, 0.128, -1.235],均值 = 0.000,标准差 = 1.000


步骤 4 --- 为什么需要 LayerNorm?

归一化是为了稳定训练。我们来看不 Norm 会发生什么。

问题:Internal Covariate Shift(内部协变量偏移)

每一层的输出分布会随着训练变化------前一层参数一更新,后一层收到的输入分布就变了,后一层又得重新适应新分布。层数越多,这种"地基不停动"的现象越严重,导致训练不稳、需要极小的学习率、收敛慢。

归一化把每一层的输入强制拉到稳定的分布(μ≈0,σ≈1),让后面的层不用操心"输入的尺度变了"这件事,可以安心学习真正有用的特征变换。

LayerNorm vs BatchNorm --- 为什么 Transformer 选 LayerNorm

BatchNorm(CNN 常用) LayerNorm(Transformer 用)
归一化方向 同一维度、跨 batch 内所有样本 同一个样本、跨所有维度
batch 依赖 需要足够大的 batch 才统计稳定 和 batch 大小无关
序列长度 序列长度不固定时,跨样本对齐很麻烦 和序列长度无关
推理阶段 需要维护"移动均值" 训练推理行为完全一致,无移动统计量负担

一句话区别: BatchNorm 是"纵向看"(同一维度跨样本),LayerNorm 是"横向看"(同一样本跨维度)。序列模型天然偏爱 LayerNorm。

γ 和 β 为什么要可学习?

纯归一化会破坏向量原有的信息------比如某个维度本来就应该比其他维度大。γ 和 β 让模型在归一化之后自己决定把哪个维度缩放到多大、偏移多少。初始化成 γ=1,β=0(相当于"先不做任何额外变换"),然后在训练中学出最合适的值。

一个常见混淆: LayerNorm 里的均值和标准差,是针对一个向量的所有维度算的,不是针对一批样本算的。4 维向量就只用 4 个数来算均值和方差。


步骤 5 --- 合起来:完整子层公式

把三步串成一条流水线,就是论文里那行公式的完整含义:

scss 复制代码
输出 = LayerNorm( x + Attention(x) )

bank 经过完整子层后的最终向量

d₀ d₁ d₂ d₃ 说明
输入 x 0.80 0.40 0.60 -0.10 两句相同
句子 A 1.516 -0.408 0.128 -1.235 ← river 语境
句子 B 1.312 -0.201 0.447 -1.558 ← street 语境

这两个向量会作为输入进入同一层的第二个子层(FFN) ,再经过同样的"+ LayerNorm"包装,然后传到第 2 层......一共重复 6 次。

一层的完整数据流

scss 复制代码
x ──→ [Multi-Head Attention] ──→ Attn(x)
x ──────────────────────────────────┐
                                    ↓ 相加
                              x + Attn(x)
                                    ↓ LayerNorm
                              z = LN(x + Attn(x))  ← 传给 FFN
z ──→ [Feed-Forward Network] ──→ FFN(z)
z ──────────────────────────────────┐
                                    ↓ 相加
                              z + FFN(z)
                                    ↓ LayerNorm
                              本层最终输出

残差 + LayerNorm 的分工:

  • 残差连接解决梯度消失------让梯度有条"高速公路"跨层直传,使 6 层堆叠成为可能。
  • LayerNorm 解决训练不稳------把每层的输入分布固定在 μ=0、σ=1,让各层安心学习而不必追赶分布漂移。
  • 两者各司其职,缺一不可。

接下来可以深入哪里

  • 下一步:FFN 数值推导
  • 转成 PyTorch 代码
  • 多头拼接过程
相关推荐
东风破_4 小时前
LeetCode 209 · 滑动窗口经典题型
算法
计算机安禾4 小时前
【c++面向对象编程】第48篇:Lambda表达式与std::function:OOP中的函数式编程
java·c++·算法
手写码匠4 小时前
【实战评测】华为云 MaaS 平台 DeepSeek 大模型推理服务 + Dify 一键部署全攻略
人工智能·深度学习·算法·aigc
咪饭只吃一小碗4 小时前
JS算法基础: 常用方法整理
算法·程序员
z200509305 小时前
今日算法(回溯算法)
数据结构·算法
毅炼5 小时前
今日LeetCode 摸鱼打卡
java·算法·leetcode
m0_629494735 小时前
LeetCode 热题 100-----28. 两数相加
数据结构·算法·leetcode·链表
菜菜的顾清寒5 小时前
力扣HOT100(25)环形链表
算法·leetcode·链表
学不懂飞行器6 小时前
【2024电赛H题硬核解析】自动行驶小车满分对策:多路灰度循迹与陀螺仪“交替盲走”融合算法(附源码)
stm32·单片机·嵌入式硬件·算法·电赛