1.训练 / 开发 / 测试集
前面介绍了如何构建神经网络,接下来介绍如何有效运作神经网络,涉及到参数调优、如何构建数据、优化算法快速运行从而使学习算法在合理时间内完成学习。
训练神经网络时,需要作出很多决策,例如神经网络分多少层、每层有多少隐藏单元、学习速率是多少、每层激活函数是什么等等。创建新应用时,我们一开始不可能很准确设定这些信息和其他超级参数。深度学习是典型的高度迭代过程,需要多次循环往复,才能为应用程序找到一个称心的神经网络,其中很关键的一个因素是是创建高质量的训练数据集、验证集和测试集,这有助于提高循环效率。
一般会把数据分为三部分,一部分作为训练集,一部分作为简单交叉验证集(验证集),一部分作为测试集。在训练集上训练,然后在验证集上验证准确性,最后在测试集上评估性能。小数据时代一般把数据分为70%训练集和30%测试集或者60%训练集、20%验证集和20%测试集,在如今大数据时代,验证集和测试集划分会趋向更小。
现在越来越多的人在训练和测试集分布不匹配的情况下进行训练。假设你要构建一个用户可以上传大量图片的应用程序,目的是找到并呈现所有猫咪图片,训练集可能是从网上下载的猫咪图片,而验证集和测试集是用户在这个应用上上传的猫咪图片。下载的图片往往分辨率很高很专业,而手机上传的图片可能是手机随意拍的,像素低比较迷糊,从而使数据不是同一分布。这种情况,根据经验法则,建议要确保训练集和测试集的数据来自同一分布。
实际应用中可能不划分测试集,则我们把训练集/验证集称为训练集/测试集。
2.偏差/方差
几乎所有的机器学习从业人员都期望深刻理解偏差和方差,这两个概念易学难精,都属于误差问题。即使自认为已经理解了偏差和方差的基本概念,但是权衡两者是比较头疼的问题,目前关于偏差和方差学术研究较少。
如图5.3,从左往右为欠拟合(即高偏差)、拟合得好、过度拟合(即高方差)。
如图5.4,在猫识别中,假设最优误差(即贝叶斯误差)为0%,训练集和验证集误差为1%和11%则为高方差,训练集和验证集误差为15%和16%则为高偏差,训练集和验证集误差为15%和30%则为高方差兼高偏差,训练集和验证集误差为0.5%和1%则拟合得很好,如果最优误差为15%,则训练集和验证集误差为15%和16%为拟合得很好。
3.机器学习基础
如图5.5,训练神经网络时,首先要看偏差高不高,通过评估训练数据集的误差得到偏差,如果偏差较高,如果偏差确实很高,甚至无法拟合训练集,那么就要选择一个新的网络,比如含有更多隐层或者隐藏单元的网络,或者花费更多时间来训练网络,或者尝试更先进的优化算法,也可以尝试其他方法,也许有用,也许没用,通常采取大规模网络有用,不断尝试,直到拟合数据集解决偏差。
偏差降低到可接受值后,开始检查方差有没有问题,通过评估验证数据集的误差得到方差,如果方差高,解决方法就是采用更多的训练数据,但有的时候我们无法获得更多的数据,我们可以通过正则化来减少过拟合;总之,就是不断尝试,直到找到满足低偏差、低方差的神经网络。
对于偏差和方差权衡,在如今深度学习和大数据时代,通常,在正则化适度情况下,构建更大的网络,可以在不过多影响方差情况下减少偏差,采用更多数据,可以在不过多影响偏差情况下减少方差。
4.正则化
如果怀疑神经网络过度拟合了数据,即存在高方差问题,那么最先想到解决方法的应该是正则化,当然准备更多的数据也能解决,但通常准备更多的数据成本太大。
如图5.6,logistic回归的正则化即是在成本函数后面加上λ/2m * ,称为L2正则化,λ是正则化参数,λ是Python保留关键字,在编程中我们使用lambd表示λ。是L2范数的平方。参数w几乎涵盖所有参数,所以不需要对一个b做正则化。
也有人使用范数L1做正则化(称为L1正则化),但L1有稀疏性(倾向于选择数目较少的一些非常大的值或者数目较多的不重要的小值),一般不作为正则化使用。
logistic回归只有一个隐藏层一个隐藏单元,正则化使用的是衡量向量大小的范数,即向量L2范数;而在神经网络中每个隐藏层多个隐藏单元,正则化使用的是衡量矩阵大小的范数,即弗罗贝尼乌斯范数(类似向量L2范数,有一些特殊的原因不被称为矩阵L2范数)。
如图5.7,神经网络的L2正则化即是在成本函数后面加上λ/2m * ,是弗罗贝尼乌斯范数的平方。计算的是对应层的所有参数组成的矩阵的弗罗贝尼乌斯范数的平方,即= ,表示第l层单元数,表示第l-1层单元数(表示第l层每个单元中的参数w的数量)。梯度下降时=(1-αλ/m) –α(原梯度下降值)。由于 (1-αλ/m) <1,所以L2正则化也叫“权重衰减”。
5.为什么L2正则化可以减少过拟合
如图5.8,假设激活函数是tanh,即g(z)=tanh(z),当z大小处于0附近时,则激活函数是接近线性的。加入L2正则化后则w值会衰减,则z值跟着衰减,从而使激活函数接近线性。线性函数和线性函数组合最后还是线性函数,所以就不会出现过拟合的情况。为保证你的成本函数在梯度下降训练中总是递减的,务必要加上正则化。
6.Dropout 正则化
除了L2正则化,还有一种非常实用的正则化方法,即dropout正则化。
dropout正则化即是在神经网络的每一隐藏层按设置的概率消除该层的节点,比如设置概率为0.5,则神经网络每一隐藏层都消除一半的节点。最后得到一个节点较少、规模较少的神经网络,反向传播也在精简后的神经网络上进行。对每次梯度下降都会重新按设置概率重新消除节点,只在训练集中使用dropout正则化,在验证集、测试集或实际应用中不需要保留着dropout正则化,避免对同一样本每次预测值不一致。
如图5.9,在实施dropout最常用的方法Inverted dropout中,假设在第三层,概率为0.2,则会有keep-prob=0.8的节点被保留,
先计算出那些节点被保留哪些节点被消除
d3=np.random.rand(a3.shape[0], a3.shape[1]) < keep-prob
然后消除节点
a3=np.multiply(a3,d3)即a3*=d3
最后执行a3/= keep-prob,这一步的目的是希望输出给下一层的值总大小不变,即神经网络规模没有变小,所以在验证集、测试集或实际应用中不需要保留着dropout正则化。
7.为什么Dropout 正则化有效
两点原因:
第一是Dropout 正则化会精简神经网络,使神经网络规模变小,小的神经网络一般不会有过拟合问题。
第二是每一个特征都有可能被丢弃,所以整个网络不会偏向于某一个特征(即某特征的权重的值很大),这样会把每一个特征的权重都赋得很小,这就有点类似于L2正则化了,能够起到减轻过拟合的作用。
Dropout 正则化可以在每一层使用不同的概率,也可以只在部分层正则化。
8.其他正则化方法
除了L2正则化和Dropout 正则化,还有几种方法可以减少神经网络中的过拟合。
数据扩增也是一种正则化方法,如图5.10,往往在获取更多的数据比较困难,我们可以对猫图片旋转、缩放等操作或对光学符号进行扭曲来扩增图片量,这些操作能把数据集扩大好几倍,相对于去获取更多数据节约了很大成本。
提早结束也是一种正则化方法,在训练数据集迭代过程中,记录验证集错误率变化,一旦验证集错误率不断上升则提早结束训练。但有一个缺点就是可能迭代次数不够,成本函数值可能不够小。
9.归一化输入特征
可以加速神经网络训练的方法是归一化输入特征。
如图5.12,假设有两个输入特征,分别为x1、x2,数据集一开始分布为第一个图。归一化输入需要两个步骤,
第一步是零均值化:
平均值µ=1/m * ,
x=x-µ,零均值化后数据集分布为第二个图,
第二步是归一化方差(数据集原分布特征值x1方差大于特征值x2):
方差=1/m * **2(此时的x已经是零均值化后的),
x /= ,归一化方差后每个特征值方差都等于1,数据集分布为第三个图。
如果训练集归一化输入,在验证集和测试集同样需要归一化输入,使用相同的µ和 ,µ和 由训练集计算出来。
如图5.13,不使用归一化输入的话,不同特征值取值范围可能相差很大,导致损失函数图是狭长的碗型,即等高线是椭圆,梯度下降的方向与等高线的切线方向垂直的,所以梯度下降路线并不是一条相对笔直的路线,从而学习率不能设置太大,参数初始化位置也很关键,学习效率很慢;归一化输入后特征值取值范围一致,损失函数图为圆形的碗型,无论参数初始化位置在哪都能很快收敛至最优值。
10.梯度消失与梯度爆炸
梯度消失与梯度爆炸是指在训练很深的神经网络时,导数有时会变得非常小或非常大,甚至以指数级别变小或变大,这大大加大了训练难度。
如图5.14,假设有个很深的网络,每层激活函数都是g(z)=z,暂时忽略b,则
=...x,如果参数都初始化为0.5,那么神经网络梯度将成指数级变小,如果参数都初始化为1.5,那么神经网络梯度将成指数级变大。这里只是列举了一个简单激活函数说明问题,其他激活函数也可能会出现梯度消失与梯度爆炸问题。
11.神经网络权重(即参数w)初始化
对于梯度消失与梯度爆炸,有一个不完整解决方案,虽然不能彻底解决问题,但是很有用,即合理初始化权重。
对于tanh激活函数,我们将权重初始化为0均值,方差为1/的正态分布(Xavier初始化),即=np.random.randn(shape) * np.sqrt(1/),
对于Relu激活函数,我们将权重初始化为0均值,方差为2/ 的正态分布(He初始化),即=np.random.randn(shape) * np.sqrt(2/ ),
为l-1层的神经元数量,
经过这样初始化,使激活函数的输出是一个合理的值,不会太大也不会太小。实践中,我们统一将权重初始化为0均值,方差为2/ 的正态分布(He初始化)。
12.梯度的数值逼近
在梯度下降编程中,为了保证推算出来的导数是正确的,需要做梯度检验,下面先介绍数值逼近。
如图5.15,坐标系的函数是f(θ)= ,求导数
f`(θ)= (f(θ+ε)- f(θ-ε)) / 2ε或 f`(θ)= (f(θ+ε)- f(θ)) /ε,前者精度高一般使用前者,当ε→0时,就是在θ处的导数。
手动推算出来的导数为g(θ)=3。
设定ε=0.01(或者更小),当θ=1时,f`(θ)=3.0001,g(θ)=3,误差为0.0001,如果该误差在可接受范围,则推算出来的导数g(θ)就是正确的。
13.梯度校验
梯度检验能检查代码中导数是否正确,找出代码中的bug。
将 , , … ,, 放入一大向量θ,将 ,, … , , 放入一大向量dθ,使用数值逼近计算梯度:
for i in range(参数数量):
d[i]=(J(,…,…) - J(,…,…)) / 2ε
然后计算误差check= / (),
一般的,如果check < 10-7,则推算出来的导数是正确的。
14.关于梯度校验注意事项
1.不要在训练中使用梯度检验,它只用于调试,因为计算d[i]非常耗时。
2.如果梯度检验出梯度有问题,则要检查所有项,检查每d和差异,直到找出bug。
3.如果神经网络使用L2正则化,成本函数J记得加上λ/2m * 。
4.梯度检验不能与dropout正则化同时使用,因为dropout正则化随时可能删除某个节点,很艰难去计算成本函数J,除非将dropout正则化的keep-prod设置为1。
5.初始化时w和b很小,梯度检验可能是对的,但随着w和b越来越大,梯度检验出来的误差越来越大,所以有时我们在反复训练之后,w和b没那么小时,再运行梯度检验看看梯度是否正确。