本章节对深度学习中几个关键性问题做成指导
- 寻找最优参数:有助于提升寻找最优参数的速度
- 权重初始值:直接影响到学习速度乃至学习是否成功
- Batch Normalization:通过对激活值进行正规化,提升学习效果
- 过拟合:解决模型在训练集合上过于匹配,而对于测试集合效果不好的问题
- 寻找超参数:如何高效寻找超参数(学习率、权值衰减系数、batch大小等)
寻找最优参数 Optimizer
神经网络进行学习的目的是找到最优参数,从而使损失函数的值降到最低,这个寻找最优参数的过程称为最优化(optimization)。寻找最优参数的过程之所以曲折,是因为神经网络的参数空间复杂,无法通过简单运算求解,而深度学习又大大增加了参数的数量。
随机梯度下降法 SGD
Stochastic gradient descent,简称SGD,把损失函数对参数求梯度后,找到损失函数值下降最快的方向,沿着这个梯度方向逐步更新参数,不断重复此步骤,从而靠近最优参数。SGD是一种直观的方法,但存在着效率低下、不容易收敛的问题。
图:沿切线(梯度)方向前进,以接近最低点
图:SGD的算式更新过程
在研究optimizer的过程中,可以声明一个接口update,不同类型的optimizer实现该接口,从而能够自由替换(模板模式),以下是SGD的更新参数过程。
class SGD:
"""随机梯度下降法(Stochastic Gradient Descent)"""
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() # 支持替换为其它optimizer
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的缺点在于,处理一些问题时存在震荡,导致学习效率低下,出现问题的场景是单点的梯度没有指向最小值,以下面这个式子为例,它在y轴方向的梯度值远大于x轴方向,且并未指向最小值(0, 0)。
图:Y轴方向梯度并未指向最低点(0,0)
为了解决这种更新参数效率低下的问题,神经网络研究者们提出了Momentum、AdaGrad、Adam等方法。
动量法 Momentum
图:动量法算式
可以观察到上例中,y轴方向的速度虽大,但其符号(方向)是不断变化的,而x轴方向虽然速度小,但方向始终指向(0,0),因此引入了加速度的概念,对于速度持续不变的方向,让其速度不断变大。同时设定一个阻力值a=0.9,可以想象一个小球在弧面上滚动。其实现如下:
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) # 初始速度0
for key in params.keys():
self.v[key] = self.momentum * self.v[key] - self.lr * grads[key] # 更新速度,如果运动方向不变,则速度绝对值会变大
params[key] += self.v[key] # 更新梯度
学习率衰减法 AdaGrad
学习率是非常重要的超参数,在神经网络的学习过程中,如果使用的学习率过大,会导致学习过程发散,无法使模型收敛。如果使用的学习率过小,则学习次数过多、效率低下。
如果在优化过程中动态调节学习率尺度,前期大步快走,后期小步精细化,不失是一种明智的策略。AdaGrad(Adaptive Gradian)就是这样一种优化方法,通过记录历史梯度的平方和,用作学习率η的分母,这样随着学习轮次不断进行,分母越来越大,从而使学习率逐渐变小。
图:AdaGrad算式
如果无止境地学习下去,更新量会变为0,。对此,RMSProp方法提出逐步遗忘最古老的梯度,在做加法运算时更多反映出新梯度的信息,这在专业上称为“指数移动平均”
# AdaGrad方法
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():
sef.h[key] += grads[key] *grads[key] # 梯度平方和
params[key] -= self.lr * grads[key]/(np.sqrt(self.h[key])) + 1e-7) # 微小值防止除0
图:AdaGrad的更新路径
动量+学习率衰减法 Adam
Adam是AdaGrad+Momentumd的简称,在2015年提出。从名字上可以看出它综合了这两者的优点,能实现参数空间的高效搜索。此外,进行超参数的偏置校正也是Adam的特征。它的更新路径如下:
图:Adam更新路径
关于optimizer的小结
本文主要介绍了4种优化器optimizer,在实际使用时,要根据所解决的问题灵活选用,并不存在一个可以适用于任何场景的优化器,每一种方法都有各自擅长解决的问题和不擅长解决的问题。
图:四种优化器路径
权重初始值 Initial Weight
第二个课题是关于权重初始值的,虽然学习的目的是将权重收敛为最佳(使损失函数的值最小),但并不是所有的初始值都能正确导向最佳值,设定什么样的权重初始值,经常可以关系到神经网络的学习是否能够成功。
通常应当将权重初始值设定为较小的值,如0.01*np.random.randn(10,100),以防止过大的权重值产生过拟合。
权重初始值不可以设置为相等(0)
严格来说,是权重的初始值不可以全部设置成相同的值,其原因在于,反向传播时,相同的权重会产生相同的更新,因此,权重被更新为相同的值,并且拥有了对称(重复)的值,这就导致神经网络拥有许多不同权重的意义消失了。为了瓦解权重的对称结构,必须随机生成初始值。
梯度消失与表现力受限
采用不同的权重分布,会产生不同分布的隐藏层激活值输出,以下代码分别设置了标准差为1和0.01的权重初始矩阵。
# coding: utf-8
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def ReLU(x):
return np.maximum(0, x)
def tanh(x):
return np.tanh(x)
input_data = np.random.randn(1000, 100) # 1000个数据
node_num = 100 # 各隐藏层的节点(神经元)数
hidden_layer_size = 5 # 隐藏层有5层
activations = {} # 激活值的结果保存在这里
x = input_data
for i in range(hidden_layer_size):
if i != 0:
x = activations[i-1]
# 改变初始值进行实验!
w = np.random.randn(node_num, node_num) * 1
# w = np.random.randn(node_num, node_num) * 0.01
# w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num)
# w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num)
a = np.dot(x, w)
# 将激活函数的种类也改变,来进行实验!
z = sigmoid(a)
# z = ReLU(a)
# z = tanh(a)
activations[i] = z
# 绘制直方图
for i, a in activations.items():
plt.subplot(1, len(activations), i+1)
plt.title(str(i+1) + "-layer")
if i != 0: plt.yticks([], [])
# plt.xlim(0.1, 1)
# plt.ylim(0, 7000)
plt.hist(a.flatten(), 30, range=(0,1))
plt.show()
图:标准差1的初始权重学习过程
各层激活值偏向0和1分布,随着输出不断靠近0和1,其导数值无限趋近于0,会造成反向传播过程中梯度值不断减小最后消失,这称为梯度消失(gradient vanishing)问题。层次越深的深度学习,越容易出现梯度消失的情况。
图:标准差0.01的初始权重学习过程
在初始权重标准差为0.01的场景里,虽然没有发生梯度消失,但可以看到,各层的激活值都向同一区域集中,这种现象称为表现力受限,因为理想中不同神经元的输出应当分布在不同区域,这样的模型才是具有活力的。如果多个神经元输出都分布在同一个区域,它们的功能其实可以收缩为一个神经元,神经网络的意义也就不存在了。
解决方案:Xavier与He初始值
Xavier Glorot在论文中提出了一种适用度广泛的权重初始值设定规则,称为Xavier初始值,其定义为:如果前一层节点数是n,则初始值使用标准差为1/sqrt(n)的分布。
# Xavier初始值
node_num = 100 # 前一层初始值
w = np.random.randn(node_num, node_num) / np.sqrt(node_num)
图:Xavier初始值下,各层激活值分布
Xavier初始值下,各层激活值呈现了比之前更有广度的分布,因此sigmoid函数的表现力不受限制,有望进行更高效的学习。
Xavier初始值适用于线性函数作为激活函数的场景(sigmoid、tanh函数左右对称且中央店附近可以视为线性函数),对于采用ReLU作为激活函数的场景,可以选用He初始值,它的定义是Xavier初始值的根号2倍,即sqrt(2/n)。直观上可以解释为,因为ReLU的负值区域均为0,为了使它更有广度,扩大权重值。
图:激活函数使用ReLU时,不同权重初始值的激活值分布变化
批量正规化 Batch Normalization
简称Batch Norm,实现思路是调整各层的激活值分布使其拥有适当的广度,手段是在神经网络中插入对数据分布进行正规化的层,即Batch Normalization层。它有如下优点:
- 可以使学习快速进行(可增大学习率)
- 不那么依赖初始值(对于初始值不敏感)
- 抑制过拟合(降低Dropout等的必要性)
Batch Norm层通常跟在Affine全连接层之后,但最后一层除外,最后一层Affine通常与Softmax相连,如下图:
Batch Norm在学习时以mini-batch为单位,进行是数据分布的均值为0、方差为1的正规化。
图:正规化,
ε是防止除0的微小值,如10e-7
接着进行缩放和平移变换。
图:缩放与平移
图:Batch Normalization的计算图
使用Batch Norm层以后,学习速度显著加快。
应对过拟合 Overfitting
机器学习中,过拟合是一个很常见的问题,其定义为模型只能拟合训练数据,但不能很好地拟合不包含在训练数据中的其他数据的状态。机器学习的目标是获取具有泛化能力的模型,过拟合违背了泛化的目标。
发生过拟合的原因主要有两点:
- 模型拥有大量参数,表现力强
- 训练数据少
一个典型的过拟合模型表现如下图,训练数据的识别精度无限接近1.0,而测试数据精度与训练数据差距较大。
要抑制过拟合,采取的思路是对于较大的权重进行遏制,通常有权值衰减和Dropout两种方式。
权值衰减
权值衰减为损失函数加上权重的平方范数(L2范数),从而抑制权重变大。如果将权重记为W,则L2范数的权值衰减就是0.5λW^2,将其加在损失函数上(λ取值0.1),对较大的权重进行惩罚。
图:增加权值衰减后,训练数据和测试数据识别精度相互靠近
Dropout
Dropout是一种边学习边随机删除神经元的方法(卸磨杀驴),被删除的神经元不再传递信号。
class Dropout:
"""
http://arxiv.org/abs/1207.0580
"""
def __init__(self, dropout_ratio=0.5):
self.dropout_ratio = dropout_ratio
self.mask = None
def forward(self, x, train_flg=True):
if train_flg:
self.mask = np.random.rand(*x.shape) > self.dropout_ratio # 大于ration则置为True
return x * self.mask
else:
return x * (1.0 - self.dropout_ratio)
def backward(self, dout):
return dout * self.mask
机器学习中经常使用集成学习,是指让多个模型单独进行学习,推理时再取多个模型的输出的平均值。Dropout利用集成学习的思想,每一次学习都相当于一个独立的模型。因此可以提升神经网络的识别精度。
超参数 Hyper Parameter
超参数是指神经网络中除了权重和偏置以外的参数,如神经元数量、batch大小、学习率或者权值衰减等,设定超参数的过程是一个不断试错、迭代演化求出最优解的过程。
评估超参数时,不能使用训练数据,这会发生过拟合,通常是单独开辟出一块验证数据(validation data),用于评估超参数的好坏。举例说明,对于MNIST数据集,从训练数据中切割20%作为验证数据。
超参数最优化策略
采用随机采样的搜索方式,先大致指定超参数范围(0.001~1000),随机采样设定超参数值,根据识别精度,缩小超参数的范围。
- 设定超参数的范围
- 从设定的超参数范围中随机采样
- 使用步骤1中采样到的超参数的值进行学习,通过验证数据评估识别精度(但是要将epoch设置得很小)
- 重复步骤1和步骤2(100次等),根据它们的识别精度的结果,缩小超参数的范围
重复上述步骤,直到将超参数的范围缩小到足够小,此时从范围里取一个值作为超参数。
更严密高效的方法可以选用贝叶斯最优化(Bayesian optimization),运用贝叶斯定理进行论证取值
超参数最优化实现
超参数随机取值样例代码如下:
weight_decay = 10 ** np.random.uniform(-8, -4) # 权重10e-8 ~ 10e-4
lr = 10 *8 np.random.uniform(-6, -2) # 学习率10e-6 ~ 10e-2
多次试验后,可以得出类似上图的识别精度对比图,其中Best 1~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、权值衰减系数在10e−8到10e−6之间时,学习可以顺利进行。
小结
本节学习了神经网络中的一系列优化技巧,有优化参数迭代过程的SGD、AdaGrad等更新方法,设置权重初始值的Xavier、He方法,以及提升学习效率的Batch Norm,解决过拟合的Dropout方法。最后提出了随机采样来缩小超参数选择范围的实践思路。