optimizer = tf.keras.optimizers.SGD(lr=0.01,decay=1e-4)
def exponential_decay(lr0,s):
def exponential_decay_fn(epoch):
return lr0*0.1**(epoch/s)
return exponential_decay_fn
exponential_decay_fn = exponential_decay(lr0=0.01,s=20)
def piecewise_constant_fn(epoch):
if epoch<5:
return 0.01
elif epoch<15:
return 0.005
else:
return 0.001
简单来说,1cycle scheduling, 使用的是一种周期性学习率,从较小的学习率开始学习,缓慢提高至较高的学习率,然后再慢慢下降,周而复始,每个周期的长度略微缩短,在训练的最后部分,学习率比之前的最小值降得更低。这不仅可以加速训练,还有助于防止模型落入损失平面的陡峭区域,使模型更倾向于寻找更平坦部分的极小值,从而缓解过拟合现象。
One-Cycle-Policy 大概有三个步骤:
我们逐渐将学习率从 lr_max / div_factor 提高到 lr_max,同时我们逐渐减少从 mom_max 到 mom_min 的动量(momentum)。
反向再做一次:我们逐渐将学习率从 lr_max 降低到 lr_max / div_factor,同时我们逐渐增加从 mom_min 到 mom_max 的动量。
我们进一步将学习率从 lr_max / div_factor 降低到 lr_max /(div_factor x 100),我们保持动力稳定在 mom_max。
我们来分别简单说明一下:
慢启动的想法并不新鲜:通常使用较低的值来预热训练,这正是 one-cycle 第一步实现的目标。Leslie 不建议直接切换到更高的值,而是线性提高的最大值。
他在实验过程中观察到的是,在 Cycle 中期,高学习率将作为正则化的角色,并使 NN 不会过拟合。它们将阻止模型落在损失函数的陡峭区域,而是找到更平坦的最小值。他在另一篇论文中解释了通过使用这一政策,Hessian 的近似值较低,表明 SGD 正在寻找更宽的平坦区域。
然后训练的最后一部分,学习率下降直到消失,将允许我们得到更平滑的部分内的局部最小值。在高学习率的同时,我们没有看到 loss 或 acc 的显着改善,并且验证集的 loss 有时非常高。但是当我们最终在最后降低学习率时,我们会发现这是很有好处的。
代码实现
class OneCycleScheduler(keras.callbacks.Callback): def __init__(self, iterations, max_rate, start_rate=None, last_iterations=None, last_rate=None): self.iterations = iterations self.max_rate = max_rate self.start_rate = start_rate or max_rate / 10 self.last_iterations = last_iterations or iterations // 10 + 1 self.half_iteration = (iterations - self.last_iterations) // 2 self.last_rate = last_rate or self.start_rate / 1000 self.iteration = 0 def _interpolate(self, iter1, iter2, rate1, rate2): return ((rate2 - rate1) * (self.iteration - iter1) / (iter2 - iter1) + rate1) def on_batch_begin(self, batch, logs): if self.iteration < self.half_iteration: rate = self._interpolate(0, self.half_iteration, self.start_rate, self.max_rate) elif self.iteration < 2 * self.half_iteration: rate = self._interpolate(self.half_iteration, 2 * self.half_iteration, self.max_rate, self.start_rate) else: rate = self._interpolate(2 * self.half_iteration, self.iterations, self.start_rate, self.last_rate) rate = max(rate, self.last_rate) self.iteration += 1 K.set_value(self.model.optimizer.lr, rate)