深度学习训练稳定性:从梯度到初始化
从一层线性模型开始,逐步讲清楚梯度、反向传播、梯度消失、梯度爆炸、奇异值、谱半径、Xavier 初始化和 He 初始化。
- AI
- 深度学习
深度学习训练稳定性:从梯度到初始化
在 机器学习建模 里,我们讨论了线性模型、逻辑回归和泛化。再往前走,就会进入神经网络。
你已有的 浅谈神经网络原理 给出了一个重要直觉:
神经网络的核心计算是矩阵乘法、加权求和和非线性变换。
但神经网络不是把层数堆起来就能工作。层数越深,训练越容易遇到数值问题:梯度消失、梯度爆炸、loss 震荡、nan。这篇文章就从最基础的梯度开始,慢慢引出这些问题。
先从一层模型开始
线性回归可以写成:
训练时,我们定义损失函数 ,然后调整 ,让损失变小。
梯度下降的更新形式是:
其中:
- 是参数。
- 是学习率。
- 是梯度。
梯度回答的是:
参数变一点,损失会怎么变?
如果梯度方向告诉我们“往右损失变大”,那我们就往左更新。
多层网络多了什么
一层模型只有一次变换。多层神经网络会把很多层串起来:
这里 是激活函数,比如 ReLU、sigmoid、tanh。
前向传播时,输入一层层变成输出。反向传播时,梯度从损失函数一层层传回前面的参数。
问题就在这里:梯度传回去时,会经过很多次链式法则。
反向传播为什么会连乘
假设网络里有很多中间变量:
x -> h1 -> h2 -> h3 -> L
如果想知道 对损失 的影响,根据链式法则:
深层网络里,这样的乘法会非常多。
如果每一项大多小于 1,乘很多次后会越来越小;如果每一项大多大于 1,乘很多次后会越来越大。
这就是梯度消失和梯度爆炸的根源。
梯度消失:前面的层学不到
假设每一层反向传播都让梯度乘以 0.5。传 20 层:
梯度已经几乎没有了。前面的层参数更新非常小,就像没有学习。
这就是梯度消失。
常见表现:
- loss 下降很慢。
- 前面层参数几乎不变。
- 网络加深后效果不升反降。
sigmoid 和 tanh 在饱和区容易造成这个问题。以 sigmoid 为例:
它的导数是:
导数最大也只有 0.25。当很多层都乘上小导数时,梯度很容易消失。
梯度爆炸:参数更新失控
反过来,如果每层都让梯度乘以 1.5,传 40 层:
梯度会变得非常大。
常见表现:
- loss 突然变得很大。
- 参数更新过猛。
- 训练出现
inf或nan。 - 模型直接崩掉。
这就是梯度爆炸。
权重矩阵也会放大或压缩信号
先忽略激活函数,一个多层网络可以粗略看成多个矩阵相乘:
矩阵会改变向量的长度和方向。某些方向被放大,某些方向被压缩。
衡量这种放大或压缩,一个重要概念是奇异值:
- 奇异值大于 1,对应方向会被放大。
- 奇异值小于 1,对应方向会被压缩。
- 多层连乘后,这种放大和压缩会累积。
在循环神经网络里,还经常看谱半径:
谱半径大致反映矩阵反复作用时信号是容易增长还是衰减。
这也是为什么权重初始化不能随便写一个随机数。
初始化到底想控制什么
好的初始化不是为了让模型“一开始就很准”,而是为了让信号在网络里稳定传播。
它希望做到两件事:
- 前向传播时,每层激活的尺度不要越来越大或越来越小。
- 反向传播时,每层梯度的尺度不要越来越大或越来越小。
换句话说,初始化是在给深层网络一个可训练的起点。
Xavier 初始化
Xavier 初始化常用于 tanh、sigmoid 这类较对称的激活函数。
它让权重方差大致满足:
其中:
fan_in是输入维度。fan_out是输出维度。
目标是平衡输入和输出的方差,让信号不要在层间快速放大或缩小。
PyTorch 示例:
import torch
from torch import nn
linear = nn.Linear(128, 64)
nn.init.xavier_uniform_(linear.weight)
nn.init.zeros_(linear.bias)
He 初始化
He 初始化主要用于 ReLU 及其变体。
ReLU 会把负数变成 0:
这意味着大约一部分激活会被截断。为了补偿这种影响,He 初始化常用:
PyTorch 示例:
import torch
from torch import nn
linear = nn.Linear(128, 64)
nn.init.kaiming_normal_(linear.weight, nonlinearity="relu")
nn.init.zeros_(linear.bias)
如果网络主要使用 ReLU,一般优先考虑 He 初始化。
训练稳定性的其他工具
初始化只是起点。现代深度网络还会配合其他方法。
| 方法 | 解决的问题 | 直觉 |
|---|---|---|
| 残差连接 | 深层网络梯度难传 | 给梯度一条更短路径 |
| BatchNorm / LayerNorm | 激活分布不稳定 | 稳定每层输入尺度 |
| 梯度裁剪 | 梯度爆炸 | 限制梯度最大范数 |
| 合理学习率 | 更新过大或过小 | 控制每次移动幅度 |
| AdamW | 参数尺度差异大 | 自适应调整更新 |
梯度裁剪示例:
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()
如果梯度范数超过 1.0,它会被缩放回来,避免一次更新把模型推崩。
和 Attention 里的缩放有什么关系
在 Attention 机制 里,Scaled Dot-Product Attention 有一项:
为什么要除以 ?本质也是控制数值尺度。
如果 的分数太大,softmax 会变得非常尖锐,梯度可能变小,训练会不稳定。
所以从初始化到 attention 缩放,它们背后的共同目标都是:
不要让数值在网络里失控。
一个排查顺序
训练深度网络不稳定时,可以按这个顺序看:
- 输入数据有没有归一化。
- loss 是否出现
nan或inf。 - 学习率是否过大。
- 初始化是否匹配激活函数。
- 梯度范数是否长期接近 0 或突然暴涨。
- 是否需要梯度裁剪。
- 是否缺少归一化层或残差连接。
可以打印梯度范数:
total_norm = 0.0
for p in model.parameters():
if p.grad is not None:
total_norm += p.grad.data.norm(2).item() ** 2
total_norm = total_norm ** 0.5
print("grad_norm:", total_norm)
如果它长期接近 0,要警惕梯度消失;如果突然极大,要警惕梯度爆炸。
总结
这篇文章从一层模型走到深层网络:
梯度下降 -> 链式法则 -> 梯度消失/爆炸 -> 权重尺度 -> 初始化 -> 稳定训练技巧
深度学习不是简单堆层数。网络越深,越要关注前向信号和反向梯度能不能稳定穿过整张计算图。
下一步可以继续读 文本预处理 和 Embedding与位置编码,从一般神经网络进入大语言模型的输入层。
尺素
0 封尺素
正在取尺素...