目录
训练、验证、测试集
偏差、方差
机器学习基础
正则化
为什么正则化有利于预防过拟合
dropout正则化
理解dropout
其它正则化方法
归一化输入
梯度消失/梯度爆炸
神经网络的权重初始化
梯度的数值逼近
梯度检验
梯度检验应用的注意事项
【此为本人学习吴恩达的深度学习课程的笔记记录,有错误请指出!】
训练、验证、测试集
应用深度学习是一个典型的迭代过程,需要多次循环往复,才能为应用程序找到一个称心的神经网络,因此循环该过程的效率是决定项目进展速度的一个关键因素,而创建高质量的训练集,验证集和测试集也有助于提高循环效率。
在机器学习发展的小数据量时代,常见做法是将所有数据三七分,就是常说的 70% 验证集, 30%测试集,如果没有明确设置验证集,也可以按照 60%训练, 20%验证集和 20%测试集来划分。这是前几年机器学习领域普遍认可的较好的实践方法。 如果只有 100 条, 1000 条或者 1 万条数据,那么上述比例划分是非常合理的。
但是在大数据时代,数据量可能是百万级别,那么验证集和测试集占数据总量的比例会变得更小。比如有 100 万条数据,其中 1 万条作为验证集, 1 万条作为测试集。对于数据量过百万的应用,训练集可以占到 99.5%,验证和测试集各占 0.25%(甚至更小)。
现代深度学习的另一个趋势是越来越多的人在训练集与验证集和测试集分布不匹配的情况下进行训练。如图片识别应用,训练集可能是从网上下载的图片,而验证集和测试集是用户自已上传的图片。 根据经验,建议要确保验证集和测试集的数据来自同一分布。
最后一点,就算没有测试集也不要紧,测试集的目的是对最终所选定的神经网络系统做出无偏估计,如果不需要无偏估计,也可以不设置测试集。因为验证集中已经涵盖测试集数据,其不再提供无偏性能评估。
在机器学习中,如果只有一个训练集和一个验证集,而没有独立的测试集,遇到这种情况,训练集还称为训练集,而验证集则被称为测试集, 不过在实际应用中, 人们只是把测试集当成简单交叉验证集使用,并没有完全实现该术语的功能,因为人们把验证集数据过度拟合到了测试集中。
所以,搭建验证集和测试集能够加速神经网络的集成,也可以更有效地衡量算法的偏差和方差,从而帮助我们更高效地选择合适方法来优化算法。
偏差、方差
理解偏差(欠拟合)和方差(过拟合)的两个关键数据是训练集误差( Train set error)和验证集误差( Dev set error)。
如图:
训练集误差
|
1%
|
15%
|
15%
|
0.5%
|
验证集误差
|
11%
|
16%
|
30%
|
1%
|
效果
|
高方差
|
高偏差
|
高偏差、高方差
|
低偏差、低方差
|
机器学习基础
解决高偏差问题:选择新网络(更多隐藏层、更多隐藏单元)、选择更先进的优化算法等。
解决高方差问题:增加更多训练数据、正则化等。
偏差和方差的权衡问题:解决高偏差后,可能带来高方差,或解决高方差后,可能带来高偏差。
其实我们不用太过关注如何平衡偏差和方差,对于深度学习, 如果出现了高偏差,只要持续训练一个更大的网络,或选择更高级的优化算法,这样模型至少可以很好的拟合训练集。这时,模型虽然处于低偏差,但是可能带来了高方差,我们可以采用更多的数据或通过适当的正则化来解决高方差问题。
正则化
正则化的意思是对模型的参数(如权重 )进行惩罚,也就是减小参数值(或权重衰减),这样就减少了模型的高方差,2 正则化是最常见的正则化类型。
还有 1 正则化,但是人们在训练网络时,越来越倾向于使用 2 正则化。
为什么正则化有利于预防过拟合
直观上理解就是如果正则化 设置得足够大,权重矩阵 就被设置为接近于 0 的值,也就是把多隐藏单元的权重设为 0,于是基本上消除了这些隐藏单元对模型的影响。
另一个直观理解是,当 被设置为很小时, 的取值范围也会很小, 这样() 大致呈线性(微观上呈直线),且每层几乎都是线性的,这就变得和线性回归函数一样:
如果每层都是线性的,那么整个网络就是一个线性网络, 因此,它不适用于拟合数据的非线性决策边界。
正则化 的取值至关重要,如果设置的太大,会使模型从高方差状态过度到高偏差状态,因此需要不断地测试以找到合适的 值。
dropout正则化
除了2正则化, 还有一个非常实用的正则化方法——“Dropout( 随机失活) ”。
dropout的工作原理:
假设上图的神经网络存在过拟合问题, dropout 会遍历网络的每一层,设置每个节点要保留的概率(保留和消除的概率都是0.5),然后删除掉要消除的节点,对剩下的节点重新连线,最后得到规模较小的网络。如图:
对于每个训练样本,我们都将采用一个精简后神经网络来训练它。
如何实施 dropout 呢? 最常用的方法是 inverted dropout(反向随机失活)。
用一个三层( = 3)网络来举例说明:
首先要定义向量 ,
表示一个三层的 dropout 向量:
d3 = np.random.rand(a3.shape[0], a3.shape[1])
判断 d3 是否小于某个数(该数称为:keep-prob):
d3 = np.random.rand(a3.shape[0], a3.shape[1]) < keep-prob
keep-prob可以是0.5或0.8等,这里取 0.8,表示要保留的概率值。此时 d3 是布尔数组,运算时 python 会把 true 和 false 翻译为 1 和 0。
然后把
乘以
:
a3 =np.multiply(a3, d3)
该公式的作用是让
中 0 元素与
中相应元素归零。
最后用
除以 keep-prob 参数:
3 /= keep-prob
为什么还要除以 keep-prob ?该例子中第 3 层隐藏层被删除的单元有20%,也就是
中有20%的元素被归零,为了不影响下一层隐藏层(第 4 层)中
的期望值,需要计算
,它会 修正或弥补被 dropout 掉的那 20% 的影响(或理解为需要加上被删除掉的部分数值),以确保
的期望值不变。
不论 keep-prop 的值是多少,如 0.8、 0.9 甚至是 1,如果 keep-prop 设置为 1,那么就不存在 dropout,因为它会保留所有节点。
注意:dropout是在训练阶段使用,在测试阶段不使用dropout。在训练阶段执行dropout,只是调整了输出数值范围,并不影响输出结果的期望值。在测试阶段进行预测时,并不期望输出结果是随机的。
Inverted dropout 函数在除以 keep-prob 时可以记住上一步的状态,目的是确保在测试阶段不执行 dropout时,激活函数的预期结果也不会发生变化。
理解dropout
直观上理解:dropout不依赖于任何特征,因为该单元的输入特征可能随时被清除,为了不影响输出特征的整体期望值,对于剩下的输入特征增加权重(也就是加上被dropout掉的特征的期望值),这样将产生收缩参数 的平方范数的效果,也就和 2 正则化类似了。
实施 dropout 的一个细节是,要为不同的隐藏层选择不同的超参数 keep-prob:
如果某一层的 权重矩阵较大(参数较多,容易过拟合),keep-prob值可以设置低一些,如:0.5。
如果某一层的 权重矩阵较小(参数较少,不易过拟合),keep-prob值可以设置高一些,如:0.7。
如果在某一层,不必担心其过拟合的问题,那么 keep-prob 可以为 1,也就是不应用dropout。
通常情况下,不对输入层应用dropout。
如果每一层隐藏层都应用dropout,缺点就是要不断使用交叉验证来寻找较多的超参数 keep-prob,所以常用的做法是,在一些层上应用dropout,而有些层不用dropout,以便减少超参数数量。
dropout正则化主要应用于计算机视觉领域,因为该领域通常没有足够的数据,容易产生过拟合。
dropout的一大缺点就是代价函数 不再被明确定义,每次迭代,都会随机移除一些节点,在检查梯度下降的性能时,实际上是很难进行复查的,也就很难检查梯度变化。通常做法是先关闭 dropout 函数,将 keepprob 的值设为 1,以确保 函数单调递减。然后再打开 dropout 函数,希望在dropout 过程中,代码并未引入bug。
其它正则化方法
(1)数据扩增:通过增加数据来解决过拟合,该方法和正则化相似。
一般扩增数据的代价较高,可以通过人工合成数据方式来增加数据,如:图片翻转和裁剪等。
(2) early stopping:提早停止训练神经网络
如图:
在训练过程中, 代价函数 都在下降,而验证集的误差通常会先呈下降趋势,然后在某个节点处开始上升。early stopping的作用就是,在验证集的误差处于较低点的位置时,就停止训练过程。
在机器学习中,由于超参数激增,选出可行的算法也变得越来越复杂。在优化代价函数 时,主要关注两个问题,第一个是误差 (, ) 的值越小越好,第二个是防止过拟合。early stopping的主要缺点是不能独立地处理这两个问题,而是同时解决了这两个问题,这样可能因为提早停止训练,此时误差 (, ) 还不够小,虽然解决了过拟合问题,但可能又引起了欠拟合问题。
early stopping 的优点是,只运行一次梯度下降,就可以找出 的较小值,中间值和较大值,而无需尝试 2 正则化超参数 的很多值。
建议还是使用 2 正则化,尝试许多不同的 值,虽然计算量较大,但通常效果会更好,除非 early stopping 也能得到相似效果,否则建议使用 2 正则化。
归一化输入
归一化需要两个步骤:
1、零均值
2、归一化方差
训练集和测试集都是通过相同的 和
定义的数据转换,这两个是由训练集得出来的。
如图:
第一步是零均值化: = - , 意思是移动训练集,直到它完成零均值化。
第二步是归一化方差:
是一个向量,它的每个特征都是方差,注意此时 已经完成了零均值化,最后把所有数据除以向量
。
归一化输入特征的作用:提高训练算法的速度,使梯度下降收敛更快,也就是代价函数 优化起来更简单快速:
所以如果输入特征处于不同的范围内,也就是不同特征的取值范围差距较大,此时的归一化特征就变得非常重要了。
梯度消失/梯度爆炸
深度神经网络训练时经常面临的一个问题就是梯度消失或梯度爆炸。
梯度消失: 导数或坡度会变得非常小, 甚至于以指数方式变小。
梯度爆炸:导数或坡度会变得非常大, 甚至于以指数方式变大。
导致梯度消失或梯度爆炸的原因?
为了简化,假设使用激活函数 () = , 也就是线性激活函数,并且忽略 ,得到的输出:
如图:
假设每个权重矩阵(1.5倍的单位矩阵):
最后的计算结果 是(为了简化计算,这里忽略掉输出单元
):
可以看出,如果 值较大, 值将会爆炸式增长,实际上是呈指数增长,增长的比例是
。
相反的,如果权重是 0.5:
这样每个矩阵都小于 1,如果 值较大, 值将会变得非常小(或消失),实际上是呈指数递减。
从以上看出激活函数出现指数增长或递减与 相关,所以在求导时,同样影响梯度的指数增长或递减,也就是梯爆炸或消失。这会导致训练难度上升,尤其是梯度指数递减时,梯度下降算法的步长会非常非常小,这将会花费很长的时间来学习。
要解决梯度消失或爆炸问题,在于如何选择初始化权重。
神经网络的权重初始化
为了解决梯度消失或爆炸问题,如何选择初始化权重尤为重要。
我们先举一个神经单元初始化地例子,然后再演变到整个深度网络。如图:
其中:
我们并不希望 值过大或过小。输入特征 是不变的,如果 ( 表示神经元的输入特征数量)越大,那么希望 越小,这样加权和 值才不会过大。
实际上就是设置某层 的权重矩阵(高斯随机变量 * 方差1/n):
有一点需要注意,这种方法并没有彻底解决梯度消失和爆炸问题,而是通过设置合理的权重值,来降低了产生梯度消失和爆炸的问题。权重值不能比 1 大很多,也不能比 1 小很多,所以梯度没有爆炸或消失的过快。
对于 relu 激活函数,方差可以设置为:
对于 tanh 激活函数,方差可以设置为:
至于方差的选择是 1/n,还是 2/n,这里可以添加一个超参数,然后不断调优该超参数,与其它超参数相比,它的优先级比较低,一般是放到最后来调优。
梯度的数值逼近
在实现 backprop (反向传播)时,为了检验 backprop 过程中的梯度是否正确,需要计算梯度的数值逼近。
利用微积分求导公式(双向微分):
在检验反向传播是否正确时,使用该求导公式的结果更准确。
梯度检验
为了执行梯度检验, 需要把所有
矩阵转换成向量, 然后把所有的
向量和
向量首尾相接拼在一起,得到一个巨型向量 , 代价函数 () 也就是所有 和 的函数。同样,把反向传播的得到的所有
和
转成一个新的和 维度相同的向量 。如图:
将 函数展开为 (1, 2, 3, … … ),不论参数向量 的维度是多少,为了实施梯度检验,要做的就是循环执行,从而对每个 计算近似微分 approx[] 的值,也就是对 的偏微分或偏导数,这里使用双向微分:
只对 增加 ,其它项保持不变,因为使用的是双向微分,对另一边做同样的操作,只不过是减去 , 其它项保持不变。计算出所有 的偏导数数后,得到一个向量 approx, 它与 具有相同维度。
最后,需要验证 approx 和 是否大致相等。
如以下公式:
||approx - || / ( ||approx|| + |||| )
||approx - ||: approx[] - [] 的欧几里得距离(两个向量的差的平方和再开方)。
||approx|| + ||||:approx 和 的欧几里得距离之和。公式中,除以这个分母,就是对 ||approx - || 长度进行标准化,防止计算结果太大或太小,这样结果的值在 0~1 之间。
如图:
一般情况下, 的取值为
,如果该公式的计算结果小于
,那就认为微分近似是正确的。
如果该公式的计算结果在
量级,那就要小心了,也许该结果没有问题,但是要检查 approx 的所有分量,要确保没有一项过大,如果某一项过大,有可能计算有错误了。
如果该公式的计算结果在
量级,那计算很有可能是错误的,它的结果应该远远小于
。这时应该检查所有 项,看看是否存在哪一项,使得 approx[] 与 [] 很不一样,并追踪检查求导计算是否正确,经过调试,最终的结果应该是非常小的值(
)。
在进行梯度检验时,结果可能有一个相对较大的值,这时就要不断的进行调试,直至算法通过了梯度检查。
梯度检验应用的注意事项
1、不要在训练过程中使用梯度检验,因为计算 approx[] 是一个非常漫长的过程,只在调试中使用,使用完成后就关闭梯度检验,在训练过程中避免执行它。
2、如果梯度检验失败,找出 approx[] 与 [] 的值相差很大的 项,然后检查 和 在计算求导时是否存在错误。
3、在执行梯度检验时,如果使用了正则化,要注意在求导时也要包括正则项。
4、梯度检验不能与 dropout 同时使用。因为每次迭代过程中, dropout 会随机消除隐藏层单元的不同子集,难以计算 dropout 在梯度下降上的代价函数 。
5、最后一点几乎很少会出现,也就是当 和 接近 0 时,梯度下降的实施是正确的,随着梯度下降的进行, 和 变大,算法的精确度就有所下降。通常做法是, 在随机初始化过程中,运行梯度检验,然后再训练网络, 和 会有一段时间接近 0 附近(即很小的随机初始值),在进行几次训练的迭代后,再运行梯度检验,以便检查 和 是否变大。