CS231n课程笔记六 训练神经网络

神经网络入门

简单介绍

单个神经元建模

神经网络算法领域最初是被对生物神经系统建模这一目标启发,但随后与其分道扬镳,成为一个工程问题,并在机器学习领域取得良好效果。然而,讨论将还是从对生物系统的一个高层次的简略描述开始,因为神经网络毕竟是从这里得到了启发。

生物动机与连接

大脑的基本计算单位是神经元(neuron)。人类的神经系统中大约有860亿个神经元,它们被大约1014-1015个突触(synapses)连接起来。下面图表的左边展示了一个生物学的神经元,右边展示了一个常用的数学模型。每个神经元都从它的树突获得输入信号,然后沿着它唯一的轴突(axon)产生输出信号。轴突在末端会逐渐分枝,通过突触和其他神经元的树突相连。
在神经元的计算模型中,沿着轴突传播的信号(比如x0)将基于突触的突触强度(比如w),与其他神经元的树突进行乘法交互(比如

w_0x_0
)。其观点是,突触的强度(也就是权重w),是可学习的且可以控制一个神经元对于另一个神经元的影响强度(还可以控制影响方向:使其兴奋(正权重)或使其抑制(负权重))。在基本模型中,树突将信号传递到细胞体,信号在细胞体中相加。如果最终之和高于某个阈值,那么神经元将会 激活,向其轴突输出一个峰值信号。在计算模型中,我们假设峰值信号的准确时间点不重要,是激活信号的频率在交流信息。基于这个 速率编码的观点,将神经元的激活率建模为 激活函数f,它表达了轴突上激活信号的频率。由于历史原因,激活函数常常选择使用sigmoid函数,该函数输入实数值(求和后的信号强度),然后将输入值压缩到0-1之间。在本节后面部分会看到这些激活函数的各种细节。
CS231n课程笔记六 训练神经网络_第1张图片
image.png

每个激活函数(或非线性函数)的输入都是一个数字,然后对其进行某种固定的数学操作。下面是在实践中可能遇到的几种激活函数:

CS231n课程笔记六 训练神经网络_第2张图片

左边是Sigmoid非线性函数,将实数压缩到[0,1]之间。右边是tanh函数,将实数压缩到[-1,1]。

sigmoid

函数图像如上图的左边所示。在前一节中已经提到过,它输入实数值并将其“挤压”到0到1范围内。更具体地说,很大的负数变成0,很大的正数变成1。在历史上,sigmoid函数非常常用,这是因为它对于神经元的激活频率有良好的解释:从完全不激活(0)到在求和后的最大频率处的完全饱和(saturated)的激活(1)。然而现在sigmoid函数已经不太受欢迎,实际很少使用了,这是因为它有两个主要缺点:
Sigmoid函数饱和使梯度消失。sigmoid神经元有一个不好的特性,就是当神经元的激活在接近0或1处时会饱和:在这些区域,梯度几乎为0。回忆一下,在反向传播的时候,这个(局部)梯度将会与整个损失函数关于该门单元输出的梯度相乘。因此,如果局部梯度非常小,那么相乘的结果也会接近零,这会有效地“杀死”梯度,几乎就有没有信号通过神经元传到权重再到数据了。还有,为了防止饱和,必须对于权重矩阵初始化特别留意。比如,如果初始化权重过大,那么大多数神经元将会饱和,导致网络就几乎不学习了。
Sigmoid函数的输出不是零中心的。这个性质并不是我们想要的,因为在神经网络后面层中的神经元得到的数据将不是零中心的。这一情况将影响梯度下降的运作,因为如果输入神经元的数据总是正数,那么关于w的梯度在反向传播的过程中,将会要么全部是正数,要么全部是负数(具体依整个表达式f而定)。这将会导致梯度下降权重更新时出现z字型的下降。然而,可以看到整个批量的数据的梯度被加起来后,对于权重的最终更新将会有不同的正负,这样就从一定程度上减轻了这个问题。因此,该问题相对于上面的神经元饱和问题来说只是个小麻烦,没有那么严重。

CS231n课程笔记六 训练神经网络_第3张图片
image.png

ReLU。在近些年ReLU变得非常流行。它的函数公式是

f(x)=max(0,x)
。换句话说,这个激活函数就是一个关于0的阈值(如上图左侧)。使用ReLU有以下一些优缺点:
优点:相较于sigmoid和tanh函数,ReLU对于随机梯度下降的收敛有巨大的加速作用( Krizhevsky *
"); background-size: cover; background-position: 0px 2px;">*等的论文指出有6倍之多)。据称这是由它的线性,非饱和的公式导致的。
优点:sigmoid和tanh神经元含有指数运算等耗费计算资源的操作,而ReLU可以简单地通过对一个矩阵进行阈值计算得到。
缺点:在训练的时候,ReLU单元比较脆弱并且可能“死掉”。举例来说,当一个很大的梯度流过ReLU的神经元的时候,可能会导致梯度更新到一种特别的状态,在这种状态下神经元将无法被其他任何数据点再次激活。如果这种情况发生,那么从此所以流过这个神经元的梯度将都变成0。也就是说,这个ReLU单元在训练中将不可逆转的死亡,因为这导致了数据多样化的丢失。例如,如果学习率设置得太高,可能会发现网络中40%的神经元都会死掉(在整个训练集中这些神经元都不会被激活)。通过合理设置学习率,这种情况的发生概率会降低。

误差检查

使用相对误差来比较。比较数值梯度![f'_n]和解析梯度![f'_a]的细节有哪些?如何得知此两者不匹配?你可能会倾向于监测它们的差的绝对值

|f'_a-f'_n|
或者差的平方值,然后定义该值如果超过某个规定阈值,就判断梯度实现失败。然而该思路是有问题的。想想,假设这个差值是1e-4,如果两个梯度值在1.0左右,这个差值看起来就很合适,可以认为两个梯度是匹配的。然而如果梯度值是1e-5或者更低,那么1e-4就是非常大的差距,梯度实现肯定就是失败的了。因此,使用 相对误差总是更合适一些:
\displaystyle \frac{|f'_a-f'_n|}{max(|f'_a|,|f'_n|)}
上式考虑了差值占两个梯度绝对值的比例。注意通常相对误差公式只包含两个式子中的一个(任意一个均可),但是我更倾向取两个式子的最大值或者取两个式子的和。这样做是为了防止在其中一个式子为0时,公式分母为0(这种情况,在ReLU中是经常发生的)。然而,还必须注意两个式子都为零且通过梯度检查的情况。在实践中:
相对误差>1e-2:通常就意味着梯度可能出错。

1e-2>相对误差>1e-4:要对这个值感到不舒服才行。

1e-4>相对误差:这个值的相对误差对于有不可导点的目标函数是OK的。但如果目标函数中没有kink(使用tanh和softmax),那么相对误差值还是太高。

1e-7或者更小:好结果,可以高兴一把了。

要知道的是网络的深度越深,相对误差就越高。所以如果你是在对一个10层网络的输入数据做梯度检查,那么1e-2的相对误差值可能就OK了,因为误差一直在累积。相反,如果一个可微函数的相对误差值是1e-2,那么通常说明梯度实现不正确。
使用双精度。一个常见的错误是使用单精度浮点数来进行梯度检查。这样会导致即使梯度实现正确,相对误差值也会很高(比如1e-2)。在我的经验而言,出现过使用单精度浮点数时相对误差为1e-2,换成双精度浮点数时就降低为1e-8的情况。
保持在浮点数的有效范围。建议通读《What Every Computer Scientist Should Konw About Floating-Point Artthmetic*
"); background-size: cover; background-position: 0px 2px;">*》一文,该文将阐明你可能犯的错误,促使你写下更加细心的代码。例如,在神经网络中,在一个批量的数据上对损失函数进行归一化是很常见的。但是,如果每个数据点的梯度很小,然后又用数据点的数量去除,就使得数值更小,这反过来会导致更多的数值问题。这就是我为什么总是会把原始的解析梯度和数值梯度数据打印出来,确保用来比较的数字的值不是过小(通常绝对值小于1e-10就绝对让人担心)。如果确实过小,可以使用一个常数暂时将损失函数的数值范围扩展到一个更“好”的范围,在这个范围中浮点数变得更加致密。比较理想的是1.0的数量级上,即当浮点数指数为0时。
目标函数的不可导点(kinks)。在进行梯度检查时,一个导致不准确的原因是不可导点问题。不可导点是指目标函数不可导的部分,由ReLU(

max(0,x)
)等函数,或SVM损失,Maxout神经元等引入。考虑当
x=-1e6
的时,对ReLU函数进行梯度检查。因为
x<0
,所以解析梯度在该点的梯度为0。然而,在这里数值梯度会突然计算出一个非零的梯度值,因为
f(x+h)
可能越过了不可导点(例如:如果1e-6" eeimg="1" style="box-sizing: inherit; overflow: hidden; display: inline-block; max-width: 100%; margin: 0px 3px; vertical-align: middle;">),导致了一个非零的结果。你可能会认为这是一个极端的案例,但实际上这种情况很常见。例如,一个用CIFAR-10训练的SVM中,因为有50,000个样本,且根据目标函数每个样本产生9个式子,所以包含有450,000个
max(0,x)
式子。而一个用SVM进行分类的神经网络因为采用了ReLU,还会有更多的不可导点。
注意,在计算损失的过程中是可以知道不可导点有没有被越过的。在具有
max(x,y)
形式的函数中持续跟踪所有“赢家”的身份,就可以实现这一点。其实就是看在前向传播时,到底x和y谁更大。如果在计算
f(x+h)
f(x-h)
的时候,至少有一个“赢家”的身份变了,那就说明不可导点被越过了,数值梯度会不准确。
使用少量数据点。解决上面的不可导点问题的一个办法是使用更少的数据点。因为含有不可导点的损失函数(例如:因为使用了ReLU或者边缘损失等函数)的数据点越少,不可导点就越少,所以在计算有限差值近似时越过不可导点的几率就越小。还有,如果你的梯度检查对2-3个数据点都有效,那么基本上对整个批量数据进行梯度检查也是没问题的。所以使用很少量的数据点,能让梯度检查更迅速高效。

在操作的特性模式中梯度检查。有一点必须要认识到:梯度检查是在参数空间中的一个特定(往往还是随机的)的单独点进行的。即使是在该点上梯度检查成功了,也不能马上确保全局上梯度的实现都是正确的。还有,一个随机的初始化可能不是参数空间最优代表性的点,这可能导致进入某种病态的情况,即梯度看起来是正确实现了,实际上并没有。例如,SVM使用小数值权重初始化,就会把一些接近于0的得分分配给所有的数据点,而梯度将会在所有的数据点上展现出某种模式。一个不正确实现的梯度也许依然能够产生出这种模式,但是不能泛化到更具代表性的操作模式,比如在一些的得分比另一些得分更大的情况下就不行。因此为了安全起见,最好让网络学习(“预热”)一小段时间,等到损失函数开始下降的之后再进行梯度检查。在第一次迭代就进行梯度检查的危险就在于,此时可能正处在不正常的边界情况,从而掩盖了梯度没有正确实现的事实。
不要让正则化吞没数据。通常损失函数是数据损失和正则化损失的和(例如L2对权重的惩罚)。需要注意的危险是正则化损失可能吞没掉数据损失,在这种情况下梯度主要来源于正则化部分(正则化部分的梯度表达式通常简单很多)。这样就会掩盖掉数据损失梯度的不正确实现。因此,推荐先关掉正则化对数据损失做单独检查,然后对正则化做单独检查。对于正则化的单独检查可以是修改代码,去掉其中数据损失的部分,也可以提高正则化强度,确认其效果在梯度检查中是无法忽略的,这样不正确的实现就会被观察到了。
记得关闭随机失活(dropout)和数据扩张(augmentation)。在进行梯度检查时,记得关闭网络中任何不确定的效果的操作,比如随机失活,随机数据扩展等。不然它们会在计算数值梯度的时候导致巨大误差。关闭这些操作不好的一点是无法对它们进行梯度检查(例如随机失活的反向传播实现可能有错误)。因此,一个更好的解决方案就是在计算

f(x+h)
f(x-h)
前强制增加一个特定的随机种子,在计算解析梯度时也同样如此。
检查少量的维度。在实际中,梯度可以有上百万的参数,在这种情况下只能检查其中一些维度然后假设其他维度是正确的。 注意****:确认在所有不同的参数中都抽取一部分来梯度检查。在某些应用中,为了方便,人们将所有的参数放到一个巨大的参数向量中。在这种情况下,例如偏置就可能只占用整个向量中的很小一部分,所以不要随机地从向量中取维度,一定要把这种情况考虑到,确保所有参数都收到了正确的梯度。

合理性检查的提示与技巧

在进行费时费力的最优化之前,最好进行一些合理性检查:

寻找特定情况的正确损失值。在使用小参数进行初始化时,确保得到的损失值与期望一致。最好先单独检查数据损失(让正则化强度为0)。例如,对于一个跑CIFAR-10的Softmax分类器,一般期望它的初始损失值是2.302,这是因为初始时预计每个类别的概率是0.1(因为有10个类别),然后Softmax损失值正确分类的负对数概率:-ln(0.1)=2.302。对于Weston Watkins SVM,假设所有的边界都被越过(因为所有的分值都近似为零),所以损失值是9(因为对于每个错误分类,边界值是1)。如果没看到这些损失值,那么初始化中就可能有问题。

第二个合理性检查:提高正则化强度时导致损失值变大。

对小数据子集过拟合。最后也是最重要的一步,在整个数据集进行训练之前,尝试在一个很小的数据集上进行训练(比如20个数据),然后确保能到达0的损失值。进行这个实验的时候,最好让正则化强度为0,不然它会阻止得到0的损失。除非能通过这一个正常性检查,不然进行整个数据集训练是没有意义的。但是注意,能对小数据集进行过拟合并不代表万事大吉,依然有可能存在不正确的实现。比如,因为某些错误,数据点的特征是随机的,这样算法也可能对小数据进行过拟合,但是在整个数据集上跑算法的时候,就没有任何泛化能力。

你可能感兴趣的:(CS231n课程笔记六 训练神经网络)