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