0
点赞
收藏
分享

微信扫一扫

【笔记】深度学习入门:基于Python的理论与实现(四)

与学习(神经网络的学习阶段)相关的技巧

本章主题涉及寻找最优权重参数的最优化方法、权重参数的初始值、超参数的设定方法等。此外,为了应对过拟合,本章还将介绍权值衰减、Dropout 等正则化方法,并进行实现。 最后将对近年来众多研究中使用的Batch Normalization方法进行简单的介绍。

参数的更新

前面学习的随机梯度下降法(stochastic gradient descent), 简称 SGD

SGD

复习一下:

class SGD:
	def __init__(self, lr=0.01):
		# learning rate(学习率)
		self.lr = lr
	# p g分别保存了权重参数和它们的梯度
	def update(self, params, grads): 
		for key in params.keys():
			params[key] -= self.lr * grads[key]

缺陷:

如果函数的形状非均向(anisotropic),比如呈延伸状,搜索 的路径就会非常低效,比如:

【笔记】深度学习入门:基于Python的理论与实现(四)_过拟合_02

【笔记】深度学习入门:基于Python的理论与实现(四)_权重_03

Momentum

动量,这里新出现了一个变量 v,对应物理上的速度,表示了物体在梯度方向上受力,在这个力的作用下,物体的速度增 加这一物理法则,αv 这一项。在物体不受任何力时,该项承担使物体逐渐减速的任务(α设定为0.9之类的值),对应物理上的地面摩擦或空气阻力

【笔记】深度学习入门:基于Python的理论与实现(四)_过拟合_04

python实现:

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]

虽然 x 轴方向上受到的力非常小,但是一直在同一方向上受力,所以朝同一个方向会有一定的加速。反过来,虽然 y 轴方向上受到的力很大,但是因为交互地受到正方向和反方向的力,它们会互相抵消,所以 y 轴方向上的速度不稳定

【笔记】深度学习入门:基于Python的理论与实现(四)_数据_05

AdaGrad

学习率衰减(learning rate decay)的方法,即随着学习的进行,使学习率逐渐减小,AdaGrad进一步发展了这个想法,AdaGrad 会为参数的每个元素适当地调整学习率,与此同时进行学习 (AdaGrad 的 Ada 来自英文单词 Adaptive,即“适当的”的意思)。

【笔记】深度学习入门:基于Python的理论与实现(四)_权重_06

这里新出现了变量 h,如式 (6.5) 所示,它保 存了以前的所有梯度值的平方和(式1中的O表示对应矩阵元素的乘法),使变动大的参数的学习率逐渐减小,因此,学习越深入,更新的幅度就越小

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]
		# 微小数这是为了防止当 self.h[key] 中有 0 时,将 0 用作除数的情况
		params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)

【笔记】深度学习入门:基于Python的理论与实现(四)_数据_07

由于 y 轴方向上的梯度较大,因此刚开始变动较大,但是后面会根据这个较大的变动按比例进行调整,减小更新的步伐。因此,y 轴方向上的更新程度被减弱,“之” 字形的变动程度有所衰减。

Adam

融合了Momentum和AdaGrad,通过组合前面两个方法的优点,有望 实现参数空间的高效搜索

【笔记】深度学习入门:基于Python的理论与实现(四)_权重_08

相比之下,Adam 的小球左右摇晃的程度 有所减轻。这得益于学习的更新程度被适当地调整了。

对比:

【笔记】深度学习入门:基于Python的理论与实现(四)_过拟合_09

基于 MNIST 数据集的更新方法的比较

这个实验以一个 5 层神经网络为对象,其中每层有 100 个神经元。激活 函数使用的是 ReLU。一般而言,与SGD相比,其他3种方法可 以学习得更快,有时最终的识别精度也更高。

【笔记】深度学习入门:基于Python的理论与实现(四)_权重_10

权重的初始值

可以将权重初始值设为 0 吗

将权重初始值设为 0 的话,将无法正确进行学习。为了防止“权重均一化” (严格地讲,是为了瓦解权重的对称结构),必须随机生成初始值'

隐藏层的激活值的分布

观察权重初始值是如何影响隐藏层的激活值(激活函数的输出数据)的分布的。例子:向一个5层神经网络(激活函数使用 sigmoid 函数)传入随机生成的输入数据,用直方图绘制各层激活值的数据分布

import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
	return 1 / (1 + np.exp(-x))
x = np.random.randn(1000, 100) # 1000个数据 
node_num = 100 # 各隐藏层的节点(神经元)数 
hidden_layer_size = 5 # 隐藏层有5层 
activations = {} # 激活值的结果保存在这里
for i in range(hidden_layer_size): if i != 0:
	x = activations[i-1]
	# 标准差为 0.01 和 1 的高斯分布,但实验的目的是通过改变这个尺度(标准差),观察激活值的分布如何变化
	# w = np.random.randn(node_num, node_num) * 0.01
	w = np.random.randn(node_num, node_num) * 1
	z = np.dot(x, w)
	a = sigmoid(z) # sigmoid函数 
	activations[i] = a
# 绘制直方图
for i, a in activations.items():
plt.subplot(1, len(activations), i+1) plt.title(str(i+1) + "-layer") plt.hist(a.flatten(), 30, range=(0,1))
plt.show()

各层激活值的分布

【笔记】深度学习入门:基于Python的理论与实现(四)_过拟合_11

权重的标准差设为 0.01

【笔记】深度学习入门:基于Python的理论与实现(四)_过拟合_12

这次呈集中在 0.5 附近的分布。因为不像刚才的例子那样偏向 0 和 1,所以不会发生梯度消失的问题

Xavier 的论文中,为了使各层的激活值呈现出具有相同广度的分布,推导了合适的权重尺度。推导出的结论是,如果前一层的节点数为 n,则初始值使用标准差为 1/n**1/2 的分布,使用 Xavier 初始值后,前一层的节点数越多,要设定为目标节点的初始 值的权重尺度就越小。

【笔记】深度学习入门:基于Python的理论与实现(四)_数据_13

node_num = 100 # 前一层的节点数
w = np.random.randn(node_num, node_num) / np.sqrt(node_num)

使用 Xavier 初始值后的结果:

【笔记】深度学习入门:基于Python的理论与实现(四)_数据_14

ReLU 的权重初始值

为 sigmoid 函数和 tanh 函数左右对称,且中央附近可以视作线性函数,所以适 合使用 Xavier 初始值。但当激活函数使用 ReLU 时,一般推荐使用 ReLU 专 用的初始值“He初始值”,He 初始值使用标准差为 2/n1/2 的高斯分布。当 Xavier 初始值是 1/n1/2 时,(直观上)可以解释为,因为 ReLU 的负值区域的值 为 0,为了使它更有广度,所以需要 2 倍的系数。

Batch Normalization

想法:为了使各层拥有适当的广度,“强制性”地调整激活值的分布,优点是:• 可以使学习快速进行(可以增大学习率)。 • 不那么依赖初始值(对于初始值不用那么神经质)。 • 抑制过拟合(降低Dropout等的必要性)。

【笔记】深度学习入门:基于Python的理论与实现(四)_数据_15

正规化:

求均值 μB 和 方差OB2,可以减小数据分布的偏向

【笔记】深度学习入门:基于Python的理论与实现(四)_数据_16

然后,Batch Norm层会对正规化后的数据进行缩放和平移的变换

γ 和 β 是参数。一开始 γ = 1,β = 0,然后再通过学习调整到合适的值。

【笔记】深度学习入门:基于Python的理论与实现(四)_数据_17

Batch Normalization的评估

观察使用Batch Norm层和不使用Batch Norm层时学习的过程,会发现使用Batch Norm后,学习进行得更快了

【笔记】深度学习入门:基于Python的理论与实现(四)_权重_18

正则化

过拟合

发生过拟合的原因,主要有以下两个。• 模型拥有大量参数、表现力强。• 训练数据少。

我们故意满足这两个条件,制造过拟合现象,会发现对于测试数据,离 100% 的识别精度还有较大的差距。如此大的识别精度差距,是只拟合了训练数据的结果:

【笔记】深度学习入门:基于Python的理论与实现(四)_过拟合_19

权值衰减

权值衰减是一直以来经常被使用的一种抑制过拟合的方法。该方法通过 在学习的过程中对大的权重进行惩罚,来抑制过拟合。神经网络的学习目的是减小损失函数的值。这时,例如为损失函数加上权重的平方范数 ( L 2 范 数 )。 这样一来 , 就可以抑制权重变大,如果将权重记为 W,L2 范数的权值衰减就是1/2yW**2

【笔记】深度学习入门:基于Python的理论与实现(四)_权重_20

与没有使用权值衰减的图结果相比,差距变小了。这说明过拟合受到了抑制

Dropout

Dropout 是一种在学习的过程中随机删除神经元的方法。被删除的神经元不再进行信号的传递,训练时,每传递一次数据,就会随机选择要删除的神经元。 然后,测试时,虽然会传递所有的神经元信号,但是对于各个神经元的输出, 要乘上训练时的删除比例后再输出。

python实现:

class Dropout:
	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 会随机生成和 x 形状相同的数组,并将值比 dropout_ratio 大的元素设为 True,以 False 的形式保 存要删除的神经元
			self.mask = np.random.rand(*x.shape) > self.dropout_ratio
			return x * self.mask 
		else:
			return x * (1.0 - self.dropout_ratio)
	def backward(self, dout): 
		# 反向传播时的行为和 ReLU 相同
		return dout * self.mask

实验结果:

左边没使用,右边使用了Dropout(dropout_rate=0.15)

【笔记】深度学习入门:基于Python的理论与实现(四)_数据_21

另一种解读:

集成学习,就是让多个模型单 独进行学习,推理时再取多个模型的输出的平均值。可以将 Dropout 理解为,通过在学习过程中随机删除神经元,从而每一次都让不同 的模型进行学习。并且,推理时,通过对神经元的输出乘以删除比 例(比如,0.5 等),可以取得模型的平均值。也就是说,可以理解成, Dropout 将集成学习的效果(模拟地)通过一个网络实现了

超参数的验证

这里所说的超参数是指,比如各层的神经元数量、batch 大小、参 数更新时的学习率或权值衰减等

验证数据

调整超参数时,必须使用超参数专用的确认数据。用于调整超参数的数据,一般称为验证数据(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.20
validation_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:]

超参数的最优化

是指一开始先大致设定一个范围,从这个范围中随机选 出一个超参数(采样),用这个采样到的值进行识别精度的评估;然后,多次 重复该操作,观察识别精度的结果,根据这个结果缩小超参数的“好值”的范围。 通过重复这一操作,就可以逐渐确定超参数的合适范围。在超参数的最优化中,要注意的是深度学习需要很长时间(比如,几天 或几周)。因此,在超参数的搜索中,需要尽早放弃那些不符合逻辑的超参数。

超参数最优化的实现

# 权值衰减系数的初始范围为10**−8到10**−4
weight_decay = 10 ** np.random.uniform(-8, -4)
# 学 习率的初始范围为 10**−6 到 10**−2
lr = 10 ** np.random.uniform(-6, -2)

观察可以使学习顺利进行的超参 数的范围,从而缩小值的范围。然后,在这个缩小的范围中重复相同的操作。这样就能缩小到合适的超参数的存在范围,然后在某个阶段,选择一个最终的超参数的值。

小结

• 参数的更新方法,除了SGD之外,还有Momentum、AdaGrad、 Adam 等方法。• 权重初始值的赋值方法对进行正确的学习非常重要。• 作为权重初始值,Xavier初始值、He初始值等比较有效。 • 通过使用Batch Normalization,可以加速学习,并且对初始值变得健壮。 • 抑制过拟合的正则化技术有权值衰减、Dropout等。 • 逐渐缩小“好值”存在的范围是搜索超参数的一个有效方法。

举报

相关推荐

0 条评论