指数移动平均(EMA)的原理及PyTorch实现

在深度学习中,经常会使用EMA(指数移动平均)这个方法对模型的参数做平均,以求提高测试指标并增加模型鲁棒。

EMA的定义

指数移动平均(Exponential Moving Average)也叫权重移动平均(Weighted Moving Average),是一种给予近期数据更高权重的平均方法。

假设我们有n个数据: [\theta_1, \theta_2, ...,\theta_n]

  • 普通的平均数: \bar v = \frac{1}{n} \sum_{i=1}^{n} \theta_i
  • EMA: v_t = \beta \cdot v_{t-1} + (1-\beta) \cdot \theta_t ,其中, v_t 表示前t条的平均值 ( v_0=0 ), \beta 是加权权重值 (一般设为0.9-0.999)。

Andrew Ng在Course 2 Improving Deep Neural Networks中讲到,EMA可以近似看成过去1/(1-\beta)个时刻v值的平均。

普通的过去n时刻的平均是这样的:

                                                                               v_t = \frac{(n-1) \cdot v_{t-1} + \theta_t}{n}

类比EMA,可以发现当\beta=\frac{n-1}{n}时,两式形式上相等。需要注意的是,两个平均并不是严格相等的,这里只是为了帮助理解。

实际上,EMA计算时,过去1/(1-\beta)个时刻之前的数值平均会decay到\frac{1}{e}的加权比例,证明如下。

如果将这里的v_t展开,可以得到:

                                                            v_t=\alpha^n v_{t-n} + (1-\alpha)(\alpha^{n-1}\theta_{t-n+1}+...+\alpha^0\theta_t)

其中,n=\frac{1}{1-\alpha},代入可以得到\alpha^n=\alpha^{\frac{1}{1-\alpha}}\approx \frac{1}{e}

 

在深度学习的优化中的EMA

上面讲的是广义的ema定义和计算方法,特别的,在深度学习的优化过程中, \theta_t 是t时刻的模型权重weights, v_t 是t时刻的影子权重(shadow weights)。在梯度下降的过程中,会一直维护着这个影子权重,但是这个影子权重并不会参与训练。基本的假设是,模型权重在最后的n步内,会在实际的最优点处抖动,所以我们取最后n步的平均,能使得模型更加的鲁棒。

EMA的偏差修正

实际使用中,如果令 v_0=0 ,且步数较少,ema的计算结果会有一定偏差。

 

理想的平均是绿色的,因为初始值为0,所以得到的是紫色的。

因此可以加一个偏差修正(bias correction):

                                                                                       v_t=\frac{v_t}{1-\beta^{t}}

 

显然,当t很大时,修正近似于1。

EMA为什么有效

网上大多数介绍EMA的博客,在介绍其为何有效的时候,只做了一些直觉上的解释,缺少严谨的推理,在这补充一下,不喜欢看公式的读者可以跳过。

令第n时刻的模型权重(weights)为v_n,梯度为g_n,可得:

                                           \theta_n=\theta_{n-1}-g_{n-1} =\theta_{n-2}-g_{n-1}-g_{n-2}=...=\theta_1-\sum_{i=1}^{n-1}g_i

 

令第n时刻EMA的影子权重为v_n ,可得:

                                                              v_n=\alpha v_{n-1}+(1-\alpha)\theta_n

                                                                   =\alpha (\alpha v_{n-2}+(1-\alpha)\theta_{n-1}) + (1-\alpha)\theta_n

                                                                   =...

                                                                   =\alpha^n v_0 + (1-\alpha)(\theta_{n} + \alpha \theta_{n-1} + \alpha^2 \theta_{n-2} + ... + \alpha^{n-1} \theta_{1})

代入上面\theta_n的表达,令v_0=\theta_1展开上面的公式,可得:

                                                               v_n=\alpha^n v_0 + (1-\alpha)(\theta_{n} + \alpha \theta_{n-1} + \alpha^2 \theta_{n-2} + ... + \alpha^{n-1} \theta_{1})

                                                                    =\alpha^n v_0 + (1-\alpha)(\theta_{1} - \sum_{i=1}^{n-1}g_i + \alpha (\theta_{1} - \sum_{i=1}^{n-2}g_i) +...+ \alpha^{n-2} (\theta_{1} - \sum_{i=1}^{1}g_i) + \alpha^{n-1}\theta_1 )

                                                                    =\alpha^n v_0 + (1-\alpha)(\frac{1-\alpha^n}{1-\alpha}\theta_1 - \sum_{i=1}^{n-1}\frac{1-\alpha^{n-i}}{1-\alpha}g_i)

                                                                    =\alpha^n v_0 + (1-\alpha^n)\theta_1 - \sum_{i=1}^{n-1} (1-\alpha^{n-i}) g_i

                                                                    =\theta_1 - \sum_{i=1}^{n-1} (1-\alpha^{n-i}) g_i

对比两式:

                                                                     \theta_n=\theta_1 - \sum_{i-1}^{n-1}g_i

                                                             v_n=\theta_1 - \sum_{i=1}^{n-1}(1-\alpha^{n-i})g_i

EMA对第i步的梯度下降的步长增加了权重系数1-\alpha^{n-i},相当于做了一个learning rate decay。

PyTorch实现

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()

参考:https://zhuanlan.zhihu.com/p/68748778

  • 40
    点赞
  • 213
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值