|
## 梯度下降原理
### 核心思想
梯度下降(Gradient Descent)是一种**一阶优化算法**,通过沿函数梯度的**反方向**迭代更新参数,寻找函数的**局部最小值**。
### 数学基础
对于函数 f(w),其在点 w 处的梯度 ∇f(w) 指向函数增长**最快**的方向。因此,**负梯度方向** -∇f(w) 指向函数下降**最快**的方向。
### 更新公式
```
w_(t+1) = w_t - η × ∇f(w_t)
```
其中:
- **w_t**:第 t 次迭代的参数值
- **η (eta)**:学习率(Learning Rate),控制步长
- **∇f(w_t)**:函数在 w_t 处的梯度
### 几何解释
想象你在山上(损失函数曲面),想要下山到最低点:
1. **梯度**告诉你当前位置的**坡度方向**(最陡上升方向)
2. **负梯度**就是**下坡方向**(最陡下降方向)
3. **学习率**决定你每步走**多远**
4. 重复这个过程,最终到达**山谷最低点**
### 为什么有效
**泰勒展开**(一阶近似):
```
f(w + Δw) ≈ f(w) + ∇f(w)·Δw
```
选择 Δw = -η∇f(w):
```
f(w - η∇f(w)) ≈ f(w) - η||∇f(w)||²
```
当 η > 0 时,f(w - η∇f(w)) < f(w),函数值**必然下降**。
### 损失函数的作用
在深度学习中,我们定义**损失函数** L(θ) 衡量模型预测与真实标签的差异:
```
目标: min_θ L(θ)
方法: θ = θ - η × ∇L(θ)
```
通过不断调整参数 θ,使损失函数值不断减小,模型预测越来越准确。
|
|
## 学习率
### 学习率的定义
学习率(Learning Rate, η)是梯度下降算法中的**超参数**,控制每次参数更新的**步长大小**。
### 学习率的影响
| 学习率大小 | 效果 | 问题 |
|-----------|------|------|
| **过大** | 收敛快,初期下降快 | 可能振荡、发散,无法收敛 |
| **适中** | 稳定收敛 | 需要调试找到合适值 |
| **过小** | 稳定,精度高 | 收敛极慢,陷入局部最优 |
### 可视化理解
```
大学习率: 小学习率:
* *
* * ***
* * *****
* * → 最低 ******* → 很慢
* * *****
* * ***
* *
```
### 学习率调度策略
**1. 固定学习率**
```
η = 0.01 (保持不变)
```
**2. 学习率衰减**
```
η_t = η_0 / (1 + decay × t) # 线性衰减
η_t = η_0 × decay^t # 指数衰减
η_t = η_0 / (1 + decay × epoch) # 阶梯衰减
```
**3. 常用调度器**
- **StepLR**:每 n 个 epoch 衰减一次
- **ReduceLROnPlateau**:验证损失不降时衰减
- **CosineAnnealing**:余弦退火
- **Warmup**:初期用小学习率预热
### 学习率选择经验
| 场景 | 推荐学习率 |
|-----|-----------|
| SGD训练 | 0.01 ~ 0.1 |
| Adam训练 | 0.001 ~ 0.0001 |
| 微调预训练模型 | 0.0001 ~ 0.00001 |
| 大Batch训练 | 随Batch大小线性缩放 |
### PyTorch实现
```python
import torch.optim as optim
# 固定学习率
optimizer = optim.SGD(model.parameters(), lr=0.01)
# 学习率调度
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)
for epoch in range(100):
train(...)
scheduler.step() # 更新学习率
```
|
|
## 梯度下降常见问题
### 1. 局部最优
**问题**:陷入局部最小值,无法到达全局最小值。
```
/\ 全局最小
/ \__/
/\ / 局部最小
__/\/ \__/
```
**解决方法**:
- 使用**动量**(Momentum)冲过局部最优
- **随机重启**:从不同位置开始
- **模拟退火**:早期允许随机跳转
### 2. 鞍点
**问题**:在某些方向是极大值,某些方向是极小值,梯度接近0。
```
鞍点形状:
/\
/ \
/ \
/ 高维 \
-----------
```
**解决方法**:
- 使用**二阶优化**(Newton法)
- **Adam**等自适应优化器
- **添加噪声**跳出鞍点
### 3. 梯度消失
**问题**:深层网络中,梯度逐层衰减,底层参数几乎不更新。
**原因**:
- 链式法则:多个小于1的数相乘
- Sigmoid/Tanh激活函数导数<1
**解决方法**:
- 使用**ReLU**激活函数
- **残差连接**(ResNet)
- **批归一化**(BatchNorm)
- **LSTM/GRU**(RNN中)
### 4. 梯度爆炸
**问题**:梯度逐层放大,导致参数更新过大,模型发散。
**解决方法**:
- **梯度裁剪**(Gradient Clipping)
```
if ||∇L|| > max_norm:
∇L = ∇L × max_norm / ||∇L||
```
- 降低学习率
- 使用**批归一化**
### 5. 振荡
**问题**:参数在最小值附近来回振荡。
```
最优值
↓
* * * *
* * * * * * * *
* * * *
```
**解决方法**:
- 降低学习率
- 使用**动量**平滑振荡
- 使用**Adam**自适应调整
|
|
## 梯度计算
### 自动微分
现代深度学习框架(PyTorch、TensorFlow)使用**自动微分**自动计算梯度。
### 计算图
计算过程被表示为**有向无环图**(DAG):
- **节点**:张量(Tensor)
- **边**:运算操作
```
x → [+] → [*] → [+] → loss
↑ ↑ ↑
w b y
```
### 反向传播
**前向传播**:计算图构建,存储中间结果
**反向传播**:从输出向输入回传,计算梯度
### 链式法则
```
∂L/∂w = ∂L/∂y × ∂y/∂z × ∂z/∂w
```
### PyTorch示例
```python
import torch
# 定义可求导张量
x = torch.tensor([2.0], requires_grad=True)
w = torch.tensor([3.0], requires_grad=True)
b = torch.tensor([1.0], requires_grad=True)
# 前向传播
y = x * w + b # y = 2*3 + 1 = 7
loss = y ** 2 # loss = 49
# 反向传播
loss.backward()
# 查看梯度
print(w.grad) # ∂loss/∂w = 2*y*x = 2*7*2 = 28
print(b.grad) # ∂loss/∂b = 2*y = 2*7 = 14
```
### 梯度清空
PyTorch默认**累加梯度**,每次backward前需清空:
```python
# 方法1:手动清空
optimizer.zero_grad()
# 方法2:参数级清空
model.zero_grad()
# 方法3:in-place清空
param.grad.zero_()
```
### 梯度裁剪
防止梯度爆炸:
```python
# 按值裁剪
torch.nn.utils.clip_grad_value_(model.parameters(), clip_value=1.0)
# 按范数裁剪
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
```
|
|
## 实践建议
### 训练流程
```python
import torch
import torch.nn as nn
import torch.optim as optim
# 1. 定义模型
model = nn.Linear(10, 1)
# 2. 定义损失函数
criterion = nn.MSELoss()
# 3. 定义优化器
optimizer = optim.SGD(model.parameters(), lr=0.01)
# 4. 训练循环
for epoch in range(100):
# 前向传播
outputs = model(inputs)
loss = criterion(outputs, targets)
# 反向传播
optimizer.zero_grad() # 清空梯度
loss.backward() # 计算梯度
optimizer.step() # 更新参数
```
### 调试技巧
**1. 检查梯度**
```python
for name, param in model.named_parameters():
print(f"{name}: grad norm = {param.grad.norm():.4f}")
```
**2. 梯度监控**
```python
if torch.isnan(loss) or torch.isinf(loss):
print("Gradient exploded!")
break
```
**3. 学习率测试**
```python
# 测试不同学习率
lr_finder = LRFinder(model, optimizer, criterion, device)
lr_finder.range_test(train_loader, end_lr=10, num_iter=100)
lr_finder.plot() # 选择损失下降最快的lr
```
### 超参数调优
| 超参数 | 初始值 | 调整策略 |
|-------|--------|---------|
| 学习率 | 0.01 | 从0.001到0.1网格搜索 |
| Batch Size | 32 | 16, 32, 64, 128 |
| 权重衰减 | 0 | 1e-4, 1e-5, 1e-6 |
| 动量 | 0.9 | 0.9, 0.99 |
### 收敛判断
```python
# 早停策略
if val_loss > best_loss:
patience_counter += 1
else:
best_loss = val_loss
patience_counter = 0
if patience_counter > max_patience:
print("Early stopping!")
break
```
|
|
## SGD及其变体
### 标准SGD
随机梯度下降(Stochastic Gradient Descent):
```
w = w - η × ∇L(w)
```
### SGD with Momentum
动量加速收敛,减少振荡:
```
v_t = β × v_(t-1) + ∇L(w_t) # 速度更新
w_t = w_(t-1) - η × v_t # 位置更新
```
- **v**:速度(累积梯度)
- **β**:动量系数(通常0.9)
**效果**:像小球滚下山,积累动量冲过局部最优。
### Nesterov Accelerated Gradient (NAG)
前瞻型动量:
```
v_t = β × v_(t-1) + ∇L(w_(t-1) - β × v_(t-1))
w_t = w_(t-1) - η × v_t
```
先看"前面"的梯度,再决定怎么走,减少震荡。
### 对比
| 方法 | 特点 | 适用场景 |
|-----|------|---------|
| SGD | 简单、稳定 | 通用 |
| SGD+Momentum | 加速收敛 | 大多数任务 |
| NAG | 更快收敛、更少震荡 | RNN等 |
### PyTorch实现
```python
# 标准SGD
optimizer = optim.SGD(model.parameters(), lr=0.01)
# SGD with Momentum
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
# Nesterov SGD
optimizer = optim.SGD(model.parameters(), lr=0.01,
momentum=0.9, nesterov=True)
```
|
|
## Adam优化器
### Adam简介
Adam(Adaptive Moment Estimation)结合了**动量**和**自适应学习率**的优点。
### 核心思想
为每个参数**独立**调整学习率:
- 梯度大的参数 → 小学习率
- 梯度小的参数 → 大学习率
### 算法
```
# 一阶矩估计(动量)
m_t = β1 × m_(t-1) + (1-β1) × ∇L(w_t)
# 二阶矩估计(梯度平方)
v_t = β2 × v_(t-1) + (1-β2) × ∇L(w_t)²
# 偏差修正
m̂_t = m_t / (1-β1^t)
v̂_t = v_t / (1-β2^t)
# 参数更新
w_t = w_(t-1) - η × m̂_t / (√v̂_t + ε)
```
**超参数**:
- β1 = 0.9(一阶矩衰减)
- β2 = 0.999(二阶矩衰减)
- ε = 1e-8(防止除零)
### Adam变体
| 变体 | 改进 |
|-----|------|
| AdamW | 解耦权重衰减 |
| Adamax | 使用∞-范数 |
| AdaBound | 自适应边界,收敛到SGD |
| RAdam | 方差修正 |
### 何时使用Adam
| 场景 | 推荐优化器 |
|-----|-----------|
| 快速原型开发 | Adam |
| 训练深层网络 | Adam |
| Transformer模型 | AdamW |
| 追求最佳性能 | SGD + 调度 |
| 微调预训练模型 | Adam (小lr) |
### PyTorch实现
```python
# Adam
optimizer = optim.Adam(model.parameters(), lr=0.001)
# AdamW(推荐用于Transformer)
optimizer = optim.AdamW(model.parameters(), lr=0.001,
weight_decay=0.01)
```
|
|
## 学习率调度
### 学习率衰减策略
**1. StepLR(阶梯衰减)**
```python
scheduler = optim.lr_scheduler.StepLR(optimizer,
step_size=30,
gamma=0.1)
# 每30个epoch,lr × 0.1
```
**2. ExponentialLR(指数衰减)**
```python
scheduler = optim.lr_scheduler.ExponentialLR(optimizer,
gamma=0.95)
# 每个epoch,lr × 0.95
```
**3. CosineAnnealing(余弦退火)**
```python
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer,
T_max=100)
# lr按余弦曲线从初始值降到0
```
**4. ReduceLROnPlateau(自适应)**
```python
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer,
mode='min',
factor=0.1,
patience=10)
# 验证损失不降时衰减lr
```
**5. OneCycleLR(循环)**
```python
scheduler = optim.lr_scheduler.OneCycleLR(optimizer,
max_lr=0.1,
total_steps=1000)
# lr先升后降,单周期策略
```
### Warmup策略
训练初期使用**小学习率**逐步增加:
```python
scheduler = optim.lr_scheduler.LambdaLR(optimizer,
lr_lambda=lambda epoch: min(1.0, epoch / warmup_epochs))
```
**作用**:
- 稳定早期训练
- 避免大梯度破坏预训练权重
- 让优化器适应数据分布
### 学习率查找
```python
# LR Range Test
for lr in [1e-5, 1e-4, 1e-3, 1e-2, 1e-1]:
train_one_epoch(lr=lr)
# 观察loss变化,选择最快下降且稳定的lr
```
|
|
## 正则化技术
### L1/L2正则化
在损失函数中添加**惩罚项**:
```
L_total = L_data + λ × ||w||²
```
- **L1正则化**:||w||₁(稀疏解,特征选择)
- **L2正则化**:||w||²(权重衰减,防止过拟合)
### PyTorch实现
```python
# 方法1:优化器中设置
optimizer = optim.SGD(model.parameters(), lr=0.01,
weight_decay=1e-4) # L2正则
# 方法2:手动添加
l2_reg = torch.tensor(0., requires_grad=True)
for param in model.parameters():
l2_reg = l2_reg + torch.norm(param)
loss = criterion(output, target) + 1e-4 * l2_reg
```
### Dropout
训练时**随机丢弃**神经元,防止过拟合:
```python
class Net(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 256)
self.dropout = nn.Dropout(p=0.5) # 50% dropout
self.fc2 = nn.Linear(256, 10)
def forward(self, x):
x = F.relu(self.fc1(x))
x = self.dropout(x) # 训练时生效
x = self.fc2(x)
return x
```
**注意**:`model.eval()` 时dropout自动关闭。
### Early Stopping
验证损失不再下降时停止训练:
```python
best_val_loss = float('inf')
patience = 10
counter = 0
for epoch in range(max_epochs):
train_loss = train_one_epoch()
val_loss = validate()
if val_loss < best_val_loss:
best_val_loss = val_loss
counter = 0
save_checkpoint()
else:
counter += 1
if counter >= patience:
print("Early stopping!")
break
```
### 数据增强
扩充训练数据,提升泛化:
```python
transforms.Compose([
transforms.RandomHorizontalFlip(),
transforms.RandomRotation(15),
transforms.ColorJitter(brightness=0.2, contrast=0.2),
transforms.ToTensor(),
])
```
|
|
## 批归一化(Batch Normalization)
### 核心问题
深度网络训练时,**数据分布**不断变化:
- 内部协变量偏移(Internal Covariate Shift)
- 每层输入分布不稳定,影响训练
### BN原理
对每个mini-batch进行**标准化**:
```
μ_B = (1/m) Σ x_i # batch均值
σ²_B = (1/m) Σ (x_i - μ_B)² # batch方差
x̂_i = (x_i - μ_B) / √(σ²_B + ε) # 标准化
y_i = γ × x̂_i + β # 缩放平移
```
**γ, β**:可学习的参数,恢复表达能力
### BN的优势
| 优势 | 说明 |
|-----|------|
| **加速收敛** | 允许使用更大学习率 |
| **稳定训练** | 减少内部协变量偏移 |
| **正则化效果** | 引入噪声,轻微正则化 |
| **减少初始化依赖** | 对初始化更鲁棒 |
### BN的位置
```
Conv → BN → ReLU → Conv → BN → ReLU
```
**注意**:BN在激活**之前**。
### PyTorch实现
```python
class Net(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 64, 3)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU()
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x) # BN在激活之前
x = self.relu(x)
return x
```
### 其他归一化
| 类型 | 应用场景 |
|-----|---------|
| BatchNorm | CNN,batch较大时 |
| LayerNorm | Transformer,RNN |
| InstanceNorm | 风格迁移 |
| GroupNorm | 小batch情况 |
### 训练与推理
**训练时**:使用batch统计量
**推理时**:使用全局统计量(running mean/var)
```python
model.train() # 训练模式,BN更新统计量
model.eval() # 评估模式,BN使用固定统计量
```
|
Adam论文: Adam: A Method for Stochastic Optimization
BatchNorm论文: Batch Normalization
PyTorch优化器文档
Why Momentum Really Works