在上周小Mi给大家的介绍中,我们谈到了怎样使用反向传播算法计算代价函数的导数。今天,小Mi将继续带大家了解神经网络的实现过程,废话不多说,赶快跟小Mi一起学起来吧~
4 实现注意:展开参数
首先,有一个细节的实现过程值得关注一下,就是如何把参数从矩阵展开成向量,以便我们在高级最优化步骤中使用。
具体来说,小Mi执行了代价函数,输入的参数是theta,函数返回代价值以及导数值,然后就可以将返回值传递给高级最优化算法fminunc,顺便需要说一下的是,fminunc并不是唯一的算法,当然也可以使用别的优化算法,但它们的功能都属取出这些输入值,比如@costFunction以及theta值的一些初始值,并且这些程序都假定theta和这些theta初始值都是参数向量(也许是n维或者n+1维),但它们都是向量。同时假定代价函数第二个返回值,也就是梯度值也是n维或者n+1维,所以这也是一个向量,这部分在逻辑回归的使用过程中是没有任何问题的,但现在我们用神经网络时,我们的参数不再是向量了,而是矩阵。因此对于一个完整的神经网络,我们的参数矩阵为,代码编写过程中可以设为matrices(Theta1,Theta2,Theta3),同样地,这些梯度项也是需要得到的返回值,之前小Mi也带大家学习了如何计算这些梯度矩阵,分别是,程序中可以直接编写为matrices(D1,D2,D3)。那么如何取出这些矩阵并且将它们展开以便最终成为恰当的格式,从而可以传入上述的Theta中并得到梯度返回值呢?
更进一步来说,假设我们有一个这样的神经网络,其输入层有10个输入单元,隐藏层也有10个单元,最后的输出层只有一个输出单元,因此等于第一层的单元数,等于第二层的单元数,等于第三层的单元个数。在这种情况下,矩阵和矩阵D的维度(实际上这里多出来一个和D矩阵)将由表达式决定。比如说,是一个10*11的矩阵,以此类推,如果你想在矩阵和向量之间来回转化,那么需要做的就是取出Theta1,Theta2,Theta3,然后使用下面部分的代码,从而取出三个矩阵中的所有元素,然后把它们全部展开称为一个很长的向量,也就是thetaVec;同样地,下图中的第二句代码将取出D矩阵中的所有元素,然后展开成一个长向量,叫做DVec。
最后的话,如果想从向量表达返回到矩阵表达式的话,比如要想得到Theta1,那么取thetaVec,抽出前110个元素,Theta1就有110个元素,因为它是一个10*11的矩阵,所以抽出前110个元素,然后你就能使用命令reshape来改变矩阵大小得到Theta1,同样,要重新得到Theta2,就需要抽出下一组110个元素并且重新组合,然后对于Theta3,也需要抽出最后110个元素,然后执行reshape命令得到Theta3。
构建三个独立的矩阵Theta1,Theta2,Theta3,最后把所有这些矩阵变成一个向量,现在thetaVec就变成了一个很长的向量,含有231个元素,包括三个矩阵的所有元素。
那如果想重新得到原来的矩阵,可以对thetaVec使用reshape命令,抽出前110个元素,并将它们重组为一个10*11的矩阵,这样我们又再次得到了Theta1的矩阵,同理,获取Theta2,Theta3也是如此。
为了使这个过程更具体,我们可以看下如何将这一方法应用于学习算法中。假设我们有一些初始参数值,。我们要做的是取这些参数,然后将它们展开为一个长向量,可以称之为初始的Theta值。然后作为Theta参数的初始设置传入优化函数fminunc,而要做的另一件事就是实现代价函数。代价函数的实现算法如下:
代价函数会得到输入函数thetaVec,其包含了所有的参数向量,所有的参数都展开成一个向量的形式。因此我们要做的第一件事是使用thetaVec和重组函数reshape,抽出thetaVec中的元素,然后重组以得到我的初始参数矩阵,这些就是我们需要得到的矩阵,这样就有了一个使用这些矩阵的更为方便的形式,从而执行前向传播和反向传播来计算出导数以及代价函数。最后的话,可以取出这些导数值然后展开它们,让它们保持和展开的值同样的顺序,展开D1,D2,D3来得到gradientVec,这个值又可由代价函数返回,以一个向量的形式返回这些导数值。
现在大家是不是对参数的矩阵表达式与向量表达式之间的来回转换有了一个更清晰的认识呢?使用矩阵表达式的好处就是,当你的参数以矩阵的形式存储的话,进行正向传播和反向传播时会更加便捷;而将参数存储为矩阵时,也会更容易充分利用向量化实现。还有一点需要注意的是,假设遇到thetaVec或者DVec这样的矩阵时,使用一些高级的优化算法,而这些算法会通常要求把所有的参数都展开成一个长向量的形式,希望通过本节的学习大家可以更加灵活地在两种形式之间来回切换~
5 梯度检验
之前我们已经讨论过如何在神经网路中使用前向传播和反向传播来计算导数,但是反向传播算法含有很多细节,因此实现起来比较困难,同时它还有一个不好的特性,当我们对一个较为复杂的模型(例如神经网络)使用梯度下降算法时,可能会存在一些不容易察觉的错误,看起来它确实能正常运行,并且代价函数在每次梯度下降的迭代过程中也在不断减小,尽管运行情况看起来确实不错,但最终的结果其误差可能会比无bug的情况下高出一个量级,并且你很可能不知道你得到的结果是由bug导致的。那么面对这种情况,我们应该如何解决呢?
为了避免这样的问题,我们采取一种叫做梯度的数值检验(Numerical Gradient Checking)方法,它能解决几乎所有这种问题。如果在神经网络或者其他算法中实现反向传播或者类似梯度下降算法时,可以都进行一下梯度检验,从而可以完全保证前向传播以及后向传播都是百分之百正确的。而这种问题之所以会出现,是因为绝大多数都和反向传播的错误实现有关。之前我们提到的关于的计算公式,以及关于d的,等等,这些确实都可以计算出代价函数的梯度。而当我们去实现梯度检验的时候,可以自己去验证自己的代码是否能够正确计算出代价函数的导数。
现在小Mi就带大家具体来说明一下梯度检验啦~大家可以看一下上面的图例,假设有一个代价函数,有一个值(在这里假设是实数),假如我们想估计函数在这一点上的导数,那么该点的导数就是图像在该点切线的斜率。现在我们要从数值上来逼近它的导数,或者说这是一种从数值上来求近似导数的方法。首先计算出,在的右边一点点,然后再计算出,也就是在的左边一点点,现在要做的就是把这两个值对应的点用直线连起来,然后所得红线的斜率(即蓝色切线的斜率)就是所求该点导数的近似值,数学上来讲的话,该斜率等于该段的垂直高度除以该段的水平宽度,即(是一个很小的值)。实际上只要足够小,该点真正的导数就成了函数在这一点上真实的斜率。当然,需要注意的是,过分小的值会引发很多数值问题,所以通常会给取左右的值。可能以后我们还会遇到单侧差分这样的式子,而我们计算的是双侧差分。
可以执行以下代码实现,计算出gradApprox(该点导数的近似值):
gradApprox = (J(theta + eps) – J(theta - eps)) / (2*eps)
上述小Mi的所有讲解都是针对是实数而言,更普遍的情况也就是当为向量参数的时候,假设是一个n维向量(可能是神经网络中参数的展开式),同理,所有的偏导数项的近似值也可以直接求出,比如代价函数关于第一个参数的偏导数,关于第二个参数的偏导数等等。
代码实现:
对神经网络中代价函数所有参数的偏导数的计算,然后与我们在反向传播中得到的梯度进行比较,DVec是从反向传播中得到的导数,而反向传播是计算代价函数关于所有参数的导数或者偏导数的一种有效方法,接下来要做的就是验证计算出的导数gradApprox是否接近于用反向传播所计算出的导数DVec,如果两者非常接近,只有几位小数的差距,那么就可以说明反向传播的实现是正确的。从而向量DVec用于梯度下降或者其他高级优化算法中时,就可以确信计算的导数是正确的,那么代码也会正常运行,并可以很好地优化。
总结下,如何实现数值上梯度检验的步骤:
1.通过反向传播来计算DVec(DVec可能是矩阵展开的形式);
2.实现数值上的梯度检验,计算出gradApprox
3.确认DVec和gradApprox是否相似,只有几位小数的差距。
4.在训练网络之前,关闭梯度检验,不要再去用导数计算公式来计算gradApprox了(因为梯度检验计算量非常大的,速度比较慢),相对地,反向传播算法是一个高性能的计算方法,一旦我们通过检验确定反向传播的实现是正确的,就应该关掉梯度检验,不要再去使用了。
6 随机初始化
前面小Mi的介绍已经包括了神经网络中所有需要实现和训练的内容,不过随机初始化的思想也是需要学习的。当我们执行一个算法,比如梯度下降法或者高级优化算法时,我们需要为变量选取一些初始值。对于高级优化算法,它会默认你会为变量提供一些初始值。对于梯度下降算法,同样我们也需要对进行初始化。初始化完毕之后,就可以一步一步通过梯度下降来最小化代价函数J。那么又应该如何对设置初始值呢?有一种想法是将的初始值全部设为0,这样的初始方法对于逻辑回归来说是可行的,但实际上在训练网络时将所有的初始值都设为0起不到任何作用。
以这个神经网络的训练为例,假设我们将所有的参数都初始化为0,这样的话就意味着隐藏单元和都是以同一输入函数来计算的。而对于神经网络中的所有训练样本最后总能得到,即使梯度下降进行了迭代,但隐藏单元依然以相同的函数输入,计算相同的特征,这是一种高度冗余的现象,最终只会得到一个特征,这就阻止了神经网络去学习别的更多的东西。
为了解决这个问题,对参数进行初始化时,要采用随机初始化的思想,具体来说,随机初始化就是解决对称权重的问题(所有的权重都一样)。我们将其初始化为一个范围在的随机值。(和之前梯度检验中的没有任何关系)
代码实现:
因此,为了训练神经网络,应该首先将权重随机初始化为一个接近0的数,然后进行反向传播,再进行梯度检验,最后使用梯度下降或其他高级优化算法来最小化代价函数J。
7 总结
在训练一个网络的时候,我们要做的第一件事就是要选择一种网络架构(神经元之间的连接模式):比如上图中第一种包含三个输入单元,五个隐藏单元和四个输出单元,第二种则是三个输入单元,两组五个隐藏单元,四个输出单元等等。也就是说我们需要决定选择多少层以及决定每层分别有多少个单元。
那么这个时候又该如何选择呢?
第一层的单元数即我们训练集的特征数量。
最后一层的单元数是我们训练集的结果的类的数量。
如果隐藏层数大于1,确保每个隐藏层的单元个数相同,通常情况下隐藏层单元的个数越多越好。
因此,我们真正要决定的是隐藏层的层数和每个隐藏层的单元数,而每个隐藏层所包含的单元数量需要与输入x的维度特征相匹配,隐藏单元的数目可以和输入特征数目相同,或者是其二倍、三倍等等。
好了,这下小Mi终于可以总结下啦~训练神经网络实现步骤:
1.构建神经网络,然后随机初始化权重
2.执行前向传播算法计算所有的,也就是输出向量
3.通过代码计算代价函数
4.利用反向传播算法计算所有偏导数
5.利用梯度检验方法检验这些偏导数
6.使用优化算法来最小化代价函数
8 实例:自主驾驶
其实在我们生活中,神经网络能运用到的很常见的一个场景就是自动驾驶。在自动驾驶系统中,车辆会通过摄像头和传感器不断收集周围环境的数据,并指导车辆完成相应动作。车辆的传感器主要完成探测周围物体位置及预测其运动趋势的任务,摄像头则完成对周围物体进行识别以及对交通标志进行分类的任务。摄像头在对图像分类过程中使用的算法是机器学习中的神经网络算法。
小Mi带大家看看 Dean Pomerleau基于神经网络实现的自主驾驶是什么样儿的。
在下图的左下方是汽车所看到的前方的路况图像。
在图中我们依稀能看出一条道路,朝左延伸了一点,又向右了一点。还可以看到一条水平的菜单栏显示的是驾驶操作人选择的方向。就是这里的这条白亮的区段显示的就是人类驾驶者选择的方向。比如:最左边的区段,对应的操作就是向左急转,而最右端则对应向右急转的操作。因此,稍微靠左的区段,也就是中心稍微向左一点的位置,则表示在这一点上人类驾驶者的操作是慢慢的向左拐。
这幅图的第二部分对应的就是学习算法选出的行驶方向。并且,类似的,这一条白亮的区段显示的就是神经网络在这里选择的行驶方向,是稍微的左转,并且实际上在神经网络开始学习之前,你会看到网络的输出是一条灰色的区段,就像这样的一条灰**段覆盖着整个区域这些均称的灰**域,显示出神经网络已经随机初始化了,并且初始化时,我们并不知道汽车如何行驶,或者说我们并不知道所选行驶方向。只有在学习算法运行了足够长的时间之后,才会有这条白色的区段出现在整条灰**域之中。显示出一个具体的行驶方向这就表示神经网络算法,在这时候已经选出了一个明确的行驶方向,不像刚开始的时候,输出一段模糊的浅灰**域,而是输出一条白亮的区段,表示已经选出了明确的行驶方向。
ALVINN (Autonomous Land Vehicle In a Neural Network)是一个基于神经网络的智能系统,通过观察人类的驾驶来学习驾驶,ALVINN能够控制NavLab,装在一辆改装版军用悍马,这辆悍马装载了传感器、计算机和驱动器用来进行自动驾驶的导航试验。实现ALVINN功能的第一步,是对它进行训练,也就是训练一个人驾驶汽车。
然后让ALVINN观看,ALVINN每两秒将前方的路况图生成一张数字化图片,并且记录驾驶者的驾驶方向,得到的训练集图片被压缩为30x32像素,并且作为输入提供给ALVINN的三层神经网络,通过使用反向传播学习算法,ALVINN会训练得到一个与人类驾驶员操纵方向基本相近的结果。一开始,我们的网络选择出的方向是随机的,大约经过两分钟的训练后,我们的神经网络便能够准确地模拟人类驾驶者的驾驶方向,对其他道路类型,也重复进行这个训练过程,当网络被训练完成后,操作者就可按下运行按钮,车辆便开始行驶了。
每秒钟ALVINN生成12次数字化图片,并且将图像传送给神经网络进行训练,多个神经网络同时工作,每一个网络都生成一个行驶方向,以及一个预测自信度的参数,预测自信度最高的那个神经网络得到的行驶方向。比如这里,在这条单行道上训练出的网络将被最终用于控制车辆方向,车辆前方突然出现了一个交叉十字路口,当车辆到达这个十字路口时,我们单行道网络对应的自信度骤减,当它穿过这个十字路口时,前方的双车道将进入其视线,双车道网络的自信度便开始上升,当它的自信度上升时,双车道的网络,将被选择来控制行驶方向,车辆将被安全地引导进入双车道路。
这就是基于神经网络的自动驾驶技术。当然,我们还有很多更加先进的试验来实现自动驾驶技术。但很多人都认为,使用这样一个简单的基于反向传播的神经网络,训练出如此强大的自动驾驶汽车,的确是非常值得称赞的(小Mi也竖起了大拇指)。
好啦,对于神经网络的具体介绍就先到这里啦,下周小Mi给大家讲解如何应用机器学习。我们,下期再见呦~(挥手十分钟)