Practical aspects of Deep Learning : setting up your ML application
目录
3.Regularization Reduces Overfitting
3.1.1 Prior knowledge:L1 Norm and L2 Norm(L1、L2范数)
3.2 Why regularization reduces overfitting
3.4 Other regularization methods
5.梯度消失、梯度爆炸及如何避免(Vanishing/exploding gradient)
1.train/dev/test sets
应用深度学习是一个反复迭代的过程,需要通过反复多次的循环训练得到最优化参数。决定整个训练过程快慢的关键在于单次循环所花费的时间,单次循环越快,训练过程越快。而设置合适的训练集(Training sets)、验证集(Development sets)、测试集(Test sets)的数量,能有效提高训练效率。基于Idea,先选择初始的参数值,构建神经网络模型结构;然后通过代码Code的形式,实现这个神经网络;最后,通过实验Experiment验证这些参数对应的神经网络的表现性能。根据验证结果,我们对参数进行适当的调整优化,再进行下一次的Idea->Code->Experiment循环。通过很多次的循环,不断调整参数,选定最佳的参数值。
Train/Dev/Test sets的设置:
Train sets用来训练你的算法模型;
Test sets用来测试最好算法的实际表现,作为该算法的无偏估计。
Dev sets用来验证不同算法的表现情况,从中选择最好的算法模型。
样本数量较小时的比例分配:
Train sets和Test sets的数量比例为70%和30%。如果有Dev sets,则设置比例为60%、20%、20%,分别对应Train/Dev/Test sets。
样本数量较大时的比例分配:
科学的做法是要将Dev sets和Test sets的比例设置得很低。Train/Dev/Test sets的比例通常可以设置为98%/1%/1%,或者99%/0.5%/0.5%。
问题:训练样本和测试样本分布上不匹配
比如,假设你开发一个识别猫的手机app,可以让用户上传图片。在app识别算法中,训练样本可能来自网络下载,验证和测试样本可能来自不同用户的上传。从网络下载的图片一般像素较高而且比较正规,而用户上传的图片往往像素不稳定,且图片质量不一。因此,训练样本和验证/测试样本可能来自不同的分布。
解决:
尽量保证Dev sets和Test sets来自于同一分布
扩大训练样本的数量,从而让该模型更加强大(即使Train sets和Dev/Test sets不来自同一分布,使用这些技巧也能提高模型性能):将现有的训练样本做一些处理,例如图片的翻转、假如随机噪声等。
2.Bias/Variance
下图显示了二维平面上high bias,just right,high variance的例子。
在输入特征是高维度的情况下,我们可以通过两个数值Train set error和Dev set error来理解bias和variance。一般,Train set error体现了是否出现bias,Dev set error体现了是否出现variance(正确地说,应该是Dev set error与Train set error的相对差值)
Train set error | Dev set error | Note |
1% | 11% | high variance 对训练样本识别较好,对验证集识别不太好。该模型对训练样本可能存在过拟合,模型泛化能力不强,导致验证集识别率低。 |
15% | 16% | high bias 该算法模型对训练样本和验证集的识别都不太好。该模型对训练样本存在欠拟合。 |
15% | 30% | high bias & high variance 深度学习中最坏的情况 |
0.5% | 1% | low bias & low variance 深度学习中最好的情况 |
注:以上的这些假设都是建立在base error是0的基础上,即人类都能正确识别所有猫类图片。base error不同,相应的Train set error和Dev set error会有所变化,但没有相对变化。
传统机器学习算法中,Bias和Variance通常是对立的,减小Bias会增加Variance,减小Variance会增加Bias。而在现在的深度学习中,通过使用更复杂的神经网络和海量的训练样本,一般能够同时有效减小Bias和Variance。这也是深度学习之所以如此强大的原因之一。
目的 | 方法 |
减少high bias | 增加神经网络的隐藏层个数、神经元个数; 训练时间延长; 选择其它更复杂的NN模型... |
减少high variance | 增加训练样本数据; 正则化Regularization; 选择其他更复杂的NN模型... |
温馨提示:再来看这段视频(@莫烦python),生动地了解一下过拟合。
3.Regularization Reduces Overfitting
3.1 L1、L2 Regularization
温馨提示:理解这部分最好先看下L1 L2 Regularization @莫烦python
3.1.1 Prior knowledge:L1 Norm and L2 Norm(L1、L2范数)
L1 Norm
Q:L1产生稀疏矩阵的作用?
A:稀疏模型有助于进行特征选择,即L1可用来选择对结果贡献较大的主要特征。
解释说明:
稀疏矩阵指的是很多元素为0、只有少数元素是非零值的矩阵。以线性回归为例,即得到的线性回归模型的大部分系数都是0,这表示只有少数特征对这个模型有贡献,从而实现了特征选择。(如手语翻译项目中,有手、肢体、眼、口等特征值,其中手、肢体的特征值对该模型有主要贡献,那么就可以利用L1产生稀疏矩阵,从而有助于特征选择)
Q:为什么L1可以产生稀疏模型,即L1是怎么让系数等于0的?
A:最优解处某些权重可能为0。
解释说明:
现在我们的目标是求解,换句话说,我们的任务是在L1的约束下求出取最小值的解。假设只考虑二维的情况,即只有两个权值和,此时的L1正则化公式为L1=||+||。对J使用梯度下降法求解,则求解的过程可以画出等值线,同时L1正则化的函数也可以在二维平面上画出来。如下图:
等值线与L1图形首次相交的地方就是最优解,我们很容易发现L1黑色方形必然首先与等值线相交于方形顶点处。可以直观想象,因为L1函数有很多"突出的角"(二维情况下有四个,多维情况下更多),与这些角接触的概率远大于与其它部分接触的概率。而这些点某些权重为0(以上图为例,交点处为0),从而会使部分特征等于0。
L2 Norm
Q:为什么L2范数可以防止过拟合呢?
A:二维平面下L2正则化的函数图形是个圆,与方形相比,没有突出的棱角。因此交点在坐标轴的概率很低,即使权重等于0的概率小了许多。由上图可知,L2中得到的两个权值倾向于均为非零的较小数。
注:过拟合是指模型参数较大,模型过于复杂,模型抗扰动能力弱。只要测试数据偏移一点点,就会对结果造成很大的影响。因此,要防止过拟合,其中一种方法就是让参数尽可能的小一些。如上述链接中的视频中讲到,当批数据训练时,每次批数据都会得到不同的误差曲线。L2对于这种变化,交点(误差最小的点,又是参数正规化后的最优解)的移动不会很大,但L1下交点的跳动较大,从而侧面反应了L1下的解不稳定。
Q:为什么L2正则化可以获得值很小的参数?
A:每一次迭代,参数都要乘以一个小于1的因子,从而使其不断减小,因此总的来看,参数是不断减小的。(下面有公式推导)
在所有特征中只有少数特征起重要作用的情况下,选择L1范数比较合适,因为它能自动选择特征。而如果所有特征中,大部分特征都能起作用,而且起的作用很平均,那么使用L2范数也许更合适。
3.1.2 L1 L2 Regularization
相比于扩大训练样本数量,正则化regularization是解决过拟合(high variance)更可行有效的办法。因为通常获得更多训练样本的成本太高,比较困难。
L1 regularization:
Logistic regression 中进行 L2 regularization:
注意:为什么只对w进行正则化而不对b进行正则化呢?其实也可以对b进行正则化。但是一般w的维度很大,而b只是一个常数。相比较来说,参数很大程度上由w决定,改变b值对整体模型影响较小。所以,一般为了简便,就忽略对b的正则化了。
深度学习模型中的L2 regularization:
被称为Frobenius范数,记作 。一个矩阵的Frobenius范数就是计算所有元素平方和再开方,即
由于加入了正则化项,梯度下降算法中的计算表达式需要做如下修改:
L2 regularization 也被称做weight decay。这是因为,由于加上了正则项,有个增量,在更新的时候,会多减去这个增量,使得比没有正则项的值要小一些。不断迭代更新,不断地减小。
3.2 Why regularization reduces overfitting
下面从两个角度来解释
(一)由上图中W更新的推导公式可得,增加到足够大,会接近于0(实际上是不会发生这种情况),而减少许多隐藏单元的影响(直觉上认为大量隐藏单元被完全消除了, 其实不然,实际上是该神经网络的所有隐藏单元依然存在),从而使这个网络变得更简单,简单到越来越接近逻辑回归,可是深度却很大,它会使这个网络从Over Fitting的状态(右图)更接近Under Fitting的状态(左图)。从而会找到一个合适的中间值λ,使网络接近Just Right的状态(中图)。
(二)更直观地理解,如果你使用的激活函数是, 那么当取值很大的时候,取值很小,经计算得出的也很小,很小意味着取值集中于红色部分,相当于函数的线性部分。
大致呈线性,那么这个神经元起的作用就相当于是线性回归(linear regression)。如果每个神经元对应的权重都比较小,那么整个神经网络模型相当于是多个linear regression的组合,即可看成一个线性网络(linear network),得到的分类超平面就会比较简单,不会出现过拟合现象。
3.3 Dropout Regularization
温馨提示:吴恩达课程dropout这节看不懂的可以借鉴一下这个教学视频。
Dropout是另一种防止过拟合的方法。
原理:
训练时以p的概率保留神经元,以1-p的概率丢弃神经元及与之相连的边,以此得到每次训练的子网络;
每次单独训练时,每个不完整的神经网络都各不相同,但权值共享。
因为每次单独训练的神经网络不同,所以每次的预测结果都不会依赖于某个特定的神经元,从而防止过拟合。(通俗来说L1、L2正则化通过惩罚权值来防止过拟合,dropout则随机drop神经元而从根本上使神经网络过于依赖)
测试时,所有神经元都保留,但权重由 变为 。测试时的结果相当于训练时的期望结果值。
具体做法:
实现方法:
常用的方法是Inverted dropout(反向随机失活)
假设对于第L层神经元,设定保留神经元比例概率keep_prob=0.8,即该层有20%的神经元停止工作。 为dropout向量,设置 为随机vector,其中80%的元素为1,20%的元素为0。在python中可以使用如下语句生成 dropout vector:
dl = np.random.rand(al.shape[0],al.shape[1])<keep_prob
实施dropout的另一个细节:一个拥有三个输入特征的网络,其中一个要选择的参数是keep-prob,它代表每一层上保留单元的概率。所以不同层的keep-prob也可以变化。第一层,矩阵 是7×3,第二个权重矩阵 是7×7,第三个权重矩阵 是3×7,以此类推,是最大的权重矩阵,因为 拥有最大参数集,即7×7,为了预防矩阵的过拟合,它的keep-prob值应该相对较低,假设是0.5。对于其它层,过拟合的程度可能不会那么严重,它们的keep-prob值可能高一些,可能是0.7。如果在某一层,我们不必担心其过拟合的问题,那么keep-prob可以为1
总结:如果你担心某些层比其它层更容易发生过拟合,可以把某些层的keep-prob值设置得比其它层更低,缺点是为了使用交叉验证,你要搜索更多的超级参数,另一种方案是在一些层上应用dropout,而有些层不用dropout,应用dropout的层只含有一个超级参数keep-prob。
dropout是一种正则化方法,它有助于预防过拟合,因此除非算法过拟合,不然是不会用dropout的,所以它在其它领域应用得比较少,主要存在于计算机视觉领域,因为我们通常没有足够的数据,所以一直存在过拟合,这就是有些计算机视觉研究人员如此钟情于dropout函数的原因。
dropout缺点:代价函数 不再被明确定义,每次迭代,都会随机移除一些节点,如果再三检查梯度下降的性能,实际上是很难进行复查的。定义明确的代价函数 每次迭代后都会下降,因为我们所优化的代价函数 实际上并没有明确定义,或者说在某种程度上很难计算,所以我们失去了调试工具来绘制这样的图片。
一种解决办法:先关闭dropout函数,将keep-prob的值设为1,运行代码,确保函数单调递减。然后打开dropout函数,希望在dropout过程中,代码并未引入bug。
3.4 Other regularization methods
3.4.1数据扩增(data augmentation)
在做图片分类器时,当获取更多数据集代价较高时,可以通过水平翻转图片、随意裁剪图片来扩增数据集。注意,像这样人工合成数据的话,我们须要通过算法验证,图片中的猫经过水平翻转之后依然是原类别。
光学字符识别,可以通过添加数字,随意旋转或轻微地扭曲数字来扩增数据,但它们仍然是数字,把这些数字添加到训练集。这里对字符做了强变形处理(便于清楚理解),所以数字4看起来是波形的,其实不用对数字4做这么夸张的扭曲,只要轻微的变形就好。
3.4.2 early stopping
验证集误差通常会先呈下降趋势,然后在某个节点处开始上升,early stopping 的作用是,神经网络已经在这个迭代过程中表现得很好了,我们在此停止训练吧。
early stopping 代表提早停止训练神经网络,但是它也有一个缺点,分析如下:
机器学习过程的其中2个步骤:
其一选择一个算法来优化代价函数,该问题的解决工具:如梯度下降、Momentum、RMSprop和Adam等等,在重点优化代价函数时,你只需要留意 和,使 的值越小越好,只需要想办法减小这个值,其它的不用关注。
其二是优化代价函数之后,不想发生过拟合,解决方法:如正则化,扩增数据等等。预防过拟合还有其他任务,换句话说就是减少方差,这一步我们用另外一套工具来实现,这个原理有时被称为“正交化”(Orthogonalization)(后面讲)
early stopping 的主要缺点就是不能独立地处理上述这两个问题,因为提早停止梯度下降,也就是停止了优化代价函数,因为现在不再尝试降低代价函数,所以代价函数的值可能不够小,同时你又希望不出现过拟合,但没有采取不同的方式来解决这两个问题,而是用一种方法同时解决两个问题,这样做的结果使考虑的东西变得更复杂。
如果不用early stopping,另一种方法就是L2 正则化,训练神经网络的时间就可能很长。这导致超级参数搜索空间更容易分解,也更容易搜索,但是缺点在于,你必须尝试很多正则化参数 的值,这也导致搜索大量 值的计算代价太高。
early stopping的优点是,只运行一次梯度下降,你可以找出的较小值,中间值和较大值,而无需尝试L2 正则化超级参数 的很多值。
4.归一化(Normalize inputs)
因为如果输入特征都大致在相同范围内,代价函数更易优化。归一化可以提高神经网络的训练速度,非归一化下,如果使用梯度下降,必须使用一个很小的学习率,而且某个位置要迭代多次才能找到最小值;归一化后的图形如果类似于一个椭球形,可能每个点都可直接找到最小值,而且可以采用较大的步长。
归一化的2个步骤:
1)零均值化
2)归一化方差
零均值化后,特征 的方差比特征 的方差要大得多
我们希望不论是训练数据还是测试数据,都是通过相同和 定义的相同数据转换,其中 和 是由训练集数据计算得来的。
5.梯度消失、梯度爆炸及如何避免(Vanishing/exploding gradient)
如果各层权重都大于1或者都小于1,那么各层激活函数的输出将随着层数L的增加,呈指数型增大或减小。当层数很大时,出现数值爆炸或消失。同样,这种情况也会引起梯度呈现同样的指数型增大或减小的变化。L非常大时,例如L=150,则梯度会非常大或非常小,引起每次更新的步进长度过大或者过小,这让训练过程十分困难。
为改善这种问题,要对神经网络的权重做一些初始化处理,为了让z不会过大或者过小,思路是让w与n有关,且n越大,w应该越小才好。这样能够保证z不会过大。有以下三种初始化权重的方法:
#如果激活函数是tanh,令其方差为1/n
w[l] = np.random.randn(n[l],n[l-1])*np.sqrt(1/n[l-1])
#如果激活函数的输入特征被零均值,则是标准方差1,相当于对权重未作处理
#如果激活函数是ReLu,令其方差为2/n——Xavier初始化
w[l] = np.random.randn(n[l],n[l-1])*np.sqrt(2/n[l-1])
#Yoshua Bengio提出了另外一种初始化w的方法,令其方差为2/(n[l-1]*n[l])
w[l] = np.random.randn(n[l],n[l-1])*np.sqrt(2/n[l-1]*n[l])
我们可以对这些初始化方法中设置某些参数,作为超参数,通过验证集进行验证,得到最优参数,来优化神经网络
6.梯度检验(Gradient checking)
目的:
检查验证反向传播过程中梯度下降算法是否正确,验证训练过程是否出现bug。
过程:
1)构造一维向量
分别将这些矩阵构造成一维向量,然后将这些一维向量组合起来构成一个更大的一维向量。这样cost function就可以表示成。
然后将反向传播过程通过梯度下降算法得到的按照一样的顺序构造成一个一维向量。
的维度与一致。
2)求出近似梯度
利用微分思想,近似求出梯度值,即函数在点 处的梯度可以表示成:
其中, >0,且足够小。
利用对每个计算近似梯度,其值与反向传播算法得到的 相比较,检查是否一致。例如对于第个元素,近似梯度为:
具体步骤:
代码实现:
thetaplus = theta + epsilon # Step 1
thetaminus = theta - epsilon # Step 2
J_plus = forward_propagation(x, thetaplus) # Step 3
J_minus = forward_propagation(x, thetaminus) # Step 4
gradapprox = (J_plus - J_minus) / (2 * epsilon) # Step 5
3)比较 与 的接近程度。
具体步骤:
- 1'. compute the numerator using np.linalg.norm(...)
- 2'. compute the denominator. You will need to call np.linalg.norm(...) twice.
- 3'. divide them.
代码实现:
numerator = np.linalg.norm(grad - gradapprox) # Step 1'
denominator = np.linalg.norm(grad) + np.linalg.norm(gradapprox) # Step 2'
difference = numerator / denominator # Step 3'
一般来说,如果欧氏距离越小,例如,则表明二者越接近,即反向梯度计算是正确的,没有bug。如果欧氏距离较大,例如,则表明梯度计算可能出现问题,需要再次检查是否有bug存在。如果欧氏距离很大,例如,甚至更大,则表明二者差别很大,梯度下降计算过程有bug,需要仔细检查。
注:
梯度检查仅作为debug使用,不要在整个训练过程中都进行梯度检查。
如果梯度检查出现错误,找到对应出错的梯度,检查其推导是否出现错误。
注意不要忽略正则化项,计算近似梯度的时候要包括进去。
梯度检查时关闭dropout,检查完毕后再打开dropout。
随机初始化时运行梯度检查,经过一些训练后再进行梯度检查(不常用)。