训练神经网络的技巧
训神经网络90% 的时间都在调超参。这一章把工业界常用的技巧系统讲一遍。
一、数据预处理
标准化(必须)
把所有特征缩放到零均值、单位方差:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train) # 训练集用 fit_transform
X_test = scaler.transform(X_test) # 测试集只用 transform
为什么必须? 如果特征在 [0, 1] 和 [0, 1000000] 之间,梯度更新会严重偏向大方差的方向,训练极慢。
归一化(可选,某些场景)
把数据压到 [0, 1]:x_new = (x - x_min) / (x_max - x_min)。图像像素处理常用。
二、权重初始化
不能全初始化为 0(那样所有神经元对称更新,等于一个神经元)。也不能太大(梯度爆炸)或太小(梯度消失)。
Xavier / Glorot 初始化
适用 tanh / sigmoid。公式:Var(w) = 2 / (n_in + n_out)
He 初始化
适用 ReLU。公式:Var(w) = 2 / n_in
nn.init.kaiming_normal_(layer.weight, nonlinearity='relu')
三、学习率调度
学习率是最重要的超参。太大学不到东西,太小训练太慢。
三大策略
1. 固定学习率:简单但僵化
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
2. Step Decay:每隔 N 轮降一次
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)
3. Cosine Annealing:余弦曲线下降,平滑
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=100)
4. Warmup + Cosine(现代大模型标配)
# 前 5% 步数线性 warmup, 之后余弦下降
def lr_lambda(step):
if step < warmup_steps:
return step / warmup_steps
progress = (step - warmup_steps) / (total_steps - warmup_steps)
return 0.5 * (1 + math.cos(math.pi * progress))
scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda)
四、Batch Normalization(标配)
在每层激活前对输入做归一化,让分布稳定。
nn.Sequential(
nn.Linear(256, 256),
nn.BatchNorm1d(256), # 关键!
nn.ReLU()
)
好处:
- 允许更大学习率
- 减少对初始化的敏感
- 轻微的正则化效果
- 加速收敛
五、Dropout(防止过拟合)
训练时随机让一部分神经元"罢工",推理时全部启用。
nn.Sequential(
nn.Linear(256, 256),
nn.ReLU(),
nn.Dropout(0.5), # 50% 概率失活
nn.Linear(256, 10)
)
Dropout 率怎么选:
- 输入层:
0.1 ~ 0.2 - 隐藏层:
0.3 ~ 0.5 - 输出层:不用
六、梯度裁剪(防爆炸)
训练 RNN / Transformer 时,梯度爆炸很常见。裁剪:
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
七、优化器选择
| 优化器 | 特点 | 适用 |
|---|---|---|
| SGD + Momentum | 经典,稳定,慢 | 简单任务,需要仔细调 |
| Adam | 自适应学习率,快 | 99% 情况的默认 |
| AdamW | Adam + 正确权重衰减 | Transformer 时代标准 |
| RMSProp | 老的自适应 | RNN |
# AdamW(现代推荐)
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-2)
八、训练循环模板
model = MyModel()
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-2)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=100)
criterion = nn.CrossEntropyLoss()
for epoch in range(100):
model.train()
for x, y in train_loader:
optimizer.zero_grad() # 重要!梯度清零
pred = model(x)
loss = criterion(pred, y)
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) # 梯度裁剪
optimizer.step() # 更新
scheduler.step() # 学习率调度
print(f"Epoch {epoch}, loss={loss.item():.4f}, lr={scheduler.get_last_lr()[0]:.6f}")
九、超参数调优的经验法则
小结
- 数据标准化 + 合理初始化 + 学习率调度 是三件套
- BatchNorm + Dropout 是现代网络的标配
- AdamW 几乎是万能优化器
- 训练循环注意
optimizer.zero_grad()否则梯度会累加 - 学习率是最重要的超参,先调它
练习思考
- 为什么
optimizer.zero_grad()必须在loss.backward()之前调用?不调会怎样? - BatchNorm 在训练和推理时的行为为什么不同?
model.eval()起什么作用? - 用 PyTorch 训一个 MNIST 分类器,比较不调学习率 / 用 StepLR / 用 CosineAnnealing 三种情况的训练曲线。
章末小测验
检验你对《训练神经网络的技巧》的掌握程度。
1
以下哪个超参数最重要,通常先调?
2
BatchNorm 的作用是?
学完这章, 你可能想看
讨论区(0)
加载评论中...