残差连接的原理与梯度传播

残差连接的原理与梯度传播

Jackknifer
Jackknifer 博主

深层神经网络需要让信息与梯度经过许多计算模块。残差连接通过 y=x+F(x)y=x+F(x) 提供直接的加法路径,使模块能够学习输入的更新量。本文说明残差连接的计算结构、梯度传播、二维数值计算,以及它在 Transformer 中的应用。

一、深层网络为什么需要残差连接

普通神经网络模块写为 xl+1=Fl(xl)x_{l+1}=F_l(x_l)。随着网络加深,模型可能出现训练误差上升的退化问题(Degradation Problem)。即使新增加的网络层在理论上能够学习恒等映射,实际优化过程也未必容易得到相应参数。

残差模块将输出写成:

y=x+F(x)\boxed{y=x+F(x)}

这里,xx 是输入,F(x)F(x) 是变换分支的结果。输入还会沿恒等路径直接传给加法节点。

二、残差的含义

如果目标映射为 H(x)H(x),可写为:

H(x)=x+F(x),F(x)=H(x)−xH(x)=x+F(x),\qquad F(x)=H(x)-x

例如:

x=[253],H(x)=[2.54.83.1]x=\begin{bmatrix}2\\5\\3\end{bmatrix},\quad H(x)=\begin{bmatrix}2.5\\4.8\\3.1\end{bmatrix}

那么残差为:

F(x)=[0.5−0.20.1]F(x)=\begin{bmatrix}0.5\\-0.2\\0.1\end{bmatrix}

相加后得到目标输出。残差可以理解为相对于输入的调整量。残差数值较小,不代表这个函数一定容易学习。

2.1 交互演示:输入与残差

调整滑块,观察普通变换的结果与残差相加后的结果。

单独打开残差结构演示:调整输入和残差更新量,支持单独打开。

三、残差连接与梯度传播

3.1 普通网络的梯度

先考虑标量情况。普通网络中:

xl+1=Fl(xl)x_{l+1}=F_l(x_l)

由链式法则:

∂L∂xl=∂L∂xl+1Fl′(xl)\frac{\partial L}{\partial x_l} =\frac{\partial L}{\partial x_{l+1}}F_l'(x_l)

经过 nn 层:

∂L∂x0=∂L∂xn∏l=0n−1Fl′(xl)\boxed{ \frac{\partial L}{\partial x_0} =\frac{\partial L}{\partial x_n} \prod_{l=0}^{n-1}F_l'(x_l) }

如果 20 层的局部导数均为 0.50.5,则乘积为:

0.520≈9.54×10−70.5^{20}\approx9.54\times10^{-7}

梯度可能因多次相乘而变得很小,导致靠近输入的网络层更新缓慢。

3.2 残差模块的梯度

残差模块为:

xl+1=xl+Fl(xl)x_{l+1}=x_l+F_l(x_l)

求导得到:

∂xl+1∂xl=1+Fl′(xl)\boxed{ \frac{\partial x_{l+1}}{\partial x_l} =1+F_l'(x_l) }

继续应用链式法则:

∂L∂xl=∂L∂xl+1(1+Fl′(xl))\frac{\partial L}{\partial x_l} = \frac{\partial L}{\partial x_{l+1}} (1+F_l'(x_l))

公式中的 11 来自恒等路径。例如,当 Fl′(xl)=0.01F_l'(x_l)=0.01 时,整个模块的导数是 1.011.01。变换分支的导数接近零时,模块的导数接近 1。

向量情况下,用 Jacobian(雅可比矩阵)表示:

Jy=I+JFJ_y=I+J_F ∇xL=(I+JF)T∇yL\boxed{\nabla_xL=(I+J_F)^T\nabla_yL}

II 是单位矩阵。上游梯度沿恒等路径直接传播,同时还经过变换分支,最后在输入位置相加。

3.3 多层残差网络

多个残差模块连续连接:

xl+1=xl+Fl(xl)x_{l+1}=x_l+F_l(x_l)

逐层展开:

xn=x0+∑l=0n−1Fl(xl)\boxed{x_n=x_0+\sum_{l=0}^{n-1}F_l(x_l)}

对标量输入求导:

∂xn∂x0=1+∂∂x0∑l=0n−1Fl(xl)=∏l=0n−1(1+Fl′(xl))\frac{\partial x_n}{\partial x_0} =1+\frac{\partial}{\partial x_0}\sum_{l=0}^{n-1}F_l(x_l) =\prod_{l=0}^{n-1}(1+F_l'(x_l))

这一结果揭示了恒等连接提供的直接梯度路径。

需要注意,残差连接并不能保证梯度始终稳定。当 Fl′=−1F_l'=-1 时,局部导数 1+Fl′1+F_l' 等于零。多个局部导数绝对值较大时,仍可能出现梯度爆炸。

3.4 交互实验:梯度倍率

假设每层变换分支的标量导数都等于 aa,那么:

Gplain=∣a∣n,Gresidual=∣1+a∣nG_{\mathrm{plain}}=|a|^n,\qquad G_{\mathrm{residual}}=|1+a|^n

拖动滑块改变层数与局部导数。特别可以观察 a=−1a=-1 时的结果。

单独打开梯度传播实验:调整层数和局部导数,实时更新曲线与计算结果,支持单独打开。

四、二维数值计算

考虑线性残差模块:

y=x+Wx,x=[12],W=[0.10.2−0.10.3]y=x+Wx,\qquad x=\begin{bmatrix}1\\2\end{bmatrix},\quad W=\begin{bmatrix}0.1&0.2\\-0.1&0.3\end{bmatrix}

4.1 前向传播

先计算变换分支:

Wx=[0.1×1+0.2×2−0.1×1+0.3×2]=[0.50.5]Wx= \begin{bmatrix} 0.1\times1+0.2\times2\\ -0.1\times1+0.3\times2 \end{bmatrix} =\begin{bmatrix}0.5\\0.5\end{bmatrix}

因此:

y=[1.52.5]\boxed{y=\begin{bmatrix}1.5\\2.5\end{bmatrix}}

4.2 损失与输出梯度

令目标向量为 t=[1,1]Tt=[1,1]^T,采用平方误差:

L=12∥y−t∥2=12[(1.5−1)2+(2.5−1)2]=1.25L=\frac12\|y-t\|^2 =\frac12[(1.5-1)^2+(2.5-1)^2] =1.25

输出梯度为:

∇yL=y−t=[0.51.5]\nabla_yL=y-t= \begin{bmatrix}0.5\\1.5\end{bmatrix}

4.3 输入梯度

因为 y=(I+W)xy=(I+W)x,根据向量链式法则:

∇xL=(I+W)T∇yL\nabla_xL=(I+W)^T\nabla_yL

代入:

∇xL=[1.1−0.10.21.3][0.51.5]=[0.42.05]\nabla_xL= \begin{bmatrix}1.1&-0.1\\0.2&1.3\end{bmatrix} \begin{bmatrix}0.5\\1.5\end{bmatrix} =\boxed{\begin{bmatrix}0.4\\2.05\end{bmatrix}}

也可以分解为两条路径:

∇xL=[0.51.5]⏟恒等连接+[−0.10.55]⏟变换分支\nabla_xL= \underbrace{\begin{bmatrix}0.5\\1.5\end{bmatrix}}_{\text{恒等连接}} + \underbrace{\begin{bmatrix}-0.1\\0.55\end{bmatrix}}_{\text{变换分支}}

梯度沿两条路径传播,并在共同输入位置相加。

五、为什么容易实现恒等映射

考虑 y=x+F(x;θ)y=x+F(x;\theta)。只要残差分支输出零,整个模块就实现恒等映射:

F(x;θ)=0⟹y=xF(x;\theta)=0\quad\Longrightarrow\quad y=x

例如:

F(x)=W2σ(W1x+b1)+b2F(x)=W_2\sigma(W_1x+b_1)+b_2

当 W2=0W_2=0 且 b2=0b_2=0 时,残差为零。这使新增模块在结构上容易表示恒等映射。实际训练是否找到理想参数,仍取决于优化算法、参数初始化与数据。

六、Transformer 中的残差连接

Transformer 的 Attention(注意力机制)负责不同 Token 之间的信息交换,FFN(前馈神经网络)进一步变换每个 Token 的特征。二者通常分别具有残差连接。

输入形状一般是:

X∈RB×T×dX\in\mathbb R^{B\times T\times d}

其中 BB 是批量大小,TT 是序列长度,dd 是隐藏特征维度。

6.1 Pre-LN

Pre-LN(预归一化)在子模块之前执行 LayerNorm:

U=X+MHA⁡(LN⁡(X))U=X+\operatorname{MHA}(\operatorname{LN}(X)) Y=U+FFN⁡(LN⁡(U))Y=U+\operatorname{FFN}(\operatorname{LN}(U))

6.2 Post-LN

Post-LN(后归一化)在残差相加之后执行 LayerNorm:

U=LN⁡(X+MHA⁡(X))U=\operatorname{LN}(X+\operatorname{MHA}(X)) Y=LN⁡(U+FFN⁡(U))Y=\operatorname{LN}(U+\operatorname{FFN}(U))

对于抽象变换 FF,对应的 Jacobian 为:

Pre-LN:J=I+JFJLN⁡\text{Pre-LN}:\quad J=I+J_FJ_{\operatorname{LN}} Post-LN:J=JLN⁡(I+JF)\text{Post-LN}:\quad J=J_{\operatorname{LN}}(I+J_F)

Pre-LN 中显式存在单位矩阵项,因此常有利于深层模型的梯度传播。两种结构的训练表现还取决于其他设计和训练设置。

6.3 交互演示:归一化位置

选择 Pre-LN 或 Post-LN,查看 Attention、FFN 及两条残差连接的计算次序。

单独打开Transformer 残差结构:切换 Pre-LN 与 Post-LN,查看公式和计算路径,支持单独打开。

6.4 输入与输出维度

残差加法通常要求:

shape⁡(X)=shape⁡(F(X))\operatorname{shape}(X)=\operatorname{shape}(F(X))

例如,输入形状为 32×128×76832\times128\times768 时,Attention 的最终输出通常保持这一形状。FFN 可以使用 768→3072→768768\rightarrow3072\rightarrow768,中间扩大特征维度,最后恢复原来的维度。

如果维度变化,可以用投影连接:

y=P(x)+F(x),P(x)=Wsxy=P(x)+F(x),\qquad P(x)=W_sx

投影矩阵 WsW_s 带来额外的可训练参数,相应梯度需要经过投影变换。

七、训练稳定性与残差缩放

连续的残差相加可能使激活数值增大。深度模型通常结合归一化、参数初始化、学习率控制与残差缩放来改善训练稳定性。

残差缩放写成:

y=x+αF(x)y=x+\alpha F(x)

其 Jacobian 为:

J=I+αJFJ=I+\alpha J_F

系数 α\alpha 控制变换分支对结果的影响,可以是固定值,也可以通过训练学习。

残差结构提供直接利用输入的能力,但不能保证每个原始特征数值都会保留。例如,若 F(x)=−xF(x)=-x,则输出为零。

八、PyTorch 实现

下面给出具有 Pre-LN 结构的 FFN 残差模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import torch
import torch.nn as nn


class ResidualBlock(nn.Module):
def __init__(self, hidden_dim):
super().__init__()
self.layer_norm = nn.LayerNorm(hidden_dim)
self.linear1 = nn.Linear(hidden_dim, hidden_dim * 4)
self.activation = nn.GELU()
self.linear2 = nn.Linear(hidden_dim * 4, hidden_dim)

def forward(self, x):
hidden = self.layer_norm(x)
hidden = self.linear1(hidden)
hidden = self.activation(hidden)
hidden = self.linear2(hidden)
return x + hidden


model = ResidualBlock(hidden_dim=4)
x = torch.randn(2, 3, 4, requires_grad=True)
y = model(x)
assert y.shape == x.shape

loss = y.square().mean()
loss.backward()
assert x.grad.shape == x.shape

这里的 x+hiddenx+hidden 是逐元素相加。PyTorch 自动求导会计算两条路径的梯度。

下面使用第四节的数据核对结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
import torch

x = torch.tensor([1.0, 2.0], requires_grad=True)
weight = torch.tensor([[0.1, 0.2], [-0.1, 0.3]])
target = torch.tensor([1.0, 1.0])

y = x + weight @ x
loss = 0.5 * ((y - target) ** 2).sum()
loss.backward()

assert torch.allclose(y.detach(), torch.tensor([1.5, 2.5]))
assert torch.allclose(x.grad, torch.tensor([0.4, 2.05]))
assert abs(loss.item() - 1.25) < 1e-6

九、常见问题

9.1 残差连接会增加参数吗?

恒等连接本身不增加参数。使用 Wsx+F(x)W_sx+F(x) 这样的投影连接时,矩阵 WsW_s 需要训练。

9.2 残差连接总能改善效果吗?

效果取决于模型深度、任务、数据和训练设置。残差连接在深层 ResNet 和 Transformer 中尤其常见。

9.3 残差连接与 LayerNorm 各有什么作用?

残差连接提供直接的加法与梯度传播路径,LayerNorm 调整特征的数值分布,两者共同影响训练稳定性。

9.4 为什么 Attention 和 FFN 都有残差连接?

Attention 更新 Token 间的信息,FFN 更新各 Token 的特征。分别设置残差连接后,两个模块都能在已有表示的基础上继续学习变化。

十、参考文献

评论

评论由 GitHub Giscus 提供。若当前网络无法加载,可 前往 GitHub Discussions 查看或参与讨论。