共计 2051 个字符,预计需要花费 6 分钟才能阅读完成。
在 ResNet50 的训练过程中,前 3 层的梯度幅度通常会衰减到最后一层的 $10^{-4}$~$10^{-6}$ 倍。这种梯度消失现象导致浅层网络参数更新缓慢,使得深层网络的训练效率大幅降低。实测数据显示,当使用传统 SGD 时,前 3 层参数每轮的更新量仅为最后一层的 0.01%~1%。
一、梯度下降算法技术对比
-
传统 SGD 的梯度更新公式 :
$$\theta_{t+1} = \theta_t – \eta \cdot \nabla_\theta J(\theta_t)$$
其中 $\eta$ 为固定学习率,容易在平坦区域陷入局部最优。 -
Momentum 的改进 :
$$v_t = \gamma v_{t-1} + \eta \nabla_\theta J(\theta_t)$$
$$\theta_{t+1} = \theta_t – v_t$$
通过引入动量项 $\gamma$(通常取 0.9),可加速峡谷区域的收敛。 -
Adam 的自适应特性 :
$$m_t = \beta_1 m_{t-1} + (1-\beta_1)g_t$$
$$v_t = \beta_2 v_{t-1} + (1-\beta_2)g_t^2$$
$$\hat{m}t = m_t/(1-\beta_1^t)$$
$$\hat{v}_t = v_t/(1-\beta_2^t)$$
$$\theta+\epsilon)$$} = \theta_t – \eta \cdot \hat{m}_t/(\sqrt{\hat{v}_t
二、PyTorch 优化方案实现
# Python 3.8 + torch 1.9
class ClippedAdam(torch.optim.Optimizer):
def __init__(self, params, lr=1e-3, clip=1.0):
defaults = dict(lr=lr, clip=clip)
super().__init__(params, defaults)
def step(self):
for group in self.param_groups:
for p in group['params']:
if p.grad is None: continue
# 梯度裁剪
grad = torch.clamp(p.grad,
-group['clip'],
group['clip'])
# Adam 更新逻辑
state = self.state[p]
if len(state) == 0:
state['step'] = 0
state['exp_avg'] = torch.zeros_like(p)
state['exp_avg_sq'] = torch.zeros_like(p)
exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq']
beta1, beta2 = 0.9, 0.999
state['step'] += 1
bias_correction1 = 1 - beta1 ** state['step']
bias_correction2 = 1 - beta2 ** state['step']
exp_avg.mul_(beta1).add_(grad, alpha=1-beta1)
exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1-beta2)
denom = (exp_avg_sq.sqrt() / math.sqrt(bias_correction2)).add_(1e-8)
step_size = group['lr'] / bias_correction1
p.data.addcdiv_(exp_avg, denom, value=-step_size)
三、学习率动态调整策略
# Cosine 退火学习率
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(
optimizer,
T_max=50, # 总 epoch 数
eta_min=1e-5 # 最小学习率
)
# 每个 epoch 结束时调用
for epoch in range(50):
train(...)
scheduler.step()
四、关键避坑指南
- 批量归一化注意事项 :
- BN 层的 scale 参数会放大学习率效果,建议初始学习率降低 10 倍
-
冻结 BN 层统计量时(如微调阶段),需关闭 affine 参数更新
-
显存不足解决方案 :
# 梯度累积(batch_size=128 等效)optimizer.zero_grad() for micro_step in range(4): # 实际 batch_size=32 data = next(batch_iterator) loss = model(data) loss.backward() # 梯度累积 optimizer.step()
五、CIFAR-10 对比实验
测试环境:RTX 3080, CUDA 11.2
| 方法 | 最终准确率 | 收敛 epoch |
|---|---|---|
| Vanilla SGD | 72.3% | 45 |
| 优化方案 | 76.8% | 32 |

思考题
当训练损失出现震荡时,你认为应该优先调整:
1. 学习率(可能导致欠拟合或过拟合)
2. 批量大小(影响梯度方向稳定性)
3. 优化器类型(改变参数更新方式)
