Autograd
- 网络中需要优化的参数是通过requires_grad=True来标记的
- 在PyTorch中,梯度值通常存储在模型的参数(torch.nn.Parameter)的.grad属性中。在反向传播过程中(调用loss.backward()),PyTorch会自动计算每个参数的梯度,并将结果存储在对应参数的.grad属性中。
- zero_grad方法:
在训练深度学习模型时,梯度是通过反向传播(loss.backward())计算的。每次调用backward()时,梯度会累积(而不是覆盖)到参数的.grad属性中。例如,如果连续两次调用loss.backward()而不清除梯度,第二次的梯度会是两次梯度的和。
optimizer.zero_grad() 会遍历优化器中所有可训练参数(通过model.parameters()注册的参数),并将这些参数的.grad属性设置为None或零。具体实现是调用参数的.grad.detach_()和.grad.zero_()方法,确保梯度被清除。
Optimizer
Adam
Adam(Adaptive Moment Estimation)是一种广泛使用的优化算法,结合了动量(Momentum)和自适应学习率的思想,适用于训练深度神经网络。它通过动态调整每个参数的学习率,加速收敛并减少手动调参的复杂性。
1. Adam算法的核心思想
Adam的核心在于自适应学习率和动量的结合:
- 自适应学习率:为每个参数维护独立的学习率,根据历史梯度的平方(二阶矩)动态调整。
- 动量:通过维护历史梯度的一阶矩(指数加权平均)来加速收敛,减少震荡。
2. Adam算法的更新步骤
Adam的更新过程分为以下几步:
(1) 初始化参数
- 初始化参数
(模型权重)。
- 初始化一阶矩估计
(动量)。
- 初始化二阶矩估计
(梯度平方的指数加权平均)。
- 设置时间步
。
(2) 计算梯度
-
在当前时间步
,计算损失函数
关于参数
的梯度:
(3) 更新一阶矩和二阶矩估计
- 更新一阶矩(动量):
- 更新二阶矩(梯度平方的指数加权平均):
其中表示逐元素平方。
(4) 偏差修正
- 由于
和
是指数加权平均的初始值,可能存在偏差,需要进行修正:
(5) 更新参数
- 更新参数
:
其中:-
是初始学习率。
-
是一个很小的常数(如
),用于防止除以零。
-
(6) 时间步递增
-
,重复步骤 (2) 到 (6)。
3. Adam算法的参数详解
Adam算法中有几个关键参数,它们的设置会影响训练效果:
参数 | 默认值 | 说明 |
---|---|---|
|
0.001 | 控制参数更新的步长。通常需要调参,常见范围是 |
|
0.9 | 控制一阶矩估计的衰减速度。通常设为 0.9,表示对历史梯度的平均依赖较强。 |
|
0.999 | 控制二阶矩估计的衰减速度。通常设为 0.999,表示对历史梯度平方的平均依赖较强。 |
|
防止除以零的数值稳定性参数。通常不需要调整。 |
默认参数在大多数情况下表现良好。
AdamW
AdamW算法详解
AdamW(Adam with Weight Decay)是一种改进的优化算法,由Ilya Loshchilov和Frank Hutter在2017年提出,旨在解决传统Adam优化器在权重衰减(Weight Decay)方面的问题。
核心思想
- 解耦权重衰减:在原始Adam中,权重衰减(L2正则化)是直接加到梯度上的,这可能导致优化偏差。AdamW通过将权重衰减从梯度更新中解耦,直接作用于参数更新步骤,使得权重衰减更有效地应用。
-
参数更新公式:
其中:-
和
分别是偏差修正后的一阶矩和二阶矩估计。
-
是独立的权重衰减系数。
-
是学习率。
-
AdamW算法的参数
AdamW的参数与Adam类似,但增加了一个独立的权重衰减系数。以下是主要的参数及其说明:
参数 | 默认值 | 说明 |
---|---|---|
|
0.001 | 控制参数更新的步长。通常需要调参,常见范围是 |
|
0.9 | 控制一阶矩估计的衰减速度。通常设为 0.9,表示对历史梯度的平均依赖较强。 |
|
0.999 | 控制二阶矩估计的衰减速度。通常设为 0.999,表示对历史梯度平方的平均依赖较强。 |
|
防止除以零的数值稳定性参数。通常不需要调整。 | |
|
0.01 | 控制权重衰减的强度。较大的值会增加正则化强度,但可能导致欠拟合;较小的值可能不足以防止过拟合。 |
实际应用中的建议
-
默认参数:对于大多数任务,可以直接使用AdamW的默认参数
,但权重衰减系数
可能需要根据任务调整。
- 学习率调整:如果训练不稳定,可以尝试减小学习率;如果收敛慢,可以尝试增大学习率。
-
权重衰减系数:通常在
到
之间调整。较大的模型(如Transformer)可能需要更大的权重衰减系数。
- 结合其他技术:可以结合学习率调度器(如ReduceLROnPlateau)或早停(Early Stopping)来优化训练。
- 参数分组:在PyTorch中,可以为模型的不同参数设置不同的学习率和权重衰减系数,例如对预训练部分使用较小的学习率。
lr_scheduler: CosineDecayWithWarmupScheduler
CosineDecayWithWarmupScheduler 是一种结合了学习率预热(Warmup)和余弦衰减(Cosine Decay)的学习率调度策略,常用于深度学习模型的训练中,以提升训练的稳定性和最终性能。
核心思想
-
预热阶段(Warmup):
- 在训练初期,学习率从一个较小的初始值逐渐线性增加到预定的最大学习率。
- 预热阶段有助于防止模型在训练初期因学习率过大而导致梯度爆炸或震荡,尤其是在使用较大的初始学习率时。
-
余弦衰减阶段(Cosine Decay):
- 在预热阶段结束后,学习率按照余弦函数的形状逐渐衰减。
- 余弦衰减的特点是学习率在训练后期逐渐趋近于零,但衰减过程较为平滑,有助于模型在训练后期进行精细调整,避免过拟合。
数学公式
-
预热阶段:
- 学习率从
initial_learning_rate
线性增加到max_learning_rate
,持续warmup_steps
步。 - 公式:
lr = initial_learning_rate + (max_learning_rate - initial_learning_rate) * (step / warmup_steps)
- 学习率从
-
余弦衰减阶段:
- 学习率从
max_learning_rate
按照余弦函数衰减到min_learning_rate
,持续decay_steps
步。 - 公式:
lr = min_learning_rate + 0.5 * (max_learning_rate - min_learning_rate) * (1 + cos(π * (step - warmup_steps) / decay_steps))
- 学习率从
参数说明
- initial_learning_rate:预热阶段的初始学习率。
- max_learning_rate:预热阶段结束时的最大学习率,也是余弦衰减阶段的初始学习率。
- min_learning_rate:余弦衰减阶段结束时的最小学习率。
- warmup_steps:预热阶段的步数。
- decay_steps:余弦衰减阶段的步数(通常为总训练步数减去预热步数)。
代码示例(PyTorch)
import torch
from torch.optim.lr_scheduler import LambdaLR
import math
class CosineDecayWithWarmupScheduler:
def __init__(self, optimizer, warmup_steps, decay_steps, initial_learning_rate, max_learning_rate, min_learning_rate):
self.optimizer = optimizer
self.warmup_steps = warmup_steps
self.decay_steps = decay_steps
self.initial_learning_rate = initial_learning_rate
self.max_learning_rate = max_learning_rate
self.min_learning_rate = min_learning_rate
self.current_step = 0
def step(self):
self.current_step += 1
if self.current_step <= self.warmup_steps:
# 预热阶段
lr = self.initial_learning_rate + (self.max_learning_rate - self.initial_learning_rate) * (self.current_step / self.warmup_steps)
else:
# 余弦衰减阶段
progress = (self.current_step - self.warmup_steps) / self.decay_steps
lr = self.min_learning_rate + 0.5 * (self.max_learning_rate - self.min_learning_rate) * (1 + math.cos(math.pi * progress))
for param_group in self.optimizer.param_groups:
param_group['lr'] = lr
# 示例使用
model = torch.nn.Linear(10, 2)
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
scheduler = CosineDecayWithWarmupScheduler(optimizer, warmup_steps=100, decay_steps=1000, initial_learning_rate=0.01, max_learning_rate=0.1, min_learning_rate=0.001)
for epoch in range(100):
for batch in range(10):
# 训练代码...
optimizer.step()
scheduler.step()
AMP (Automatic Mixed Precision)
Automatic Mixed Precision(AMP,自动混合精度) 是一种在深度学习训练中优化计算效率和内存使用的技术,通过动态混合使用单精度浮点数(FP32)和半精度浮点数(FP16)来加速训练过程,同时尽可能减少对模型精度的影响。
核心原理
-
FP32与FP16的优缺点
- FP32(单精度浮点数):精度高,但计算和内存开销大,适合需要高精度的操作(如权重更新、梯度累积)。
- FP16(半精度浮点数):计算速度快,内存占用低,但数值范围较小,可能导致溢出或下溢(如梯度值过小或过大时)。
-
AMP的工作机制
- 前向传播:在支持FP16的算子(如矩阵乘法、卷积)中使用FP16进行计算,以加速计算并减少显存占用。
- 反向传播:同样使用FP16计算梯度,但通过动态损失缩放(Dynamic Loss Scaling)避免梯度下溢。
- 权重更新:将梯度转换为FP32,并使用FP32的权重进行更新,以确保数值稳定性。
-
动态损失缩放
- 由于FP16的数值范围有限,直接使用FP16计算梯度可能导致梯度下溢(梯度值过小被截断为0)。
- AMP通过动态调整损失的缩放因子 grad_scaler,在反向传播前将损失值放大,从而放大梯度值,避免下溢。更新权重时再将梯度值缩小回原始尺度。
EMA (Exponential Moving Average)
EMA(指数移动平均)简介
EMA是一种常用的技术,用于平滑模型参数的更新过程。它的核心思想是:
在训练过程中,维护一个“影子模型”(shadow model),其参数是原始模型参数的指数移动平均。
EMA可以减少训练过程中的噪声,使模型更稳定,通常用于提升模型的泛化能力。
具体来说,它会根据原始模型的参数和当前的EMA参数,计算新的EMA值:
其中,是EMA的衰减率(通常接近1,如0.999)。
典型应用场景
- 模型平滑:在训练过程中使用EMA可以减少模型参数的波动,提升训练稳定性。
- 测试阶段:在测试时使用EMA参数(而不是训练时的参数)可能会提升模型性能。
- 半精度训练:在混合精度训练(如AMP)中,EMA可以用于平滑参数更新,减少数值不稳定性的影响。
代码示例(假设性)
class EMAPolicy:
def __init__(self, model, decay=0.999):
self.model = model
self.decay = decay
self.ema_params = {name: param.clone().detach() for name, param in model.named_parameters()}
def update_ema_modules(self):
for name, param in self.model.named_parameters():
self.ema_params[name] = self.decay * self.ema_params[name] + (1 - self.decay) * param
# 可选:将EMA参数同步回模型(或用于推理)
# param.data.copy_(self.ema_params[name])
# 使用示例
model = ... # 定义模型
policy = EMAPolicy(model, decay=0.999)
for data, target in dataloader:
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()
policy.update_ema_modules() # 更新EMA参数