Stanford-Andrew Ng《Machine Learning》week5

神经网络参数的反向传播算法

1. 代价函数

假设有一个与下图类似的神经网络结构,再假设有一个下面这样的训练集,其中有m组训练样本(x^{(i)},y^{(i)})

用大写字母L来表示这个神经网络结构的总层数,所以对于上图的神经网络结构,能够得出L= 4。接着用s_l来表示第L层的单元数,也就是第l层的神经元数量,这其中不包括第l层的偏差单元。举个例子,在上图中,输入层 s_1 有3个单元,s_2 有5个单元,输出层 s_4(也就是s_L,因为L等于4)有4个单元。

我们将会考虑两种分类问题,第一种是二元分类,y只能是0或是1。

在这种情况下,我们会有一个输出单元,也就是计算出来的h_\Theta(x),同时神经网络的输出结果h_\Theta(x)会是一个实数,在这种情况下,s_L是输出单元的个数,其中的L同样代表最后一层的序号,所以s_L=1,在这类问题里,为了简化记法,会把K设为1,这样你可以把K也当做输出层的单元数目。

要考虑的第二种分类问题是多类别分类问题,也就是说会有K个不同的类,之前的例子中,如果我们有4类的话,输出结果y就是四维向量,这类问题中会有K个输出单元,我们的假设h_\Theta(x)会输出K维向量,同时输出单元的个数s_L = K。

通常情况下,这类问题里 K≥3,因为如果只有两个类别,就不需要使用这种一对多的方法,只有当K≥3时,才会使用这种一对多的方法。因此,如果只有两个类别,我们就只需要一个输出单元就可以了。

我们在神经网络里使用的代价函数是逻辑回归中使用的代价函数的一般形式。

对于一个神经网络来说,我们的代价函数就会是这个式子的一般形式。

这里不再仅有一个逻辑回归输出单元,取而代之的是K个,所以下图就是我们的代价函数。

神经网络现在输出的是K维向量,如果处理的是二元分类问题,这里的K可能为1。(h_\Theta(x))_i 表示神经网络输出向量中的第i个元素。代价函数是-\frac{1}{m} 乘一个类似于在逻辑回归里所用的求和项,除了在这里求的k从1到K的所有和,这个求和项主要是K个输出单元之和。所以如果有4个输出单元,那么这个求和项就是求k等于从1到4的每一个逻辑回归算法的代价函数,然后按四次输出的顺序,依次把这些代价函数加起来。所以,你会特别注意到这个求和符号应用于y_k^{(i)}(h_\Theta(x^{(i)}))_k,因为我们主要是将第k个输出单元的值和y_k的值的大小作比较,y_k的值就是向量中的第k个元素。

最后,这里的第二项就是类似于在逻辑回归里所用的正则化项。

这个求和项看起来十分复杂,并且它总是对\Theta_j_i^{(l)}这些项求和,对所有的 i、j、l的值求和,正如在逻辑回归里一样,这里要除去那些对应于偏差的项,不对它们进行求和。具体而言,我们不对那些\Theta_j_i^{(l)}中i为0的项进行求和,这是因为,当我们计算神经元的激励值时,我们会有像 \Theta_i_0^{(2)}x_0 这样的项,这些含有0的项就对应是乘上了某个x_0或是a_0的项,因此这就是个有些类似于偏差单元的项,类比于在处理逻辑回归时所做的那样,我们就不会将这些项加到正则项中,因为我们并不想正则化这些项并把这些项设定为0。但这只是一个合理的约定,即使你真的将它们加进去了,也就是i从0加到s_l,这依然是有效的,并且不会有大的区别,不过可能这个不将偏差项正则化的规定会更常用一些。

2. 反向传播算法

下面将讲解让代价函数最小化的算法,具体来说将主要讲解反向传播算法。

这个是之前提到过的代价函数,我们要做的就是设法找到参数θ,使得J(Θ)取到最小值。为了使用梯度下降法,或者其他某种高级优化算法,我们需要做的就是写一段代码,这段代码获得输入参数Θ并计算J(Θ)和偏导项,如下图所示,这里的\Theta_j_i^{(l)}是实数。

为了计算代价函数J(Θ),我们可以利用上面代价函数的公式。

下面,我们从只有一个训练样本的情况开始说起,假设你的整个训练集只包含一个训练样本,也就是实数对(x,y),我们先粗略看一下使用这一个训练样本来计算的顺序。首先,我们应用前向传播方法来计算一下在给定输入的时候,假设函数是否会真的输出结果。

具体来说,这里的a^{(1)}就是第一层的激活值,也就是输入层所在位置,所以假设它为x,接着我们计算z^{(2)}=\Theta^{(1)}a^{(1)},然后a^{(2)}=g(z^{(2)}),其中g是一个sigmoid激活函数,这就会计算出第一个隐藏层的激活值,也就是神经网络的第二层的激活值,我们还给第二层增加一个偏置项。接下来,再用两次前向传播来计算出a^{(3)}和最后的a^{(4)}。这里就实现了把前向传播向量化,这使得我们可以计算神经网络结构里的每一个神经元的激活值。

接下来,为了计算导数项,我们将采用一种叫做反向传播的算法。

反向传播算法从直观上说就是对每一个节点,我们计算这样一项 \delta_j^l,这表示了第l层的第j个节点的误差,我们知道a_j^{(l)}表示的是第l层第j个单元的激活值,所以这个 \delta_j^l 在某种程度上,就捕捉到了我们在这个神经节点的激活值的误差。所以,我们可能希望这个节点的激活值稍微不一样。具体地讲,我们用下面这个有四层的神经网络结构作为例子,所以这里L= 4。

对于每一个输入单元,我们将计算\delta_j^l ,所以第4层的第j个单元的\delta就等于这个单元的激活值减去训练样本里的真实值。所以a_j^{(4)}同样可以写成(h_\Theta(x))_j,所以\delta这一项就是假设的输出值和训练集y值之间的差,这里y的下标j 就是训练集里向量y的第j个元素的值。顺便提一下,如果你把\delta、a和y这三个都看做向量,那么你同样可以这样写并且得出一个向量化的表达式。也就是\delta^{(4)}=a^{(4)}-y  这里的每一个值都是一个向量,并且向量维数等于输出单元的数目。

所以现在我们计算出网络结构的误差项\delta^{(4)},我们下一步就是计算网络中前面几层的误差项\delta

下面这个就是计算\delta^{(3)}的公式

这里的上标T表示转置,然后这里的 .* 表示的是矩阵(或向量)的对应元素相乘,g{'}(z^{(3)})其实是对激活函数g在输入值为z^{(3)}的时候所求的导数,如果你会微积分的话,你可以试着自己解出来再简化式子,就可以得到 g{'}(z^{(3)})=a^{(3)}.*(1-a^{(3)}),这里的a^{(3)}是激活向量,1是以1为元素的向量。

接下来,应用一个相似的公式来计算\delta^{(2)}

同样地,g{'}(z^{(2)})=a^{(2)}.*(1-a^{(2)}),这里的导数是偏导数。到这就结束了,这里没有\delta^{(1)},因为\delta^{(1)}对应输入层,那只是表示我们在训练集观察到的,所以不会存在误差,也就是说,我们是不想改变这些值的,所以这个例子中,我们的\delta项就只有第二、三、四层。

反向传播算法这个名字源于我们从输出层开始计算\delta项,然后我们返回到上一层计算第三层(隐藏层)的\delta项,接着我们再往前一步来计算\delta^{(2)}。所以说,我们是类似于把输出层的误差反向传播给了第3层,然后再传到第2层,这就是反向传播的意思。

最后,这个推导过程是出奇的麻烦和出奇的复杂,但是如果你按照上面几个步骤来计算,就有可能简单直接地完成复杂的数学证明,如果不是在特别严谨的情况下,那么可以证明我们要求的偏导数项恰好就等于

这里我们忽略了λ或者说标准化项,如果λ是等于0的。

现在把所有内容整合在一起,然后说说如何实现反向传播算法来计算这些参数的偏导数,当我们有一个非常大的训练样本时,而不是像上面例子里这样的一个训练样本。

假设有m个样本的训练集

我们要做的第一件事就是固定这些带下标i,j的Δ。这个三角形符号其实就是大写的希腊字母\delta,之前写的符号是小写。

设每一个i和j对应的Δ等于0,实际上这些 \Delta_i_j^{(l)} 会被用来计算偏导项,对应 \Theta_i_j^{(l)} 的偏导项。所以这些\Delta_i_j^{(l)} 会被作为累加项,慢慢地增加以算出这些偏导数。

接下来我们将遍历我们的训练集,从1到m,对于第 i 次循环而言,我们将取训练样本(x^{(i)},y^{(i)}),于是我们要做的第一件事是设定a^{(1)},也就是输入层的激活函数,设定它等于x^{(i)}x^{(i)}是第i个训练样本的输入值,接下来运用正向传播来计算第二层的激活值,然后是第三层,第四层,一直这样直到最后一层L层,接下来,我们将用样本的输出值y^{(i)}来计算这个输出值所对应的误差项\delta^{(L)}\delta^{(L)} 就是假设的输出值减去目标输出。接下来,我们将运用反向传播算法来计算\delta^{(L-1)}\delta^{(L-2)},一直这样直到\delta^{(2)}再强调一下,这里没有\delta^{(1)},因为我们不需要对输入层考虑误差项。最后,我们将用这些大写的Δ来累计我们在前面写好的偏导数项。

顺便说一下,观察一下这个式子,你可以把它写成向量形式。具体地说,如果你把\Delta_i_j看作一个矩阵,(i,j)代表矩阵中的位置,那么如果\Delta^{(L)}是一个矩阵,我们就可以写成\Delta^{(L)}:=\Delta^{(L)}+\delta^{(L+1)}(a^{(l)})^{T},这就是用向量化的形式实现了对所有i和j的自动更新值。

最后,执行完这个for循环体之后,我们跳出这个for循环,然后计算下面这些式子

按照上面的公式计算D,对于j=0和j≠0分两种情况讨论,在j=0的情况下对应偏差项,所以当j=0时,这就是为什么没有写额外的标准化项。

最后,尽管严格的证明对于你来说太复杂,你现在可以做的是,一旦计算出来了上面的这些D项,它就正好是代价函数关于每个参数的偏导数,然后你可以使用梯度下降或者另一种高级优化算法。

3. 理解反向传播

很多人第一次看到反向传播这个算法时,第一印象通常是“哇,这个算法看起来好难,而且不同的步骤也很多,不知道该怎么联系这些步骤,它就像一个'黑箱',包含了各种复杂的步骤 ”,如果你也有这种感觉,实际上是很正常的,相比于线性回归和逻辑回归而言,反向传播算法从数学上讲并不是一个简洁的算法。

为了更好地理解反向传播,我们先进一步地研究一下前向传播的过程。

这是一个含两个输入单元的神经网络结构(偏置单元没有计算在内),然后第二层有两个隐藏单元,再下一层也有两个,最后一层只有一个输出单元。

为了更好地说明前向传播,下面用不同的颜色来表示这个网络,这里故意把神经网络中的节点都画成了很胖的椭圆形方便在里面写东西。

在进行前向传播时,我们可能有一些特定样本,比如(x^{(i)},y^{(i)}),我们把x^{(i)}传到输入层中,因此可以把它们表示为x_1^{(i)}x_2^{(i)},这是我们为输入层设置的值。当对其进行前向传播,传播到第一个隐藏层时,我们要计算出z_1^{(2)}z_2^{(2)},它们是输入单元的加权和,然后我们将sigmoid逻辑函数应用到z值上,得到这些激活值,也就是a_1^{(2)}a_2^{(2)},然后继续前向传播,计算出z_1^{(3)},再应用sigmoid逻辑函数得到a_1^{(3)},然后以此类推,最后得到z_1^{(4)},再应用sigmoid函数得到a_1^{(4)},它是网络中最后的输出值。

如果关注一下计算过程,比如隐藏单元z_1^{(3)},先标注出它对应的权值。

那么计算z_1^{(3)}的方法就是,z_1^{(3)}=\Theta_1_0^{(2)}*1+\Theta_1_1^{(2)}*a_1^{(2)}+\Theta_1_2^{(2)}*a_2^{(2)} ,这就是前向传播。

事实上,反向传播的过程和前向传播非常相似,只是这两个算法计算的反向不一样而已,在反向传播中,是从右往左进行运算。

为了更好地理解反向传播算法的过程,让我们先看看代价函数。

这个代价函数一般应用于只有一个输出单元的情况,如果有不止一个输出单元,只需用k作为输出单元的下标,然后对它们进行求和即可。我们同一组样本中同时使用了前向传播和反向传播算法,来关注一下这组单独的样本(x^{(i)},y^{(i)}),关注这种只有一个输出单元的情况,于是y^{(i)} 就是一个实数,我们忽略正则化,即λ=0,所以正则化项就没有了。

观察括号中这个求和项,会发现这项代价函数对应第i个训练样本,即代价函数对应的训练样本(x^{(i)},y^{(i)})是由cost(i)=y^{(i)}logh_\Theta(x^{(i)})+(1-y^{(i)})log(1-h_\Theta(x^{(i)}))这个式子给出的,所以第i个样本的代价函数可以写成这个式子。这个代价函数扮演了一个类似方差的角色,比起关注这个复杂的表达式,可以把cost(i)近似地当成是神经网络的输出值与实际值的方差。

就像逻辑回归中,实际中会偏向于选择比较复杂的,带对数形式的代价函数,但为了方便理解,可以把这个代价函数看作是某种方差函数。因此cost(i)表示了神经网络预测样本值的准确程度,也就是网络的输出值和实际观测值y^{(i)}的接近程度。

现在再看看反向传播的过程,一种直观的理解是反向传播算法就是在计算 \delta_j^{(l)}项,我们可以把它看作是我们在第l层的第j个单元中得到的激活项的误差。更正式地说,\delta项实际上是代价函数cost(i)关于z_j^{(l)}的偏导数,z_j^{(l)}也就是计算出的z项的加权和。或者说代价函数关于z项的偏导数。具体来说,这个代价函数是一个关于标签y^{(i)}和神经网络中h_\Theta(x^{(i)})的输出值的函数,如果分析网络的内部,稍微把z_j^{(l)}改一下,就会影响到神经网络的这些h_\Theta(x^{(i)})的值,最终将改变代价函数的值。

这些z_j^{(l)}衡量的是,为了影响这些中间值,我们想要改变神经网络中的权重的程度,进而影响整个神经网络的输出h(x),并影响所有的代价函数。

刚才这段关于偏导数的表述可能不太容易理解,如果听不懂的话也不用担心,因为接下来的部分并不需要了解偏导数。

让我们继续深入下去,继续了解反向传播的过程。对于输出层而言,如果我们设\delta,比如\delta_1^{(4)}=y^{(i)}-a_1^{(4)},这是一个误差值,也就是y的实际值与预测值的差,然后可以算出\delta_1^{(4)}的值,然后对这些值进行反向传播,然后可以计算出前一层的这些\delta项,也就是\delta_1^{(3)}\delta_2^{(3)},然后计算出\delta_1^{(2)}\delta_2^{(2)}

反向传播的计算过程和前向传播非常地相似,只是方向反了过来,下面来详细说一下,看一下是如何计算出\delta_2^{(2)}的值的。

要计算\delta_2^{(2)},与前向传播相似,先标出一对权重,然后再来看\delta_2^{(2)}是怎么计算的,我们要做的是用\delta_1^{(3)}和对应的权重相乘再加上\delta_2^{(3)}乘对应权重,实际上就是后一层中\delta项的加权和,由对应边的强度来进行加权。

\delta_2^{(2)}=\Theta_1_2^{(2)}\delta_1^{(3)}+\Theta_2_2^{(2)}\delta_2^{(3)} ,这就是计算\delta值的方法。顺带一提,这些\delta值都只关于隐藏单元,并不包括偏置单元,这取决于你对反向传播的定义以及你实现算法的方式,你也可以用其它方式来计算包含偏置单元的\delta值,偏置单元的输出总是"+1",并且它们一直都是这样,我们无法改变它,这些都取决于你对反向传播的实现。

4. 使用注意:展开参数

现在介绍一个细节的实现过程,怎样把参数从矩阵展开成向量,以便在高级最优化步骤中的使用需要,具体来讲,执行代价函数,输入参数是theta,函数返回代价值以及导数值。

然后可以将返回值传递给高级最优化算法fminunc,顺便提醒fminunc并不是唯一的算法,也可以使用别的优化算法。

但它们的功能都是取出这些输入值@costFunction以及theta值的一些初始值,并且这些程序都假定theta和这些theta初始值(initialTheta)都是参数向量,也许是n或者n+1维,但它们都是向量,同时假定这个代价函数的第二个返回值,也就是梯度值也是n维或者n+1维,所以它也是一个向量,这部分在我们使用逻辑回归的时候没有问题,但现在我们用神经网络,我们的参数不再是向量了,而是矩阵了。因此,对于一个完整的神经网络,我们的参数矩阵为\Theta^{(1)}\Theta^{(2)}\Theta^{(3)},在Octave中我们可以设为矩阵Theta1,Theta2,Theta3。

同样的梯度项gradient也是需要得到的返回值,它是由D^{(1)}D^{(2)}D^{(3)} 组成的,在Octave中我们用D1,D2,D3来表示。

下面将介绍怎样取出这些矩阵并且将它们展开成向量,以便它们最终成为恰当的格式,能够传入上面函数中的Theta并且得到梯度返回值gradient。

具体来说,假设我们有这样一个神经网络,其输入层有10个输入单元,隐藏层有10个单元,最后的输出层只有一个输出单元。

因此s_1等于第一层的单元数,s_2 等于第二层的单元数,s_3 等于第三层的单元个数。在这种情况下,矩阵Θ和矩阵D的维度如上图所示,比如说,\Theta^{(1)}是一个10x11的矩阵。因此,在Octave中,如果你想在矩阵和向量之间转化,那么你要做的是取出你的Theta1,Theta2,Theta3,用下面这段代码取出。

这段代码将取出三个θ矩阵中的所有元素,也就是说取出Theta1,Theta2,Theta3的所有元素,然后把它们全部展开,变成一个很长的向量,也就是thetaVec。

同样的下面这段代码将取出D矩阵的所有元素然后展开成一个长向量即Dvec。

最后,如果想从向量表达返回到矩阵表达式的话,比如想要得到Theta1,那么取thetaVec抽出前110个元素,所以Theta1就有110个元素,因为它是一个10x11的矩阵,所以抽出前110个元素,然后就能使用命令reshape来改变矩阵大小得到Theta1,同样地,要重新得到Theta2,需要抽出下一组110个元素并且重新组合,然后对于Theta3,需要抽出最后110个元素然后执行reshape命令得到Theta3。

以下是这一过程的Octave演示,对于这一个例子,让我们把Theta1设为一个10x11的全为1的矩阵。

然后把Theta2设为10*11的全为2的矩阵。

接着设Theta3为1*11的全为3的矩阵。

现在把所有这些矩阵变成一个向量。

现在thetaVec就变成了一个很长的向量,含有231个元素。

如果想重新得到原来的矩阵,可以对thetaVec使用reshape命令,抽出前110个元素,将它们重组为一个10x11的矩阵。

这样又再次得到了Theta1矩阵,然后再取出接下来的110个元素,也就是111到220元素,就又还原了第二个矩阵。

如果再抽出221到最后一个元素,也就是到第231个元素,然后重组为1x11的矩阵就又得到了Theta3矩阵。

为了使这个过程更具体,下面来看怎样将这一方法应用于我们的学习算法。

假设你有一些初始参数值\Theta^{(1)}\Theta^{(2)}\Theta^{(3)},我们要做的是取出这些参数,然后把它们展开为一个长向量,我们称之为初始的Theta值,然后作为theta参数的初始设置传入优化函数fminunc。

我们要做的另一件事是实现代价函数,代价函数实现算法如下:

代价函数会得到输入参数thetaVec,它包含了所有的参数向量,所有的参数都展开成一个向量形式。因此,要做的第一件事是,使用thetaVec和重组函数reshape,因此要抽出thetaVec中的元素,然后重组以得到初始参数矩阵\Theta^{(1)}\Theta^{(2)}\Theta^{(3)},这样就有了使用这些矩阵更方便的形式,这样就能执行前向传播和反向传播来计算出导数以及代价函数J(θ)。最后可以取出这些导数值,然后展开它们,让它们保持和展开的θ值同样的顺序,展开D^{(1)}D^{(2)}D^{(3)} 来得到gradientVec,这个值可由代价函数返回,它可以以一个向量的形式返回这些导数值。

总结一下,使用矩阵表达式的好处是当你的参数以矩阵形式存储时,在进行前向传播和反向传播时会觉得更加方便,当将参数存储为矩阵时也更容易充分利用向量化实现。相反地,向量表达式的优点是如果有像thetaVec或者DVec这样的矩阵,当使用一些高级的优化算法时,这些算法通常要求把所有的参数要展开成一个长向量的形式。

5. 梯度检测

反向传播算法含有许多细节,因此实现起来比较困难,并且它有一个不好的特性,很容易产生一些微妙的bug,当它与梯度下降或是其它算法一同工作时,看起来它确实能正常运行,并且代价函数J(θ)在每次迭代中也在不断减小,虽然在反向传播的实现中存在一些bug,但运行情况看起来确实不错。虽然J(θ)在不断减小,但是到了最后,得到的神经网络其误差将会比无bug的情况下高出一个量级,并且很可能不知道得到的结果是由bug所导致的。有一种思想叫做梯度检验,它能解决几乎所有这种问题。在神经网络或是其它复杂模型中实现反向传播,或者类似梯度下降算法时,如果做梯度检验,它将会完全保证前向传播以及后向传播都会是百分之百正确。这种问题的出现,绝大多数都和反向传播的错误实现有关。

考虑下面的例子,假设有一个代价函数J(θ),并且有这样一个θ值,在这个例子中,假设θ是一个实数,假如想估计函数在这一点上的导数,该点的导数就是图像在该点上切线的斜率,现在来从数值上逼近这点的导数。或者说这是一种从数值上来求近似导数的方法。

首先计算出θ+ε,它在θ的右边一点点,然后再计算出θ-ε,现在把这两个值对应的点用直线连起来,这条红线的斜率就是我们所求的该点导数的近似值,它真正的导数那条蓝色切线的斜率,看起来,这是一个不错的近似。数学上,这条红线的斜率等于该段的垂直高度除以该段的水平宽度,上面那个点的值是J(θ+ε),下面那个点的值是J(θ-ε),于是这两点的高度差就是J(θ+ε)-J(θ-ε),两点的宽度差就是2ε,所以该近似就是J(θ)关于θ的导数,约等于 \frac{J(\Theta +\epsilon) -J(\Theta -\varepsilon )}{2\varepsilon },通常来说ε是一个很小的值,比如说10^-^4这样的值,ε在很大范围内的各种取值都是可行的,实际上如果ε足够小,从数学上来讲,它就变成了该点真正的导数,成了函数在这一点上真实的斜率,但我们不会想去使用如此小的ε值,这样会引发很多数值问题,所以通常会给ε取10^-^4左右的值。可能有些人见过上图右侧那种用于估计导数的公式,这个公式叫单侧差分,而左边的叫双侧差分,双侧差分能得到更加准确的结果,所以相比单侧差分,通常都是用双侧差分。具体来说,需要在Octave中执行上图中的代码这样的运算来计算出该点导数的近似值,这样就会给出图像在该点的导数的近似数值,在这个例子中,这是个很不错的估计。

现在来考虑更普遍的情况,也就是当θ为向量参数的时候,设θ是一个n维向量,它可能是神经网络中参数的展开式。所以θ是一个有n个元素的向量,从\Theta_1\Theta_n

可以用类似的思想来估计所有的偏导数项。

具体来说,一个代价函数关于第一个参数\Theta_1的偏导数可以通过增加J中的\Theta_1得到,所以让\Theta_1加上ε,其余不变,后面部分让\Theta_1减去ε,最后除以2ε。关于第二个参数,\Theta_2的偏导数也是一样的,只是现在是让\Theta_2加上ε,后面是让\Theta_2减去ε,就像这样计算下去直到\Theta_n为止。这些等式让你能够从数值上去估算代价函数J所关于的任何参数的偏导数。

具体地,需要实现以下这些东西,这是在Octave里为了估算导数所要实现的东西。

假设i的值为1到n,这里的n是参数向量θ的维度,通常用它来计算展开形式的参数,因为θ只是神经网络中所有参数的集合。先让thetaPlus = theta,然后再让thetaPlus(i)加上EPSILON,这么做就是使所有的thetaPlus项都赋上theta值,除了thetaPlus(i),thetaPlus(i)还要加上ε。对应的,后面的两行代码是对thetaMinus进行类似的操作,只不过现在\Theta_i+\epsilon 变成了\Theta_i-\varepsilon,最后,计算出gradApprox(i)就能近似地得到J(θ)关于\Theta_i 在这一点的偏导数。

我们在神经网络中使用这种方法时运用了for循环来完成我们对神经网络中代价函数的所有参数的偏导数的计算,然后与我们在反向传播中得到的梯度进行比较。

DVec是从反向传播中得到的导数,接下来通常要做的是验证这个计算出的导数,gradApprox也就是我们刚才的计算结果,验证它是否相等于,或者说在数值上非常地接近于用反向传播所计算出的导数DVec,如果这两种方法所计算出来的导数是一样的,或者说非常接近,只有几位小数的差距,那么就可以更加确信反向传播的实现是正确的,当把向量DVec用到梯度下降中或者其它高级优化算法中时,就可以确信计算的导数是正确的,代码也能够正确地运行,并且能很好地优化J(θ)。

最后,把实现数值上的梯度检验的整个过程总结一下

第一件事是通过反向传播来计算DVec,Dvec可能是矩阵的展开式。之后要做的是实现数值上的梯度检验,计算出gradApprox。

之后需要确保DVec与gradApprox都能得出相似的值,确保它们只有几位小数的差距。最后,也是最重要的一步,在使用代码进行学习,或者说训练网络之前,重要的是要关掉梯度检验,不要再去计算导数gradApprox,这是因为梯度检验代码是一个计算量非常大的,也是非常慢的计算导数的程序,相对地,反向传播算法计算DVec的方法,它是一个高性能的计算导数的方法。所以,一旦通过检验确定反向传播的实现是正确的,就应该关掉梯度检验,不再去使用它,再重申一下,在运行算法之前,比如多次迭代的梯度下降,或是多次迭代来训练分类器的高级优化算法,先禁用梯度检验代码。具体地,如果在每次梯度下降迭代或者在每次代价函数的内循环里都运行一次梯度检验,程序就会变得非常慢,因为梯度检验代码比反向传播算法要慢很多,反向传播算法就是我们计算\delta^{(4)}\delta^{(3)}\delta^{(2)}等等的方法,它比梯度检验要快上很多。所以一旦验证了反向传播的实现是正确的,就应当在训练之前关闭梯度检验,或者禁用梯度检验代码,否则程序将会变得非常慢。

6. 随机初始化

当执行一个算法时,例如梯度下降法或者高级优化算法时,我们需要为变量θ选取一些初始值,对于高级优化算法,它默认你会为变量θ(theta)提供一些初始值。

对于梯度下降法,同样地,我们也需要对θ进行初始化,初始化完毕以后,就可以一步一步通过梯度下降来最小化代价函数J(θ),那么应该如何对θ设初始值呢?

有一种想法是,将θ的初始值全部设为0。

尽管在逻辑回归中这样做是被允许的,但实际上在训练网络时,将所有参数初始化为0,起不到任何作用。

以这个神经网络的训练为例,假如我们将网络中所有参数都初始化为0,这样的话,就意味着在这个例子中这些蓝色、红色和绿色直线所代表的权重都等于0,这就意味着,隐藏单元a_1^{(2)}a_2^{(2)} 都是以同一输入函数来计算的,于是,对于神经网络中所有的训练样本,最后总能得到a_1^{(2)} = a_2^{(2)},具体的推理过程就不再细说了,由于这些权重都是相等的,就可以得出这些\delta值也都相同,具体地说,会得到\delta_1^{(2)}\delta_2^{(2)},关于这些参数的偏导数将会满足下列条件 ,

神经网络中关于这两条蓝色权重的偏导数,这两个偏导数是相等的,这意味着,即使在每一次的迭代下降更新中,第一条蓝色的权重会被更新为学习率乘上图等式左边的式子,第二条蓝色的权重会被更新为学习率乘上图等式右边的式子,这就意味着,当对这两条蓝色的权重进行梯度下降更新,最后这两个蓝色的参数将会相等,现在它们都不为0了,当它们一定是相等的,同样地,就算再进行一次梯度下降,这两个参数值仍然相等,而且它们都不为0,那两条红色的值也一样,也是相等的,同样地,两条绿色的权重,它们同时发生改变,最后也都同时相等。所以每次更新之后,这两个隐藏单元的每个参数输入都是相等的,也就是说蓝色、红色以及绿色的权重依然相等,这意味着即使梯度下降进行了一次迭代,但这两个隐藏单元依然以相同的函数作为输入来计算,因此a_1^{(2)} 仍然等于 a_2^{(2)}。在这个例子中,如果继续进行梯度下降,两条蓝色、红色、绿色的权重将会继续相等,这就意味着这个神经网络计算不出什么有趣的函数,想象一下你有不止两个隐藏单元,而是有很多很多的隐藏单元,这也就是说所有的隐藏单元都在计算相同的特征,所有的隐藏单元都以相同的函数作为输入,这是一种高度冗余的现象,因此这意味着最后的逻辑回归单元只能得到一个特征,因为所有的单元都一样,这种情况阻止了神经网络去学习任何有趣的东西。

为了解决这个问题,在神经网络中对参数进行初始化时,要使用随机初始化的思想。

具体来说,刚才我们所见到的问题有时被称作是对称权重问题,也就是所有的权重都是一样的,所以随机初始化就是解决这种对称问题的方法,因此对每一个θ值将其初始化为一个范围在-ε到ε之间的随机值,因此这些参数的权重将会被随机初始化为在-ε到ε之间的某个数,在Octave中可以通过下面这样的代码实现。

rand(10,11)就会生成一个10x11的随机矩阵,矩阵中的值都介于0到1之间,它们都是实数,是0到1之间的连续值。然后这些0到1之间的数字会与2ε相乘再减去ε,最后得到一个数值在-ε到ε之间的数。顺便一提,这里的ε和我们之前在梯度检验中提到的ε没有任何关系,当我们进行数值梯度检验时会将一些ε值赋给θ,但这些值和这里的ε没关联。

总而言之,为了训练神经网络,应首先要将权重随机初始化为一个接近0的范围在-ε到ε之间的数,然后进行反向传播,再进行梯度检验,最后使用梯度下降或者其他高级优化算法来最小化代价函数J(θ),整个过程从为参数选取一个随机初始化的值开始,这是一种打破对称性的流程,随后,通过梯度下降或者高级优化算法就能计算出θ的最优值。

7. 组合到一起

在训练一个神经网络时,我们要做的第一件事就是选择一种网络架构,这里说的架构意思是神经元之间的连接模式,我们可能会从以下几种架构中选择。

你可以选择每一层有多少个隐藏单元以及有多少个隐藏层,这些都是构建时的选择。那么该如何做出选择呢?首先我们已经定义了输入单元的数量,一旦你确定了特征集x,输入单元的数量就等于x^{(i)} 的维度,输入单元的数目将会由此确定。如果你正在进行多类别分类,那么输出层的单元数目将会由分类问题中所要区分的类别个数确定,提醒一下,如果你多元分类问题的y的取值范围是在1到10之间,那么你就有10个可能的分类,记得把输出y重新写成向量的形式。所以如果其中一个样本被分到第五类,也就是说y=5,那么在神经网络中就不能直接用数字5来表达,而应该用一个向量来表达

这个向量的第五个位置是1,其余全是0。

所以对于输入单元和输出单元数目的选择还是比较容易理解的,那么对于隐藏层单元的个数以及隐藏层的数目。一个合理的默认选项是只使用单个隐藏层,只有一个隐藏层的神经网络可能是最常见的,或者如果你使用不止一个隐藏层的话,同样也有一个合理的默认选项,那就是每一个隐藏层,通常都应有相同的单元数,例如下图中的隐藏层都是5个单元数。

但实际上通常来说,左边这个结构是较为合理的默认结构,而对于隐藏单元的个数,通常情况下,隐藏单元越多越好,不过需要注意的是,如果有大量隐藏单元,计算量一般会比较大。当然,一般来说隐藏单元还是越多越好,并且一般来说,每个隐藏层所包含的单元数量还应该和输入x的维度相匹配,即和特征的数目匹配,隐藏单元的数目可以和输入特征的数量相同,或者是它的二倍,或者三倍、四倍,因此隐藏单元的数目和输入特征数相匹配,或者比特征数大几倍都是有效的。

如果你遵循了这些建议,可能会得到比较好的模型结构。

下面来介绍训练神经网络需要实现的步骤。

第一步是构建一个神经网络,然后随机初始化权重,通常我们把权重初始化为很小的值,接近于0。然后执行前向传播算法,也就是对于该神经网络的任意一个输入x^{(i)} 计算出对应的 h_\Theta(x^{(i)}),也就是一个输出值y的向量。接下来通过代码计算出代价函数J(Θ),然后执行反向传播算法来算出这些偏导数项,也就是J(Θ)关于参数θ的偏导数。

具体来说,使用反向传播要对所有训练样本使用一个for循环进行遍历。

在这个for循环里,我们对这个样本执行前向和反向算法,具体来说就是,把 x^{(i)} 传入输入层,然后执行前向传播和反向传播,这样就能得到神经网络每一层中每一个单元对应的激励值和\delta项,接下来还是在for循环中,要计算下面这些项

最后,for循环体外面的部分将用别的代码来计算出偏导数项, 这些偏导数项还应该把正则化项λ考虑在内。

接下来就是第五步了,要做的就是使用梯度检测来比较这些已经计算得到的偏导数项,把用反向传播算法得到的偏导数值与用数值方法得到的估计值进行比较,因此,通过进行梯度检测来确保两种方法得到基本接近的两个值,通过梯度检测,我们能确保我们的反向传播算法得到的结构是正确的,接下来非常重要的一点是要停用梯度检测,因为梯度检测的计算非常慢。

最后,我们就可以使用一个最优化算法,比如说梯度下降算法或者更加高级的优化方法,比如说LBFGS算法,共轭梯度法或者其它内置到fminunc函数中的方法,将这些优化方法和反向传播算法相结合,反向传播算法计算出这些偏导数项的值,就能使用某个最优化方法来最小化关于θ的代价函数J(θ)。另外,顺便提一下,对于神经网络,代价函数J(θ)是一个非凸函数,因此理论上可能停留在局部最小值的位置,实际上,梯度下降算法和其他一些高级优化方法理论上都可能收敛于局部最小值,但一般来讲,在实际操作中,这不是大问题,尽管我们不能保证这些优化算法一定会得到全局最优值,但通常来讲,像梯度下降这类的算法在最小化代价函数J(θ)的过程中还是表现得很不错的,通常能够得到一个很小的局部最小值,尽管这可能不一定是全局最优值。

最后,梯度下降算法似乎对于神经网络来说还是比较神秘,下面给出一张图来解释

这里只显示了两个参数值,当然实际上,在神经网络里,可以有很多的参数值,\Theta^{(1)}\Theta^{(2)}(这些都是矩阵)等等,因此可以有高维参数,由于绘图所限,不能绘出更高维度情况的图像,所以这里假设这个神经网络中只有两个参数值,代价函数J(θ)度量的就是这个神经网络对训练数据的拟合情况,所以如果取一点,比如下面这一点

在这个点上,J(θ)的值是非常小的,这一点的位置所对应一组参数,对于大部分的训练样本,假设函数的输出会非常接近于y^{(i)}

因此梯度下降法的原理是,从某个随机的初始点开始,它将会不停地往下下降,那么反向传播算法的目的就是算出梯度下降的方向,而梯度下降的作用就是沿着这个方向一点点的下降,一直到希望得到的点。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值