目录
本章将介绍神经网络的学习中的一些重要观点,主题涉及寻找最优权重参数的最优化方法、权重参数的初始值、超参数的设定方法等。此外,为了应对过拟合,本章还将介绍权值衰减、Dropout等正则化方法,并进行实现。最后将对近年来众多研究中使用的Batch Normalization方法进行简单的介绍。使用本章介绍的方法,可以高效地进行神经网络 ( 深度学习 ) 的学习,提高识别精度。让我们一起往下看吧!
1、参数的更新:
最优权重的最优化方法包括SGD、Momentum、AdaGrad、Adam。
1.1SGD(随机梯度下降法):
SGD的策略就是蒙着眼下山,看不见方向,不知道地图,只能用脚感受山的坡度,心里只记得一件事,就是朝着坡度下降最大的方向下山,总有一天会到达“谷底”,也就是损失函数最小的地方。
SGD的数学表达式为:
w ← w − η ∂ L ∂ w w\leftarrow w-\eta \frac{\partial L}{\partial w} w←w−η∂w∂L
w
w
w为权重
η
\eta
η为学习率,一般去0.01或者0.001
←
\leftarrow
←为用右边的值更新左边的值
∂
L
∂
w
\frac{\partial L}{\partial w}
∂w∂L为损失函数关于
w
w
w的梯度
class SGD:
def __init__(self, lr=0.01):
self.lr = lr
def update(self, params, grads):
for key in params.keys():
params[key] -= self.lr * grads[key]
network = TwoLayerNet(...)
optimizer = SGD()
for i in range(10000):
...
x_batch, t_batch = get_mini_batch(...) # mini-batch
grads = network.gradient(x_batch, t_batch)
params = network.params
optimizer.update(params, grads)
...
在深度学习的框架中都实现了各种最优化方法,并且提供了可以简单切换这些方法的构造。
SGD的缺点:
思考一下式子的最下值:
f
(
x
,
y
)
=
1
2
x
2
+
y
2
f(x,y)=\frac{1}{2} x_{2}+y_{2}
f(x,y)=21x2+y2
它的最小值为(0,0)处,但是这个式子是向x方向延伸的“碗”状函数。
梯度特征为y方向梯度大,x方向梯度小,可以看出虽然函数的最小值在(0,0),但是梯度在很多地方并没有指向(0,0)。
尝试对函数应用SGD,从(-7,2)处开始检索,结果如图所示。SGD之所以沿“之”字形移动,是因为梯度方向并没有指向最小值方向,这就造成使用SGD求最小值就很低效。
一般当函数的形状非均向,比如呈延伸状、SGD搜索的路径就会非常低效。
为了改正SGD的缺点,下面我们将会介绍Momentum、AdaGrad、Adam来取代SGD。
1.2 Momentum
Momentum的数学表达式:
v
←
α
v
−
η
∂
L
∂
w
v\leftarrow \alpha v-\eta \frac{\partial L}{\partial w}
v←αv−η∂w∂L
w
←
w
+
v
w\leftarrow w+v
w←w+v
相比SGD,这里处理了一个新变量 v v v,保存物体的速度,物体在梯度方向上受力,这力的作用下,物体的速度就增加。就像下面的下面在斜坡上运动的小球一样:
在没有力,也就是在没有坡度的平地上运动时, α v \alpha v αv这一项充当地面上摩擦力的作用使物体逐渐减速( α \alpha α小于1)。
代码实现:
class Momentum:
def __init__(self, lr=0.01, momentum=0.9):
self.lr = lr
self.momentum = momentum
self.v = None
def update(self, params, grads):
if self.v is None:
self.v = {}
for key, val in params.items():
self.v[key] = np.zeros_like(val)
for key in params.keys():
self.v[key] = self.momentum*self.v[key] - self.lr*grads[key]
params[key] += self.v[key]
现在尝试用Momentum来解决 f ( x , y ) = 1 2 x 2 + y 2 f(x,y)=\frac{1}{2} x_{2}+y_{2} f(x,y)=21x2+y2最优化问题,如图,可以看出路径变得平缓,“之”字形的“程度”也变轻了,我的理解为在变量优化的式子中加入速度v,v在梯度也就是力的作用下存在惯性,所以路径不会繁盛急速的转弯,由此变得平缓。
1.3 AdaGrad
在神经网络中,学习率的选取非常重要,学习率过大会造成学习发散而不能正常学习,学习率过小会造成学习花费过多的时间。
学习率衰减(learning rate decay)的方法可以有效改变的学习率,也就是随着学习的进行,使得学习率逐渐减小。实际上,一开始“多学”,后来“少学”的方法在神经网络中应用广泛。
在学习率衰减的想法中,相当于让“全体”参数的学习率一起降低,但是AdaGrad对此进行了改进,它会为每个参数的==每个元素(比如下面问题的x,y元素)==适当的调整学习率,与此同时进行学习。
公式为:
h
←
h
+
∂
L
∂
w
⨀
∂
L
∂
w
h\leftarrow h+\frac{\partial L}{\partial w} \bigodot \frac{\partial L}{\partial w}
h←h+∂w∂L⨀∂w∂L
w
←
w
−
η
1
h
∂
L
∂
w
w\leftarrow w-\eta \frac{1}{\sqrt{h}} \frac{\partial L}{\partial w}
w←w−ηh1∂w∂L
在这里新添加了一个元素 h h h,它保存了以前的所有梯度值的平方和($ \bigodot$表示对应矩阵的元素相乘)。这就意味着,参数的元素中变动较大(被大幅更新)的元素的学习率变小。这就可以按照参数的变化来调整学习率的变化了。
代码实现:
class AdaGrad:
def __init__(self, lr=0.01):
self.lr = lr
self.h = None
def update(self, params, grads):
if self.h is None:
self.h = {}
for key, val in params.items():
self.h[key] = np.zeros_like(val)
for key in params.keys():
self.h[key] += grads[key] * grads[key]
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
在这里加上1e-7是为了防止h[key]为0,除数为0,在许多的深度学习框架中,还会把这个微笑值设置为参数,这里使用固定值。
按照AdaGrad的方式解决 f ( x , y ) = 1 2 x 2 + y 2 f(x,y)=\frac{1}{2} x_{2}+y_{2} f(x,y)=21x2+y2最优化问题,结果如图所示:
在最开始y方向的梯度较大,所以变动较大,但是变动大,学习率就衰减,造成y方向的更新程度被减弱,“之”字形的变动程度有所衰减。
1.4 Adam
Adam的基本思路是将Momentum和AdaGrad两种方法进行结合。
按照Adam的方式解决 f ( x , y ) = 1 2 x 2 + y 2 f(x,y)=\frac{1}{2} x_{2}+y_{2} f(x,y)=21x2+y2最优化问题,结果如图所示:
Adam会设置3个超参数。一个是学习率 ( 论文中以α出现 ) ,另外两个是一次momentum系数β 1 和二次momentum系数β 2 。根据论文,标准的设定值是β 1 为0.9,β 2 为0.999。设置了这些值后,大多数情况下都能顺利运行。
1.5 使用哪种优化方法呢?
以上共介绍了四种参数优化的方法,那具体应用的时候,应该选哪一种呢?直接选Adam?
非常遗憾, ( 目前) 并不存在能在所有问题中都表现良好的方法。这4种方法各有各的特点,都有各自擅长解决的问题和不擅长解决的问题。很多研究中至今仍在使用SGD。Momentum和AdaGrad也是值得一试的方法。最近,很多研究人员和技术人员都喜欢用Adam。大家可以根据自己的项目的需求,进行尝试。再来决定会用哪种。
下图是在数字识别中使用不同的优化方法的结果:
结果显示,与SGD相比,其他3种方法可以学习得更快,识别精度也更高。
值得注意的是,实验结果会随着学习率等超参数、神经网络的结构(层数等)的不同而发生变化。
2、权重的初始值
在神经网络中,权重参数的初始化十分重要,设定什么样的初始值意味着神经网络的学习会不会成功。
2.1可以直接把权值初始值设置为0吗?
答案是不可以。因为这样会造成在反向传播时,权值会进行相同的更新。为什么会造成权值进行相同更新呢?假设网络的第一、二层的权重初始化为0,第一层的输出一样,导致第二层的输入一样,一维在反向传播时,第二层的神经元会进行相同的更新。这样就会造成无法正常学习。
因此,权重被更新为相同的值,并拥有了对称的值 (重复的值) 。这使得神经网络拥有许多不同的权重的意义丧失了。为了防止 “权重均一化”( 严格地讲,是为了瓦解权重的对称结构 ) ,必须随机生成初始值。
那到底应该怎么初始化才好呢?
后面我们会介绍抑制过拟合、提高泛化能力的技巧——权值衰减 ( weight decay ) 。简单地说,权值衰减就是一种以减小权重参数的值为目的进行学习的方法。通过减小权重参数的值来抑制过拟合的发生。如果想减小权重的值, 一开始就将初始值设为较小的值才是正途。实际上,在这之前的权重初始值都是像 0.01 * np.random.randn(10, 100) 这样,使用由高斯分布生成的值乘以0.01后得到的值 (标准差为0.01的高斯分布) 。
2.2隐藏层的激活值的分布
做一个简单的实验,观察权重初始值是如何影响隐藏层的激活值的分布的。这里要做的实验是,向一个5层神经网络 ( 激活函数使用sigmoid函数 ) 传入随机生成的输入数据,用直方图绘制各层激活值的数据分布。 这里我们将激活函数的输出数据称为 “激活值” 。
#标准差为1的高斯分布
w = np.random.randn(100,100) * 1
从图6-10可知,各层的激活值呈偏向0和1的分布。这里使用的sigmoid函数是S型函数,随着输出不断地靠近0 ( 或者靠近1) ,它的导数的值逐渐接近0。因此,偏向0和1的数据分布会造成反向传播中梯度的值不断变小,最后消失。这个问题称为梯度消失 ( gradient vanishing ) 。层次加深的深度学习中,梯度消失的问题可能会更加严重。
下面,将权重的标准差设为0.01:
# w = np.random.randn(node_num, node_num) * 1
w = np.random.randn(node_num, node_num) * 0.01
这次激活值都集中在0.5,就不会出现梯度消失的问题,但是激活值的分布有所偏向,表现力存在很多问题。之所以这么说是因为当所有的神经元输出都几乎一样的值的话,那就完全可以用一个神经元来代替它们表达基本相同的事情。因此,激活值在分布上有所偏向会出现“表现力受限”的问题。
各层的神经元激活值的分布应该具有广度,为什么呢?因为只有通过在各层传递多样性的数据,神经网格才能高效学习。
接着, 我们尝试使用Xavier Glorot等人的论文中推荐的权重初始值(俗称 “Xavier初始值” ) 。现在,在一般的深度学习框架中,Xavier初始值已被作为标准使用。比如, Cafe框架中, 通过在设定权重初始值时赋予xavier参数,就可以使用Xavier初始值。Xavier的论文中,为了使各层的激活值呈现出具有相同广度的分布,推导了合适的权重尺度。推导出的结论是,如果前一层的节点数为n,则初始值使用标准差为 1 n \frac{1}{\sqrt{n}} n1的分布 。
node_num = 100 # 前一层的节点数
w = np.random.randn(node_num, node_num) / np.sqrt(node_num)
可以看出各层间传递的数据有适当的广度,所以sigmoid函数的表现力不受限制,有望进行高效的学习。
在6-13中,会发现激活值的分布呈稍微倾斜的状态,用双曲线函数tanh代替sigmoid函数乐意缓解这种情况。tanh函数是关于(0,0)对称的S型曲线。众所周知,激活函数最好具有关于原点对称的性质。
2.3ReLu的权重初始化
Xavier 初始值是以激活函数是线性函数为前提而推导出来的。因为sigmoid函数和tanh函数左右对称,且中央附近可以视为线性函数,所以适合使用Xavier 初始值,但是ReLu函数无法近似成线性函数。He初始值使用标准差为 2 n \frac{2}{\sqrt{n}} n2的高斯分布。因为ReLU的负值区域的值为0,为了使它更有广度,所以需要2倍的系数。
现在来看一下激活函数使用ReLU时激活值的分布。我们给出了3个实验的结果 ( 图6-14 ) ,依次是权重初始值为标准差是0.01的高斯分布 (下文简写为 “std = 0.01” ) 时、初始值为Xavier初始值时、初始值为ReLU专用的“He初始值” 时的结果。
总结一下,当激活函数使用ReLU时,权重初始值使用He初始值,当激活函数为 sigmoid 或 tanh 等S型曲线函数时,初始值使用Xavier初始值。这是目前的最佳实践。
综上,在神经网络的学习中,权重初始值非常重要。很多时候权重初始值的设定关系到神经网络的学习能否成功。权重初始值的重要性容易被忽视,而任何事情的开始 (初始值 ) 总是关键的,因此在结束本节之际,再次强调一下权重初始值的重要性。
3、Batch Normalization(批标准化)
具体的内容可以看我的另外一篇文章:一篇文章搞懂Batch Normalization批标准化
如果设定合适的权值初始值,可以使得激活值会有适当的广度,但是是否可以强制性的调整激活值的分布呢?Batch Normalization 方法就是基于这个想法而产生的。
BN具有以下优点:
• 可以使学习快速进行 ( 可以增大学习率) 。
• 不那么依赖初始值 ( 对于初始值不用那么神经质) 。
• 抑制过拟合 ( 降低Dropout等的必要性) 。
如前所述,Batch Norm的思路是调整各层的激活值分布使其拥有适当的广度。为此,要向神经网络中插入对数据分布进行正规化的层,即Batch Normalization层 ( 下文简称Batch Norm层) ,如图6-16所示。
图6-19是权重初始值的标准差为各种不同的值时的学习过程图。我们发现,几乎所有的情况下都是使用Batch Norm时学习进行得更快。同时也可以发现,实际上,在不使用Batch Norm的情况下,如果不赋予一个尺度好的初始值,学习将完全无法进行。
4、正则化
机器学习的目标是提高泛化能力,我们可以制作复杂的,表现力强的模型,但是相应地,抑制过拟合的技巧也很重要。
4.1 过拟合
发生过拟合的原因,主要有以下两个。
• 模型拥有大量参数、表现力强。
• 训练数据少。
4.2 权值衰减
权值衰减进场用于抑制过拟合,这个方法的主要思路是在训练过程中对大的权重进行惩罚,从而来抑制过拟合。
权值越大,就会造成过拟合吗?
如何进行权值衰减呢?
对于所有的权重,权值衰减方法都会为损失函数加上
1
2
λ
w
2
\frac{1}{2}\lambda w^{2}
21λw2。
1
2
λ
w
2
\frac{1}{2}\lambda w^{2}
21λw2是L2范数的权值衰减。
λ
\lambda
λ是控制正则化强度的超参数,
λ
\lambda
λ设置的越大,对大的权重的惩罚就越大。L2范数相当于各个元素的平方和。
加权值衰减前:
加权值衰减后:
4.3 Dropout
权值衰减方法在某种程度上能够抑制过拟合。但是,如果网络的模型变得很复杂,只用权值衰减就难以应对了。在这种情况下,我们经常会使用Dropout 方法。
Dropout是一种在学习的过程中随机删除神经元的方法。
正向传播时传递了信号的神经元,反向传播时按原样传递信号;正向传播时没有传递信号的神经元,反向传播时信号将停在那里。
并且,训练数据也没有到达100%的识别精度。像这样,通过使用Dropout,即便是表现力强的网络,也可以抑制过拟合。
5、超参数的验证:
超参数 (hyper-parameter) :各层的神经元数量、batch大小、参数更新时的学习率或权值衰减等。
如果这些超参数没有设置合适的值,模型的性能就会很差。虽然超参数的取值非常重要,但是在决定超参数的过程中一般会伴随很多的试错。本节将介绍尽可能高效地寻找超参数的值的方法。
5.1验证数据
之前我们使用的数据集分成了训练数据和测试数据,训练数据用于学习,测试数据用于评估泛化能力。
调整超参数时,必须使用超参数专用的确认数据。用于调整超参数的数据,一般称为验证数据 ( validation data ) 。
为什么不能用测试数据评估超参数的性能呢?这是因为如果使用测试数据调整超参数,超参数的值会对测试数据发生过拟合。换句话说,用测试数据确认超参数的值的 “好坏” ,就会导致超参数的值被调整为只拟合测试数据。这样的话,可能就会得到不能拟合其他数据、泛化能力低的模型。这一点非常重要,但也容易被忽视。
训练数据用于参数 ( 权重和偏置 ) 的学习;
验证数据用于超参数的性能评估。为了确认泛化能力;
要在最后使用 ( 比较理想的是只用一次 )测试数据。
MNIST数据集,获得验证数据的最简单的方法就是从训练数据中事先分割20%作为验证数据:
(x_train, t_train), (x_test, t_test) = load_mnist()
# 打乱训练数据
x_train, t_train = shuffle_dataset(x_train, t_train)
# 分割验证数据
validation_rate = 0.20validation_num = int(x_train.shape[0] * validation_rate)
x_val = x_train[:validation_num]
t_val = t_train[:validation_num]
x_train = x_train[validation_num:]
t_train = t_train[validation_num:]
接下来,我们使用验证数据观察超参数的最优化方法。
5.2超参数的最优化:
优化超参数的过程为:先确定一个大致的范围,再从这个范围内随机选取一个超参数,进行实验精度的评估,进行多次实验,观察精度的变化,从而来缩小合适的超参数的范围。
使用MNIST数据集进行超参数的最优化。
步骤0
设定超参数的范围:
在该实验中,权值衰减系数的初始范围为
1
0
−
8
10^{-8}
10−8 到
1
0
−
4
10^{-4}
10−4,学习率的初始范围为
1
0
−
6
10^{-6}
10−6 到
1
0
−
2
10^{-2}
10−2。
步骤1
从设定的超参数范围中随机采样:
weight_decay = 10 ** np.random.uniform(-8, -4)
lr = 10 ** np.random.uniform(-6, -2)
步骤2
使用步骤1中采样到的超参数的值进行学习,通过验证数据评估识别精度 ( 但是要将epoch设置得很小 ) 。
图 6-24 中,按识别精度从高到低的顺序排列了验证数据的学习的变化。从图中可知,直到 “Best-5” 左右,学习进行得都很顺利。因此,我们来观察一下 “Best-5” 之前的超参数的值 (学习率和权值衰减系数) ,结果如下所示。
Best-1 (val acc:0.83) | lr:0.0092, weight decay:3.86e-07
Best-2 (val acc:0.78) | lr:0.00956, weight decay:6.04e-07
Best-3 (val acc:0.77) | lr:0.00571, weight decay:1.27e-06
Best-4 (val acc:0.74) | lr:0.00626, weight decay:1.43e-05
Best-5 (val acc:0.73) | lr:0.0052, weight decay:8.97e-06
从这个结果可以看出,学习率在0.001到0.01、权值衰减系数在10 −8 到10 −6 之间时,学习可以顺利进行。像这样,观察可以使学习顺利进行的超参数的范围,从而缩小值的范围。
步骤3
重复步骤1和步骤2 ( 100次等 ) ,根据它们的识别精度的结果,缩小超参数的范围:
反复进行上述操作,不断缩小超参数的范围,在缩小到一定程度时,从该范围中选出一个超参数的值。这就是进行超参数的最优化的一种方法。
小结:
损失函数优化方法:SGD、Adam、Momentum、AdaGrad
权重参数初始化:不能初始化为0,Xavier初始值、He初始值等比较有效。
Batch Normalization可以加速学习速度,并且是初始值变得健壮(也就不太依靠初始值的设定)
正则化:权值衰减法、Dropout
超参数设置