转载 卢明冬
参考
学习率和batchsize如何影响模型的性能? - yumoye - 博客园
学会使用顶级算法的秘诀是什么?如何找到合适的学习率?-电子发烧友网
深度学习论文 - Cyclical Learning Rates for Training Neural Networks
(pytorch-CLR实现)
(各种方法的实现)https://github.com/anandsaha/pytorch.cyclic.learning.rate
(实现了各种方法-只不过调用还需要自己额外运行)https://github.com/thomasjpfan/pytorch/blob/401ec389db2c9d2978917a6e4d1101b20340d7e7/torch/optim/lr_scheduler.py
可以参考 (Keras实现)神经网络学习速率设置指南(CLR Callback,LRFinder,SGDR等最新的学习率设置方案)附完整代码解析_初识-CV的博客-CSDN博客_神经网络学习速率
以下是其他参考资料
(CLR实现)DeepLearning论文阅读笔记(一):Cyclical Learning Rates for Training Neural Networks(CLR)_MLlotus的博客-CSDN博客
(NN训练 trick - lr 设置)百度安全验证
手把手教你估算深度神经网络的最优学习率(附代码 & 教程)手把手教你估算深度神经网络的最优学习率(附代码&教程)
(fast.ai 库实现)手把手教你估算深度神经网络的最优学习率(附代码&教程)_52AI人工智能的博客-CSDN博客
(bckenstler/CLR keras 实现) https://github.com/bckenstler/CLR
(Keras实现)探索学习率设置技巧以提高Keras中模型性能 | 炼丹技巧 - 简书
目录
1.学习率的重要性
2.学习率的设定类型
1)固定学习率
2)不同的参数使用不同的学习率
3)动态调整学习率
4)自适应学习率
3.学习率的设定策略
3.1.固定学习率(Fixed Learning Rate)
3.2.学习率衰减(Learning Rate Decay)
3.3.找到合适的学习率
3.4.基于Armijo准则的线性回溯搜索算法
1)二分线性搜索(Bisection Line Search)
2)回溯线性搜索 (Backing Line Search)
3)二次插值法
示例代码
3.5.循环学习率(Cyclical Learning Rate)
3.6.余弦退火(Cosine annealing)
3.7.热重启随机梯度下降(SGDR)
3.8.不同网络层使用不同学习率(Differential Learning Rates)
3.9.快照集成和随机加权平均(Snapshot Ensembling And Stochastic Weight Averaging)
从传统的集成学习一路走来
权重空间内的解
窄极值和宽极值
快照集成(Snapshot Ensembling)
快速几何集成(Fast Geometric Ensembling,FGE)
随机加权平均(Stochastic Weight Averaging,SWA)
方法实现
4.小结
5.参考资料
1.学习率的重要性如果把梯度下降算法比作机器学习中的一把 “神兵利器”,那么学习率就是梯度下降算法这把武器对应的 “内功心法”,只有调好学习率这个超参数,才能让梯度下降算法更好地运作,让模型产生更好的效果。
在《梯度下降算法总结》一文中,我们已经谈到过在实际应用中梯度下降学习算法可能会遇到局部极小值和鞍点两大挑战。那么,什么样的梯度下降才算是 “合格” 的,简单总结一下其实就两个字,“快” 和 “准”。“快”,即收敛速度要尽量快,“准”,即能够准确找到最优解。
也就是说,好的梯度下降是尽量在快的时间找到最优的解。我们结合 《梯度下降算法总结》中的内容,看看可能的影响因素有哪些:
1)学习率设置太小,需要花费过多的时间来收敛
2)学习率设置较大,在最小值附近震荡却无法收敛到最小值
3)进入局部极值点就收敛,没有真正找到的最优解
4)停在鞍点处,不能够在另一维度继续下降
那么除了一些客观的因素外,可能还会有一些主观因素,如业务需求对时间和准确率的侧重点不一样,或者有的场景可能为了减少过拟合,还会适当降低对训练数据准确性来提高模型的泛化能力,比如深度学习中早停止策略,通过合理地提前结束迭代避免过拟合。
梯度下降算法有两个重要的控制因子:一个是步长,由学习率控制;一个是方向,由梯度指定。
因此,要想对梯度下降的 “快” 和 “准” 实现调控,就可以通过调整它的两个控制因子来实现。因梯度方向已经被证明是变化最快的方向,很多时候都会使用梯度方向,而另外一个控制因子学习率则是解决上述影响的关键所在,换句话说,学习率是最影响优化性能的超参数之一。
2.学习率的设定类型 1)固定学习率介绍梯度下降时,我们讲到的学习率都是固定不变的,每次迭代每个参数都使用同样的学习率。找到一个比较好的固定学习率非常关键,否则会导致收敛太慢或者不收敛。
2)不同的参数使用不同的学习率如果数据是稀疏的且特征分布不均,似乎我们更应该给予较少出现的特征一个大的更新。这时可能需要对不同特征对应的参数设定不同的学习率。深度学习的梯度下降算法中 Adagrad 和 Adam 方法都针对每个参数设置了相应的学习率,这部分内容详见《梯度下降算法总结》,本篇不作讨论。
3)动态调整学习率动态调整就是我们根据应用场景,在不同的优化阶段能够动态改变学习率,以得到更好的结果。动态调整学习率是本篇的重点内容,为了解决梯度学习在一些复杂问题时出现的挑战,数据科学家们在动态调整学习率的策略上做了很多研究和尝试。
4)自适应学习率自适应学习率从某种程度上讲也算是动态调整学习率的范畴,不过更偏向于通过某种算法来根据实时情况计算出最优学习率,而不是人为固定一个简单策略让梯度下降按部就班地实行。
3.学习率的设定策略 3.1.固定学习率(Fixed Learning Rate)固定学习率适用于那些目标函数是凸函数的模型,通常为了保证收敛会选一个稍微小的数值,如 0.01、0.001。固定学习率的选择对梯度下降影响非常大,下图展示了不同学习率对梯度下降的影响。
一般情况下,初始参数所得目标值与要求的最小值距离比较远,随着迭代次数增加,会越来越靠近最小值。学习率衰减的基本思想是学习率随着训练的进行逐渐衰减,即在开始的时候使用较大的学习率,加快靠近最小值的速度,在后来些时候用较小的学习率,提高稳定性,避免因学习率太大跳过最小值,保证能够收敛到最小值。
衰减方式既可以是线性衰减也可以是指数衰减,可参考以下几种方式 [1]:

逐步下降的方法适用于目标函数不太复杂的情况,相比固定学习率方法主要体现在速度提升上,但在神经网络和深度学习场景中,对于跳出众多鞍点和局部极小值的帮助并不大。
3.3.找到合适的学习率Leslie N. Smith 在一篇 《Cyclical Learning Rates for Training Neural Networks》论文中提出一个非常简单的寻找最佳学习率的方法。这种方法可以用来确定最优的初始学习率,也可以界定适合的学习率的取值范围 [2]。
在这种方法中,我们尝试使用较低学习率来训练神经网络,但是在每个批次中以指数形式增加(或线性增加)。
目前,该方法在 Fast.ai 包中已经作为一个函数可直接进行使用。Fast.ai 包是由 Jeremy Howard 开发的一种高级 pytorch 包(就像 Keras 之于 Tensorflow)。
相应代码如下:
1
2
3
4
# run on learn object where learning rate is increased exponentially
learn.lr_find()
# plot graph of learning rate against iterations
learn.sched.plot_lr()
每次迭代后学习率以指数形式增长:
同时,记录每个学习率对应的 Loss 值,然后画出学习率和 Loss 值的关系图:
1
2
# plots the loss against the learning rate
learn.sched.plot()
通过找出学习率最高且 Loss 值仍在下降的值来确定最佳学习率。在上述情况中,该值将为 0.01。
3.4.基于Armijo准则的线性回溯搜索算法基于 Armijo 准则的线性回溯搜索算法主要目的是通过计算的方式得到合适的学习率 [5]。
学习率的计算标准
二次插值法是回溯线性搜索的继续优化,利用了多项式插值 (Interpolation) 方法。多项式插值的思想是通过多项式插值法拟合简单函数,然后根据该简单函数估计原函数的极值点,这里我们使用二次函数来拟合。例如在上面的算法中,我们需要通过使得 h′(α)=0 来求极值,而使用二次插值法是找到同样过 α 点的二次函数求极值,结果近似 h(α) 求极值。
先来看看怎样构造这个二次函数。
如果知道 3 个点,那就可以确定一个二次曲线经过这三个已知点,换句话说为了确定一个二次曲线就需要 3 个类型的信息,因此我们就可以这样想:如果题目给定了在一个点 x1 处的函数值 y1=f(x1)、在该点处的切线值即 x1 处的导数值 f′(x1)、x2 点处的函数值 y2=f(x2),那么也是能唯一的确定一个二次函数,看下图:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
#! /usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Author: LuMingdong.cn
@Project: ML
@File: learning_rate.py
@Create Date: 2018/8/28 0028 9:49
@Version: 1.0
@Description: Now is better than never. ——The Zen of Python
"""
import numpy as np
def Bisection(dfun, dir, x, alpha):
"""
:param dfun: 梯度函数
:param dir: 梯度方向,-1 为负方向, 1位正方向
:param x: 当前点,向量
:param alpha: 初始学习速率
:return: 返回找到的学习速率
"""
# d: 当前点x处的导数,因为要寻找的是当前点处的最佳学习速率alpha,当前点的梯度是固定的,是个值,向量
d = dir * dfun(x)
v_ha = np.dot(dfun(x + alpha * d), d)
eps = 1e-6 # 设置返回阈值
if abs(v_ha) 0:
while v_ha1 > 0:
a1 /= 10
v_ha1 = np.dot(dfun(x + a1 * d), d)
elif v_ha now + slope * c * alpha and iterstep > 0:
""" 折半搜索 """
alpha = alpha / 2
nextv = fun(x + alpha * d)
iterstep -= 1
return alpha
def ArmijoQuad(fun, dfun, dir, x, alpha, c=0.3):
"""
基于Armijo的二次插值线性搜索
:param fun: 目标函数,是个函数
:param dfun: 梯度函数
:param dir: 梯度方向,-1 为负方向,1位正方向
:param x: 当前点,向量
:param alpha: 初始学习速率
:param c: 参数c, 一般小于0.5
:return: 返回找到的学习速率
"""
d = dir * dfun(x)
now = fun(x)
nextv = fun(x + alpha * d)
count = 50
while nextv 0:
""" 寻找最大的alpha """
alpha = alpha * 2
nextv = fun(x + alpha * d)
count -= 1
iterstep = 50
slope = np.dot(dfun(x), d)
while nextv > now + slope * c * alpha and iterstep > 0:
""" 二次插值 """
# h'(0) = slope
# h(0) = now
# h(alpha) = nextv
a1 = (slope * alpha * alpha) / (2 * (slope * alpha + now - nextv))
if a1 0,按原来的折半
alpha = alpha / 2
else:
alpha = a1
nextv = fun(x + alpha * d)
iterstep -= 1
return alpha
def GradientDescent(k, fun, dfun, dir, x, alpha, itersteps):
"""
梯度下降
:param k: 搜索alpha的算法类型
:param fun: 目标函数,是个函数
:param dfun: 梯度函数
:param dir: 梯度方向,-1 为负方向,1位正方向
:param x: 当前点,向量
:param alpha: 初始学习速率
:param c: 参数c, 一般小于0.5
:return: 返回学习率和函数值的过程数据
"""
alpha_list = []
x_list = []
fx_list =[]
for i in range(itersteps):
if k == 0:
# 固定学习率
alpha = alpha
elif k == 1:
# 二分线性搜索
alpha = Bisection(dfun, dir, x, alpha)
elif k == 2:
# 回溯搜索
alpha = ArmijoBacktrack(fun, dfun, dir, x, alpha, c=0.3)
elif k == 3:
# 二次插值
alpha = ArmijoQuad(fun, dfun, dir, x, alpha, c=0.3)
else:
raise Exception("k must be one of [0, 1, 2, 3]")
d = dir * dfun(x)
x = x + alpha * d
# 保存过程数据
alpha_list.append(alpha)
x_list.append(x)
fx_list.append(fun(x))
return alpha_list, x_list, fx_list
def fun(args):
"""
x^2+y^4+z^6
:param args: 参数
:return: 函数值
"""
return args[0] ** 2 + args[1] ** 4 + args[2] ** 6
# return args[0] ** 4
def dfun(args):
"""
x^2+y^4+z^6
:param args: 参数
:return: 各参数梯度,向量
"""
return np.array([2 * args[0], 4 * args[1] ** 3, 6 * args[2] ** 5])
# return 4*args[0]**3
if __name__ == '__main__':
# 基础参数
args = np.array([4,3,2], dtype=float) # x
k = 3 # 0:固定学习率 1:二分搜索 2:回溯搜索 3:二次查找值
dir = -1
alpha = 0.01
itersteps = 100
# 梯度下降
a, theta, fx=GradientDescent(k, fun, dfun, dir, args, alpha, itersteps)
# 画图
import matplotlib.pyplot as plt
x = range(itersteps)
plt.plot(x, fx)
# 设置坐标轴刻度
# my_x_ticks = np.arange(0, itersteps, 5)
# my_y_ticks = np.arange(0, 10, 0.05)
# plt.xticks(my_x_ticks)
# plt.yticks(my_y_ticks)
plt.show()
3.5.循环学习率(Cyclical Learning Rate)使用较快的学习率也有助于我们在训练中更早地跳过一些局部极小值。
人们也把早停和学习率衰减结合起来,在迭代 10 次后损失函数没有改善的情况下学习率开始衰减,最终在学习率低于某个确定的阈值时停止。
近年来,循环学习率变得流行起来,在循环学习率中,学习率是缓慢增加的,然后缓慢减小,以一种循环的形式持续着 [3]。
上图是 Leslie N. Smith 提出的 Triangular 和 Triangular2 循环学习率方法。左侧的最大学习率和最小学习率保持不变。右侧的区别在于每个周期之后学习率减半。
3.6.余弦退火(Cosine annealing)余弦退火可以当做是学习率衰减的一种方式,早些时候都是使用指数衰减或者线性衰减,现在余弦衰减也被普遍使用 [2]。
在采用小批量随机梯度下降(MBGD/SGD)算法时,神经网络应该越来越接近 Loss 值的全局最小值。当它逐渐接近这个最小值时,学习率应该变得更小来使得模型不会超调且尽可能接近这一点。
余弦退火利用余弦函数来降低学习率,进而解决这个问题,如下图所示:
从上图可以看出,随着 x 的增加,余弦值首先缓慢下降,然后加速下降,再次缓慢下降。这种下降模式能和学习率配合,以一种十分有效的计算方式来产生很好的效果。
我们可以用 Fast.ai 库中的 learn.fit() 函数,来快速实现这个算法,在整个周期中不断降低学习率。
1
2
# Calling learn fit automatically takes advantage of cosine annealing
learn.fit(0.1, 1)
效果如下图所示,在一个需要 200 次迭代的周期中学习率不断降低,:
同时,在这种方法基础上,我们可以进一步引入重启机制。
3.7.热重启随机梯度下降(SGDR)在训练时,梯度下降算法可能陷入局部最小值,而不是全局最小值。下图展示了陷入局部最小值的梯度下降算法 [2]。
梯度下降算法可以通过突然提高学习率,来 “跳出” 局部最小值并找到通向全局最小值的路径。Loshchilov 和 Hutter 在《SGDR: Stochastic Gradient Descent with Warm Restarts》论文中提出了热重启随机梯度下降(Stochastic Gradient Descent with Warm Restarts, SGDR)方法,这种方法将余弦退火与热重启相结合,使用余弦函数作为周期函数,并在每个周期最大值时重新开始学习速率。“热重启” 是因为学习率重新开始时并不是从头开始的,而是由模型在最后一步收敛的参数决定的。
用 Fast.ai 库可以快速导入 SGDR 算法。当调用 learn.fit(learning_rate, epochs) 函数时,学习率在每个周期开始时重置为参数输入时的初始值,然后像上面余弦退火部分描述的那样,逐渐减小。
上图中每 100 次迭代,学习率下降到最小点,我们称为一个循环。
循环的迭代次数也可以是不一样的,下面设定了每个循环所包含的周期都是上一个循环的 2 倍。
1
2
3
4
5
6
7
8
9
10
11
12
# decide how many epochs it takes for the learning rate to fall to
# its minimum point. In this case, 1 epoch
cycle_len = 1
# at the end of each cycle, multiply the cycle_len value by 2
cycle_mult=2
# in this case there will be three restarts. The first time with
# cycle_len of 1, so it will take 1 epoch to complete the cycle.
# cycle_mult=2 so the next cycle with have a length of two epochs,
# and the next four.
learn.fit(0.1, 3, cycle_len=2, cycle_mult=2)
结果如下图表示:
一般情况下,在训练时通过优化网络层会比提高网络深度要更重要,在网络中使用有差别的学习率(Differential Learning rates),可以很好的提高模型性能 [2]。
在计算机视觉深度学习中,通过已有模型来训练深度学习网络,是一种已经被验证过非常可靠高效的方法。目前大部分网络(如 Resnet、VGG 和 Inception 等)都是在 ImageNet 数据集训练的,因此我们要根据所用数据集与 ImageNet 图像的相似性,来适当改变网络权重。
在修改这些权重时,我们通常要对模型的最后几层进行修改,因为这些层被用于检测基本特征(如边缘和轮廓),不同数据集有着不同基本特征。
首先,要使用 Fast.ai 库来获得预训练的模型,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# import library for creating learning object for convolutional #networks
from fastai.conv_learner import *
# assign model to resnet, vgg, or even your own custom model
model = VVG16()
# create fast ai data object, in this method we use from_paths where
# inside PATH each image class is separated into different folders
PATH = './folder_containing_images'
data = ImageClassifierData.from_paths(PATH)
# create a learn object to quickly utilise state of the art
# techniques from the fast ai library
learn = ConvLearner.pretrained(model, data, precompute=True)
创建学习对象之后(learn object),通过快速冻结前面网络层并微调后面网络层来解决问题:
1
2
3
4
5
6
# freeze layers up to the last one, so weights will not be updated.
learn.freeze()
# train only the last layer for a few epochs
learning_rate = 0.1
learn.fit(learning_rate, epochs=3)
当后面网络层产生了良好效果,我们会应用 “有差别学习率” 的方法来改变前面网络层。在实际中,一般将学习率的缩小倍数设置为 10 倍:
1
2
3
4
5
6
7
8
9
# set requires_grads to be True for all layers, so they can be updated
learn.unfreeze()
# learning rate is set so that deepest third of layers have a rate of 0.001.
# middle layers have a rate of 0.01, and final layers 0.1.
learning_rate = [0.001, 0.01, 0.1]
# train model for three epoch with using differential learning rates
learn.fit(learning_rate, epochs=3)
3.9.快照集成和随机加权平均(Snapshot Ensembling And Stochastic Weight Averaging)最后将要提到的策略可以说是多个优化方法综合应用的策略,可能已经超出了 “学习率的设定” 主题的范围了,不过,我觉得下面的方法是最近一段时间研究出来的一些非常好的优化方法,因此也包括了进来,权当做是学习率优化的综合应用了。
本小节主要涉及三个优化策略:快照集成(Snapshot Ensembling)、快速几何集成(Fast Geometric Ensembling,FGE)、随机加权平均(Stochastic Weight Averaging,SWA)。
相关内容可参考以下论文:
《Snapshot Ensembles: Train 1, get M for free》by Gao Huang et. al
《Loss Surfaces, Mode Connectivity, and Fast Ensembling of DNNs》by Garipov et. al
《Averaging Weights Leads to Wider Optima and Better Generalization》by Izmailov et. al
下面我们详细了解这些优化策略。
从传统的集成学习一路走来在经典机器学习中,集成学习(Ensemble learning)是非常重要的思想,很多情况下都能带来非常好的性能,因此几乎是机器学习比赛中必用的 “神兵利器”。
集成学习算法本身不算一种单独的机器学习算法,而是通过构建并结合多个机器学习器来完成学习任务。可以说是 “集百家之所长”,完美的诠释了 “三个臭皮匠赛过诸葛亮”。集成学习在机器学习算法中拥有较高的准确率,不足之处就是模型的训练过程可能比较复杂,效率不是很高。
强力的集成学习算法主要有 2 种:基于 Bagging 的算法和基于 Boosting 的算法,基于 Bagging 的代表算法有随机森林,而基于 Boosting 的代表算法则有 Adaboost、GBDT、XGBOOST 等,这部分内容我们后面会单独讲到。
集成学习的思路就是组合若干不同的模型,让它们基于相同的输入做出预测,接着通过某种平均化方法决定集成模型的最终预测。这个决定过程可能是通过简单的投票或取均值,也可能是通过另一个模型,该模型能够基于集成学习中众多模型的预测结果,学习并预测出更加准确的最终结果。岭回归是一种可以组合若干个不同预测的结果的方法,Kaggle 上卫星数据识别热带雨林竞赛的冠军就使用过这一方法 [4]。
集成学习的思想同样适用于深度学习,集成应用于深度学习时,组合若干网络的预测以得到一个最终的预测。通常,使用多个不同架构的神经网络得到的性能会更好,因为不同架构的网络一般会在不同的训练样本上犯错,因而集成学习带来的收益会更大。
当然,你也可以集成同一架构的模型,也许效果会出乎意料的好。就好比本小节将要提到的快照集成方法,在训练同一个网络的过程中保存了不同的权值快照,然后在训练之后创建了同一架构、不同权值的集成网络。这么做可以提升测试的表现,同时也超省事,因为你只需要训练一个模型、训练一次就好,只要记得随时保存权值就行。
快照集成应用了我们刚才提到的热重启随机梯度下降(Stochastic Gradient Descent with Warm Restarts, SGDR),这种循环学习率几乎为快照集成量身打造,利用热重启随机梯度下降法的特点,每次收敛到局部极值点的时候就可以缓存一个权重快照,缓存那么几个就可以做集成学习了。
无论是经典机器学习中的集成学习,还是深度学习里面的集成学习,抑或是改良过的快照集成方法,都是模型空间内的集成,它们均是组合若干模型,接着使用这些模型的预测以得到最终的预测结果。而一些数据科学家还提出了一种全新的权值空间内的集成,这就是随机加权平均法,该方法通过组合同一网络在训练的不同阶段的权值得到一个集成,接着使用组合后的权值做出预测。这种方法有两个好处:
-
- 组合权重后,我们最终仍然得到一个模型,这有利于加速预测。
- 事实证明,这种方法胜过当前最先进的快照集成。
在了解其实现原理之前,我们首先需要理解损失平面(loss surface)和泛化解(generalizable solution)。
权重空间内的解第一个不得不提到的是,经过训练的网络是高维权值空间中的一个点。对给定的架构而言,每个不同的网络权值组合都代表了一个不同的模型。任何给定架构都有无穷的权重组合,因而有无穷多的解。训练神经网络的目标是找到一个特定的解(权值空间中的点),使得训练数据集和测试数据集上的损失函数的值都比较低。
在训练期间,训练算法通过改变权值来改变网络并在权值空间中漫游。梯度下降算法在一个损失平面上漫游,该平面的海拔为损失函数的值。
窄极值和宽极值坦白的讲,可视化并理解高维权值空间的几何特性非常困难,但我们又不得不去了解它。因为随机梯度下降的本质是,在训练时穿过这一高维空间中的损失平面,试图找到一个良好的解——损失平面上的一个损失值较低的 “点”。不过后来我们发现,这一平面有很多局部极值。但这些局部极值并不都有一样好的性质。
一般极值点会有宽的极值和窄的极值,如下图所示:
数据科学家研究试验后发现:宽的局部极小值在训练和测试过程中产生类似的损失;但对于窄的局部极小值而言,训练和测试中产生的损失就会有很大区别。这意味着,宽的极值比窄的极值有更好的泛化性。
平坦度可以用来衡量一个解的优劣。其中的原理是,训练数据集和测试数据集会产生相似但不尽相同的损失平面。你可以将其想象为测试平面相对训练平面而言平移了一点。对窄的解来说,一个在测试的时候损失较低的点可能因为这一平移产生变为损失较高的点。这意味着窄的(尖锐的)解的泛化性不好——训练损失低,测试损失高。另一方面,对于宽的(平坦的)解而言,这一平移造成的训练损失和测试损失间的差异较小。
之所以提到窄极值和宽极值,是因为随机加权平均(SWA)就能带来讨人喜欢的、宽的(平坦的)解。
快照集成(Snapshot Ensembling)快照集成应用了应用了热重启随机梯度下降(SGDR),最初,SGD 会在权值空间中跳出一大步。接着,由于余弦退火,学习率会逐渐降低,SGD 逐步收敛到局部极小值,缓存权重作为一个模型的 “快照”,把它加入集成模型。然后将学习率恢复到更高的值,这种更高的学习率将算法从局部极小值推到损失面中的随机点,然后使算法再次收敛到另一个局部极小值。重复几次,最后,他们对所有缓存权重集的预测进行平均,以产生最终预测。
上图对比了使用固定学习率的单个模型与使用循环学习率的快照集成的收敛过程,快照集成是在每次学习率周期末尾保存模型,然后在预测时使用。
快照集成的周期长度为 20 到 40 个 epoch。较长的学习率周期是为了在权值空间中找到足够具有差异化的模型,以发挥集成的优势。如果模型太相似,那么集成模型中不同网络的预测将会过于接近,以至于集成并不会带来多大益处了。
快照集成表现优异,提升了模型的表现,但快速几何集成效果更好。
快速几何集成(Fast Geometric Ensembling,FGE)《Loss Surfaces, Mode Connectivity, and Fast Ensembling of DNNs》中提出的快速几何集成 FGE 和快照集成非常像,但是也有一些独特的特点。它们的不同主要有两点。第一,快速几何集成使用线性分段周期学习率规划,而不是余弦变化。第二,FGE 的周期长度要短得多——2 到 4 个 epoch。乍一看大家肯定直觉上觉得这么短的周期是不对的,因为每个周期结束的时候的得到的模型互相之间离得太近了,这样得到的集成模型没有什么优势。然而作者们发现,在足够不同的模型之间,存在着损失较低的连通路径。我们有机会沿着这些路径用较小的步长行进,同时这些模型也能够有足够大的差异,足够发挥集成的优势。因此,相比快照集成, FGE 表现更好,搜寻模型的步长更小(这也使其训练更快)。
左图:根据传统的直觉,良好的局部极小值被高损失区域分隔开来(虚线)
中/右图:局部极小值之间存在着路径,这些路径上的损失都很低(实线)。
FGE 沿着这些路径保存快照,从而创建快照的集成。
要从快照集成或 FGE 中受益,需要存储多个模型,接着让每个模型做出预测,之后加以平均以得到最终预测。因此,我们为集成的额外表现支付了更高的算力代价。所以天下没有免费的午餐。真的没有吗?这就是随机加权平均的用武之地了。
随机加权平均(Stochastic Weight Averaging,SWA)随机加权平均只需快速几何集成的一小部分算力,就可以接近其表现。SWA 可以用在任意架构和数据集上,都会有不错的表现。根据论文中的实验,SWA 可以得到我之前提到过的更宽的极小值。在经典认知下,SWA 不算集成,因为在训练的最终阶段你只得到一个模型,但它的表现超过了快照集成,接近 FGE。
左图:W1、W2、W3 分别代表 3 个独立训练的网络,WSWA 为其平均值。
中图:WSWA 在测试集上的表现超越了 SGD。
右图:WSWA 在训练时的损失比 SGD 要高。
结合 WSWA 在测试集上优于 SGD 的表现,这意味着尽管 WSWA 训练时的损失较高,它的泛化性更好。
SWA 的直觉来自以下由经验得到的观察:每个学习率周期得到的局部极小值倾向于堆积在损失平面的低损失值区域的边缘(上图左侧的图形中,褐色区域误差较低,点 W1、W2、W3 分别表示 3 个独立训练的网络,位于褐色区域的边缘)。对这些点取平均值,可能得到一个宽阔的泛化解,其损失更低(上图左侧图形中的 WSWA)。
下面是 SWA 的工作原理。它只保存两个模型,而不是许多模型的集成:
-
- 第一个模型保存模型权值的平均值(WSWA)。在训练结束后,它将是用于预测的最终模型。
- 第二个模型(W)将穿过权值空间,基于周期性学习率规划探索权重空间。
SWA 权重更新公式
WSWA←WSWA⋅nmodels+Wnmodels+1
在每个学习率周期的末尾,第二个模型的当前权重将用来更新第一个模型的权重。因此,在训练阶段,只需训练一个模型,并在内存中储存两个模型。预测时只需要平均模型,基于其进行预测将比之前描述的集成快很多,因为在那种集成中,你需要使用多个模型进行预测,最后再进行平均。
方法实现论文的作者自己提供了一份 PyTorch 的实现https://github.com/timgaripov/swa
此外,基于 fast.ai 库的 SWA 可见https://github.com/fastai/fastai/pull/276/commits
4.小结本文主要介绍了几种梯度下降学习率的设定策略,其中 “固定学习率”、“学习率衰减” 适用于简单不太复杂的应用场景,“基于 Armijo 准则的线性回溯搜索算法” 可以当做一种自适应学习率调整,不过由于计算复杂且无法有效解决陷入局部极小值点和鞍点处的问题,使用的人并不多。在 “找到合适的学习率” 一小节中,我们介绍了一种简单有效的方法,可以快速找到一个适合的学习率,同时这种方法也可以界定学习率设定的合理范围,推荐使用。“热重启随机梯度下降” 是 “循环学习率” 和 “余弦退火” 的结合,可以非常有效的解决梯度下降容易陷入局部极值点和鞍点等问题,它正在成为当前效果最好的、也是最标准的做法,它简单易上手,计算量很轻,可以说事半功倍,尤其在深度学习中表现非常好,推荐使用。最后介绍了 “分层学习率”、“快照集成”,“随机加权平均”,是近段时间比较好的研究成果,也是不错的综合优化方法。
梯度下降是机器学习和深度学习中非常重要的优化算法,而学习率是用好梯度下降法的关键。除了一些其他客观的原因,学习率的设定是影响模型性能好坏的非常重要的因素,所以应该给予足够的重视。最后,还记得上面提到过的 “梯度下降算法有两个重要的控制因子:一个是步长,由学习率控制;一个是方向,由梯度指定。” 吗?我们已经在学习率上有了深入的探索和研究,那在方向上是否还有可以优化的方法?如果搜索方向不是严格的梯度方向是否也可行?这里就涉及使用二阶导数的牛顿法和拟牛顿法了。不过个人觉得它们远不及梯度下降重要,如果有时间再更新这方面的内容吧。
5.参考资料[1] 小胖蹄儿. learning rate 四种改变方式. CSDN
[2] Samuel Lynn-Evans. Ten Techniques Learned From fast.ai. FloydHub Blog
[3] Hafidz Zulkifli. Understanding Learning Rates and How It Improves Performance in Deep Learning. Towards Data Science
[4] Max Pechyonkin. Stochastic Weight Averaging — a New Way to Get State of the Art Results in Deep Learning. Towards Data Science
[5] 邹博. 机器学习