pytorch_lesson15.1 学习率调度基本概念与手动实现学习率调整+常用学习率调度思路

提示:仅仅是学习记录笔记,搬运了学习课程的ppt内容,本意不是抄袭!望大家不要误解!纯属学习记录笔记!!!!!!


前言

学习率作为模型优化的重要超参数,在此前的学习中,我们已经看到了学习率的调整对模型训练在诸多方面的显著影响。这里我们先简单回顾此前遇到的学习率调整的场景:

·缓解Dead ReLU Problem:在ReLU激活函数叠加的神经网络中,由于ReLU本身负值归零的特性,可能会出现Dead ReLU Problem,根据Lesson 13.4的实验结果,我们可以通过减小学习率来降低模型落入活性失效陷进的概率。不过降低学习率也代表模型收敛速度更慢;

·提升模型表现:同时,学习率调整的也将显著影响模型性能。根据Lesson14中的实验结果可知,在学习率绝对数值的调整过程中,学习率对模型性能的影响整体呈现U型(以准确率评估指标时是倒U型)特征,即学习率过大或者过小都不好,学习过大可能会导致模型无法穿越狭窄的通道最终抵达最小值点,而学习率太小则容易在最小值点附近停止收敛,因此在进行模型训练时,我们需要找到一个适中的准确率取值。

整体来看,如果模型学习率设置太大虽然前期收敛速度较快但容易出现收敛过程不稳定、收敛结果不佳、或者神经元活性失效等问题而如果学习率设置太小虽然收敛过程将相对平稳并且能够有效规避神经元活性坏死的问题但容易出现收敛速度慢、收敛结果不佳等问题。为了深入理解该问题,同时也为了为后续实验储备对比数据,首先,我们可以通过设计实验来观测不同超参数取值对模型收敛速度、收敛结果影响。


一、学习率对模型训练影响

仍然利用Lesson 14中所定义的模型类和数据,通过设置不同学习率,来观察学习率对模型收敛速度和收敛结果两方面影响情况。

# 设置随机数种子
torch.manual_seed(420)

# 创建最高项为2的多项式回归数据集
features, labels = tensorGenReg(w=[2, -1, 3, 1, 2], bias=False, deg=2)

# 进行数据集切分与加载
train_loader, test_loader = split_loader(features, labels, batch_size=50)

# 设置随机数种子
torch.manual_seed(24)

# 关键参数
num_epochs = 20

# 实例化模型
tanh_model1 = net_class2(act_fun = torch.tanh, in_features=5, BN_model='pre')
tanh_model2 = net_class2(act_fun = torch.tanh, in_features=5, BN_model='pre')
tanh_model3 = net_class2(act_fun = torch.tanh, in_features=5, BN_model='pre')
tanh_model4 = net_class2(act_fun = torch.tanh, in_features=5, BN_model='pre')

# tanh_model进行模型训练
train_l1, test_l1 = model_train_test(tanh_model1,
                                     train_loader,
                                     test_loader,
                                     num_epochs = num_epochs,
                                     criterion = nn.MSELoss(),
                                     optimizer = optim.SGD,
                                     lr = 0.03,
                                     cla = False,
                                     eva = mse_cal)

train_l2, test_l2 = model_train_test(tanh_model2,
                                     train_loader,
                                     test_loader,
                                     num_epochs = num_epochs,
                                     criterion = nn.MSELoss(),
                                     optimizer = optim.SGD,
                                     lr = 0.01,
                                     cla = False,
                                     eva = mse_cal)

train_l3, test_l3 = model_train_test(tanh_model3,
                                     train_loader,
                                     test_loader,
                                     num_epochs = num_epochs,
                                     criterion = nn.MSELoss(),
                                     optimizer = optim.SGD,
                                     lr = 0.005,
                                     cla = False,
                                     eva = mse_cal)

train_l4, test_l4 = model_train_test(tanh_model4,
                                     train_loader,
                                     test_loader,
                                     num_epochs = num_epochs,
                                     criterion = nn.MSELoss(),
                                     optimizer = optim.SGD,
                                     lr = 0.001,
                                     cla = False,
                                     eva = mse_cal)

plt.plot(list(range(num_epochs)), train_l1, label='train_mse_0.03')
plt.plot(list(range(num_epochs)), train_l2, label='train_mse_0.01')
plt.plot(list(range(num_epochs)), train_l3, label='train_mse_0.005')
plt.plot(list(range(num_epochs)), train_l4, label='train_mse_0.001')
plt.legend(loc = 1)
plt.show()

在这里插入图片描述

由此我们能够清楚的看到,学习率较大的模型收敛速度较快,但效果却不一定最好。而学习率非常小的模型不仅收敛速度较慢,并且效果也不尽如人意。对于tanh2来说,在当前数据集上,学习率为0.001时表现较好。

不过,值得注意的是,尽管学习率的调整会对模型训练造成很大的影响,但在进行学习率优化的时候一般不会采用Lesson 14.3中同时训练多组学习率采用不同取值的模型来对比选择最佳学习率的方法。在实际生产生活中,训练一个模型已经是非常耗费时间的了,为了一个超参数同时训练多组模型并不划算。而要通过学习率数值调整来让模型“又好又快”的收敛,就需要采用一种名为学习率调度的优化算法。实际生产生活中,训练一个模型已经是非常耗费时间的了,为了一个超参数同时训练多组模型并不划算。而要通过学习率数值调整来让模型“又好又快”的收敛,就需要采用一种名为学习率调度的优化算法。

二、学习率调度基本概念与手动实现方法

既然我们无法通过训练多组模型来寻找最优学习率,那么一个最基本的想法,就是让学习率伴随模型训练过程动态调整。并且,根据上述实验结果我们也不难发现,一个比较好的学习率动态调整策略是,先让学习率取得较大数值,从而能够让模型在最开始能够以较快的速度收敛;然后在经过一段时间迭代之后,将学习率调小,从而能够让收敛过程穿过损失函数的“隘口”,抵达更小的解。

1.模型调度基本概念

既然我们无法通过训练多组模型来寻找最优学习率,那么一个最基本的想法,就是让学习率伴随模型训练过程动态调整。并且,根据上述实验结果我们也不难发现,一个比较好的学习率动态调整策略是,先让学习率取得较大数值,从而能够让模型在最开始能够以较快的速度收敛;然后在经过一段时间迭代之后,将学习率调小,从而能够让收敛过程穿过损失函数的“隘口”,抵达更小的解。

这样的一个学习率调整的策略,也被称为学习率调度(Learning rate scheduler)。

此处学习率的调整和一般的超参数调整不太一样。一般来说,模型的超参数调整是需要找出一个有助于模型提升效果的确定性的数值,比如模型层数、激活函数、归一化方法选取等,而一旦超参数数值确定,无论是训练过程还是测试过程都将以该参数的取值为准。但学习率参数本质上并不是一个影响模型结构的参数,而是辅助训练过程的参数,更准确的来说,是辅助模型各线性层(目前为止)参数取得最优解的参数。因此,学习率确实是否是确定的值其实并不重要,学习率取值可以随着迭代过程不断变化,只要这样的一个动态变化的过程最终能够让模型“又好又快”的收敛、即让各线性层参数以较快速度收敛至最小值。

2.手动实现学习率调度

2.1 本地实现方法

而要在PyTorch中实现学习率的动态调整,首先我们能够想到的是在fit过程手动调整学习率,例如,第一次fit设置2轮epochs、lr设置为0.03,第二次fit设置2轮epochs、lr设置0.01等等依此类推。我们可以借助Python中的input函数来手动实现学习率伴随迭代epoch次数的动态调整,具体操作方法如下:

实例化模型
#实例化模型
#设置随机数种子
torch.manual_seed(24)
#实例化模型
tanh_model = net_class2(act_fun=torch.tanh, in_features=5, BN_model='pre')
更新fit函数

接下来我们对fit函数进行更新,在原有fit函数基础上加上每一轮迭代后模型评估结果的记录功能。

测试函数性能
trian_l, test_l = fit_rec(net=tanh_model,criterion=nn.MSELoss(),optimizer=optim.SGD(tanh_model.parameters(), lr=0.03),train_data = train_loader,test_data = test_loader,epochs=3,cla=False,eva=mse_cal)
print(trian_l)
#[tensor(37.9793), tensor(32.4801), tensor(30.2007)]
print(test_l)
#[tensor(37.7321), tensor(33.3702), tensor(32.1592)]
手动动态调整过程

接下来,我们尝试借助Python中的input函数功能,来执行手动动态调整模型训练过程中的学习率。

所谓手动调整学习率,指的是可以在自定义的训练间隔中灵活调整模型学习率,而要将手动输入的数值作为模型当前阶段训练的参数,就需要使用input函数。Python中的input函数通过提供用户可交互的输入窗口来捕捉用户的即时输入,并将其转化为字符串形式传入当前操作空间中。

# 创建空列表容器
train_mse = []
test_mse = []


while input("Do you want to continue the iteration? [y/n]") == "y":
    epochs = int(input("Number of epochs:"))
    lr = float(input("Update learning rate:"))
    train_l, test_l = fit_rec(net=tanh_model,
                              criterion=nn.MSELoss(),
                              optimizer=optim.SGD(tanh_model.parameters(), lr=lr),
                              train_data=train_loader,
                              test_data=test_loader,
                              epochs=epochs,
                              cla=False,
                              eva=mse_cal)
    train_mse.extend(train_l)
    test_mse.extend(test_l)

plt.plot(train_mse, label='train_mse',color='red')
plt.xlabel('epochs')
plt.ylabel('MSE')
plt.legend(loc=1)
plt.show()

请添加图片描述
在这里插入图片描述
然后我们再创建另一组空列表,进行60轮都是0.03的学习率对比:


# 创建空列表容器
train_msel = []
test_msel = []


while input("Do you want to continue the iteration? [y/n]") == "y":
    epochs = int(input("Number of epochs:"))
    lr = float(input("Update learning rate:"))
    train_l, test_l = fit_rec(net=tanh_model,
                              criterion=nn.MSELoss(),
                              optimizer=optim.SGD(tanh_model.parameters(), lr=lr),
                              train_data=train_loader,
                              test_data=test_loader,
                              epochs=epochs,
                              cla=False,
                              eva=mse_cal)
    train_msel.extend(train_l)
    test_msel.extend(test_l)

请添加图片描述
在这里插入图片描述
肉眼可见的,当学习率调整为0.01之后,后30轮的loss逐渐趋于平稳。

2.2 配合tensorboard实现方法

from torch.utils.tensorboard import SummaryWriter
# 实例化writer对象
writer = SummaryWriter(log_dir='l3')

# 创建空列表容器
train_mse = []
test_mse = []

提前打开终端,在命令行中输入 tensorboard --logdir=‘l3’

然后打开网址http://localhost:6006/

# 创建总遍历次数计数器
num_epochs = 0

while input("Do you want to continue the iteration? [y/n]") == "y":
    epochs = int(input("Number of epochs:"))
    lr = float(input("Update learning rate:"))
    train_l, test_l = fit_rec(net = tanh_model, 
                              criterion = nn.MSELoss(), 
                              optimizer = optim.SGD(tanh_model.parameters(), lr = lr), 
                              train_data = train_loader,
                              test_data = test_loader,
                              epochs = epochs, 
                              cla = False, 
                              eva = mse_cal)
    train_mse.extend(train_l)
    test_mse.extend(test_l)
    for i, j in zip(list(range(num_epochs, num_epochs+epochs)), train_mse[num_epochs: num_epochs+epochs]):
        writer.add_scalar('train_mse', j, i)
    for i, j in zip(list(range(num_epochs, num_epochs+epochs)), test_mse[num_epochs: num_epochs+epochs]):
        writer.add_scalar('test_mse', j, i)
    num_epochs += epochs

请添加图片描述
迭代60轮之后,刷新tensorboard网址界面,会出现下面两张图:
请添加图片描述
请添加图片描述
上面这张图片里,隐形的那条线是lr=0.03的曲线,比较显眼的曲线是lr=0.01的曲线,从上图我们可以看出来如果学习率一直都是0.01的话,loss收敛的并没有那么快,并且较为平稳。

3.常用学习率调度思路

根据上述实验,我们基本能够总结得出,伴随模型遍历数据集次数增加、学习率逐渐降低,能够使得模型整体性能有所提升。但是,上述手动实验过程中学习率调整的方法还是相对来说比较粗糙,我们只是每隔30个epochs将学习率调小一次,很多结果也是通过尝试最后得出。在实际生产工作中,我们需要使用理论更加严谨的学习率调度方法。

在长期的实践经验总结基础上,目前来看,从实现思路上划分,比较通用的学习率调度的策略分为以下五类:

(1) 幂调度:

随着迭代次数增加,学习率呈现幂律变化,例如, l r lr lr为初始(第一轮)学习率,第二轮迭代时学习率调整为 l r / 2 lr/2 lr/2,第三轮迭代时学习率调整为 l r / 3 lr/3 lr/3等;(实践过程具体数值有所差异)

(2)指数调度:

随着迭代次数增加,学习率呈现指数变化,例如, l r lr lr为初始(第一轮)学习率,第二轮迭代时学习率调整为 l r / 1 0 1 lr/10^1 lr/101,第三轮迭代时学习率调整为 l r / 1 0 2 = l r / 100 lr/10^2 = lr/100 lr/102=lr/100等;(实践过程具体数值有所差异)

(3)分段恒定调度:

即每隔几轮迭代调整一次学习率,例如1-10轮学习率为 l r lr lr,10-20轮时学习率为 l r / 100 lr/100 lr/100等,不难发现此前我们所做的实验就是一种特殊的分段恒定调度策略;

1)性能调度:

即每隔一段时间观察误差变化情况,如果误差基本不变,则降低学习率继续迭代;

2) 周期调度:

和前面几种学习率调度策略一味将学习率递减有所不同,周期调度允许学习率在一个周期内进行先递增后递减的变化;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值