神经网络和深度学习,第2章:反向传播算法如何工作


上一章中,我们了解了神经网络如何使用梯度下降算法独立学习权重和偏移。 但是,我们的解释存在差距:我们没有讨论成本函数梯度的计算。 这是一个不错的差距! 在本章中,我将介绍一种用于计算此类梯度的快速算法,称为反向传播。

反向传播算法最早是在1970年代发明的,但是直到1986年由David Rumelhart,Joffrey Hinton和Ronald Williams撰写的著名著作才充分了解了它的重要性。 这篇论文描述了几种神经网络,其中反向传播的速度比早期的学习方法快得多,这就是为什么现在可以使用神经网络来解决以前无法解决的问题的原因。 如今,反向传播算法已成为学习神经网络的主力军。

本章包含的数学知识比本书中其他所有知识都要多。 如果您不特别喜欢数学,可能会想跳过本章,而只是将反向分布视为黑匣子,您可以忽略其详细信息。 为什么要浪费时间研究它们?

原因当然是理解。 反向传播基于成本函数C相对于网络权重w(或偏差b)的偏导数∂C/∂w的表达式。 该表达式显示了权重和偏移量更改时值更改的速度。 尽管这种表达方式非常复杂,但它具有自己的美感,因为其每个元素都有自然而直观的解释。 因此,反向传播不仅是一种快速学习算法。 它使我们详细了解了如何更改权重和偏移量会更改所有网络行为。 研究细节是值得的。

考虑到所有这些,如果您只是想翻阅本章或跳到下一章,就可以了。 我写了本书的其余部分,使之易于理解,即使我们将反向分布视为黑盒。 当然,在本书的后面,我会参考一些章节来介绍本章的结果。 但是那一刻,即使您没有遵循所有论点,您也应该了解基本结论。

进行预热:一种用于计算神经网络输出的快速矩阵方法


在讨论反向传播之前,让我们获取一些快速矩阵算法来计算神经网络的输出。 在上一章末,我们实际上已经遇到了该算法,但是我很快对其进行了描述,因此值得再次详细考虑。 特别地,这将是适应反向分发中使用的记录的一种好方法,但是要在熟悉的环境中进行。

让我们从一条记录开始,该记录使我们可以清楚地指示网络上的权重。 我们将使用w l jk表示第(l-1)层中的神经元k与第1层中的神经元j的连接权重。 因此,例如,下图显示了第二层的第四神经元与第三层的第二神经元的连接权重:



起初,这样的录音似乎很尴尬,并且需要一些习惯。 但是,对您来说,这似乎很简单自然。 其特征之一是索引j和k的顺序。 您可以决定使用j来指定输入神经元,使用k-来表示输出神经元,而不是像我们一样,反之亦然。 我将在下面解释此功能的原因。

对于偏移量和网络激活,我们将使用类似的符号。 特别是,b l j表示第1层神经元j的位移。 a l j表示层1中神经元j的激活。 下图显示了如何使用此条目的示例:



有了这样的记录,通过以下等式(与等式(4)及其在上一章中的讨论相比),第l层中j号神经元的l j的激活与第(l-1)层中的激活相关联:

alj= sigma sumkwljkal1k+blj\标23



其中总和遍及层(l-1)中的所有神经元k。 为了以矩阵形式重写该表达式,我们为每层l定义一个权重矩阵w l。 权重矩阵的元素只是连接到第1层的权重,也就是说,第j行和第k列中的元素将为w l jk 。 类似地,我们为每一层l定义位移矢量b l 。 您可能已经猜到了它是如何工作的-位移矢量的分量将只是值b l j ,即第1层中每个神经元的一个分量。 最后,我们定义激活向量a l ,其分量为激活a l j

重写(23)所需的最后一个成分是函数σ的向量化的矩阵形式。 在上一章中我们偶然遇到了矢量化-想法是我们想将函数σ应用于矢量v的每个元素。 我们使用明显的符号σ(v)表示该函数在元素上的应用。 即,分量σ(v)只是σ(v) j =σ(v j )。 例如,如果我们有一个函数f(x)= x 2 ,则向量化形式f给出

f\开bmatrix23\结bmatrix=\开bmatrixf2f3 endbmatrix=\开bmatrix49\结bmatrix\标24



也就是说,向量化的f只是将向量的每个元素平方。

考虑到所有这些书写形式,等式(23)可以用漂亮而紧凑的矢量化形式重写:

al= sigmawlaal1+bl\标25



这样的表达式使我们可以更全面地了解一层的激活与上一层的激活之间的关系:我们只需将权重矩阵应用于激活,添加位移矢量,然后再采用S型。 顺便说一下,正是该记录需要使用记录w l jk 。 如果使用索引j表示输入神经元,使用索引k表示输出神经元,则必须将等式(25)中的权重矩阵替换为转置的那个。 这是一个很小但令人讨厌的变化,我们将失去关于“将权重矩阵应用于激活”的陈述(和反思)的简单性。 这样的全局方法比poneuron方法更简单,更简洁(并且使用更少的索引!)。 这只是避免索引混乱而又不会丢失正在发生的准确性的一种方法。 该表达式在实践中也很有用,因为大多数矩阵库都提供了乘法矩阵,添加向量和向量化的快速方法。 上一章中的代码直接使用此表达式来计算网络行为。

使用公式(25)计算a l ,我们计算中间值z l≡w l a l − 1 + b l 。 该值对于命名非常有用:我们称z为第1层神经元加权输入。 稍后,我们将积极使用此加权输入。 有时通过加权输入来写等式(25),即l =σ(z l )。 还值得注意的是z l具有分量 zlj= sumkwljkal1k+blj 也就是说,z l j只是第1层中神经元j的激活函数的加权输入。

关于成本函数的两个基本假设


反向传播的目标是针对每个权重w和网络的偏差b计算成本函数C的偏导数∂C/∂w和∂C/∂b。 为了使反向传播起作用,我们需要对成本函数的形式做出两个主要假设。 但是,在此之前想象一个成本函数的示例将很有用。 我们使用上一章中的二次函数(等式(6))。 在上一部分的条目中,它看起来像

C= frac12n sumx||yxaLx||2\标26



其中:n是培训示例的总数; 所有示例x的总和; y = y(x)是所需的输出; L表示网络中的层数; 当x在输入处时,a L = a L (x)是网络激活的输出向量。

好的,那么对于应用反向传播的成本函数C,我们需要什么样的假设? 首先,成本函数可以写成各个训练样本x的成本函数C x的平均值C = 1 / n ∑ x C x。 这是在二次成本函数的情况下完成的,其中一个训练示例的成本为C x = 1/2 || y-a L ||。 2 。 对于我们将在书中遇到的所有其他成本函数,此假设将成立。

我们需要这个假设,因为实际上反向传播使我们能够计算偏导数∂C/∂w和∂C/∂b,取训练样本的平均值。 接受这个假设,我们将假设训练示例x是固定的,然后停止指定索引x,将C x的值记为C。然后我们将返回x,但现在最好是简单地表示它。

关于成本函数的第二个假设-可以将其写成神经网络输出的函数:



例如,二次成本函数满足此要求,因为一个训练示例x的二次成本可以写成

C=1/2||yaL||2=1/2 sumjyjaLj2\标27



这将是输出激活的功能。 当然,这个成本函数还取决于期望的输出y,您可能想知道为什么我们不将C也视为y的函数。 但是,请记住,输入训练示例x是固定的,因此输出y也固定。 特别是,我们不能通过改变权重和位移来改变它,也就是说,这不是神经网络学到的东西。 因此,将C仅视为输出激活a L的函数并将y视为有助于确定它的参数是有意义的。

Hadamard sdamt的工作


反向传播算法基于线性代数的常规运算-向量相加,向量与矩阵相乘等。 但是,操作之一的使用频率较低。 假设s和t是同一维的两个向量。 然后,我们表示两个向量的逐元素乘法。 那么,分量s⊙t就是(s⊙t) j = s j t j 。 例如:

\开bmatrix12\结bmatrix odot\开bmatrix34\结bmatrix=\开bmatrix1324\结bmatrix=\开bmatrix38\结bmatrix\标28



有时将这种分段工作称为Hadamard的工作或Schur的工作。 我们将其称为Hadamard的工作。 良好的用于矩阵的库通常可以快速实现Hadamard产品,并且在实现反向传播时会很方便。

反向传播的四个基本方程式


反向传播涉及了解如何更改网络的权重和偏移量,从而改变成本函数。 本质上,这意味着计算偏导数∂C/ / w l jk和∂C/ / b l j 。 但是为了进行计算,我们首先计算中间值δl j ,我们称其为第1层神经元j的误差。 反向传播将为我们提供一个计算误差δl j的过程 ,然后将δl j与∂C/∂wl jk和∂C/∂bl j关联。

要了解如何确定错误,请想象一下我们的神经网络中有一个恶魔开始了:



他坐在第一层的神经元j上。 接收到输入数据后,守护程序会破坏神经元的操作。 它将Δzl j的微小变化添加到神经元的加权输入中,而不是产生σ(z l j ),而是神经元会产生σ(z l j +Δzl j )。 这种变化还将传播到网络的后续层,最终将使总成本变化(∂C/∂zlj)*Δzl j

但是我们的恶魔是很好的,他正试图帮助您提高成本,即找到降低成本的Δzl j 。 假设theC /∂zl j的值很大(正数或负数)。 然后,恶魔可以通过选择符号与∂C/∂zl j相反的Δzl j来严重降低成本。 但是,如果∂C/∂zl j接近零,那么恶魔就无法通过改变加权输入z l j来极大地提高成本。 因此,从恶魔的角度来看,神经元已经接近最佳状态(当然,这仅适用于较小的Δzlj。假设这些是恶魔动作的限制)。 因此,在启发式意义上,∂C/ / z l j是神经元误差的量度。

在这个故事的激励下,我们将层l中神经元j的误差δl j定义为

 deltalj equiv frac\部C\部zlj\标29



按照我们通常的惯例,我们使用δl表示与l层相关的误差向量。 反向传播将为我们提供一种计算任意层的δl的方法,然后将这些误差与我们真正感兴趣的数量correlat C /∂w l jk和∂C/∂bl j相关联

您可能想知道守护程序为什么更改加权输入z l j 。 毕竟,想象一下恶魔会改变输出激活a l j,以便我们使用∂C/∂a l j作为误差的量度,将是更自然的。 实际上,如果您这样做,那么所有结果都将与我们将进一步讨论的内容非常相似。 但是,在这种情况下,反向传播的表示将在代数上更加复杂。 因此,我们将变体δl j = / C / jz l j作为误差的度量。

在分类问题中,术语“错误”有时表示错误分类的数量。 例如,如果神经网络正确分类了96.0%的数字,则错误将是4.0%。 显然,这根本不是矢量δ的意思。 但是实际上,您通常可以轻松理解含义。

攻击计划 :反向传播基于四个基本方程式。 它们共同为我们提供了一种计算误差δl和成本函数的梯度的方法。 我在下面给他们。 无需指望他们的即时发展。 您会失望的。 反向传播方程是如此之深,以至于良好的理解需要明显的时间和耐心,并且问题需要逐步加深。 好消息是这种耐心将得到丰厚的回报。 因此,在本节中,我们的推理才刚刚开始,可以帮助您遵循对方程式的深刻理解。

以下是我们稍后将如何研究这些方程式的示意图:我将简要说明这些方程式,以帮助解释为什么它们成立。 我们将以伪代码形式的算法形式重写它们,并查看如何在真实的python代码中实现它; 在本章的最后部分,我们将对反向传播方程的含义以及如何从头开始找到它们有了直观的认识。 我们将定期返回这四个基本方程式,您对它们的理解越深,它们对您来说就越舒适,也许美丽而自然。

输出层的误差方程δLδL的分量被认为是

 deltaLj= frac\部C\部aLj sigmazLj\标BP1



非常自然的表情。 右边的第一项∂C/∂aL j衡量成本随输出激活编号j变化的速度。 例如,如果C不特别依赖于特定的输出神经元j,则δL j将会很小。 右边的第二项σ'(z L j )测量激活函数σ在z L j中变化的速度。

请注意,(BP1)中的所有内容都很容易计算。 特别地,我们在计算网络行为时计算z L j ,并且将花费更多的资源来计算σ'(z L j )。 当然,确切的形式∂C/∂aL j取决于成本函数的形式。 但是,如果成本函数已知,那么计算∂C/ La L j就不会有问题。 例如,如果我们使用二次成本函数,则C = 1/2 ∑ j (y j -a L j2 ,因此∂C/∂aL j =(a L j -y j ),这很容易计算。

公式(BP1)是δL的分解表达式 它是完全正常的,但没有以矩阵形式记录,这是我们需要反向分配的形式。 但是,很容易以矩阵形式重写,因为

 deltaL= nablaaC odot sigmazL tagBP1a



在此,∇a C定义为向量,其分量为偏导数∂C/∂aL j 。 它可以表示为C相对于输出激活的变化率的表达。 很容易看到方程(BP1a)和(BP1)是等价的,因此下面我们将使用(BP1)来指代它们中的任何一个。 例如,在二次值的情况下,我们有= a C =(a L -y),因此全矩阵形式(BP1)将为

 deltaL=aLy odot sigmazL tag30



此表达式中的所有内容都具有方便的矢量形式,并且可以使用诸如Numpy之类的库轻松进行计算。

通过下一层中的误差δl + 1来表示误差δl

 deltal=wl+1T deltal+1 cdot sigmazl tagBP2



其中(w l +1T是第(l +1)层的权重矩阵w l +1的转置。 这个方程看起来很复杂,但是每个元素都很容易解释。 假设我们知道层(l + 1)的误差δl + 1。 权重矩阵(w l +1T的转置可以想象为通过网络向后移动误差,这为我们提供了在第1层输出处的一些误差度量。 然后我们考虑Hadamard乘积⊙σ'(z l )。 这通过层1中的激活函数将错误推回,从而在层1的加权输入中为我们提供了错误值δl。

通过将(BP2)与(BP1)组合,我们可以计算任何网络层的误差δl。我们首先使用(BP1)计算δL,然后使用方程式(BP2)计算δL-1,然后再次计算δL-2,依此类推,回到网络的终点。

成本变化率与网络中任何偏移量相关的方程式:特别是:

的Cb Ĵ =δĴ



也就是说,误差增量 Ĵ正好等于变化∂C/∂b的速率 Ĵ这是最好的,因为(BP1)和(BP2)已经告诉我们如何计算增量 Ĵ我们可以将(BP3)改写为

的Cb =δ



其中δ是针对与偏差b相同的神经元估计的。

与网络中任何权重有关的价值变化率方程:特别是:

的C瓦特Ĵ ķ =- 1 ķ δĴ



由此我们了解如何计算的偏导数∂C/∂w JK增量的值的和一个L-1 其是已知的计算方法。该公式可以用较少加载的形式重写:

的C瓦特 =一个ÑδÒù



其中 -激活的用于权重w的神经输入,并且增量 -权重w的神经输出误差。如果你看一下更多的重量W和两个神经元连接到它们,那么我们可以得出这样的方式:



激活时,方程(32)的宜人结果小,一个 ≈0,∂C/∂w倾斜件也趋于归零。在这种情况下,我们说的是权重训练缓慢,也就是说,它在梯度下降过程中变化不大。换句话说,后果之一(BP4)是激活程度低的神经元的加权输出学习缓慢。

其他想法可以从(BP1)-(BP4)中得出。让我们从输出层开始。考虑项σ'(z L j)在(BP1)中。回想上一章中的S型曲线,当σ(z L j)接近0或1 时,它变得平坦。在这些情况下,σ'(z L j)≈0。因此,如果激活,最后一层的权重将被缓慢训练输出神经元小(≈0)或大(≈1)。在这种情况下,他们通常会说输出神经元已饱和,结果,体重已停止训练(或缓慢训练)。相同的说明对于输出神经元的位移有效。

关于较早的层,可以获得类似的想法。特别地,考虑(BP2)中的项σ'(z l)。这意味着,增量 Ĵ随着神经元趋于饱和,最有可能变小。这,反过来,这意味着在进口饱和神经元的任何重量会慢慢训练(但它不会工作当w L + 1 Ť δ L + 1是足够大的元素补偿小值σ“(Z 大号 j))。

总结:我们了解到,如果输入神经元的激活很小或输出神经元饱和,即它的激活很小或很大,则重量训练将很慢。

这并不特别令人惊讶。但是,这些观察结果有助于增进我们对训练网络时发生的情况的了解。此外,我们可以从另一侧处理这些论点。这四个基本方程对任何激活函数均有效,而不仅对标准S型有效(因为,正如我们稍后将看到的,这些属性不使用S型)。因此,这些方程式可用于开发具有某些必要学习属性的激活函数。例如,假设我们选择一个不同于S形的激活函数σ,这样σ′始终为正,不接近零。这样可以防止在正常的乙状结肠神经元饱和时发生学习减慢。在本书的后面,我们将看到激活函数以类似方式变化的示例。给定方程式(BP1)-(BP4),我们可以解释为什么需要这样的修改,以及它们如何影响情况。


:


  • . . , . , , . , (BP1) ,


δL=Σ(zL)aC



其中,Σ“(Z 大号) -平方矩阵,其对角值进行排列σ”(Z 大号 Ĵ),和其他元素等于0。注意,该矩阵与∇反应一个通过传统的矩阵乘法℃。

证明(BP2)可以重写为

δ = Σ 'Ž 瓦特+ 1Ť δ + 1



结合之前的任务,表明:

δ = Σ 'Ž 瓦特+ 1Ť ... Σ 'ž 大号- 1瓦特大号Ť Σ 'Ž 大号一个 Ç



对于习惯矩阵乘法的读者而言,此方程比(BP1)和(BP2)更容易理解。我专注于(BP1)和(BP2),因为这种方法在数值上实现起来更快。[这里的∑不是和(∑),而是大写的σ(sigma)/ 约。佩雷夫 ]

四个基本方程的证明(可选部分)


现在我们证明四个基本方程(BP1)-(BP4)。所有这些都是链规则(复杂函数的微分规则)从许多变量的函数分析中得出的结果。如果您熟悉链式规则,我强烈建议您在继续阅读之前尝试先计算一下自己的导数。

让我们从公式(BP1),这让我们对输出误差增量的表达式开始为了证明这一点,我们回顾一下,根据定义:

δ 大号Ĵ = Çž 大号Ĵ



应用链式规则,我们通过输出激活的偏导数重写偏导数:

δ 大号Ĵ = Σ ķ Ç一个大号ķ一个大号ķž 大号Ĵ



其中求和遍及输出层中的所有神经元k。当然,激活一个输出大号 ķ神经元№k仅取决于加权输入Ž 大号 Ĵ到神经元№j,当k = j的。因此的δA k中的 /∂z Ĵ消失当k≠j时。结果,我们将前面的等式简化为

δ 大号Ĵ = Ç一个大号Ĵ一个大号Ĵž 大号Ĵ



回想一个L j =σ(z L j),我们可以将右边的第二项改写为σ'(z L j),等式变为

δ 大号Ĵ = Ç一个大号Ĵ σ'Ž大号Ĵ



即在(BP1)中以分解图显示。

然后证明(BP2),给出了误差δ的方程通过误差δ在下一层L + 1要做到这一点,我们需要重写增量 Ĵ =∂C/∂z Ĵ通过增量L + 1, 第k =∂C/∂z L + 1, k个可以使用链式规则完成此操作:

δ Ĵ = Çž Ĵ

= Σ k个与cž + 1 ķž+ 1 ķž Ĵ

= Σ ķ ž + 1 ķž Ĵ δ+ 1 ķ



在最后一行中,我们在右侧交换了两项,并替换了δl + 1 k的定义要计算最后一行的第一项,请注意

ž + 1 ķ = Σ Ĵ瓦特+ 1 ķ ĴĴ + b + 1 ķ = Σ Ĵ瓦特+ 1 ķ Ĵ σ Ž Ĵ+ b + 1 ķ



差异化,我们得到

ž + 1 ķž Ĵ =瓦特+ 1 ķ Ĵ σ'ŽĴ



将其代入(42),我们得到

δ Ĵ = Σ ķ瓦特+ 1 ķ Ĵ δ + 1 ķ σ 'Ž Ĵ



即,(BP2)在一个爆炸条目中。

有待证明(BP3)和(BP4)。它们也遵循链式规则,与前两个规则大致相同。我会将它们留给您作为练习。

锻炼身体


  • 证明(BP3)和(BP4)。


这就是四个基本反向传播方程式的全部证明。这似乎很复杂。但是实际上,这仅仅是谨慎应用连锁规则的结果。简而言之,可以将反向传播想象为一种通过对许多变量的函数进行系统分析而通过链式规则的系统应用来计算成本函数梯度的一种方式。实际上,这就是反向分发的全部内容-剩下的只是细节。

反向传播算法


反向传播方程式为我们提供了一种计算成本函数梯度的方法。让我们将其明确地写为算法:
  1. 输入x:为输入层分配适当的激活1
  2. : l = 2,3,…,L z l = w l a l−1 +b l a l = σ(z l ).
  3. δ L : δ L = ∇ a C ⊙ σ'(z L ).
  4. : l = L−1,L−2,…,2 δ l = ((w l+1 ) T δ l+1 ) ⊙ σ'(z l ).
  5. : Cwljk=al1kδljCblj=δlj


查看该算法,您将了解为什么将其称为反向传播。我们计算误差向量增量倒退,从最后一层。我们通过网络向后走,这似乎很奇怪。但是,如果考虑反向传播的证据,那么反向移动是由于成本是网络输出的函数这一事实的结果。要了解成本如何随着早期权重和偏移而变化,我们需要一遍又一遍地应用链式规则,然后遍历各层以获得有用的表达式。

练习题


  • . , , f(∑ j w j x j +b), f – , . ?
  • . , σ(z) = z . .


如前所述,反向传播算法针对一个训练示例C = C x计算成本函数的梯度在实践中,当我们为许多训练示例计算梯度时,通常会将反向传播与学习算法(例如,随机梯度下降)结合使用。特别是,对于给定的m个训练示例的小型包装,以下算法基于该小型包装应用梯度下降:

  1. 入学:一组培训示例。
  2. 对于每个训练示例x,为相应的输入激活分配x,1并执行以下步骤:
    • l=2,3,…,L z x,l = w l a x,l−1 +b l a x,l = σ(z x,l ).
    • δ x,L : δ x,L = ∇ a C x ⋅ σ'(z x,L ).
    • : l=L−1,L−2,…,2 δ x,l = ((w l+1 ) T δ x,l+1 ) ⋅ σ'(z x,l ).
  3. : l=L,L−1,…,2 wl rightarrowwl frac etam sumx deltaxlaxl1T ,并根据规则进行补偿 bl rightarrowbl frac etam sumx deltaxl


当然,要在实践中实施随机梯度下降,您还需要一个外部循环以生成微型培训示例包,并且需要一个外部循环来经历几个训练时期。 为了简单起见,我省略了它们。

反向分配代码


在了解了反向传播的抽象方面之后,我们现在可以了解上一章中实现反向传播的代码。 从该章回想起,该代码包含在Network类的update_mini_batch和backprop方法中。 这些方法的代码是上述算法的直接翻译。 特别是,update_mini_batch方法通过计算当前mini_batch训练示例的梯度来更新网络权重和偏移:

class Network(object): ... def update_mini_batch(self, mini_batch, eta): """    ,          -. mini_batch –    (x, y),  eta –  .""" nabla_b = [np.zeros(b.shape) for b in self.biases] nabla_w = [np.zeros(w.shape) for w in self.weights] for x, y in mini_batch: delta_nabla_b, delta_nabla_w = self.backprop(x, y) nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)] nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)] self.weights = [w-(eta/len(mini_batch))*nw for w, nw in zip(self.weights, nabla_w)] self.biases = [b-(eta/len(mini_batch))*nb for b, nb in zip(self.biases, nabla_b)] 


大多数工作是通过以下行完成的:delta_nabla_b,delta_nabla_w = self.backprop(x,y),使用backprop方法计算偏导数xC x /∂bl j和∂Cx /∂wl jk 。 反向传播方法几乎重复了上一节的算法。 有一个小的差异-我们使用稍微不同的方法进行图层索引。 这样做是为了利用python功能,即负数组索引,该索引使您可以从末尾开始倒计数元素。 l [-3]将是数组l末尾的第三个元素。 下面给出了反向传播代码,以及用于计算S形,其导数和成本函数导数的辅助函数。 有了它们,代码就变得完整且易于理解。 如果不清楚,请参阅第一个完整的上市代码说明。

 class Network(object): ... def backprop(self, x, y): """  ``(nabla_b, nabla_w)``,      C_x. ``nabla_b``  ``nabla_w`` -    numpy,   ``self.biases`` and ``self.weights``.""" nabla_b = [np.zeros(b.shape) for b in self.biases] nabla_w = [np.zeros(w.shape) for w in self.weights] #   activation = x activations = [x] #      zs = [] #     z- for b, w in zip(self.biases, self.weights): z = np.dot(w, activation)+b zs.append(z) activation = sigmoid(z) activations.append(activation) #   delta = self.cost_derivative(activations[-1], y) * \ sigmoid_prime(zs[-1]) nabla_b[-1] = delta nabla_w[-1] = np.dot(delta, activations[-2].transpose()) """ l      ,      . l = 1    , l = 2 – ,   .    ,   python      .""" for l in xrange(2, self.num_layers): z = zs[-l] sp = sigmoid_prime(z) delta = np.dot(self.weights[-l+1].transpose(), delta) * sp nabla_b[-l] = delta nabla_w[-l] = np.dot(delta, activations[-l-1].transpose()) return (nabla_b, nabla_w) ... def cost_derivative(self, output_activations, y): """    ( C_x /  a)   .""" return (output_activations-y) def sigmoid(z): """.""" return 1.0/(1.0+np.exp(-z)) def sigmoid_prime(z): """ .""" return sigmoid(z)*(1-sigmoid(z)) 


挑战赛


  • 迷你包装上基于矩阵的反向传播方法。 我们实现随机梯度下降的过程使用了迷你包装中的一系列训练示例。 可以更改反向传播算法,以便它同时为微型包装的所有训练示例计算梯度。 可以从矩阵X = [x 1 x 2 ... x m ]开始,而不是从单个向量x开始,其矩阵是minipack的向量。 直接分配是通过权重矩阵的乘积,增加用于位移的合适矩阵以及乙状结肠的广泛使用来实现的。 反向传播遵循相同的模式。 为此方法编写伪代码以进行反向传播算法。 修改network.py,使其使用此矩阵方法。 这种方法的优点是将现代库的所有优点用于线性代数。 结果,它的运行速度可能比微型数据包周期要快(例如,在我的计算机上,该程序在MNIST的分类任务上的速度大约提高了2倍)。 实际上,所有用于反向分发的重要库都使用这种全矩阵方法或其某种版本。


从什么意义上说反向传播是一种快速算法?


从什么意义上说反向传播是一种快速算法? 要回答此问题,请考虑另一种计算梯度的方法。 想象一下神经网络研究的早期。 也许是1950年代或1960年代,您是世界上第一个提出使用梯度下降进行训练的人! 但这要起作用,您需要计算成本函数的梯度。 您回想代数,然后决定是否可以使用链式规则来计算梯度。 玩了一点之后,您会发现代数似乎很困难,并且您感到失望。 您正在尝试寻找其他方法。 您决定将成本视为仅权重C = C(w)的函数(稍后将返回位移)。 您对权重w 1 ,w 2 ,...进行编号,并想计算权重w j的 ∂C/∂wj。 显而易见的方法是使用近似值

 frac\部C\部wj\大 fracCw+ epsilonejCw epsilon\标46



其中ε> 0是一个小的正数,而e j是单位方向向量j。 换句话说,我们可以通过为两个稍有不同的wj值计算成本C来近似估算∂C/∂wj,然后应用公式(46)。 同样的想法使我们能够计算∂C/∂b关于位移的偏导数。

该方法看起来很有希望。 概念上简单,易于实现,仅使用几行代码。 它看起来比使用链式规则计算梯度的想法更有希望!

不幸的是,尽管这种方法看起来很有希望,但是当用代码实现时,事实证明它的运行速度非常慢。 要了解原因,请想象我们在网络中拥有一百万个权重。 然后,对于每个权重w j,我们需要计算C(w +εej)来计算∂C/∂wj。 这意味着要计算梯度,我们需要计算成本函数一百万次,这将需要一百万次直接通过网络(对于每个训练示例)。 而且我们还需要计算C(w),这样我们就可以通过网络获得100万次。

反向传播的诀窍是,它允许我们仅使用一次直通网络,然后进行一次反向通行,即可同时计算所有偏导数∂C/∂wj。 大致来说,回程的计算成本与直接回程的计算成本大致相同。

因此,反向传播的总成本与两次直接通过网络的总成本大致相同。 将此与实施方法(46)所需的百万和一次直接传递相比较! 因此,尽管反向传播看起来更复杂,但实际上它要快得多。

这种加速在1986年首次得到了充分的认可,并且极大地扩展了借助神经网络解决的任务范围。 反过来,这导致使用神经网络的人数增加。 当然,反向传播不是万能药。 即使在1980年代后期,人们也已经遇到了它的局限性,尤其是在尝试使用反向传播来训练深度神经网络(即具有许多隐藏层的网络)时。 稍后,我们将看到现代计算机和新的棘手想法如何使使用反向传播训练这种深度神经网络成为可能。

反向分布:一般


正如我已经解释的那样,向后传播对我们揭示了两个谜团。 该算法实际上要做的第一件事是? 我们已经针对输出中的误差开发了一种反向传播方案。 是否有可能更深入,更直观地了解向量和矩阵的所有这些乘法期间发生了什么? 第二个谜语是有人甚至还发现了反向传播? 遵循算法的步骤或其操作证明是一回事。 但这并不意味着您对问题的理解如此深刻,因此可以发明该算法。 是否有一条合理的推理路线可以导致我们发现反向传播算法? 在本节中,我将介绍两个难题。

为了更好地理解算法的操作,可以想象我们在一定权重w l jk的基础上做了一个小的变化Δwl jk



重量的这种变化将导致相应神经元的输出激活发生变化:



这将导致下一层的所有激活发生变化:



这些更改将导致下一层的更改,依此类推,直到最后一层,然后导致成本函数的更改:



ΔC的变化与Δwljk的变化有关

 DeltaC\大 frac\部C\部wljk Deltawljk\标47



因此,一种可能的计算calculatingC /∂wl jk的方法是仔细监视小变化w l jk的传播,从而导致C的小变化。如果我们能够做到这一点,请仔细地沿途表达易于计算的所有量,那么我们可以计算∂C/∂wl jk

让我们尝试一下。 Δwljk的变化会引起层1中神经元j的激活中Δalj的轻微变化。 已设置此更改。

 Deltaalj\大 frac\部alj\部wljk Deltawljk\标48



激活度变化Δalj导致下一层(l + 1)的所有激活度变化。 我们将仅关注这些变化的激活之一,例如a l + 1 q



这将导致以下更改:

 Deltaal+1q\大 frac\部al+1q\部alj Deltaalj\标49



代入公式(48),我们得到:

 Deltaal+1q\大 frac\部al+1q\部alj frac\部alj\部wljk Deltawljk\标50



当然,Δal + 1 q的变化也将改变下一层的激活。 我们甚至可以想象整个网络从w ljk到C的路径,其中每次激活的改变都会导致下一次激活的改变,最后导致输出成本的改变。 如果路径经过激活a l j ,a l + 1 q ,...,a L − 1 n ,a L m ,则最终表达式为

 DeltaC\大 frac\部C\部aLm frac\部aLm\部aL1n frac\部aL1n\部aL2p ldots frac\部al+1q\部alj frac\部alj\部wljk Deltawljk\标51



也就是说,我们为通过的每个下一个神经元以及最后的术语∂C/∂aL m选择一个∂a/∂a形式的成员。 这表示由于在通过网络的此特定路径上的激活更改而导致的C更改。 当然,有许多方法可以影响到成本的变化,我们只考虑了其中一种方法。 要计算C的总变化,可以合理地假设我们应该总结从重量到最终成本的所有可能路径:

 DeltaC\大 summnp ldotsq frac\部C\部aLm frac\部aLm\部aL1n frac\部aL1n\部aL2p ldots frac\部al+1q\部alj frac\部alj\部wljk Deltawljk\标52



在这里我们总结了中途神经元的所有可能选择。 与(47)进行比较,我们看到:

 frac\部C\部wljk= summnp ldotsq frac\部C\部aLm frac\部aLm\部aL1n frac\部一个大号- 1个 ñ \部一个大号- 2 pdö小号˚Fř一个Ç \部+ 1个 q \部Ĵ ˚Fř一个Ç \部Ĵ \部瓦特Ĵ 第k \标53   



等式(53)看起来很复杂。 但是,它确实具有很好的直观解释。 我们计算C在网络权重方面的变化。 它告诉我们,网络的两个神经元之间的每个边都与比率因子相关,比率因子只是一个神经元的激活相对于另一神经元的激活的部分导数。 对于从第一个权重到第一个神经元的肋骨,比率因子为∂al j /∂wl jk 。 路径的比率系数只是沿路径的系数的乘积。 而总变化系数∂C/∂wl jk是从初始权重到最终成本在各个方面的系数之和。 下面为一个路径显示此过程:



到目前为止,我们一直在进行启发式辩论,这是一种表示当网络权重变化时发生的情况的方法。 让我概述关于这个话题发展这一论点的另一种思路。 首先,我们可以得出方程(53)中所有单个偏导数的精确表达式。 使用简单的代数很容易做到。 之后,您可以尝试了解如何以矩阵乘积的形式按索引写下所有总和。 事实证明,这是一项繁琐的工作,需要耐心,但又非凡。 经过所有这些和最大程度的简化之后,您将看到获得了完全相同的反向传播算法! 因此,可以将反向传播算法想象为一种计算所有路径的系数之和的方法。 或者,换句话说,反向传播算法是一种跟踪权重(和偏移量)在网络中传播,到达输出并影响成本的细微变化的技巧。

在这里,我不会做所有这一切。 该业务没有吸引力,需要仔细研究细节。 如果您准备好了,您可能想这样做。 如果没有,我希望这些想法能给您一些关于反向传播目标的想法。

那么另一个谜题呢?怎么才能发现反向传播呢? 实际上,如果您遵循我概述的路径,您将收到反向传播的证据。 不幸的是,证明将比我之前描述的更长,更复杂。 那么,如何找到简短(但更为神秘)的证据呢? 如果写下长证明的所有细节,您会立即注意到一些明显的简化。 您应用简化,获得更简单的证明,然后写下来。 然后您再次遇到一些明显的简化。 然后您重复此过程。 经过几次重复,我们之前看到的证明将是简短的,但有点不可理解,因为已删除所有里程碑! 当然,我建议您信守诺言,但实际上,证据的来源并不神秘。 简化我在本节中描述的证明的工作量很大。

但是,在此过程中有一个巧妙的技巧。 在等式(53)中,中间变量是a l + 1 q类型的激活。 诀窍是切换到使用加权输入(例如z l + 1 q )作为中间变量。 如果您不使用它,而继续使用激活,那么获得的证据将比本章前面给出的证据更为复杂。

Source: https://habr.com/ru/post/zh-CN457980/


All Articles