深度学习中的优化器之SGD with Exponential Moving Average(EMA-SGD)算法原理与实现细节
一、题目描述
在深度学习中,随机梯度下降(SGD)是基础的优化算法,但训练过程中参数更新可能因噪声或小批量样本的随机性而产生震荡。SGD with Exponential Moving Average(EMA-SGD)通过在参数更新时对历史梯度进行指数移动平均来平滑梯度方向,从而稳定训练过程。本题目将详细讲解EMA-SGD的动机、数学原理、迭代步骤及实现细节。
二、算法背景与动机
- 普通SGD的问题
SGD每次更新仅基于当前小批量的梯度,容易受噪声干扰,导致参数在最优解附近震荡,收敛速度慢且不稳定。 - EMA的作用
指数移动平均(EMA)是一种加权平均方法,赋予近期数据更高权重,同时对历史数据进行衰减。在优化中,EMA可用于平滑梯度序列,减少噪声影响,提高更新的方向稳定性。 - EMA-SGD的核心思想
对每一步的梯度计算EMA,然后用平滑后的梯度更新参数,而不是直接使用原始梯度。
三、数学原理与迭代步骤
1. 符号定义
- 待优化参数:\(\theta\)
- 目标函数:\(J(\theta)\)
- 第 \(t\) 步的梯度:\(g_t = \nabla_\theta J(\theta_t)\)
- 梯度EMA的衰减率(平滑系数):\(\beta \in [0,1)\),通常取 \(\beta=0.9\) 或 \(0.99\)
- 学习率:\(\alpha > 0\)
2. 梯度EMA计算
在第 \(t\) 步,维护一个EMA变量 \(v_t\)(初始 \(v_0 = 0\)),更新规则为:
\[ v_t = \beta \cdot v_{t-1} + (1 - \beta) \cdot g_t \]
这里 \(v_t\) 是梯度序列的指数移动平均值,可看作“平滑梯度”。
3. 参数更新
使用平滑梯度 \(v_t\) 代替原始梯度 \(g_t\) 进行参数更新:
\[ \theta_{t+1} = \theta_t - \alpha \cdot v_t \]
注意:与动量法(Momentum)不同,EMA-SGD对梯度做平滑后直接更新,而不引入速度变量。
四、EMA-SGD的详细步骤(伪代码)
初始化参数 θ,学习率 α,平滑系数 β,EMA变量 v = 0
for t = 1 to T do:
采样小批量数据
计算当前梯度 g_t = ∇θ J(θ)
更新梯度EMA:v = β * v + (1 - β) * g_t
更新参数:θ = θ - α * v
end for
五、关键细节分析
- EMA的偏差修正
在初始步骤中,EMA变量 \(v_0=0\) 可能导致早期估计偏向零。可进行偏差修正(类似Adam):
\[ v_t^{\text{corrected}} = \frac{v_t}{1 - \beta^t} \]
修正后的 \(v_t^{\text{corrected}}\) 用于参数更新,尤其在训练初期更稳定。
-
与动量法的区别
- 动量法(Momentum):\(v_t = \mu \cdot v_{t-1} + g_t\),更新为 \(\theta_{t+1} = \theta_t - \alpha \cdot v_t\),其中 \(\mu\) 是动量系数。动量法直接累积梯度,不进行加权平均。
- EMA-SGD:对梯度序列做EMA,衰减率 \(\beta\) 控制历史信息的保留程度。当 \(\beta\) 接近1时,更新方向更平滑。
-
超参数选择
- \(\beta\):通常取0.9、0.99或0.999。较大的 \(\beta\) 使梯度更平滑,但可能滞后于当前梯度方向。
- \(\alpha\):学习率需根据任务调整,一般小于普通SGD的学习率,因为平滑后的梯度噪声减小。
六、算法优缺点
优点:
- 梯度噪声降低,更新更稳定,有助于收敛到平坦最小值(可能提升泛化性)。
- 实现简单,计算开销小(仅多一个EMA变量)。
缺点:
- 平滑可能使梯度方向滞后,尤其在非平稳优化问题中。
- 需要调整 \(\beta\) 和 \(\alpha\),可能增加调参负担。
七、实现示例(PyTorch)
import torch
class EMA_SGD:
def __init__(self, params, lr=0.01, beta=0.9, bias_correction=True):
self.params = list(params)
self.lr = lr
self.beta = beta
self.bias_correction = bias_correction
self.v = [torch.zeros_like(p) for p in self.params]
self.t = 0 # 时间步
def step(self):
self.t += 1
for i, param in enumerate(self.params):
if param.grad is None:
continue
g = param.grad.data
# 更新EMA
self.v[i] = self.beta * self.v[i] + (1 - self.beta) * g
# 偏差修正
if self.bias_correction:
v_corrected = self.v[i] / (1 - self.beta ** self.t)
else:
v_corrected = self.v[i]
# 参数更新
param.data -= self.lr * v_corrected
# 使用示例
model = torch.nn.Linear(10, 1)
optimizer = EMA_SGD(model.parameters(), lr=0.01, beta=0.9)
八、总结
EMA-SGD通过梯度序列的指数移动平均平滑噪声,使优化更稳定。其核心是使用衰减系数 \(\beta\) 平衡当前梯度与历史信息,配合偏差修正可提升初期训练效果。该算法是SGD的一种简单但有效的变体,适用于噪声较大的优化场景,如小批量训练或数据分布动态变化的问题。