L2正则=Weight Decay?并不是这样
文章链接是 https:// arxiv.org/pdf/1711.0510 1.pdf 。
在训练神经网络的时候,由于Adam有着收敛快的特点被广泛使用。但是在很多数据集上的最好效果还是用SGD with Momentum细调出来的。可见Adam的泛化性并不如SGD with Momentum。在这篇文章中指出了Adam泛化性能差的一个重要原因就是Adam中L2正则项并不像在SGD中那么有效,并且通过Weight Decay的原始定义去修正了这个问题。文章表达了几个观点比较有意思。
一、L2正则和Weight Decay并不等价。这两者常常被大家混为一谈。首先两者的目的都是想是使得模型权重接近于0。L2正则是在损失函数的基础上增加L2 norm, 即为 f_{t}^{reg}(x_{t})=f_{t}(x_t)+\frac{w}{2}||x||_{2}^{2} 。而权重衰减则是在梯度更新时直接增加一项, x_{t+1}=(1-w)x_t-\alpha \nabla f_t(x_t) 。在标准SGD的情况下,通过对衰减系数做变换,可以将L2正则和Weight Decay看做一样。但是在Adam这种自适应学习率算法中两者并不等价。
二、使用Adam优化带L2正则的损失并不有效。如果引入L2正则项,在计算梯度的时候会加上对正则项求梯度的结果。那么如果本身比较大的一些权重对应的梯度也会比较大,由于Adam计算步骤中减去项会有除以梯度平方的累积,使得减去项偏小。按常理说,越大的权重应该惩罚越大,但是在Adam并不是这样。而权重衰减对所有的权重都是采用相同的系数进行更新,越大的权重显然惩罚越大。在常见的深度学习库中只提供了L2正则,并没有提供权重衰减的实现。这可能就是导致Adam跑出来的很多效果相对SGD with Momentum偏差的一个原因。
三、下图中的绿色部分就是在Adam中正确引入Weight Decay的方式,称作AdamW。
我们可以自己实现AdamW,在Adam更新后的参数基础上继续做一次更新。
weights_var = tf.trainable_variables()
gradients = tf.gradients(loss, weights_var)
optimizer = tf.train.AdamOptimizer(learning_rate=deep_learning_rate)
train_op = optimizer.apply_gradients(zip(gradients, weights_var))
# weight decay operation
with tf.control_dependencies([train_op]):
l2_loss = weight_decay * tf.add_n([tf.nn.l2_loss(v) for v in weights_var])
sgd = tf.train.GradientDescentOptimizer(learning_rate=1.0)
decay_op = sgd.minimize(l2_loss)
不过tensorflow上已有AdamW修正,在tensorflow 1.10.0-rc0 中也包含了这个feature,但还没正式release,按照tensorflow的更新速度,应该很快了。可以像下面直接使用。
#optimizer = tf.train.AdamOptimizer(learning_rate=deep_learning_rate)
AdamWOptimizer = tf.contrib.opt.extend_with_decoupled_weight_decay(tf.train.AdamOptimizer)
optimizer = AdamWOptimizer(weight_decay=weight_decay, learning_rate=deep_learning_rate)
具体实现的细节可以参考一下两个函数。
def _decay_weights_op(self, var):