美文网首页
【优化技巧】指数移动平均(EMA)的原理及PyTorch实现

【优化技巧】指数移动平均(EMA)的原理及PyTorch实现

作者: AKA瓦砾 | 来源:发表于2019-07-27 15:55 被阅读0次

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

    今天瓦砾准备介绍一下EMA以及它的Pytorch实现代码。

    EMA的定义

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

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

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

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

    普通的过去n时刻的平均是这样的:
    v_t =\frac{(n-1)\cdot v_{t-1}+\theta_t}{n}
    类比EMA,可以发现当\alpha=\frac{n-1}{n}时,两式形式上相等。需要注意的是,两个平均并不是严格相等的,这里只是为了帮助理解。

    实际上,EMA计算时,过去1/(1-\alpha)个时刻之前的平均会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的偏差修正

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

    偏差

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

    因此可以加一个偏差修正(bias correction)。
    v_t = \frac{v_t}{1-\alpha^t}
    显然,当t很大时,修正近似于1。

    在深度学习的优化中的EMA

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

    EMA为什么有效

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

    令第n时刻的模型权重(weights)为v_n,梯度为g_n,可得:
    \begin{align} \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 \end{align}
    令第n时刻EMA的影子权重为v_n,可得:
    \begin{align} 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}) \end{align}

    代入上面\theta_n的表达,令v_0=\theta_1展开上面的公式,可得:
    \begin{align} 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 \end{align}
    对比两式:
    \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()
    
    

    References

    1. 机器学习模型性能提升技巧:指数加权平均(EMA)
    2. Exponential Weighted Average for Deep Neutal Networks

    相关文章

      网友评论

          本文标题:【优化技巧】指数移动平均(EMA)的原理及PyTorch实现

          本文链接:https://www.haomeiwen.com/subject/qwserctx.html