深网络训练技巧:BatchNorm / Dropout / 初始化
让深层网络真的能训起来的三大件——为什么需要它们、怎么用、什么时候用。
L3-02 我们知道反向传播怎么算梯度。 但梯度有了不等于能训好深网络 ——
2012 年之前,“训 10 层以上”对学术界都是难题。
直到一系列训练技巧让深层网络可行 —— 这篇讲三大件。
问题:深网络训不动
L3-02 提过:链式法则是连乘。 深层 + sigmoid → 局部梯度 < 0.25 → 10 层之后梯度接近 0。
经典症状:
- 前几层根本不学
- loss 训练 100 epoch 不动
- 改架构、调学习率都不行
解决方案分三个方向:
- 更好的初始化 —— 让一开始梯度就别太大太小
- 更好的激活 —— ReLU 替代 sigmoid
- 正则化 + 归一化 —— BatchNorm、Dropout 等
一、好的初始化
错误做法
W = torch.zeros(...) # 全 0 —— 每个神经元学到一样的东西
W = torch.randn(...) * 1.0 # 方差太大 —— 激活爆炸
Xavier / Glorot 初始化(2010)
让每层输出的方差 ≈ 每层输入的方差:
适合 sigmoid / tanh 激活。
He 初始化(2015)
ReLU 砍掉一半激活 → 需要更大的方差补回来:
ReLU 网络默认用这个。
# PyTorch 默认用 He
nn.init.kaiming_normal_(layer.weight, mode='fan_in', nonlinearity='relu')
一句话:初始化让深网络一开始就别处于”梯度直接死掉”的初始位置。
二、激活函数:ReLU 革命
Sigmoid 的问题
σ(x) = 1 / (1 + e^(-x))
σ'(x) ≤ 0.25 ← 最大值在 x=0 处
每层乘 0.25 —— 10 层后梯度 ≈ 0.0000001。梯度消失。
ReLU = max(0, x)
ReLU'(x) = 1 (x > 0) 或 0 (x ≤ 0)
正值区域梯度恒为 1 —— 不衰减。 计算极快(一个 max)。
问题:负值区域梯度为 0 —— 神经元”死亡”(dying ReLU)。
改良版
| 激活 | 特点 |
|---|---|
| Leaky ReLU | 负值给一点小斜率(如 0.01x) |
| GELU | 平滑版 ReLU,Transformer 默认 |
| Swish / SiLU | ,GLU 系列 |
实战:CNN 用 ReLU / Leaky ReLU;Transformer 用 GELU。
三、Batch Normalization(2015)
直觉
训练时每层的输入分布一直在变(因为前一层参数在变)—— internal covariate shift。
BatchNorm 的招:在每层激活后强制归一化,让分布稳定。
公式
对一个 batch :
- 算均值:
- 算方差:
- 归一化:
- 放缩偏移: ( 可学习)
最后那一步很关键 —— 让模型还能学到不归一化时的分布(如果它需要)。
PyTorch
import torch.nn as nn
model = nn.Sequential(
nn.Linear(100, 256),
nn.BatchNorm1d(256), # ← 这一行
nn.ReLU(),
nn.Linear(256, 10),
)
训练 vs 推理
训练时用当前 batch 的 。 推理时用训练时积累的移动平均 —— 否则单条样本统计无意义。
model.train() # 用 batch 统计
model.eval() # 用移动平均
忘了切 eval 是新手常犯 bug。
替代品
| 方法 | 何时用 |
|---|---|
| BatchNorm | CNN / 图像 |
| LayerNorm | Transformer / NLP(batch 维度有可变长度) |
| GroupNorm | 小 batch 时(< 16) |
| InstanceNorm | 风格迁移 |
Transformer 用 LayerNorm,因为序列长度可变,BN 的 batch 统计不稳。
四、Dropout(2012)
直觉
训练时随机丢弃一部分神经元(设为 0)—— 强迫网络不能依赖任何单一神经元,学到冗余表征。
类似于:
- 集成学习的”训一堆稍微不同的模型再平均”
- 噪声注入让模型 robust
公式
# 训练时
mask = (random < 0.5) # 50% 丢弃
output = input * mask / 0.5 # 缩放剩下的 keep 概率
/ 0.5 是为了让期望保持不变(inverted dropout)。
PyTorch
nn.Sequential(
nn.Linear(100, 256),
nn.ReLU(),
nn.Dropout(0.5), # 50% 丢弃率
nn.Linear(256, 10),
)
推理时关闭
model.eval() 同时关闭 dropout(不丢弃 + 不缩放)。
用量经验
| 网络 | 推荐 dropout |
|---|---|
| 全连接 MLP | 0.3-0.5 |
| CNN 全连接层 | 0.5 |
| CNN 卷积层 | 0.1-0.2 或不用 |
| Transformer | 0.1 |
| 极大模型 + 海量数据 | 0(不需要) |
现代 LLM 多数不用 dropout —— 数据量足够大,过拟合不是主要问题。
五、梯度裁剪(Gradient Clipping)
防止梯度爆炸的暴力方案:
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
如果某次 backward 整体梯度范数 > 1.0,就整体缩放到 1.0。
RNN 和 LSTM 训练里几乎必用。Transformer 训练也常用。
六、残差连接(2015,ResNet)
不是单一技巧,但根本上解决了梯度消失。
普通层: 残差层:
反向传播时:
那个 +1 —— 梯度永远有一条路径直接传回去,不会消失。
让训 100 层、200 层、甚至 1000 层都可能。 现代深度学习架构(CNN、Transformer)几乎全用残差。
一个组合模板
现代 CNN block 的标准长相:
class ResBlock(nn.Module):
def __init__(self, channels):
super().__init__()
self.conv1 = nn.Conv2d(channels, channels, 3, padding=1)
self.bn1 = nn.BatchNorm2d(channels)
self.conv2 = nn.Conv2d(channels, channels, 3, padding=1)
self.bn2 = nn.BatchNorm2d(channels)
def forward(self, x):
residual = x # ← 残差路径
out = self.bn1(self.conv1(x))
out = torch.relu(out)
out = self.bn2(self.conv2(out))
out = out + residual # ← 加回去
return torch.relu(out)
包含:He 初始化(默认)、ReLU、BatchNorm、残差。 深度学习训练的标准配方。
训练实战 checklist
新建网络训不起来?按这个顺序检查:
- ✅ 用 ReLU/GELU 不要 sigmoid 做隐藏层
- ✅ 用 He / Xavier 初始化(PyTorch 默认就行)
- ✅ 加 BatchNorm / LayerNorm
- ✅ 大模型加残差连接
- ✅ RNN 加梯度裁剪
- ✅ 全连接层加 0.3-0.5 Dropout
- ✅ 学习率从 开始试,慢慢调
- ✅ Loss 不下降 → 检查数据 / 标签是不是错了
- ✅ Loss 训练集低、验证集高 → 加正则化 / 数据增强
2012-2016 这几年深度学习的爆发,不仅是算力—— 还有这些工程技巧让深网络第一次能稳定训起来。
Hinton:「Dropout 是用过的最有效的 ML 想法之一」。
何凯明:「ResNet 让训 152 层成为现实」。
技术细节看似琐碎 —— 但每一个都是某篇论文 / 多年经验换来的。
下一篇推荐:L3-04 RNN/LSTM 兴衰 或 L3-07 注意力变体。
读到这里说明你认真在学 🎯
订阅每周精选 —— 下一篇新文章 / 新可视化第一时间送到邮箱。
讨论区
· 用 GitHub 账号登录评论src/components/Comments.astro 顶部填入
仓库 ID 和分类 ID(见组件注释里的配置步骤)。