传统的网络训练过程中,一般学习率都是逐渐减小的,像keras可以使用keras.callbacks.ReduceLROnPlateau对学习率进行调整,例如:
函数:
keras.callbacks.ReduceLROnPlateau(monitor='val_loss',
factor=0.1,
patience=10,
verbose=0,
mode='auto',
min_delta=0.0001,
cooldown=2,
min_lr=0)
回调函数将会监视其中的一个metrics,在上面的例子中,metrics为val_loss,同时设置patience,如果监视的metrics超过10个eopch都没有任何提升,那么调整学习率—往小的方向进行调整。得到新的学习率,new_lr = lr * factor。cooldown参数值得是在lr改变之后,等待2个eopch之后回复正常的操作(?)。min_delta阈值为衡量新的最优值,只关注显著变化(?)。
直观地说,将学习率向更高的学习率振荡是有帮助的。因为较高的学习率可能有助于摆脱鞍点。如果鞍点是复杂的高原,较低的学习率可能无法得到鞍点以外的梯度。
Cyclic Learning Rates
熟悉几个概念:
base_lr : 下界学习率。
max_lr : 上界学习率。
cycle: 学习率从下界学习率(base_lr)到上界学习率(max_lr)再到下界学习率(base_lr),所经历的迭代次数iterations。
stepsize: cycle迭代次数的一半。
代码:https://github.com/bckenstler/CLR
One Cycle Policy
在论文[“A disciplined approach to neural network hyper-parameters: Part 1 — learning rate, batch size, momentum, and weight decay”](A disciplined approach to neural network hyper-parameters: Part 1 — learning rate, batch size, momentum, and weight decay)中,Leslie Smith描述了设置超参数(即学习率、动量和重量衰减)和批大小的方法。他特别建议采用1 Cycle policy来调整学习率。
前提,先找到最大的学习速率max_lr,使用LRFinder-for-Keras中的方法。
我们使用较低的学习速度作为最大学习速度的1/5或1/10。
我们在步骤1中从较低的学习率到较高的学习率,然后在步骤2中回到较低的学习率。我们选择这个周期长度略小于要训练的周期总数。在最后的迭代中,我们将学习率大大低于较低的学习率值(1/10或1/100)。
其背后的动机是,在学习过程中,当学习速率较高时,学习速率作为正则化方法发挥作用,防止网络过度拟合。
代码:
import numpy as np
import keras
from keras import backend as K
from keras.callbacks import *
class CyclicLR(keras.callbacks.Callback):
def __init__(self,base_lr, max_lr, step_size, base_m, max_m, cyclical_momentum):
self.base_lr = base_lr
self.max_lr = max_lr
self.base_m = base_m
self.max_m = max_m
self.cyclical_momentum = cyclical_momentum
self.step_size = step_size
self.clr_iterations = 0.
self.cm_iterations = 0.
self.trn_iterations = 0.
self.history = {}
def clr(self):
cycle = np.floor(1+self.clr_iterations/(2*self.step_size))
if cycle == 2:
x = np.abs(self.clr_iterations/self.step_size - 2*cycle + 1)
return self.base_lr-(self.base_lr-self.base_lr/100)*np.maximum(0,(1-x))
else:
x = np.abs(self.clr_iterations/self.step_size - 2*cycle + 1)
return self.base_lr + (self.max_lr-self.base_lr)*np.maximum(0,(1-x))
def cm(self):
cycle = np.floor(1+self.clr_iterations/(2*self.step_size))
if cycle == 2:
x = np.abs(self.clr_iterations/self.step_size - 2*cycle + 1)
return self.max_m
else:
x = np.abs(self.clr_iterations/self.step_size - 2*cycle + 1)
return self.max_m - (self.max_m-self.base_m)*np.maximum(0,(1-x))
def on_train_begin(self, logs={}):
logs = logs or {}
if self.clr_iterations == 0:
K.set_value(self.model.optimizer.lr, self.base_lr)
else:
K.set_value(self.model.optimizer.lr, self.clr())
if self.cyclical_momentum == True:
if self.clr_iterations == 0:
K.set_value(self.model.optimizer.momentum, self.cm())
else:
K.set_value(self.model.optimizer.momentum, self.cm())
def on_batch_begin(self, batch, logs=None):
logs = logs or {}
self.trn_iterations += 1
self.clr_iterations += 1
self.history.setdefault('lr', []).append(K.get_value(self.model.optimizer.lr))
self.history.setdefault('iterations', []).append(self.trn_iterations)
if self.cyclical_momentum == True:
self.history.setdefault('momentum', []).append(K.get_value(self.model.optimizer.momentum))
for k, v in logs.items():
self.history.setdefault(k, []).append(v)
K.set_value(self.model.optimizer.lr, self.clr())
if self.cyclical_momentum == True:
K.set_value(self.model.optimizer.momentum, self.cm())
使用:
batch_size = train_config.BATCH_SIZE
epochs = train_config.NB_EPOCHS
max_lr = 0.01 ##
base_lr = max_lr/10
max_m = 0.98
base_m = 0.85
cyclical_momentum = True
augment = True
cycles = 2.35
iterations = round(len(train)/batch_size*epochs)
iterations = list(range(0,iterations+1))
step_size = len(iterations)/(cycles)
clr = CyclicLR(base_lr=base_lr,
max_lr=max_lr,
step_size=step_size,
max_m=max_m,
base_m=base_m,
cyclical_momentum=cyclical_momentum)
callbacks_list = [clr]