paddlepaddle 26 同时具备周期性与衰减性的学习率调度器

在我们熟知的学习率调度器中,有周期性调度器(单周期,多周期),也由衰减式调度器(按性能衰减,按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
图1 余弦退火重启动学习率指数衰减

 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
图2 余弦式衰减学习率衰减

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()

猜你喜欢

转载自blog.csdn.net/a486259/article/details/124842045