机器学习(五)——神经网络的学习算法

代价函数与反向传播

神经网络是当今最强大的学习算法之一,我将开始讲述一种在给定训练集下,为神经网络拟合参数的学习算法,正如我们讨论大多数学习算法一样,我们准备从拟合神经网络参数的代价函数开始讲起,我准备重点讲解神经网络在分类问题中的应用。我还是和以前一样,用m表示训练样本的数量,用大写字母L来表示神经网络结构的总层数,然后我准备用sl表示第L层的单元的数量,也就是神经元的数量,这其中不包括L层的偏差单元,输出层也就是sL,因为L本身等于总层数。我们将会讨论两种分类问题,第一种是二元分类,在这里y只能等于0或1,也就是神经网络的输出结果h(x)会是一个实数,在这类问题里,输出单元的个数sL将是1,为了简化记法,我会把K设为1,这样你可以把K看作输出层的单元数目。我们要考虑的第二类分类问题,就是多类别的分类问题,也就是会有K个不同的类,我们的假设输出就是一个K维向量,输出单元的个数就等于K,通常这类问题里,我们都有K大于或等于3,因为如果只有两个类别,我们就不需要使用这种一对多的方法。我们在神经网络里使用的代价函数,应该是逻辑回归里使用的代价函数的一般化形式,对于逻辑回归而言,我们通常使代价函数J(θ)最小化,也就是-1/m 乘以后面这个代价函数,然后再加上这个额外正则化项,这里是一个j从1到n的求和形式,因为我们并没有把偏差项0正则化。

图片21

对于一个神经网络来说,我们的代价函数是这个式子的一般化形式,这里不再是仅有一个逻辑回归输出单元,取而代之的是K个,所以这是我们的代价函数:

图片22

神经网络现在输出了K维的向量,这里K可以取到1,也就是原来的二元分类问题。我准备用h(x)带下标i来表示第i个输出。我的代价函数J(θ)将成为上面这样的形式:-1/m乘以一个类似于我们在逻辑回归里所用的求和项,除了这里我们求的是k从1到K的所有和,这个求和项主要是K个输出单元的求和,所以如果我的神经网络最后一层有四个输出单元,那么这个求和项就是,求k等于从1到4的每一个的逻辑回归算法的代价函数,然后按四次输出的顺序,依次把这些代价函数加起来,所以你会特别注意到,这个求和符号应用于yk和hk,因为我们主要是讨论K个输出单元,并且把它和yk的值相比,yk的值就是这些向量里表示它应当属于哪个类别的量。最后,这里的第二项,就是类似于我们在逻辑回归里所用的正则化项,这个求和项看起来确实非常复杂,它所做的就是把这些项全部相加,也就是对所有i j和l的θji的值都相加,正如我们在逻辑回归里一样,这里要除去那些对应于偏差值的项,那些项我们是不加进去的,具体地说,我们不把那些对于i等于0的项加入其中,这是因为当我们计算神经元的激励值时,我们会有这些项θ(2)i0乘以x0加上θ(2)i1乘以x1再加上等等等等,这里我认为这是第一个隐含层,所以这些带0的项对应于乘进去了x0或者是a0什么的,这就是一个类似于偏差单元的项,类比于我们在做逻辑回归的时候,我们就不应该把这些项加入到正规化项里去,因为我们并不想正规化这些项,但这只是一个合理的规定,即使我们真的把它们加进去了,也就是i从0加到sL,这依然成立,并且不会有大的差异,但是这个"不把偏差项正规化"的规定可能只是会更常见一些。

我们来说说让代价函数最小化的算法,具体来说,我们将主要讲解反向传播算法。我们要做的就是,设法找到参数,使得J(θ)取到最小值,为了使用梯度下降法或者其他某种高级优化算法,我们需要做的就是,写好一个可以通过输入参数θ然后计算J(θ)和那些偏导数项的代码,为了计算代价函数J(θ),我们就是用上面那个公式,所以我们在本节想要做的就是,重点关注如何计算那些偏导数项。我们从只有一个训练样本的情况开始说起,假设我们整个训练集只包含一个训练样本,也就是实数对(x, y),让我们粗看一遍使用这一个训练样本来计算的顺序:首先我们应用前向传播方法,来计算一下在给定输入的时候,假设函数是否会真的输出结果:

图片23

具体地说,这里的a(1)就是第一层的激励值,也就是输入层在的地方,所以我准备设定他为x,然后我们来计算z(2)等于θ(1)乘以a(1),然后a(2)就等于g(z(2))函数,其中g是一个S型激励函数,这就会计算出第一个隐藏层的激励值,也就是神经网络的第二层,我们还增加这个偏差项,接下来我们再用2次前向传播,来计算出a(3)和最后的a(4),同样也就是假设函数h(x)的输出,所以这里我们实现了把前向传播向量化,这使得我们可以计算神经网络结构里的每一个神经元的激励值。接下来,为了计算导数项,我们将采用一种叫做反向传播(Backpropagation)的算法,反向传播算法从直观上说,就是对每一个结点,我们计算这样一项:δ下标j上标(l),这就用某种形式代表了第l层的第j个结点的误差,我们还记得a上标(l)下标j表示的是第l层第j个单元的激励值,所以这个δ项在某种程度上就捕捉到了我们在这个神经节点的激励值的误差:

图片24

具体地讲,我们用一个假设有四层的神经网络结构做例子,所以这里大写L等于4,对于每一个输出单元,我们准备计算δ项,所以第四层的第j个单元的δ就等于这个单元的激励值减去训练样本里的真实值,这一项同样可以写成h(x)下标j,所以δ这一项就是假设输出和训练集y值之间的差,这里y下标j就是我们标记训练集里向量的第j个元素的值。顺便说一下,如果你把δ a和y这三个都看做向量,那么你可以同样向量化地实现,也就是δ(4)等于a(4)减去y,这里每一个变量,也就是δ(4) a(4)和y都是一个向量,并且向量维数等于输出单元的数目。所以现在我们计算出网络结构的误差项δ(4),我们下一步就是计算网络中前面几层的误差项δ,δ(3)等于θ(3)的转置乘以δ(4)然后点乘g'(z(3)),这里的点乘是我们从MATLAB里学到的,所以θ(3)转置乘以δ(4)是一个向量,g'(z(3))同样也是一个向量,点乘就是两个向量的元素间对应相乘,其中g'(z(3))其实是对激励函数g在输入值为z(3)的时候所求的导数,你计算这个g'导数项,其实是a(3)点乘(1-a(3)),这里a(3)是激励向量,1是以1为元素的向量。接下来应用一个相似的公式,来计算δ(2),只是在这里是a(2)。最后就到这儿结束了,这里没有δ(1)项,因为第一次对应输入层,那只是表示我们在训练集观察到的,所以不会存在误差,这就是说,我们是不想改变这些值的,所以这个例子中我们的δ项就只有第2层和第3层。反向传播法这个名字,源于我们从输出层开始计算δ项,然后我们返回到上一层计算第三隐藏层的δ项,接着我们再往前一步来计算δ(2),所以说,我们是类似于把输出层的误差反向传播给了第3层,然后是再传到第二层,这就是反向传播的意思。最后,这个推导过程是出奇的麻烦且复杂的,但是如果你按照这样几个步骤计算,就有可能简单直接地完成复杂的数学证明。如果你忽略正则化所产生的项,我们可以证明,我们要求的偏导数项恰好就等于激励函数和这些δ项,这里我们忽略了λ或者说正则化项λ是等于0,我们将在之后完善这一个关于正则化项。所以到现在,我们通过反向传播计算这些δ项,可以非常快速的计算出所有参数的偏导数项。当我们有一个非常大的训练样本时,而不是像我们例子里这样的一个训练样本,我们是这样做的:

图片25

假设我们有m个样本的训练集,正如此处所写,我要做的第一件事就是固定这些带下标i j的Δ,这个三角形其实是大写的希腊字母δ,我们之前写的那个是小写,我们将令每一个i和j对应的Δ等于0,实际上,这些大写Δij会被用来计算偏导数项,就是J(θ)关于θ上标(l)下标i j的偏导数,所以正如我们接下来看到的,这些δ会被作为累加项,慢慢地增加,以算出这些偏导数。接下来我们将遍历我们的训练集,我们写成For i = 1 to m,对于第i个循环而言,我们将取训练样本(x(i), y(i)),我们要做的第一件事是设定a(1),也就是输入层的激励函数,设定它等于x(i),x(i)是我们第i个训练样本的输入值,接下来我们运用前向传播来计算第二层的激励值,然后是第三层,第四层,一直这样到最后一层L层,接下来我们将用我们这个样本的输出值y(i),来计算这个输出值所对应的误差项δ(L),所以δ(L)就是假设输出减去目标输出,接下来,我们将运用反向传播算法,来计算δ(L-1) δ(L-2)一直这样直到δ(2),再强调一下,这里没有δ(1),因为我们不需要对输入层考虑误差项,最后我们将用这些大写的Δ来累积我们在前面写好的偏导数项,顺便说一下,如果你再看下这个表达式,你可以把它写成向量形式,具体地说,如果你把Δij看作一个矩阵,i j代表矩阵中的位置,那么如果δ(L)是一个矩阵,我们就可以写成Δ(l)等于Δ(l)加上小写的δ(l+1)乘以a(l)的转置,这就是用向量化的形式实现了对所有i和j的自动更新值。最后,执行这个for循环体之后,我们跳出这个for循环,然后计算下面这些式子,我们按照如上公式计算,我们对于j=0和j≠0分两种情况讨论,当j=0的时候,我们没有写额外的标准化项。最后,尽管严格的证明对于你来说太复杂,但现在可以说明的是,一旦你计算出来了这些,这就正好是代价函数对每一个参数的偏导数,所以你可以把他们用在梯度下降法或者其他一种更高级的优化算法上,这就是反向传播算法以及你如何计算神经网络代价函数的偏导数。

我想更加深入地讨论一下反向传播算法的这些复杂的步骤,并且希望给你一个更加全面直观的感受,理解这些步骤究竟是在做什么,也希望你能理解它至少还是一个合理的算法。为了更好地理解反向传播算法,我们再来仔细研究一下前向传播的原理,下面所示的神经网络包含两个输入单元,这不包括偏差单元,在第二层有两个隐藏单元,在下一层也有两个隐藏单元,最后的输出层有一个输出单元。我把这个神经网络的节点都画成了椭圆型,以便在节点里面写字:

捕获10

在进行前向传播时,我们可以用一个具体的例子说明,假如说训练样本x(i) y(i),那么这里的x(i)将被传入输入层,因此这里就是x(i)1和x(i)2,这是我们输入层的值,那么当我们进行前向传播,传播到第一个隐藏层时,我们的做法是算出z(2)1和z(2)2,因此这两个值是输入单元的加权总和,接下来我们将S型的逻辑函数应用到z值上,因此我们得到a(2)1和a(2)2的值,然后再做一次前向传播,这里的z(3)1应用S型的逻辑函数和激励函数,得到a(3)1,类似这样进行下去,最后我们得到z(4)1应用激励函数,得到a(4)1,这也是这个网络的输出单元的值。那么,如果你仔细看这里的计算,关注第三层描线的的隐藏单元,我们知道这里用桃红色表示的权值是θ(2)10,这里的角标不重要,这里用红色来标记的权值是θ(2)11,而这里用青色表示的权值是θ(2)12,因此z(3)1的值等于这个桃红色的权值乘以这个值,也就是θ(2)10乘上1,加上这个红色的权值乘以这个值,也就是θ(2)11乘上a(2)1,最后是青色的权值乘上这个值,也就是θ(2)12乘以a(2)1,那么这就是前向传播,事实上,正如我们后面将会看到的,反向传播的做法,其过程非常类似于此,只有计算的方向不同而已,与这里前向传播的方向从左至右不同的是,反向传播的算法中计算的方向是从右往左的,但计算的过程是完全类似的。为了更好地理解反向传播算法的原理,我们把目光转向代价函数:

图片26

这个代价函数对应的情况是只有一个输出单元,如果我们有不止一个输出单元的话,只需要对所有的输出单元进行一次求和运算,我们用同一个样本同时来做正向和反向传播,那么请注意这组训练样本x(i) y(i),注意这种只有一个输出单元的情况,那么这里的y(i)就是一个实数,如果不考虑正则化,也就是说λ等于0,因此最后的正则化项就没有了,那么如果你观察这个求和运算括号里面与第i个训练样本对应的代价项,也就是说和训练样本x(i) y(i)对应的代价项,将由下面这个式子确定:

图片27

因此,第i个样本的代价值,可以写成如上的形式,而这个代价函数所扮演的角色,可以看作是平方误差,因此我们不必关心这个复杂的表达式,当然如果你愿意,你可以把cost(i)想成是该神经网络输出值与实际值的差的平方:

图片28

就像在逻辑回归中我们选择稍微复杂的一点的代价函数log函数,但为了容易理解,可以把这个代价函数看作是某种平方误差函数,因此这里的cos(i)表征了该神经网络是否能准确地预测样本i的值,也就是输出值和实际观测值y(i)的接近程度。现在我们来看反向传播是怎么做的:

捕获11

一种直观的理解是:反向传播算法就是在计算所有这些δ(l)j项,并且我们可以把它们看作是这些激励值的"误差",注意这些激励值是第l层中的第j项,更正式地说,δ项实际上是关于z(l)j的偏微分,也就是cost函数关于我们计算出的输入项的加权和,所以 实际上这个代价函数是一个关于标签y和这个h(x)的值(也就是神经网络输出值)的函数,如果我们观察该网络内部的话,把这些z(l)j项稍微改一点点,那就将影响到该神经网络的输出,并且最终会改变代价函数的值。如果你对偏微分很熟悉的话,你能理解这些δ项是什么,它们实际上是代价函数关于这些中间项的偏微分,因此,它们度量着我们对神经网络的权值做多少的改变,对中间的计算量影响是多少,进一步地,对整个神经网络的输出h(x)影响多少,以及对整个的代价值影响多少。可能刚才讲的偏微分的这种理解,不太容易理解,没关系,不用偏微分的思想,我们同样也可以理解,我们再深入一点研究一下反向传播的过程,对于输出层,如果我们设置δ项,假设我们进行第i个训练样本的正向传播和反向传播,那么δ(4)1应该等于y(i)减去a(4)1,因此这实际是两者的偏差,也就是y的实际值减去预测值得到的差值,这样我们就算出了δ(4)1的值,接下来我们要对这些值进行反向传播,那么这里我们计算出δ(3)1和δ(3)2,然后同样的再进行下一层的反向传播,这一次计算出δ(2)1以及δ(2)2,反向传播的计算和进行前向传播几乎相同,唯一的区别就是方向相反。我们来看我们是怎样得到δ(2)2的值的:

捕获12

我们要计算δ(2)2与前向传播类似,我要对一些权值进行标记,那么这条用桃红色表示的权值就是θ(2)12,然后这根用红色箭头表示的权值是θ(2)22,我们来看δ(2)2是如何得到的,实际上,我们要做的是,我们要用这个δ值和权值相乘然后加上这个δ值乘以权值的结果,也就是说,它其实是这些δ值的加权和,权值是这些对应边的强度,所以,简单地说,就是红色的权值乘以它指向的值加上桃红色的权值乘以它指向的值,这样我们就得到了上一层的δ值。再举一个例子,我们来看δ(3)2是怎么得到的,仍然是类似的过程,如果这个用绿色表示的这根箭头的权值是θ(3)12,那么δ(3)2将等于这条绿色的权值θ(3)12乘以δ(4)1,另外顺便提一下,目前为止,我写的δ值仅仅是隐藏层中的,没有包括偏差单元+1,包不包括偏差单元取决于你如何定义这个反向传播算法,或者取决于你怎样实现这个算法,你也可以对这些偏差单元计算δ的值,这些偏差单元总是取为+1的值,一直都这么取,我们不能也没有必要更改偏差单元的值,所以还是取决于你实现反向传播的方法,通常说来,我在执行反向传播的时候,我是算出了这些偏差单元的δ值,但我通常忽略掉它们,而不把它们代入计算,因为它们其实并不是计算那些微分的必要部分。

反向传播的实践

在上一段视频中 我们谈到了怎样使用反向传播算法 计算代价函数的导数 在这段视频中 我想快速地向你介绍一个细节的实现过程 怎样把你的参数 从矩阵展开成向量 以便我们在高级最优化步骤中的使用需要 具体来讲 你执行了代价函数costFunction 输入参数是theta 函数返回值是代价函数以及导数值 然后你可以将返回值 传递给高级最优化算法fminunc 顺便提醒 fminunc并不是唯一的算法 你也可以使用别的优化算法 但它们的功能 都是取出这些输入值 @costFunction 以及theta值的一些初始值 并且这些程序 都假设theta 和这些theta初始值 都是参数向量 也许是n或者n+1阶 但它们都是向量 同时假设这个代价函数 第二个返回值 也就是gradient值 也是n阶或者n+1阶 所以它也是一个向量 这部分在我们使用逻辑回归的时候 运行顺利 但现在 对于神经网络 我们的参数将不再是 向量 而是矩阵了 因此对于一个完整的神经网络 我们的参数矩阵为θ(1) θ(2) θ(3) 在Octave中我们可以设为 Theta1 Theta2 Theta3 类似的 这些梯度项gradient 也是需要得到的返回值 那么在之前的视频中 我们演示了如何计算 这些梯度矩阵 它们是D(1) D(2) D(3) 在Octave中 我们用矩阵D1 D2 D3来表示 在这节视频中 我想很快地向你介绍 怎样取出这些矩阵 并且将它们展开成向量 以便它们最终 成为恰当的格式 能够传入这里的Theta 并且得到正确的梯度返回值gradient 具体来说 假设我们有这样一个神经网络 其输入层有10个输入单元 隐藏层有10个单元 最后的输出层 只有一个输出单元 因此s1等于第一层的单元数 s2等于第二层的单元数 s3等于第三层的 单元个数 在这种情况下 矩阵θ的维度 和矩阵D的维度 将由这些表达式确定 比如说 θ(1)是一个10×11的矩阵 以此类推 因此 在Octave中 如果你想将这些矩阵 转化为向量 那么你要做的 是取出你的Theta1 Theta2 Theta3 然后使用这段代码 这段代码将取出 三个θ矩阵中的所有元素 也就是说取出Theta1 的所有元素 Theta2的所有元素 Theta3的所有元素 然后把它们全部展开 成为一个很长的向量 也就是thetaVec 同样的 第二段代码 将取出D矩阵的所有元素 然后展开 成为一个长向量 被叫做DVec 最后 如果你想从向量表达 返回到矩阵表达式的话 你要做的是 比如想再得到Theta1 那么取thetaVec 抽出前110个元素 因此 Theta1就有110个元素 因为它应该是一个10×11的矩阵 所以 抽出前110个元素 然后你就可以 reshape矩阵变维命令来重新得到Theta1 同样类似的 要重新得到Theta2矩阵 你需要抽出下一组110个元素并且重新组合 然后对于Theta3 你需要抽出最后11个元素 然后执行reshape命令 重新得到Theta3 以下是这一过程的Octave演示 对于这一个例子 让我们假设Theta1 为一个10×11的单位矩阵 因此它每一项都为1 为了更易看清 让我们把Theta2设为 一个10行11列矩阵 每个元素都为2 然后设Theta3 是一个1×11的矩阵 每个元素都为3 因此 这样我们得到三个独立的矩阵 Theta1 Theta2 Theta3 现在我们想把所有这些矩阵变成一个向量 thetaVec = [Theta1(:); Theta2(:); Theta3(:)]; 好的 注意中间有冒号 像这样 现在thetaVec矩阵 就变成了一个很长的向量 含有231个元素 如果把它打出来 我们就能看出它是一个很长的向量 包括第一个矩阵的所有元素 第二个矩阵的所有元素 以及第三个矩阵的所有元素 如果我想重新得到 我最初的三个矩阵 我可以对thetaVec使用reshape命令 抽出前110个元素 将它们重组为一个10×11的矩阵 这样我又再次得到了Theta1矩阵 然后我再取出 接下来的110个元素 也就是111到220号元素 我就又重组还原了第二个矩阵 最后 再抽出221到最后一个元素 也就是第231个元素 然后重组为1×11的矩阵 我就又得到了Theta3矩阵 为了使这个过程更形象 下面我们来看怎样将这一方法 应用于我们的学习算法 假设说你有一些 初始参数值 θ(1) θ(2) θ(3) 我们要做的是 取出这些参数并且将它们 展开为一个长向量 我们称之为initialTheta 然后作为theta参数的初始设置 传入函数fminunc 我们要做的另一件事是执行代价函数costFunction 实现算法如下 代价函数costFunction 将传入参数thetaVec 这也是包含 我所有参数的向量 是将所有的参数展开成一个向量的形式 因此我要做的第一件事是 我要使用 thetaVec和重组函数reshape 因此我要抽出thetaVec中的元素 然后重组 以得到我的初始参数矩阵 θ(1) θ(2) θ(3) 所以这些是我需要得到的矩阵 因此 这样我就有了 一个使用这些矩阵的 更方便的形式 这样我就能执行前向传播 和反向传播 来计算出导数 以求得代价函数的J(θ) 最后 我可以取出这些导数值 然后展开它们 让它们保持和我展开的θ值 同样的顺序 我要展开D1 D2 D3 来得到gradientVec 这个值可由我的代价函数返回 它可以以一个向量的形式返回这些导数值 现在 我想 对怎样进行参数的矩阵表达式 和向量表达式 之间的转换 有了一个更清晰的认识 使用矩阵表达式 的好处是 当你的参数以矩阵的形式储存时 你在进行正向传播 和反向传播时 你会觉得更加方便 当你将参数储存为矩阵时 一大好处是 充分利用了向量化的实现过程 相反地 向量表达式的优点是 如果你有像thetaVec或者DVec这样的矩阵 当你使用一些高级的优化算法时 这些算法通常要求 你所有的参数 都要展开成一个长向量的形式 希望通过我们刚才介绍的内容 你能够根据需要 更加轻松地 在两种形式之间转换

在之前,我们讨论了如何使用前向传播和反向传播计算神经网络中的导数,但反向传播作为一个有很多细节的算法,在实现的时候会有点复杂,而且有一个不好的方面是,在实现反向传播时,会遇到很多细小的错误,所以如果你把它和梯度下降法或者其他优化算法一起运行时,可能看起来它运行正常,并且你的代价函数J最后可能在每次梯度下降法迭代时都会减小,即使在实现反向传播时有一些小错误,可能也会检查不出来,所以它看起来是J(θ)在减小,但是可能你最后得到的神经网络误差比没有错误的要高,而且你很可能就是不知道你的结果是这些小错误导致的,那你应该怎么办呢?有一个想法叫梯度检验(Gradient Checking),可以解决基本所有的问题,我现在每次实现神经网络的反向传播或者类似的梯度下降算法,或者其他比较复杂的模型,我都会使用梯度检验,如果你这么做,它会帮你确定并且能很确信你实现的前向传播和反向传播,或者其他的什么是100%正确的,我见过很多这样解决那些实现时容易有小错误的问题,在之前,我一般是让你相信我给出的计算δ,D项等等之类的公式,我要求你们相信他们计算的就是代价函数的梯度,但一旦你们实现数值梯度检验,也就是这节的主题,你就能够自己验证你写的代码确实是在计算代价函数J的导数。想法是这样的,考虑下面这个例子:

捕获13

假如我有一个函数J(θ),并且我有个值θ,我假定θ只是一个实数,假如说我想估计这个函数在这一点的导数,这个导数等于这条切线的斜率,下面我要用数值方法来计算近似的导数,这个是用数值方法计算近似导数的过程:我要计算θ+ε这个在右边一点的值,然后计算θ-ε这个在左边一点的值,然后我要把这两个点用一条直线连起来,然后用这条红色线的斜率来作为我导数的近似值,真正的导数是这边这条蓝色线的斜率,这看起来是个不错的近似。在数学上,这条红线的斜率等于这个垂直的高度除以这个水平的宽度,所以上面这点是J(θ+ε),这点是J(Θ-ε),垂直方向上的差是J(θ+ε)-J(θ+ε),也就是说水平的距离就是2ε,那么,J(θ)对θ的导数近似等于J(θ+ε)-J(θ-ε)除以2ε,通常,我给ε取很小的值,比如可能取10的-4次方,ε的取值在一个很大范围内都是可行的,实际上,如果你让ε非常小,那么数学上,这里这项实际上就是导数,就变成了函数在这点上准确的斜率,只是我们不想用非常非常小的ε,因为可能会产生数值问题,所以我通常让ε差不多等于10^-4,顺便说一下,可能你们有些学习者见过另外这种估计导数的公式,右边这个叫做单侧拆分,左边这个公式叫做双侧差分,双侧差分给我们了一个稍微精确些的估计,所以我通常用那个,而不用这个单侧差分估计。具体地说,你在Octave中实现时,你的程序要调用gradApprox来计算,这个函数会通过这个公式J(θ+ε)-J(θ-ε)除以2ε,它会给出这点导数的数值估计。

gradApprox = (J(theta + EPSILON) – J(theta – EPSILON))/(2*EPSILON)

我们考虑了θ是一个实数的情况,现在我们看更普遍的情况:θ是一个向量参数,假如说θ是n维向量,它可能是我们的神经网络参数的展开形式,所以θ是一个有n个元素的向量,θ1到θn,我们可以用类似的想法来估计所有的偏导数项,具体地说,代价函数对第一个参数θ1取偏导数,它可以用J和增大的θ1得到,所以你有J(θ1+ε)等等减去J(θ1-ε),然后除以2ε,对第二个参数θ2取偏导数,还是这样,除了你要对θ2+ε取J,这里还有θ2-ε,这样计算后面的偏导数直到θn,它的算法是对θn增加和减少ε,这些公式给出一个计算J对任意参数求偏导数的数值近似的方法。

图片29

具体地说,你要实现的是下面这个:

for i = 1:n,
    thetaPlus = theta;
    thetaPlus(i) = thetaPlus(i) + EPSILON;
    thetaMinus = theta;
    thetaMinus(i) = thetaMinus(i) – EPSILON;
    gradApprox(i) = (J(thetaPlus) – J(thetaMinus)) / (2*EPSILON);
end;

#Check that gradApprox ≈ DVec

我们把这个用在Octave里来计算数值导数,假如i等于1到n,n是我们的参数向量θ的维度,我通常用参数的展开形式来计算,你知道θ只是我们神经网络模型的一长列参数,我让thetaPlus等于theta,然后给thetaPlus的第i项加上EPSILON,这就是基本的,thetaPlus等于theta,除了thetaPlus(i),它会增加EPSILON,所以如果thetaPlus等于θ1 θ2等等,那么θi增加了EPSILON,然后一直到θn,这就是thetaPlus的作用。类似的,这两行给thetaMinus类似地赋值,只是θi不是加EPSILON,而是减EPSILON,最后,你运行这个gradApprox(i),它会给你近似的J(θ)对θi的偏导数。我们实现神经网络时是这样用的:我们要实现这个用for循环来计算代价函数对每个网络中的参数的偏导数,然后我们用从反向传播得到的梯度,DVec是我们从反向传播中得到的导数,所以后向传播是一个相对比较有效率的计算代价函数对参数的导数或偏导数的方法,接下来,我通常做的是计算数值导数,就是gradApprox,我们刚从上面这里得到的,来确定它等于或者近似于,差距很小,非常接近我们从反向传播得到的DVec,如果这两种计算导数的方法给你相同的结果,或者非常接近结果,最多几位小数的差距,那么我就非常确信我实现的反向传播是正确的,然后我把这些DVec向量用在梯度下降法或者其他高级优化算法里,我就可以比较确信我计算的导数是正确的,那么,我的代码应该也可以正确运行,可以很好地优化J(θ)。最后,我想把所有的东西放在一起,然后告诉你怎么实现这个数值梯度检验,我通常做的第一件事是,实现反向传播来计算DVec,这个步骤是我们之前讲过的,计算DVec,它可能是这些D(1) D(2) D(3)矩阵的展开形式;然后我要做的是,用gradApprox实现数值梯度检验,这是我在这节讲的;然后你要确定DVec和gradApprox给出接近的结果,可能最多差几位小数;最后,这是最重要的一步,在使用你的代码去学习、训练你的网络之前,重要的是要关掉梯度检验,不再使用这个数值导数公式来计算gradApprox,这样做的原因是,我们之前讲的这个数值梯度检验代码,是一个计算量非常大的程序,它是一个非常慢的计算近似导数的方法,而相对地,我们之前讲的反向传播算法,也就是那个DVec的D(1) D(2) D(3)的算法,反向传播是一个在计算导数上效率更高的方法,所以当你确认了你的反向传播算法是正确的,你应该关掉梯度检验,就是不使用它。再重申一下,在为了训练分类器运行你的算法,做很多次梯度下降或高级优化算法的迭代之前,要确定你不再使用梯度检验的程序,具体来说,如果你在每次的梯度下降法迭代时,都运行数值梯度检验,或者你用在代价函数的内循环里,你的程序会变得非常慢,因为数值梯度检验程序比反向传播算法要慢很多,反向传播算法就是我们计算δ(4) δ(3) δ(2)等等的,那就是反向传播算法,那是一个比梯度检验更快的计算导数的方法,所以当你准备好了,一旦你验证了反向传播的实现是正确的,要确定你在训练算法时把它关闭了,或者说不再使用梯度检验程序,否则你的程序会运行得非常慢,所以如果你计算用数值方法计算导数,那是你用来确定反向传播实现是否正确的的方法,当我实现反向传播或者类似的复杂模型的梯度下降算法,我经常使用梯度检验,这的确能帮我确定我的代码是正确的。

我们总结了在神经网络的实现和训练中所有需要的知识,最后一个我想要分享给你们的内容,就是随机初始化的思想。当你运行一个算法,例如梯度下降算法或者其他高级优化算法时,我们需要给变量θ一些初始值,现在让我们考虑梯度下降,同样我们需要把θ初始化成一些值,接下来使用梯度下降方法,慢慢地执行这些步骤使θ的函数J下降到最小,那么θ的初始值该设置为多少呢?是否可以将θ的初始值设为全部是0的向量,虽然说在逻辑回归时,初始化所有变量为0是可行的,但在训练神经网络时这样做是不可行的。以训练这个神经网络为例,照之前所说将所有变量初始化为0:

捕获14

如果是这样的话,具体来说,就是当初始化这条蓝色权重,使这条被涂为蓝色的权重等于那条蓝色的权重,他们都是0,这条被涂上红色的权重同样等于被涂上红色的那条权重,同样这个被涂成绿色的权重也一样等于那条绿色的权重,那么这就意味着这两个隐藏单元a1 a2是两个相同的关于输入的函数,这样一来,对每个样本进行训练,最后a(2)1与a(2)2结果必然相等,更多的原因我就不详细讲述了,而由于这些权重相同,同样可以证明这些δ值也相同,具体地说,δ(2)1=δ(2)2,同时,如果你更深入地挖掘一下,你不难得出,这些变量对参数的偏导数满足以下条件,也就是代价函数的偏导数互为相等,我用这两条蓝色的权重为例,这也就意味着一旦更新梯度下降方法,第一个蓝色权重也会更新,等于学习率乘以这个式子,第二条蓝色权重更新为,学习率乘上这个式子,但是,这就意味着一旦更新梯度下降,这两条蓝色权重的值在最后将互为相等,因此即使权重现在不都为0,但参数的值最后也互为相等,同样地,即使更新一个梯度下降,这条红色的权重也会等于这条红色的权重,也许会有些非0的值,但两条红色的值会互为相等,同样两条绿色的权重开始它们有不同的值,最后这两个权重也会互为相等,所以每次更新后,两个隐藏单元的输入对应的参数将是相同的,这只是说,两条绿色的权重将一直相同,两条红色的权重将一直相同,两条蓝色的权重仍然相同,这就意味着即使经过一次梯度下降的循环后,你们会发现两个隐藏单元仍然是两个完全相同的输入函数,因此a(2)1仍然等于a(2)2,一直持续运行梯度下降,这两条蓝色的权重仍然相同,两条红色的权重以及两条绿色的权重也是同样的情况,这也就意味着这个神经网络的确不能计算更有价值的东西,想象一下,不止有两个隐藏单元,而是有很多很多的隐藏单元,这就是说,所有的隐藏单元都在计算相同的特征,所有的隐藏单元都通过完全相同的输入函数计算出来,这是完全多余的表达,因为这意味着最后的逻辑回归单元只会得到一种特征,因为所有的逻辑回归单元都一样,这样便阻止了神经网络学习出更有价值的信息。为了解决这个问题,神经网络变量初始化的方式采用随机初始化,具体地说,所有权重相同的问题有时被我们也称为对称权重,所以随机初始化解决的就是如何打破这种对称性,所以我们需要做的是,对θ的每个值进行初始化,范围在-ɛ到+ɛ之间,因此,变量的权重通常初始化为-ɛ到+ɛ之间的任意一个数,我在Octave里编写了这样的代码:

Theta1 = rand(10, 11) * (2*INIT_EPSILON) – INIT_EPSILON;

我之前讲过的Theta1等于这个等式,这个rand就是用来得出一个任意的10×11维矩阵,矩阵中的所有值都介于0到1之间,因此,如果取0到1之间的一个数和2ε相乘,再减去ε,然后得到一个在-ε到+ε的数,顺便说一句,这里的这个ε与在进行梯度检查中用的不是一回事,在进行数值梯度检查时,会加一些ε值给θ,这些值与这里的ε没有关系,这就是为什么我要在这里用INIT_EPSILON表示,仅仅是为了区分在梯度检查中使用的EPSILON值。总结来说,为了训练神经网络,应该对权重进行随机初始化,初始化为-ε到+ε间,接近于0的小数,然后进行反向传播,执行梯度检查,使用梯度下降或者使用高级的优化算法,试着使代价函数J达到最小,从某个随机选取的参数θ开始,通过打破对称性的过程,我们希望梯度下降或者其他高级优化算法,可以找到θ的最优值。

我们已经用了几节的内容来介绍神经网络算法,在这段中,我想结合我们所讲的所有这些内容,来做一个总体的回顾,看看这些零散的内容相互之间有怎样的联系,以及神经网络学习算法的总体实现过程。当我们在训练一个神经网络时,我们要做的第一件事,就是搭建网络的大体框架,这里我说的框架意思是神经元之间的连接模式,我们可能会从以下几种结构中选择:第一种神经网络的结构是,包含三个输入单元,五个隐藏单元和四个输出单元;第二种结构是,三个输入单元作为输入层,两组五个隐藏单元作为隐藏层,四个输出单元的输出层;然后第三种是,三个输入单元作为输入层,三组五个隐藏单元作为隐藏层,然后是四个输出单元。这些就是可能选择的结构,每一层可以选择多少个隐藏单元,以及可以选择多少个隐藏层,这些都是你构建时的选择,那么我们该如何做出选择呢?首先,我们知道我们已经定义了输入单元的数量,一旦你确定了特征集x,对应的输入单元数目也就确定了,也就是等于特征x{i}的维度;如果你正在进行多类别分类,那么输出层的单元数目将会由你分类问题中所要区分的类别个数确定,值得提醒的是,如果你的多元分类问题y的取值范围是在1到10之间,那么你就有10个可能的分类,别忘了把你的y重新写成向量的形式,所以,如果要表达第五个分类,也就是说y等于5,那么在你的神经网络中,就不能直接用数值5来表达,因为这里的输出层有十个输出单元,你应该用一个向量来表示,这个向量的第五个位置值是1,其它的都是0。对于输入单元和输出单元数目的选择,还是比较容易理解的,而对于隐藏单元的个数以及隐藏层的数目,我们有一个默认的规则,那就是只使用单个隐藏层,所以这种只有一个隐藏层的神经网络,一般来说是最普遍的,或者如果你使用不止一个隐藏层的话,同样我们也有一个默认规则,那就是每一个隐藏层通常都应有相同的单元数,而对于隐藏单元的个数,通常情况下隐藏单元越多越好,不过我们需要注意的是,如果有大量隐藏单元,计算量一般会比较大,当然一般来说,隐藏单元还是越多越好,并且一般来说,每个隐藏层所包含的单元数量还应该和输入x的维度相匹配,也要和特征的数目匹配,可能隐藏单元的数目和输入特征的数量相同,或者是它的二倍,或者三倍,四倍,因此隐藏单元的数目需要和其他参数相匹配,一般来说,隐藏单元的数目取为稍大于输入特征数目都是可以接受的。下面我们就来具体介绍,如何实现神经网络的训练过程,这里一共有六个步骤:首先第一步是,构建一个神经网络,然后随机初始化权值,通常我们把权值初始化为很小的值,接近于零;然后我们执行前向传播算法,也就是对于该神经网络的任意一个输入x(i),计算出对应的h(x)值,也就是一个输出值y的向量;接下来我们通过代码计算出代价函数J(θ);然后我们执行反向传播算法,来算出这些偏导数或偏微分项,也就是J(θ)关于参数θ的偏微分;具体来说,我们要对所有训练集数据,使用一个for循环进行遍历,对每一个训练样本进行迭代,从x(1) y(1)开始,我们对第一个样本进行前向传播运算和反向传播运算,然后在第二次循环中同样地对第二个样本执行前向传播和反向传播算法,以此类推,直到最后一个样本,其实实际上,有复杂的方法可以实现,并不一定要使用for循环,但我非常不推荐在第一次实现反向传播算法的时候,使用更复杂更高级的方法。所以具体来说就是,我们把x(i)传到输入层,然后执行前向传播和反向传播,这样我们就能得到,该神经网络中每一层中每一个单元对应的所有这些激励值a(l)和delta项,接下来,我们要计算出这些delta值,也就是用我们之前给出的公式:大delta(l)等于大delta(l)加上delta(l+1)乘以a(l)的转置矩阵,最后在循环的外面,计算出的这些delta值,我们将用别的程序来计算出这些偏导数项,那么这些偏导数项也应该考虑使用正则化项lambda值,这些公式在前面已经给出,那么搞定所有这些内容,现在你就应该已经得到了计算这些偏导数项的程序了;下面就是第五步了,我要做的就是使用梯度检查,来比较这些已经计算得到的偏导数项,把用反向传播算法得到的偏导数值与用数值方法得到的估计值进行比较,因此通过进行梯度检查,来确保两种方法得到基本接近的两个值,通过梯度检查,我们能确保我们的反向传播算法得到的结果是正确的,但必须要说明的一点是,我们需要去掉梯度检查的代码,因为梯度检查的计算非常慢;最后,我们就可以使用一个最优化算法,比如说梯度下降算法或者说是更加高级的优化方法,比如说BFGS算法、共轭梯度法,或者其他一些已经内置到fminunc函数中的方法,将所有这些优化方法和反向传播算法相结合,这样我们就能计算出这些偏导数项的值。到现在,我们已经知道了如何去计算代价函数,我们知道了如何使用反向传播算法来计算偏导数,那么我们就能使用某个最优化方法,来最小化关于theta的函数值代价函数J(θ)。另外顺便提一下,对于神经网络,代价函数J(θ)是一个非凸函数,因此理论上是能够停留在局部最小值的位置,实际上,梯度下降算法和其他一些高级优化方法,理论上都能收敛于局部最小值,但一般来讲,这个问题其实并不是什么要紧的事,尽管我们不能保证这些优化算法一定会得到全局最优值,但通常来讲,像梯度下降这类的算法,在最小化代价函数 J(θ)的过程中,还是表现得很不错的,通常能够得到一个很小的局部最小值,尽管这可能不一定是全局最优值。最后,梯度下降算法似乎对于神经网络来说还是比较神秘,希望下面这幅图能让你对梯度下降法在神经网络中的应用产生一个更直观的理解:

捕获15

这实际上有点类似我们早先时候解释梯度下降时的思路,我们有某个代价函数,并且在我们的神经网络中有一系列参数值,这里我只写下了两个参数值,当然实际上在神经网络里,我们可以有很多的参数值theta1 theta2等等,所有的这些都是矩阵,因此我们参数的维度就会很高了,由于绘图所限,我们不能绘出更高维度情况的图像,所以这里我们假设这个神经网络中只有两个参数值,实际上应该有更多参数,那么代价函数J(θ)度量的就是,这个神经网络对训练数据的拟合情况,所以如果你取某个参数,比如说这个下面这点,在这个点上J(θ)的值是非常小的,这一点的位置所对应的参数theta的情况是,对于大部分的训练集数据,我的假设函数的输出会非常接近于y(i),因此对应的是神经网络对训练集数据拟合得比较好的情况,而反过来,如果我们取这个值,也就是这个点对应的值,那么对于大部分的训练集样本,该神经网络的输出应该是远离y(i)的实际值的,也就是我们在训练集观测到的输出值,因此像这样的点,右边的这个点,对应着对训练集拟合得不好的情况,因此梯度下降算法的原理是,我们从某个随机的初始点开始,比如这一点,它将会不停的往下下降,那么反向传播算法的目的就是,算出梯度下降的方向,而梯度下降的过程,就是沿着这个方向一点点的下降,一直到我们希望得到的点,在这里我们希望找到的就是局部最优点,所以当你在执行反向传播算法并且使用梯度下降,或者更高级的优化方法时,这幅图片很好地帮你解释了基本的原理,也就是试图找到某个最优的参数值,这个值使得我们神经网络的输出值与y(i)的实际值,也就是训练集的输出观测值,尽可能的接近。



发表评论

电子邮件地址不会被公开。 必填项已用*标注