在我们熟知的学习率调度器中,有周期性调度器(单周期,多周期),也由衰减式调度器(按性能衰减,按epoch衰减)和预热式(学习率变化为低->高->缓慢变低)的。
周期性调度器对学习率比较敏感,可以通过学习率的周期性变化跳出局部最小值(在局部最小值时,学习率上升,有一定概率跳出最小值),尽可能找到鞍点;衰减式调度器,对学习率敏感度较低,但很难越过局部最小值(因为其学习率在不断下降,当到达鞍点时没有足够的动量跃出鞍点)。
为了结合这两个调度器的优势,博主对现行公开的两种学习率周期性调度器(余弦退火重启动学习率和余弦式衰减学习率)的代码做了轻微修改,实现了周期性与衰减学习率调度器的结合。
1、余弦退火重启动学习率衰减
由于paddle默认的学习率调度器里没有余弦退火重启动学习率,因此需要自习进行实现。
这里只实现了指数式衰减,参数中的eta_min是学习率最小值。
class CosineAnnealingWarmRestarts2(lr.LRScheduler):
#learning_rate:最大学习率值
#eta_min:最小学习率值
def __init__(self, min_lr,learning_rate,T_max, T_mult, eta_min=1e-8, last_epoch=-1,verbose=False):
self.min_lr=min_lr
self.base_lr = float(learning_rate)
self.last_lr = float(learning_rate)
self.last_epoch = last_epoch
self.T_max = T_max
self.T_mult = T_mult
self.Te = self.T_max
self.eta_min = eta_min
self.current_epoch = last_epoch
super(CosineAnnealingWarmRestarts2, self).__init__(learning_rate, last_epoch, verbose)
def step(self, epoch=None):
if epoch is None:
self.last_epoch += 1
self.last_lr = self.get_lr()
else:
self.last_epoch = epoch
self.last_lr = self.get_lr()
self.current_epoch += 1
## restart
if self.current_epoch == self.Te:
## reset epochs since the last reset
self.current_epoch = 0
## reset the next goal
self.Te = int(self.Te * self.T_mult)
self.T_max = self.T_max + self.Te
#----------------实现学习率衰减----------------
#实现学习率的指数式衰减
self.base_lr=self.base_lr*0.9
#实现学习率的阶梯式衰减
#self.base_lr=self.base_lr-0.0001
if self.verbose:
print('Epoch {}: {} set learning rate to {}.'.format(
self.last_epoch, self.__class__.__name__, self.last_lr))
def get_lr(self):
lr = self.eta_min + (self.base_lr - self.eta_min) *(1 + math.cos(math.pi * self.current_epoch / self.Te)) / 2
return lr
2、余弦式衰减学习率衰减
由于paddle默认的学习率调度器里包含了余弦式衰减学习率,因此不需要自习实现。但是,观测源码时发现,其是通过_get_closed_form_lr函数进行学习率更新(在paddle官方中,是推荐用get_lr进行学习率更新的,但系统库代码默认为_get_closed_form_lr)。因此,将衰减学习率的代码加在该函数下。
class CosineAnnealingDecay2(lr.CosineAnnealingDecay):
def __init__(self,min_lr=0.0001,*args,**kwargs):
super(CosineAnnealingDecay2, self).__init__(*args,**kwargs)
#设置最小lr,防止学习率无限衰减
self.min_lr=min_lr
def _get_closed_form_lr(self):
lr=self.eta_min + (self.base_lr - self.eta_min) * (1 + math.cos(math.pi * self.last_epoch / self.T_max)) / 2
#以lr为0,判断完成一个周期。也可以self.base_lr==lr作为判断条件
if lr==0 and self.base_lr>self.min_lr:
#实现学习率的指数式衰减
self.base_lr=self.base_lr*0.9
#实现学习率的阶梯式衰减
#self.base_lr=self.base_lr-0.0001
return lr
3、全部代码
全部代码如下所示,可以按照自己的需要进行调参,观测学习率的调度变化。
%matplotlib inline
import paddle
import paddle.nn as nn
import matplotlib.pyplot as plt
import math
from paddle.optimizer import lr
class CosineAnnealingWarmRestarts2(lr.LRScheduler):
#learning_rate:最大学习率值
#eta_min:最小学习率值
def __init__(self, min_lr,learning_rate,T_max, T_mult=1.2, eta_min=1e-8, last_epoch=-1,verbose=False):
self.min_lr=min_lr
self.base_lr = float(learning_rate)
self.last_lr = float(learning_rate)
self.last_epoch = last_epoch
self.T_max = T_max
self.T_mult = T_mult
self.Te = self.T_max
self.eta_min = eta_min
self.current_epoch = last_epoch
super(CosineAnnealingWarmRestarts2, self).__init__(learning_rate, last_epoch, verbose)
def step(self, epoch=None):
if epoch is None:
self.last_epoch += 1
self.last_lr = self.get_lr()
else:
self.last_epoch = epoch
self.last_lr = self.get_lr()
self.current_epoch += 1
## restart
if self.current_epoch == self.Te:
## reset epochs since the last reset
self.current_epoch = 0
## reset the next goal
self.Te = int(self.Te * self.T_mult)
self.T_max = self.T_max + self.Te
#----------------实现学习率衰减----------------
if self.base_lr>self.min_lr:
#实现学习率的指数式衰减
self.base_lr=self.base_lr*0.9
#实现学习率的阶梯式衰减
#self.base_lr=self.base_lr-0.0001
if self.verbose:
print('Epoch {}: {} set learning rate to {}.'.format(
self.last_epoch, self.__class__.__name__, self.last_lr))
def get_lr(self):
lr = self.eta_min + (self.base_lr - self.eta_min) *(1 + math.cos(math.pi * self.current_epoch / self.Te)) / 2
return lr
class CosineAnnealingDecay2(lr.CosineAnnealingDecay):
def __init__(self,min_lr=0.0001,*args,**kwargs):
super(CosineAnnealingDecay2, self).__init__(*args,**kwargs)
#设置最小lr,防止学习率无限衰减
self.min_lr=min_lr
def _get_closed_form_lr(self):
lr=self.eta_min + (self.base_lr - self.eta_min) * (1 + math.cos(math.pi * self.last_epoch / self.T_max)) / 2
#以lr为0,判断完成一个周期。也可以self.base_lr==lr作为判断条件
if lr==0 and self.base_lr>self.min_lr:
#实现学习率的指数式衰减
self.base_lr=self.base_lr*0.9
#实现学习率的阶梯式衰减
#self.base_lr=self.base_lr-0.0001
return lr
model = paddle.nn.Linear(10, 10)
#https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/optimizer/lr/CosineAnnealingDecay_cn.html
max_epoch=500
data_iters=5
if True:
scheduler = CosineAnnealingDecay2(min_lr=0.0001,learning_rate=0.0005, T_max=10, verbose=False)
#scheduler = CosineAnnealingWarmRestarts2(min_lr=0.0001,learning_rate=0.0005, T_max=10,T_mult=1.1, verbose=False)
optimizer = paddle.optimizer.AdamW(learning_rate=scheduler,parameters=model.parameters(),weight_decay=0.001)
cur_lr_list = []
plt.figure(figsize=(20,5))
for epoch in range(max_epoch):
for batch in range(data_iters):
optimizer.step()
#scheduler.step()
cur_lr=optimizer.get_lr()
cur_lr_list.append(cur_lr)
#print('cur_lr:',cur_lr,epoch ,batch,iters)
if "ReduceOnPlateau" in str(type(scheduler)):
loss=epoch
scheduler.step(metrics=loss) #ReduceOnPlateau
else:
scheduler.step(epoch)
x_list = list(range(len(cur_lr_list)))
plt.plot(x_list, cur_lr_list)
plt.title("CosineAnnealingWarmRestarts Decay")
plt.xlim(0,500)
plt.xlabel("epoch")
plt.ylabel('learning rate')
#plt.savefig("pltfig/%s.jpg"%strategy)
plt.show()