在我们刚刚接触深度学习时,对学习率只有一个很基础的认知,当学习率过大的时候会导致模型难以收敛,过小的时候会收敛速度过慢,其实学习率是一个十分重要的参数,合理的学习率才能让模型收敛到最小点而非局部最优点或鞍点。
1 什么是学习率
学习率是训练神经网络的重要超参数之一,它代表在每一次迭代中梯度向损失函数最优解移动的步长,通常用 η η η 表示。它的大小决定网络学习速度的快慢。在网络训练过程中,模型通过样本数据给出预测值,计算代价函数并通过反向传播来调整参数。重复上述过程,使得模型参数逐步趋于最优解从而获得最优模型。在这个过程中,学习率负责控制每一步参数更新的步长。合适的学习率可以使代价函数以合适的速度收敛到最小值。
2 学习率对网络的影响
梯度更新公式: θ = θ − η ∂ ∂ θ J ( θ ) \theta = \theta - \eta\frac{\partial}{\partial \theta}J(\theta) θ=θ−η∂θ∂J(θ)
根据上述公式我们可以看到,如果学习率 η η η 较大,那么参数的更新速度就会很快,可以加快网络的收敛速度,但如果学习率过大,可能会导致参数在最优解附近震荡,代价函数难以收敛,甚至可能会错过最优解,导致参数向错误的方向更新,代价函数不仅不收敛反而可能爆炸(如图1a所示)。
如果学习率 η η η 较小,网络可能不会错过最优点,但是网络学习速度会变慢。同时,如果学习率过小,则很可能会陷入局部最优点(如图1b所示)。
因此,只有找到合适的学习率,才能保证代价函数以较快的速度逼近全局最优解。
对于深度学习模型训练时,在梯度下降法中,固定学习率时,当到达收敛状态时,会在最优值附近一个较大的区域内摆动;而当随着迭代轮次的增加而减小学习率,会使得在收敛时,在最优值附近一个更小的区域内摆动。(之所以曲线震荡朝向最优值收敛,是因为在每一个mini-batch中都存在噪音)。如下图所示。
3 学习率的设置
那么如何去设置学习率这个超参数呢?总体上可以分为两种:人工调整和策略调整。
人工调整学习率一般是根据我们的经验值进行尝试,通常我们会尝试性的将初始学习率设为:0.1,0.01,0.001,0.0001等来观察网络初始阶段epoch的loss情况:
- 如果训练初期loss出现梯度爆炸或NaN这样的情况(暂时排除其他原因引起的loss异常),说明初始学习率偏大,可以将初始学习率降低10倍再次尝试;
- 如果训练初期loss下降缓慢,说明初始学习率偏小,可以将初始学习率增加5倍或10倍再次尝试;
- 如果训练一段时间后loss下降缓慢或者出现震荡现象,可能训练进入到一个局部最小值或者鞍点附近。如果在局部最小值附近,需要降低学习率使训练朝更精细的位置移动;如果处于鞍点附件,需要适当增加学习率使步长更大跳出鞍点。
- 如果网络权重采用随机初始化方式从头学习,有时会因为任务复杂,初始学习率需要设置的比较小,否则很容易梯度飞掉带来模型的不稳定(振荡)。这种思想也叫做Warmup,在预热的小学习率下,模型可以慢慢趋于稳定,等模型相对稳定后再选择预先设置的学习率进行训练,使得模型收敛速度变得更快,模型效果更佳。
- 如果网络基于预训练权重做的微调(finetune),由于模型在原数据集上以及收敛,有一个较好的起点,可以将初始学习率设置的小一些进行微调,比如0.0001。
策略调整学习率包括固定策略的学习率衰减和自适应学习率衰减,由于学习率如果连续衰减,不同的训练数据就会有不同的学习率。当学习率衰减时,在相似的训练数据下参数更新的速度也会放慢,就相当于减小了训练数据对模型训练结果的影响。为了使训练数据集中的所有数据对模型训练有相等的作用,通常是以epoch为单位衰减学习率。
在模型优化中,常用到的几种学习率衰减方法有:分段常数衰减、多项式衰减、指数衰减、自然指数衰减、余弦衰减、线性余弦衰减、噪声线性余弦衰减。
3.1 学习率衰减常用参数有哪些
参数名称 | 参数说明 |
---|---|
learning_rate | 初始学习率 |
global_step | 用于衰减计算的全局步数,非负,用于逐步计算衰减指数 |
decay_steps | 衰减步数,必须是正值,决定衰减周期 |
decay_rate | 衰减率 |
end_learning_rate | 最低的最终学习率 |
cycle | 学习率下降后是否重新上升 |
alpha | 最小学习率 |
num_periods | 衰减余弦部分的周期数 |
initial_variance | 噪声的初始方差 |
variance_decay | 衰减噪声的方差 |
3.2 分段常数衰减(Piecewise Decay)
分段常数衰减需要事先定义好的训练次数区间,在对应区间置不同的学习率的常数值,一般情况刚开始的学习率要大一些,之后要越来越小,要根据样本量的大小设置区间的间隔大小,样本量越大,区间间隔要小一点。下图即为分段常数衰减的学习率变化图,横坐标代表训练次数,纵坐标代表学习率。
3.3 指数衰减(Exponential Decay)
以指数衰减方式进行学习率的更新,学习率的大小和训练次数指数相关,其更新规则为:
d
e
c
a
y
e
d
_
l
e
a
r
n
i
n
g
_
r
a
t
e
=
l
e
a
r
n
i
n
g
_
r
a
t
e
∗
d
e
c
a
y
_
r
a
t
e
g
l
o
b
a
l
_
s
t
e
p
d
e
c
a
y
_
s
t
e
p
s
decayed{\_}learning{\_}rate =learning{\_}rate*decay{\_}rate^{\frac{global{\_step}}{decay{\_}steps}}
decayed_learning_rate=learning_rate∗decay_ratedecay_stepsglobal_step
这种衰减方式简单直接,收敛速度快,是最常用的学习率衰减方式,如下图所示,绿色的为学习率随训练次数的指数衰减方式,红色的即为分段常数衰减,它在一定的训练区间内保持学习率不变。
3.4 自然指数衰减(Natural Exponential Decay)
它与指数衰减方式相似,不同的在于它的衰减底数是
e
e
e,故而其收敛的速度更快,一般用于相对比较容易训练的网络,便于较快的收敛,其更新规则如下
d
e
c
a
y
e
d
_
l
e
a
r
n
i
n
g
_
r
a
t
e
=
l
e
a
r
n
i
n
g
_
r
a
t
e
∗
e
−
d
e
c
a
y
_
r
a
t
e
g
l
o
b
a
l
_
s
t
e
p
decayed{\_}learning{\_}rate =learning{\_}rate*e^{\frac{-decay{\_rate}}{global{\_}step}}
decayed_learning_rate=learning_rate∗eglobal_step−decay_rate
下图为为分段常数衰减、指数衰减、自然指数衰减三种方式的对比图,红色的即为分段常数衰减图,阶梯型曲线。蓝色线为指数衰减图,绿色即为自然指数衰减图,很明可以看到自然指数衰减方式下的学习率衰减程度要大于一般指数衰减方式,有助于更快的收敛。
3.5 多项式衰减(Polynomial Decay)
应用多项式衰减的方式进行更新学习率,这里会给定初始学习率和最低学习率取值,然后将会按照给定的衰减方式将学习率从初始值衰减到最低值,其更新规则如下式所示。
g
l
o
b
a
l
_
s
t
e
p
=
m
i
n
(
g
l
o
b
a
l
_
s
t
e
p
,
d
e
c
a
y
_
s
t
e
p
s
)
d
e
c
a
y
e
d
_
l
e
a
r
n
i
n
g
_
r
a
t
e
=
(
l
e
a
r
n
i
n
g
_
r
a
t
e
−
e
n
d
_
l
e
a
r
n
i
n
g
_
r
a
t
e
)
∗
(
1
−
g
l
o
b
a
l
_
s
t
e
p
d
e
c
a
y
_
s
t
e
p
s
)
p
o
w
e
r
+
e
n
d
_
l
e
a
r
n
i
n
g
_
r
a
t
e
global{\_}step=min(global{\_}step,decay{\_}steps) \\ decayed{\_}learning{\_}rate =(learning{\_}rate-end{\_}learning{\_}rate)* \left( 1-\frac{global{\_step}}{decay{\_}steps}\right)^{power} \\ +end{\_}learning{\_}rate
global_step=min(global_step,decay_steps)decayed_learning_rate=(learning_rate−end_learning_rate)∗(1−decay_stepsglobal_step)power+end_learning_rate
需要注意的是,有两个机制,降到最低学习率后,到训练结束可以一直使用最低学习率进行更新,另一个是再次将学习率调高,使用 decay_steps 的倍数,取第一个大于 global_steps 的结果,如下式所示.它是用来防止神经网络在训练的后期由于学习率过小而导致的网络一直在某个局部最小值附近震荡,这样可以通过在后期增大学习率跳出局部极小值。
d
e
c
a
y
_
s
t
e
p
s
=
d
e
c
a
y
_
s
t
e
p
s
∗
c
e
i
l
(
g
l
o
b
a
l
_
s
t
e
p
d
e
c
a
y
_
s
t
e
p
s
)
decay{\_}steps = decay{\_}steps*ceil \left( \frac{global{\_}step}{decay{\_}steps}\right)
decay_steps=decay_steps∗ceil(decay_stepsglobal_step)
如下图所示,红色线代表学习率降低至最低后,一直保持学习率不变进行更新,绿色线代表学习率衰减到最低后,又会再次循环往复的升高降低。
3.6 余弦衰减(Cosine Annealing Decay)
余弦衰减就是采用余弦的相关方式进行学习率的衰减,衰减图和余弦函数相似。该方法为论文SGDR:Stochastic Gradient Descent with Warm Restarts中cosine annealing
动态学习率。其更新机制如下式所示:
g
l
o
b
a
l
_
s
t
e
p
=
m
i
n
(
g
l
o
b
a
l
_
s
t
e
p
,
d
e
c
a
y
_
s
t
e
p
s
)
c
o
s
i
n
e
_
d
e
c
a
y
=
0.5
∗
(
1
+
c
o
s
(
π
∗
g
l
o
b
a
l
_
s
t
e
p
d
e
c
a
y
_
s
t
e
p
s
)
)
d
e
c
a
y
e
d
=
(
1
−
α
)
∗
c
o
s
i
n
e
_
d
e
c
a
y
+
α
d
e
c
a
y
e
d
_
l
e
a
r
n
i
n
g
_
r
a
t
e
=
l
e
a
r
n
i
n
g
_
r
a
t
e
∗
d
e
c
a
y
e
d
global{\_}step=min(global{\_}step,decay{\_}steps) \\ cosine{\_}decay=0.5*\left( 1+cos\left( \pi* \frac{global{\_}step}{decay{\_}steps}\right)\right) \\ decayed=(1-\alpha)*cosine{\_}decay+\alpha \\ decayed{\_}learning{\_}rate=learning{\_}rate*decayed
global_step=min(global_step,decay_steps)cosine_decay=0.5∗(1+cos(π∗decay_stepsglobal_step))decayed=(1−α)∗cosine_decay+αdecayed_learning_rate=learning_rate∗decayed
如下图所示,红色即为标准的余弦衰减曲线,学习率从初始值下降到最低学习率后保持不变。蓝色的线是线性余弦衰减方式曲线,它是学习率从初始学习率以线性的方式下降到最低学习率值。绿色噪声线性余弦衰减方式。
3.7 多间隔衰减(Multi Step Decay)
间隔衰减 (Step Decay)是指学习率按照指定的轮数间隔进行衰减,该过程可举例说明为:
learning_rate = 0.5 # 学习率初始值
step_size = 30 # 每训练30个epoch进行一次衰减
gamma = 0.1 # 衰减率
learning_rate = 0.5 if epoch < 30
learning_rate = 0.05 if 30 <= epoch < 60
learning_rate = 0.005 if 60 <= epoch < 90
多间隔衰减(Multi Step Decay)是指学习率按特定间隔进行衰减,与间隔衰减的区别在于:间隔衰减的epoch间隔是单一且固定的,而多间隔衰减中的epoch间隔是预先指定的多间隔。该过程可举例说明为:
learning_rate = 0.5 # 学习率初始值
milestones = [30, 50] # 指定轮数间隔
gamma = 0.1 # 衰减率
learning_rate = 0.5 if epoch < 30
learning_rate = 0.05 if 30 <= epoch < 50
learning_rate = 0.005 if 50 <= epoch
3.8 逆时间衰减(Inverse Time Decay)
学习率大小与当前衰减次数成反比。其计算公式如下:
n
e
w
_
l
e
a
r
n
i
n
g
_
r
a
t
e
=
l
e
a
r
n
i
n
g
_
r
a
t
e
1
+
g
a
m
m
a
∗
e
p
o
c
h
new\_learning\_rate = \frac{learning\_rate}{1 + gamma * epoch}
new_learning_rate=1+gamma∗epochlearning_rate
其中,learning_rate为初始学习率,gamma为衰减率,epoch为训练轮数。
3.9 Lambda衰减(Lambda Decay)
使用lambda函数来设置学习率,其中lambda函数通过epoch计算出一个因子,使用该因子乘以初始学习率。该衰减过程可参考如下例子:
learning_rate = 0.5 # 学习率初始值
lr_lambda = lambda epoch: 0.95 ** epoch # 定义lambda函数
learning_rate = 0.5 # 当epoch = 0时,0.5 * 0.95 ** 0 = 0.5
learning_rate = 0.475 # 当epoch = 1时,0.5 * 0.95 ** 1 = 0.475
learning_rate = 0.45125 # 当epoch = 2时,0.5 * 0.95 ** 2 = 0.45125
3.10 诺姆衰减(Noam Decay)
诺姆衰减的计算方式如下:
n
e
w
_
l
e
a
r
n
i
n
g
_
r
a
t
e
=
l
e
a
r
n
i
n
g
_
r
a
t
e
∗
d
m
o
d
e
−
0.5
∗
m
i
n
(
e
p
o
c
h
−
0.5
,
e
p
o
c
h
∗
w
a
r
m
u
p
_
s
t
e
p
s
−
1.5
)
new\_learning\_rate = learning\_rate * d_{mode}^{-0.5}*min(epoch^{-0.5}, epoch*warmup\_steps^{-1.5})
new_learning_rate=learning_rate∗dmode−0.5∗min(epoch−0.5,epoch∗warmup_steps−1.5)
其中,dmodel 代表模型的输入、输出向量特征维度,warmup_steps 为预热步数,learning_rate 为初始学习率。更多细节请参考 attention is all you need。
3.11 loss自适应衰减(Reduce On Plateau)
当loss停止下降时,降低学习率。其思想是:一旦模型表现不再提升,将学习率降低 2-10 倍对模型的训练往往有益。此外,每降低一次学习率后,将会进入一个冷静期。在冷静期内不会监控loss变化也不会进行衰减。当冷静期结束后,会继续监控loss的上升或下降。
3.12 线性学习率热身(Linear Warm Up)
线性学习率热身是一种学习率优化策略,在正常调整学习率前,先逐步增大学习率。可借鉴论文《Cyclical Learning Rates for Training Neural Networks》文中介绍了估计最小学习率和最大学习率的方法,比如在首次训练时(第1个epoch),先设置一个非常小的初始学习率,在每个batch之后都更新网络,同时增加学习率,统计每个batch计算出的loss,看增加到多大时,loss开始变差(很容易看到,调大到一定程度,loss变大,甚至变成nan),从而得到初始学习率。其核心在于将学习率由小变大。
当训练步数小于热身步数(warmup_steps)时,学习率 lr 按如下方式更新:
l
r
=
s
t
a
r
t
_
l
r
+
(
e
n
d
_
l
r
−
s
t
a
r
t
_
l
r
)
∗
e
p
o
c
h
w
a
r
m
u
p
_
s
t
e
p
s
lr = start\_lr + (end\_lr - start\_lr) * \frac{epoch}{warmup\_steps}
lr=start_lr+(end_lr−start_lr)∗warmup_stepsepoch
当训练步数大于等于热身步数(warmup_steps)时,学习率 lr 为:
l
r
=
l
e
a
r
n
i
n
g
r
a
t
e
lr = learning_rate
lr=learningrate
其中,lr 为热身之后的学习率,start_lr 为学习率初始值,end_lr 为最终学习率,epoch 为训练轮数。
4 torch.optim.lr_scheduler
对于优化器,Optimizer机制在前面PyTorch基础(六)-- optim模块已介绍,这里主要介绍辅助类torch.optim.lr_scheduler
包中提供的一些类,用于动态修改lr。
4.1 LambdaLr
将每个参数组的学习率设置为初始lr乘以给定函数。当last_epoch=-1时,将初始lr设置为初始值。
l
r
epoch
=
l
r
initial
∗
L
a
m
b
d
a
(
e
p
o
c
h
)
l r_{\text {epoch}} = l r_{\text{initial}} * Lambda(epoch)
lrepoch=lrinitial∗Lambda(epoch)
"""
:param lr_lambda(函数或列表):一个函数,给定一个整数形参epoch计算乘法因子,或一个这样的函数列表,optimizer.param_groups中的每组一个。
:param last_epoch (int):最后一个epoch的索引。默认值:1。
:param verbose (bool):如果为 True,则在每次更新时向标准输出输出一条消息。默认值: False。
"""
torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda, last_epoch=-1, verbose=False,)
model = torch.nn.Linear(2, 1)
optimizer = torch.optim.SGD(model.parameters(), lr=1.0)
lambda1 = lambda epoch: 0.65 ** epoch
scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda1) # 后面的代码主要在这里更改,所以不再赘述
lrs = []
for _ in range(10):
optimizer.step()
lrs.append(optimizer.param_groups[0]["lr"])
# lrs.append(scheduler.get_lr()) # 与上一句功能相同
# print("Factor = ", round(0.65 ** i,3)," , Learning Rate = ",round(optimizer.param_groups[0]["lr"],3))
scheduler.step()
plt.plot(range(10),lrs)
4.2 StepLR
每一个步长时期,每个参数组的学习速率以伽马衰减。请注意,这种衰减可能与这个调度程序外部对学习速率的其他改变同时发生。
KaTeX parse error: Unexpected end of input in a macro argument, expected '}' at end of input: …d{array}\right.
"""
:param step_size (int):学习率调整步长,每经过step_size,学习率更新一次。
:param gamma (float):学习率调整倍数。
:param last_epoch (int):上一个epoch数,这个变量用于指示学习率是否需要调整。当last_epoch符合设定的间隔时就会调整学习率。当设置为-1时,学习率设置为初始值。
"""
torch.optim.lr_scheduler.StepLR(optimizer, step_size, gamma=0.1, last_epoch=-1, verbose=False)
4.3 MultiStepLR
当前epoch数满足设定值时,调整学习率。这个方法适合后期调试使用,观察loss曲线,为每个实验制定学习率调整时期
l
r
epoch
=
{
G
a
m
m
a
∗
l
r
epoch - 1
,
if epoch in [milestones]
l
r
epoch - 1
,
otherwise
l r_{\text {epoch}}=\left\{\begin{array}{ll} Gamma * l r_{\text {epoch - 1}}, & \text { if } {\text{ epoch in [milestones]}} \\ l r_{\text {epoch - 1}}, & \text { otherwise } \end{array}\right.
lrepoch={Gamma∗lrepoch - 1,lrepoch - 1, if epoch in [milestones] otherwise
"""
:param milestones (list):一个包含epoch索引的list,列表中的每个索引代表调整学习率的epoch。list中的值必须是递增的。 如 [20, 50, 100] 表示在epoch为20, 50,100时调整学习率。
:param gamma (float):学习率调整倍数。
:param last_epoch (int):上一个epoch数,这个变量用于指示学习率是否需要调整。当last_epoch符合设定的间隔时就会调整学习率。当设置为-1时,学习率设置为初始值。
"""
torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones, gamma=0.1, last_epoch=-1, verbose=False)
4.4 ExponentialLR
按指数衰减调整学习率。每一个epoch以伽马衰减每个参数组的学习速率。
l
r
epoch
=
G
a
m
m
a
∗
l
r
epoch - 1
l r_{\text {epoch}}= Gamma * l r_{\text {epoch - 1}}
lrepoch=Gamma∗lrepoch - 1
"""
:param gamma (float):学习率调整倍数。
:param last_epoch (int):上一个epoch数,这个变量用于指示学习率是否需要调整。当last_epoch符合设定的间隔时就会调整学习率。当设置为-1时,学习率设置为初始值。
"""
torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma, last_epoch=-1, verbose=False,)
4.5 CosineAnneaingLR
模拟余弦退火曲线调整学习率。使用余弦退火计划设置每个参数组的学习速率。注意,因为调度是递归定义的,所以学习速率可以在这个调度程序之外被其他操作符同时修改。如果学习速率由该调度器单独设置,则每一步的学习速率为:
η
t
=
η
min
+
1
2
(
η
max
−
η
min
)
(
1
+
cos
(
T
c
u
r
T
max
π
)
)
\eta_{t}=\eta_{\min }+\frac{1}{2}\left(\eta_{\max }-\eta_{\min }\right)\left(1+\cos \left(\frac{T_{c u r}}{T_{\max }} \pi\right)\right)
ηt=ηmin+21(ηmax−ηmin)(1+cos(TmaxTcurπ))
model = torch.nn.Linear(2, 1)
optimizer = torch.optim.SGD(model.parameters(), lr=1.0)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=10, eta_min=0)
lrs = []
for _ in range(100):
optimizer.step()
lrs.append(optimizer.param_groups[0]["lr"])
scheduler.step()
plt.plot(lrs)
4.6 MultiplicativeLR
将每个参数组的学习率乘以指定函数中给定的因子。当last_epoch=-1时,将初始lr设置为初始值。
l
r
epoch
=
l
r
epoch - 1
∗
L
a
m
b
d
a
(
e
p
o
c
h
)
l r_{\text {epoch}} = l r_{\text {epoch - 1}} * Lambda(epoch)
lrepoch=lrepoch - 1∗Lambda(epoch)
# 参数与LambdaLr相同
torch.optim.lr_scheduler.MultiplicativeLR(optimizer, lr_lambda, last_epoch=-1, verbose=False,)
4.7 CyclicLR
Cyclical Learning Rates for Training Neural Networks 学习率周期性变化。
"""
:param base_lr (float or list):循环中学习率的下边界。:param max_lr (floatorlist):循环中学习率的上边界。
:param tep_size_up (int):学习率上升的步数。
:param step_size_down (int):学习率下降的步数。
:param mode (str):{triangular, triangular2, exp_range}中的一个。默认: 'triangular'。
:param gamma (float):在mode='exp_range'时,gamma**(cycle iterations), 默认:1.0。
:apram scale_fn:自定义的scaling policy,通过只包含有1个参数的lambda函数定义。0 <= scale_fn(x) <= 1 for all x >= 0. 默认:None。如果定义了scale_fn, 则忽略 mode参数
:param last_epoch (int):上一个epoch数,这个变量用于指示学习率是否需要调整。当last_epoch符合设定的间隔时就会调整学习率。当设置为-1时,学习率设置为初始值。
"""
torch.optim.lr_scheduler.CyclicLR(optimizer, base_lr, max_lr, step_size_up, mode='triangular')
scheduler1 = torch.optim.lr_scheduler.CyclicLR(optimizer, base_lr=0.001, max_lr=0.1, step_size_up=5, mode='triangular')
对OneCycleLR、OneCycleLRCosineAnnealingWarmRestarts的介绍,可以参考:图解 9 种Torch中常用的学习率调整策略
参考
- 图解 9 种Torch中常用的学习率调整策略:https://mp.weixin.qq.com/s/A-BkpToAr2ubhTnz_bvkpQ
- 学习率:https://paddlepedia.readthedocs.io/en/latest/tutorials/deep_learning/model_tuning/learning_rate.html
- 如何配置神经网络的学习率:https://blog.csdn.net/weixin_39653948/article/details/105962154
- 炼丹手册——学习率设置:https://zhuanlan.zhihu.com/p/332766013
- PyTorch优化器与学习率设置详解:https://zhuanlan.zhihu.com/p/435669796
- Transformers之自定义学习率动态调整:https://zhuanlan.zhihu.com/p/466992867