第十章 神经网络参数的反向传播算法
该系列文章为,观看“吴恩达机器学习”系列视频的学习笔记。虽然每个视频都很简单,但不得不说每一句都非常的简洁扼要,浅显易懂。非常适合我这样的小白入门。
本章含盖
- 10.1 代价函数
- 10.2 反向传播算法
- 10.3 理解反向传播
- 10.4 梯度检测
- 10.5 随机初始化
- 10.6 组合到一起
- 10.7 无人驾驶
10.1 代价函数
为神经网络拟合参数的算法
假设神经网络的训练样本有m个,每个包含一组输入x和一组输出信号y;
L表示神经网络总层数;(👆 L = 4)
S_I表示每层的neuron个数(S_l表示输出层神经元个数),S_L代表最后一层中处理单元的个数。
左边为“二元分类”问题,这种情况下,我们会有一个输出单元。
右边为“多类别分类”问题,也就是说会有K个不同的类。输出为 K维向量。其中,K >= 3
拟合神经网络参数的代价函数:
在神经网络中,我们使用的代价函数是逻辑回归中我们使用的代价函数的一般形式👆逻辑回归代价函数仅有一个输出单元,即,y^(i)
👆表示第 i 个输出。即,K 维向量中的第 i 个元素。
这个求和项,主要是 K 个输出单元之和。比如,我们有4个输出单元,那么代码函数,就是依次将每个输出单元的代价
“正则项”就是(所有 θ 的平方和)* λ
10.2 反向传播算法
让代价函数最小化的方法,即,“反向传播算法”
👆实现了,把向前传播向量化。
为了计算“导数项”,我们将采用一种叫做“反向传播(backpropagation)”的算法
👆表示第l(小写L)层的第j个节点的误差。
我们用δ来表示误差,则:δ^(4) = a^(4)-y
j 表示输出向量的第 j 个元素。
如,δ^(3) = (Θ(3))T * δ^(4) * g’(z^(3)) 可以理解为:δ^(3) / ((Θ(3))T * δ^(4)) = g’(z^(3))。即,输出的误差/输入的误差 = 斜率 (注意,这里输入的误差,是反向传播的输入误差)
如果你把,δ、a、y 都看成向量,你可以这么写,那么你就会得到一个向量化的表达式。这个时候,δ、a、y 分别表示一个向量,且维度相同,都等于输出单元的个数。
『. *』向量乘法。MATLAB中的写法。
注意,没有 δ^(1),因为:对于输入层,那只是表示我们在训练集观察到的,所以不会存在误差。也就是说,我们是不想改变这些值的。
“反向传播”名字的由来,是因为我们从输出从开始计算δ,然后是倒数第二层(隐藏层),以此类推。。。我们类似于把输出层的误差,反向传播给了第3层(即,倒数第二层),然后再传到第二层,,,
然而,这个推导过程是出奇的麻烦和出奇的复杂。。。
在不太严谨的情况下:👆忽略了标准化项,或者 λ = 0;
m 个训练集的情况下:
△ 表示 大写的 δ
j = 0,对应的是“偏差项”,所以没有标准化项:
在得到每个 ij 的偏导项之后,你就可以使用梯度下降或者另一种高级优化算法
10.3 理解反向传播
z2^(1)、 z2^(2)是输入单元的加权和。
反向传播的过程和前向传播非常相似。只是这两个算法计算的方向不一样而已。 只有一个输出单元(即,K = 2),一个输入样本,且 λ = 0 时:比起这个复杂的表达式,你可以把cost(i)近似地当成是“神经网络输出值”与“实际值”的方差
就像逻辑回归中,实际中会偏向于选择比较复杂的、带对数形式的代价函数,但为了方便理解,可以把这个代价函数看作是某种方差函数。因此,cost(i)表示了神经网络样本值的准确程度,也就是,神经网络的输出值 和 实际观测值y(i) 的接近程度。
反向传播算法,一种直观的理解是:反向传播算法就是在计算这些 δ_j^(l) 项,我们可以把它看做是我们在第 l 层中 第 j 个单元中得到的激活项的“误差”。
它们(这些 δ^(l)_j )衡量的是,为了影响这些中间值,我们想要改变神经网络中的权重的程度,进而影响整个神经网络的输出h(x),并影响所有的代价函数
注意,👆 δ 是不包括偏置单元的(bias unit)。这取决于你对“反向传播”的定义,以及实现算法的方式,你也可以用其他的方式,来计算包含偏置单元的 δ 值。偏置单元的输出总是“+1”,并且始终如此,我们无法改变。这都取决于你对反向传播的实现。我经常用的方式是,在最终的计算中,我会丢掉它们(即,δ^(j)_0),因为它们不会对偏导数造成影响。
10.4 梯度检测
反向传播算法有一个不好的特性:很容易产生一些微妙的bug,当它与梯度下降或是其他算法一同工作时,看起来它确实能正常运行,并且代价函数J(θ)在每次梯度下降的迭代中,也在不断的减小。虽然在反向传播的实现中,存在一些bug,但运行情况确实不错。虽然J(θ)在不断减小,但是到了最后,你得到的神经网络其实误差将会比无bug的情况下高出一个量级,并且你很可能不知道你得到的结果,是由bug所导致的。那我们应该如何应对了?(←这个bug几乎都是由于你的错误实现导致的)
有一种思想叫做“梯度检验”,它能解决几乎所有这种问题。这种方法的思想是通过估计梯度值来检验我们计算的导数值是否真的是我们要求的。
“红色的线”就是我们所求的“θ偏导数(蓝色切线的斜率)”的近似值。注意,ε 是离 θ 很近的一个点,如果离的太远,这个算出来的近似值误差就会很大了。。。
ε 一般取值为:10^(-4) ;一般不会取更小的值了,虽然 ε 足够小时,就可以看做是 θ点的偏导数,因为可能会引发很多数值问题。。
当 θ ∈ R(即,θ 是实数 时)
👆单测差分。另一种估计偏导数的公式。
👆双侧差分。它能得到更准确的结果
当 θ 为 向量参数的时候,θ ∈ R^n (E.g. θ is "unrolled" version of Θ(1),Θ(2),Θ^(3))
逐个元素的求偏导数。
最后,记得关闭“梯度检测”,因为,它是一个计算量非常大的,也是非常慢的计算导数程序。相对地,反向传播算法,它是一个高性能的计算导数的方法。因此,一旦通过测试,反向传播的实现是正确的,就应该关闭掉梯度检验。
当我们实现反向传播,或类似梯度下降的算法来计算复杂模型时,我经常使用梯度检验来帮助我确保我的代码是正确的。
10.5 随机初始化
关于梯度下降,我们该如何对 θ 设置初始值呢?
有一种想法是将 θ 的初始值全部设为0。尽管在逻辑回归中,这么做是被允许的,但实际上在训练网络时,将所有的参数初始化为0,起不到任何作用
-
举例:
如果我们将 θ 的初始值全部设为 0 的话,会导致同一层的a^(l)_j都是相同的(即 i 相同时,即,蓝色线的权重总是相同,红色线的权重总是相同,绿色线的权重总是相同),而且它们还有相同的输入。也因为a^(l)_j都是相同,所有 δ^(l)_j 也都是一样的了。
这意味着,即时在每一次的梯度下降更新中,以为δ(l)_j是一样的,导致更新后的a(l)_j还是相同的(即 i 相同时,即,蓝色线的权重总是相同,红色线的权重总是相同,绿色线的权重总是相同,虽然它们都不再等于0)。
又因为,👆图中 两个隐藏单元的输入参数都是相同的,而蓝色线的权重总是相同,红色线的权重总是相同,绿色线的权重总是相同。这意味着,即时进行了一次迭代,但这两个隐藏单元依然是相同的函数。这意味着,这个神经网络计算不出什么有趣的函数。因为,若每层的隐藏单元都有一样的输入时,那么每层的神经单元总是相同的函数。。。这是一种高度冗余的现象,所有的单元都在计算相同的特征,这也导致最后的输出单元也只计算了一种特征。这种做法是阻止了神经网络去学习有趣的东西。
为了解决这个问题(👆也叫做,对称权重问题),在神经网络中,对参数进行初始化时,要使用随机初始化的思想。因此对于每一个 θ 值,我们将其初始化为一个范围在 -ε 到 ε 之间的随机值。
注意,这里的 ε 和前面所说的“梯度检测”的 ε 没有任何关系。
总而言之,为了训练神经网络,我们首先要将权重随机初始化为一个接近 0 的,范围在 -ε 到 ε 之间的数。然后进行反向传播,再进行梯度检验,最后使用梯度下降或者其他高级优化算法来最小化代价函数J,以计算出 θ 的最优值。
10.6 组合到一起
小结一下使用神经网络时的步骤:
网络结构:
第一件要做的事是选择网络结构,即决定选择多少层以及决定每层分别有多少个单元。 你可以选择每一层有多少个隐藏单元,以及有多个隐藏层
① 第一层的单元数即我们训练集的特征数量。
② 最后一层的单元数是我们训练集的结果的类的数量,即,要区分的类别个数。
③ 对于隐藏层的单元个数,以及隐藏层的数目,一个合理的默认选项是:只使用单个隐藏层
如果隐藏层数大于1,同样也有一个合理的默认选项,那就是每一个隐藏层的单元个数相同。
通常情况下隐藏层单元的个数越多越好。 但需要注意,如果有多个隐藏层,计算量一般会比较大。当然一般来说,隐藏单元还是越多越好。
并且一般来说,每个隐藏层所包含的单元数量还应该和输入的 x 维度相匹配,即和特征的数目匹配,隐藏单元的数目可以和输入特征的数目相同,或者是它的2倍,或者是3、4倍。因此,隐藏单元的数目和输入特征数目相匹配或者比特征数大几倍都是有效的。
我们真正要决定的是隐藏层的层数和每个中间层的单元数。
训练神经网络:👆更先进的实现,如,使用向量化方式。而不使用 for 循环。实际上有复杂的方法可以实现,并不一定要使用for循环,但我非常不推荐,在第一次实现反向传播算法的时候使用更复杂更高级的方法
- 参数的随机初始化 。通常,我们将参数初始化为很小的值,接近于零。
- 利用正向传播方法,对任意的输入 x^(i),计算出对应的h_θ (x^(i)) ,也就是输出 y 的向量
- 编写计算代价函数 J(θ) 的代码
- 利用反向传播方法计算所有偏导数
- 利用梯度检验方法检验这些偏导数 ,在确认偏导数的正确性后,将梯度检验关闭。
- 使用梯度下降或高级的优化算法来最小化代价函数
有两个参数的代价函数图:注意,对于神经网络,代价函数 J(θ) 是一个非凸函数,就是说不是凸函数,因此理论上可能停留在局部最小值的位置。实际上,梯度下降算法和其他一些高级优化方法理论上都可能收敛于局部最小值。但一般来讲,在实际操作中,这不是大问题。尽管我们不能保证,这些优化算法一定会得到全局最优值。但通常来说,像梯度下降这类的算法,在最小化代价函数 J(θ) 的过程中,还是表现得很不错的。通常能够得到一个很小的局部最小值,尽管这可能不一定是全局最优值。
梯度下降的原理:我们从某个随机的初始点开始,它将会不停的往下下降,那么反向传播算法的目的就是算出梯度下降的方向,而梯度下降的作用就是沿着这个方向一点点的下降,一直到我们希望得到的点。👆这个例子就是局部最优点。所以当你在执行反向传播算法和梯度下降或者其他更高级的优化方法时,这幅图片解释了基本的原理。
反向传播算法能够让更复杂、强大、非线性的函数模型,跟你的数据很好的拟合。
10.7 无人驾驶
在这段视频中,我想向你介绍一个具有历史意义的神经网络学习的重要例子。那就是使用神经网络来实现自动驾驶,也就是说使汽车通过学习来自己驾驶。接下来我将演示的这段视频是我从Dean Pomerleau那里拿到的,他是我的同事,任职于美国东海岸的卡耐基梅隆大学。在这部分视频中,你就会明白可视化技术到底是什么?在看这段视频之前,我会告诉你可视化技术是什么。
在下面也就是左下方,就是汽车所看到的前方的路况图像。
在图中你依稀能看出一条道路,朝左延伸了一点,又向右了一点,然后上面的这幅图,你可以看到一条水平的菜单栏(左上图,第一条)显示的是驾驶操作人选择的方向。就是这里的这条白亮的区段显示的就是人类驾驶者选择的方向。比如:最左边的区段,对应的操作就是向左急转,而最右端则对应向右急转的操作。因此,稍微靠左的区段,也就是中心稍微向左一点的位置,则表示在这一点上人类驾驶者的操作是慢慢的向左拐。
这幅图的第二部分(左上图,第二条)对应的就是学习算法选出的行驶方向。并且,类似的,这一条白亮的区段显示的就是神经网络在这里选择的行驶方向,是稍微的左转,并且实际上在神经网络开始学习之前,你会看到网络的输出是一条灰色的区段,就像这样的一条灰色区段覆盖着整个区域这些均称的灰色区域,显示出神经网络已经随机初始化了,并且初始化时,我们并不知道汽车如何行驶,或者说我们并不知道所选行驶方向。只有在学习算法运行了足够长的时间之后,才会有这条白色的区段出现在整条灰色区域之中。显示出一个具体的行驶方向这就表示神经网络算法,在这时候已经选出了一个明确的行驶方向,不像刚开始的时候,输出一段模糊的浅灰色区域,而是输出一条白亮的区段,表示已经选出了明确的行驶方向。
ALVINN (Autonomous Land Vehicle In a Neural Network)是一个基于神经网络的智能系统,通过观察人类的驾驶来学习驾驶,ALVINN能够控制NavLab,装在一辆改装版军用悍马,这辆悍马装载了传感器、计算机和驱动器用来进行自动驾驶的导航试验。实现ALVINN功能的第一步,是对它进行训练,也就是训练一个人驾驶汽车。
然后让ALVINN观看,ALVINN每两秒将前方的路况图生成一张数字化图片,并且记录驾驶者的驾驶方向,得到的训练集图片被压缩为30x32像素,并且作为输入提供给ALVINN的三层神经网络,通过使用反向传播学习算法,ALVINN会训练得到一个与人类驾驶员操纵方向基本相近的结果。一开始,我们的网络选择出的方向是随机的,大约经过两分钟的训练后,我们的神经网络便能够准确地模拟人类驾驶者的驾驶方向,对其他道路类型,也重复进行这个训练过程,当网络被训练完成后,操作者就可按下运行按钮,车辆便开始行驶了。
每秒钟ALVINN生成12次数字化图片,并且将图像传送给神经网络进行训练,多个神经网络同时工作,每一个网络都生成一个行驶方向,以及一个预测自信度的参数,预测自信度最高的那个神经网络得到的行驶方向。比如这里,在这条单行道上训练出的网络将被最终用于控制车辆方向,车辆前方突然出现了一个交叉十字路口,当车辆到达这个十字路口时,我们单行道网络对应的自信度骤减,当它穿过这个十字路口时,前方的双车道将进入其视线,双车道网络的自信度便开始上升,当它的自信度上升时,双车道的网络,将被选择来控制行驶方向,车辆将被安全地引导进入双车道路。
这就是基于神经网络的自动驾驶技术。当然,我们还有很多更加先进的试验来实现自动驾驶技术。在美国,欧洲等一些国家和地区,他们提供了一些比这个方法更加稳定的驾驶控制技术。但我认为,使用这样一个简单的基于反向传播的神经网络,训练出如此强大的自动驾驶汽车,的确是一次令人惊讶的成就。