AdamW + 超收敛

@date : 2018.9.27

@author : codewang

1. Adam回顾

Adam是将它之前的优化器的优点集于一身的产物,其具体表达形式为:

同时考虑到网络刚开始训练时,一阶动量和二阶动量没有累积,所以加以修正:

最终的优化更新公式为:

2. Adam出了什么问题?

我们知道正则化在机器学习中是一种重要的、非常有效的控制模型过拟合的手段,但是在各种深度学习框架中正则化是怎么实现的呢?

a) L1、L2函数实现正则化

例如我们在tensroflow或者keras或者pytorch中使用参数的L2正则化,那么深度学习框架是将这些参数的正则化项直接加到loss中即修改了loss函数,这与我们正则化的数学表达一致。

b) weight decay

还是不对, 优化器里面的超参数是decay,是对lr起作用的,不是正则化, weight decay是正则化函数里的参数,还得好好研究一下

同时我们注意到在各种优化器SGD、weight_decay, 也是正则化。其实在深度学习框架设计时认为weight decay和正则化时一样的。其实他们还是有一定区别的。

c) 正则化与weight decay的区别

我们先看正则化

也就是在原有的loss的基础上加上正则项,对于L1、L2正则来说:

假设我们现在使用L2正则化,则新的loss表达式为:

参数更新时是对新的loss求梯度,然后利用对应的优化算法更新参数。在优化器更新参数时:

再看看weight decay, 假设我们使用的是SGD优化器

loss是不变化的,但是在优化器更新参数时增加一项:

所以在SGD中我们只需要调节那么weight decay就和L2正则化等效。

我们再看看在带动量的优化器下又是怎样的一番情景:(我们以一阶动量为例,二阶动量类似)

一些动量一般用于平滑梯度,用当前时间某个时间段内梯度的移动指数平均数代替梯度,来更新参数。

那么对于L2正则来说,其修改了loss函数,所以对mt的计算有影响:

那么对于带动量的优化器来说其计算过程为:

而对于weight decay来说,它还是在优化过程中起作用,不修改loss。

此时他们就不一样了,因为两者计算移动指数平均不一样了,最终的优化方向也有区别,二阶动量类似。

那么到底哪一种方式是正确的呢?或者说优化算法在计算动量时应不应该考虑正则项?

答案是:不应该考虑,Ilya Loshchilov 和 Frank Hutter 在进行了实验后建议我们应该在 Adam 算法中使用权重衰减方法,而不是像经典深度学习库中实现的 L2 正则化。当地该怎么去解释目前还不太清楚。

3. Keras Adam源码

class Adam(Optimizer):
    def __init__(self, lr=0.001, beta_1=0.9, beta_2=0.999,
                 epsilon=None, decay=0., amsgrad=False, **kwargs):
        self.iterations = K.variable(0, dtype='int64', name='iterations')
        self.lr = K.variable(lr, name='lr')
        self.beta_1 = K.variable(beta_1, name='beta_1')
        self.beta_2 = K.variable(beta_2, name='beta_2')
        self.decay = K.variable(decay, name='decay')

        self.epsilon = epsilon
        self.initial_decay = decay
        self.amsgrad = amsgrad

    def get_updates(self, loss, params):
        grads = self.get_gradients(loss, params)  # 对loss函数求偏导
        self.updates = [K.update_add(self.iterations, 1)] 

        lr = self.lr
        if self.initial_decay > 0:
            lr = lr * (1. / (1. + self.decay * K.cast(self.iterations,
                                                      K.dtype(self.decay))))

        t = K.cast(self.iterations, K.floatx()) + 1    
        lr_t = lr * (K.sqrt(1. - K.pow(self.beta_2, t)) /
                     (1. - K.pow(self.beta_1, t)))

        ms = [K.zeros(K.int_shape(p), dtype=K.dtype(p)) for p in params]
        vs = [K.zeros(K.int_shape(p), dtype=K.dtype(p)) for p in params]

        if self.amsgrad:
            vhats = [K.zeros(K.int_shape(p), dtype=K.dtype(p)) for p in params]
        else:
            vhats = [K.zeros(1) for _ in params]

        self.weights = [self.iterations] + ms + vs + vhats

        for p, g, m, v, vhat in zip(params, grads, ms, vs, vhats):
            m_t = (self.beta_1 * m) + (1. - self.beta_1) * g
            v_t = (self.beta_2 * v) + (1. - self.beta_2) * K.square(g)
            if self.amsgrad:
                vhat_t = K.maximum(vhat, v_t)
                p_t = p - lr_t * m_t / (K.sqrt(vhat_t) + self.epsilon)
                self.updates.append(K.update(vhat, vhat_t))
            else:
                p_t = p - lr_t * m_t / (K.sqrt(v_t) + self.epsilon)

            self.updates.append(K.update(m, m_t))
            self.updates.append(K.update(v, v_t))
            new_p = p_t

            # Apply constraints.
            if getattr(p, 'constraint', None) is not None:
                new_p = p.constraint(new_p)

            self.updates.append(K.update(p, new_p))
        return self.updates

results matching ""

    No results matching ""