梯度下降基础
## 梯度下降原理 ### 核心思想 梯度下降(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