在深度学习中,经常会使用EMA(Exponential Moving Average)指数移动平均方法对模型参数做平均,以提高测试指标并增加模型鲁棒性。实际上,EMA可以看作是Temporal Ensembling,在模型学习过程中融合更多的历史状态,从而达到更好的优化效果。
一、数学中EMA
1.1 示例
有关“温度-天气”数据:
- θ t \theta_t θt: 第t天的温度
- v t v_t vt :在第t天的移动平均数
- β \beta β :权重参数
v 0 = 0 v 1 = 0.9 v 0 + 0.1 θ 1 v 2 = 0.9 v 1 + 0.1 θ 2 … v t = 0.9 v t − 1 + 0.1 θ t if β = 0.9 v t = β v t − 1 + ( 1 − β ) θ t \begin{array}{l} v_{0}=0 \\ v_{1}=0.9 v_{0}+0.1 \theta_{1} \\ v_{2}=0.9 v_{1}+0.1 \theta_{2} \\ \dots \\ v_{t}=0.9 v_{t-1}+0.1 \theta_{t} \\ \text { if } \beta=0.9 \\ v_{t}=\beta v_{t-1}+(1-\beta) \theta_{t} \end{array} v0=0v1=0.9v0+0.1θ1v2=0.9v1+0.1θ2…vt=0.9vt−1+0.1θt if β=0.9vt=βvt−1+(1−β)θt
图中红线即是蓝色数据点的指数移动平均
1.2 v t v_t vt 和 β \beta β 之间的关系:
β \beta β越大,表示考虑的时间长度越长
1.3 进一步理解 v t v_t vt
v t = β ⋅ v t − 1 + ( 1 − β ) ⋅ θ t v_t = \beta \cdot v_{t-1} + (1 - \beta) \cdot \theta_t vt=β⋅vt−1+(1−β)⋅θt
当 β = 0.9 \beta=0.9 β=0.9
v 100 = 0.1 θ 100 + 0.1 ∗ 0.9 ∗ 0.1 θ 99 + 0.1 ∗ ( 0.9 ) 2 ∗ 0.1 θ 98 + 0.1 ∗ ( 0.9 ) 3 ∗ 0.1 θ 97 + 0.1 ∗ ( 0.9 ) 4 ∗ 0.1 θ 97 + … v_{100} = 0.1 \theta_{100} + 0.1 * 0.9 * 0.1 \theta_{99} + 0.1 *(0.9)^2 * 0.1 \theta_{98} + 0.1 *(0.9)^3 * 0.1 \theta_{97} + 0.1 *(0.9)^4 * 0.1 \theta_{97} + \dots v100=0.1θ100+0.1∗0.9∗0.1θ99+0.1∗(0.9)2∗0.1θ98+0.1∗(0.9)3∗0.1θ97+0.1∗(0.9)4∗0.1θ97+…
- v 100 v_{100} v100 是 θ 100 , θ 99 , θ 98 , … \theta_{100},\theta_{99},\theta_{98},\dots θ100,θ99,θ98,… 的加权求和;
- θ \theta θ 前的系数加起来为1,或逼近1;
当某项系数小于峰值系数( − )的 / 时,我们可以忽略它的影响
( 0.9 ) 10 ∼ = 0.34 ∼ = 1 / e (0.9)^{10} \sim= 0.34 \sim= 1/e (0.9)10∼=0.34∼=1/e所以当β=0.9时,相当于前10天的加权平均。
( 0.98 ) 50 ∼ = 0.36 ∼ = 1 / e (0.98)^{50} \sim= 0.36 \sim= 1/e (0.98)50∼=0.36∼=1/e所以当β=0.98时,相当于前50天的加权平均。
( 0.5 ) 2 ∼ = 0.25 ∼ = 1 / e (0.5)^2 \sim= 0.25 \sim= 1/e (0.5)2∼=0.25∼=1/e所以当β=0.5时,相当于前2天的加权平均。
二、神经网络知识补充
2.1 梯度的定义
- 一元函数 y=f(x) 在 x 0 x_0 x0处的梯度是: d f d x ∣ x = x 0 \frac{df}{dx}|_{x=x_0} dxdf∣x=x0
- 二元函数 z=f(x,y) 在点 ( x 0 , y 0 ) (x_0,y_0) (x0,y0)处的梯度是: ( ∂ f ∂ x ∣ ( x 0 , y 0 ) , ∂ f ∂ y ∣ ( x 0 , y 0 ) ) (\frac{\partial f}{\partial x} |_{(x_0, y_0)}, \frac{\partial f}{\partial y} |_{(x_0, y_0)}) (∂x∂f∣(x0,y0),∂y∂f∣(x0,y0))
对多源函数的各个变量求偏导,并把求得的这些偏导写成向量形式,就是梯度。常把函数 f 的梯度简记为 ▽ f \triangledown_f ▽f .
梯度是一个向量,用来指明在函数的某一点沿着哪个方向函数值上升最快,这个向量的模表明函数值上升程度(速度)的大小。
接下来,举例说明:
本质上,梯度就是一个向量,如果函数是n元函数,这个向量就是由n个元素组成。如果是二元函数,
计算偏导,梯度为:
带入点值,在 (1,1) 这点沿着方向 (3,1) 函数值上升最快。
红色是函数 C(x,y) 的图像,在(1,1)这一点,沿着(3,1)移动,函数值z轴上升最快。在神经网络中,经常要找到一个函数的最小值,这个函数即loss关于网络中各个参数parameter的权重的函数。
如果算出这个函数的梯度,就知道对每一个参数,如何设置能够使得损失上升最快,即参数-梯度,就能使得损失下降最快。再加上一个学习率控制这个下降速度。
2.2 梯度法
深度学习中,神经网络的主要任务是学习时找到最优的参数(权重和偏置),这个最优参数也就是损失最小是的参数。但是,一般情况下,损失函数比较复杂,参数很很多,无法确定在哪里取得最小值,因此,需要通过梯度来寻找最小值(或尽可能小的值)。
在处理复杂任务上,深度网络比浅层网络具有更好的效果,但是目前优化神经网络的方法都是基于反向传播的思想,即根据损失函数计算误差通过梯度反向传播的方式,指导深度网络权重的更新优化。由于深度网络由多层非线性层堆叠而来,每一层非线性都可以视为一个非线性函数 f(x) ,
(非线性来自于非线性激活函数),因此整个深度网络可以视为是一个复合的非线性多元函数:
F ( x ) = f n ( … f 3 ( f 2 ( f 1 ( x ) ∗ θ 1 + b ) ∗ θ 2 + b ) … ) F(x) = f_n(\dots f_3(f_2(f_1(x)* \theta_1 + b) * \theta_2 +b) \dots) F(x)=fn(…f3(f2(f1(x)∗θ1+b)∗θ2+b)…)
最终的目的是希望这个多元函数可以很好的完成输入到输出之间的映射,假设不同的输入,输出的最优解是 g(x)
那么,为了优化深度网络就是为了寻找合适的权值,满足: Loss = L(g(x), F(x))
取得极小值点,比如最简单的损失函数: L o s s = ∥ g ( x ) − F ( x ) ∥ 2 2 Loss=\parallel g(x) - F(x) \parallel_2^2 Loss=∥g(x)−F(x)∥22
2.3 梯度下降法
梯度下降的科学解释:
Θ 1 = Θ 0 − α ▽ J ( Θ ) \Theta^1 =\Theta^0 - \alpha \triangledown J(\Theta) Θ1=Θ0−α▽J(Θ)
三、深度学习中EMA
v t = β ⋅ v t − 1 + ( 1 − β ) ⋅ θ t v_t = \beta \cdot v_{t-1} + (1 - \beta) \cdot \theta_t vt=β⋅vt−1+(1−β)⋅θt
- θ t \theta_t θt :第t次更新得到的所有参数权重;
- v t v_t vt :第t次更新得到的所有参数移动平均数;
- β \beta β :权重参数
对于更新n次时普通的参数权重$ \theta_n$ ( g_n 为第n次传播得到的梯度):
对于更新n次使用EMA的权重 v_n
将 θ n \theta_n θn 带入 v n v_n vn 中,并且令 v 0 = θ 1 v_0=\theta_1 v0=θ1 得:
对比可以发现,普通的参数权重相当于一直累积更新整个训练过程的梯度,使用EMA的参数权重相当于使用训练过程中梯度的加权平均(刚开始的梯度权值很小)。由于刚开始训练不稳定,得到的梯度给较小的权值更为合理,因此EMA会有效。
在最后save模型时存储ema的值,取最近n次的近似平均值,使得模型具备更好的测试指标,更强的泛化能力。滑动平均可以看作是变量的过去一段时间取值的均值,相比对变量直接赋值而言,滑动平均得到的值在图像上更加平缓光滑,抖动性更小,不会因为某次的异常取值而使得滑动平均值波动很大,
EMA的pytorch实现
简单实现
import numpy as np
import matplotlib.pyplot as plt
# 生成随机数据
data = np.random.randn(1000)
# 计算EMA
alpha = 0.1
ema = []
prev = data[0]
for x in data:
ema.append(alpha * x + (1 - alpha) * prev)
prev = ema[-1]
# 可视化对比
plt.plot(data, label='ORGIN')
plt.plot(ema, label='EMA')
plt.legend()
plt.show()
可视化:
完整的代码
class EMA():
def __init__(self, model, decay):
self.model = model
self.decay = decay
self.shadow = {}
self.backup = {}
def register(self):
for name, param in self.model.named_parameters():
if param.requires_grad:
self.shadow[name] = param.data.clone()
def update(self):
for name, param in self.model.named_parameters():
if param.requires_grad:
assert name in self.shadow
new_average = (1.0 - self.decay) * param.data + self.decay * self.shadow[name]
self.shadow[name] = new_average.clone()
def apply_shadow(self):
for name, param in self.model.named_parameters():
if param.requires_grad:
assert name in self.shadow
self.backup[name] = param.data
param.data = self.shadow[name]
def restore(self):
for name, param in self.model.named_parameters():
if param.requires_grad:
assert name in self.backup
param.data = self.backup[name]
self.backup = {}
# 初始化
ema = EMA(model, 0.999)
ema.register()
# 训练过程中,更新完参数后,同步update shadow weights
def train():
optimizer.step()
ema.update()
# eval前,apply shadow weights;eval之后,恢复原来模型的参数
def evaluate():
ema.apply_shadow()
# evaluate
ema.restore()
训练阶段:
在模型训练阶段,model_ema作为参数传给了train_one_epoch。train_one_epoch函数经过“一番操作”后,加下来就是评估模型,当model_ema不为空,就要拿model_ema去评估模型
保存模型时: