算法形式

反向传播方程为我们提供了一种计算成本函数梯度的方法。让我们明确地将其写成一个算法形式:

  1. 输入 x: 设置输入层的相应激活rmsprop梯度下降算法的特点 梯度下降算法实现_网络
  2. 前向传播:对于网络的每一层rmsprop梯度下降算法的特点 梯度下降算法实现_rmsprop梯度下降算法的特点_02计算
    rmsprop梯度下降算法的特点 梯度下降算法实现_反向传播_03rmsprop梯度下降算法的特点 梯度下降算法实现_网络_04
  3. 计算输出误差rmsprop梯度下降算法的特点 梯度下降算法实现_反向传播_05:计算
    rmsprop梯度下降算法的特点 梯度下降算法实现_网络_06
  4. 反向传播误差:对于网络的每一层rmsprop梯度下降算法的特点 梯度下降算法实现_算法_07 计算
    rmsprop梯度下降算法的特点 梯度下降算法实现_rmsprop梯度下降算法的特点_08
  5. 输出:成本函数的梯度由下面的公式给出
    rmsprop梯度下降算法的特点 梯度下降算法实现_网络_09rmsprop梯度下降算法的特点 梯度下降算法实现_反向传播_10

观察这个算法,你可以看到为什么它被称为反向传播。我们从最后一层开始向后计算错误向量 rmsprop梯度下降算法的特点 梯度下降算法实现_rmsprop梯度下降算法的特点_11。逆向穿越网络可能看起来有些奇怪。但是如果你考虑一下反向传播的证明,向后移动是由于成本是网络输出的函数这一事实造成的。为了理解成本如何随着较早的权重和偏置变化,我们需要反复应用链式法则,逆向地通过层来获得可用的表达式。

小批量应用梯度下降

如我上面所描述的,反向传播算法计算单个训练样本的成本函数的梯度,即 rmsprop梯度下降算法的特点 梯度下降算法实现_rmsprop梯度下降算法的特点_12。在实践中,通常将反向传播与诸如随机梯度下降之类的学习算法结合起来,其中我们针对许多训练样本计算梯度。特别是,给定一个大小为 rmsprop梯度下降算法的特点 梯度下降算法实现_rmsprop梯度下降算法的特点_13

  1. 输入一组训练样本
  2. 对于每个训练样本 x: 设置相应的输入激活 rmsprop梯度下降算法的特点 梯度下降算法实现_反向传播_14,并执行以下步骤:
    (1)前向传播:对于网络的每一层rmsprop梯度下降算法的特点 梯度下降算法实现_rmsprop梯度下降算法的特点_02计算
    rmsprop梯度下降算法的特点 梯度下降算法实现_梯度下降算法_16rmsprop梯度下降算法的特点 梯度下降算法实现_rmsprop梯度下降算法的特点_17
    (2)输出误差rmsprop梯度下降算法的特点 梯度下降算法实现_算法_18
    rmsprop梯度下降算法的特点 梯度下降算法实现_算法_19
    (3)反向传播误差:对于网络的每一层rmsprop梯度下降算法的特点 梯度下降算法实现_算法_07 计算
    rmsprop梯度下降算法的特点 梯度下降算法实现_反向传播_21
  3. 梯度下降:对于每一层 rmsprop梯度下降算法的特点 梯度下降算法实现_反向传播_22根据规则更新权重和偏置:
    rmsprop梯度下降算法的特点 梯度下降算法实现_rmsprop梯度下降算法的特点_23
    rmsprop梯度下降算法的特点 梯度下降算法实现_算法_24

当然,在实践中实现随机梯度下降,您还需要一个外部循环来生成训练样本的小批量,并且需要一个外部循环来遍历多个训练时期。为简单起见,我已省略了这些。

反向传播的代码

在抽象层面理解了反向传播之后,我们现在可以理解前面用于实现反向传播的代码。回想一下那一章,代码包含在 Network 类的 update_mini_batch 和 backprop 方法中。这些方法的代码是对上面描述的算法的直接翻译。特别是,update_mini_batch 方法通过计算当前训练样本的 mini_batch 的梯度来更新 Network 的权重和偏置:
完整代码:

class Network(object):
...
    def update_mini_batch(self, mini_batch, 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 方法来计算偏导数rmsprop梯度下降算法的特点 梯度下降算法实现_算法_25rmsprop梯度下降算法的特点 梯度下降算法实现_反向传播_26。backprop 方法紧密遵循上一节中的算法。有一个小改变——我们使用了稍微不同的层次索引方法。这个改变是为了利用 Python 的一个特性,即使用负列表索引从列表的末尾开始计数,因此,例如,l[-3] 是列表 l 中倒数第三个条目。backprop 的代码如下,还有一些辅助函数,用于计算σ函数、σ’的导数以及成本函数的导数。有了这些内容,您应该能够以自包含的方式理解代码。如果遇到困难,您可能会发现参考代码的原始描述(和完整列表)会有所帮助。

基于 mini-batch 的全矩阵反向传播方法:我们的随机梯度下降实现循环遍历小批量训练样本中的训练样本。可以修改反向传播算法,使其能够同时计算小批量中所有训练样本的梯度。其思想是,我们不再从单个输入向量 x 开始,而是从一个矩阵rmsprop梯度下降算法的特点 梯度下降算法实现_rmsprop梯度下降算法的特点_27开始,其中的列是小批量中的向量。我们通过将权重矩阵相乘、添加适当的偏置项矩阵以及在所有位置应用 Sigmoid 函数来进行前向传播。我们沿着类似的方式进行反向传播。明确地为这种反向传播算法的方法编写伪代码。修改 network.py 以使用这种完全基于矩阵的方法。这种方法的优点在于它充分利用了现代线性代数库。因此,它的速度比循环遍历小批量要快得多。(例如,在我的笔记本电脑上,对于像我们在上一章中考虑的 MNIST 分类问题,运行时的加速约为两倍。)实际上,所有正式的反向传播库都使用这种全矩阵的方法或其变种。

从哪个角度来说,反向传播是一种快速算法呢

为了回答这个问题,让我们考虑另一种计算梯度的方法。想象一下,这是神经网络研究的早期阶段。也许是在上世纪50年代或60年代,你是世界上第一个想到使用梯度下降来学习的人!但是,为了使这个想法实现,你需要一种计算成本函数梯度的方法。你回想起你对微积分的知识,决定尝试使用链式法则来计算梯度。但是在尝试了一番后,代数看起来很复杂,你感到灰心。所以你试图找到另一种方法。你决定把成本视为仅仅是权重 w 的函数 rmsprop梯度下降算法的特点 梯度下降算法实现_反向传播_28(我们稍后再回到偏置)。你给权重编号为 rmsprop梯度下降算法的特点 梯度下降算法实现_网络_29想要计算某个特定权重 rmsprop梯度下降算法的特点 梯度下降算法实现_网络_30rmsprop梯度下降算法的特点 梯度下降算法实现_梯度下降算法_31 。一个显而易见的方法是使用近似:
rmsprop梯度下降算法的特点 梯度下降算法实现_网络_32

其中 ϵ > 0 是一个小正数,ej 是第 j 个方向的单位向量。换句话说,我们可以通过计算两个略有不同的 wj 值对应的成本 C,并应用方程(46),来估计 ∂C/∂wj。相同的想法也能让我们计算与偏置有关的 ∂C/∂b 的偏导数。

这种方法看起来非常有前途。从概念上来说很简单,实现起来也非常容易,只需几行代码。当然,它看起来比使用链式法则计算梯度的想法要有希望得多!

不幸的是,尽管这种方法看起来很有希望,但当你实现代码时,它却非常慢。要理解为什么,想象一下我们的网络中有一百万个权重。那么对于每个不同的权重 wj,我们都需要计算 C(w+ϵej) 来计算 ∂C/∂wj。这意味着为了计算梯度,我们需要计算成本函数一百万次,需要一百万次网络的前向传播(每个训练样本)。我们还需要计算 C(w),因此总共需要一百万零一次网络的前向传播。

反向传播的巧妙之处在于它使我们能够通过一次网络的前向传播和一次网络的反向传播同时计算所有偏导数 ∂C/∂wj。粗略地说,反向传播的计算成本大致与前向传播相同**这应该是合理的,但需要进行一些分析才能做出仔细的陈述。这是合理的,因为前向传播中的主要计算成本是与权重矩阵相乘,而在反向传播中,主要的计算成本是与权重矩阵的转置相乘。这些操作显然具有类似的计算成本。因此,反向传播的总成本大致与通过网络进行两次前向传播相同。与基于(46)的方法需要一百万零一次前向传播相比,这个成本是相当低的!因此,尽管反向传播表面上看起来比基于(46)的方法更复杂,但实际上它要快得多。

这种加速首次在1986年得到充分认可,并极大地扩展了神经网络可以解决的问题范围。这反过来又导致了大量人开始使用神经网络。当然,反向传播并不是万能的。即使在1980年代后期,人们也遇到了一些限制,特别是在试图使用反向传播来训练深度神经网络时,即具有许多隐藏层的网络。在后面的章节中,我们将看到现代计算机和一些巧妙的新思路现在使得使用反向传播来训练这种深度神经网络成为可能。

反向传播:全局视角

正如我所解释的,反向传播呈现了两个谜团。首先,这个算法究竟在做什么?我们已经形成了一个误差从输出向后传播的图像。但我们是否可以更深入地了解,对我们进行所有这些矩阵和向量乘法时发生了什么,从而建立更多的直觉?第二个谜团是,有人是如何在第一次发现反向传播的?遵循算法的步骤,甚至遵循算法有效性的证明,这都不意味着你对问题的理解如此深刻,以至于你可以在第一次发现算法。是否有一个合理的推理路径可以引导你发现反向传播算法?在本节中,我将解决这两个谜团。

为了提高我们对算法正在做什么的直觉,让我们想象我们对网络中的某个权重 rmsprop梯度下降算法的特点 梯度下降算法实现_梯度下降算法_33 做了一个小改变 rmsprop梯度下降算法的特点 梯度下降算法实现_rmsprop梯度下降算法的特点_34

rmsprop梯度下降算法的特点 梯度下降算法实现_反向传播_35


权重的变化将导致对应神经元的输出激活发生变化:

rmsprop梯度下降算法的特点 梯度下降算法实现_算法_36


这又将导致下一层中所有激活的变化:

rmsprop梯度下降算法的特点 梯度下降算法实现_梯度下降算法_37


这些变化将进一步导致下一层的变化,然后是下一层,依此类推,一直到导致最终层的变化,然后影响成本函数:

rmsprop梯度下降算法的特点 梯度下降算法实现_网络_38


成本的变化 ΔC 与权重的变化 rmsprop梯度下降算法的特点 梯度下降算法实现_rmsprop梯度下降算法的特点_34相关,其关系由以下方程确定:

rmsprop梯度下降算法的特点 梯度下降算法实现_网络_40

这表明,计算 rmsprop梯度下降算法的特点 梯度下降算法实现_梯度下降算法_41 的一个可能方法是仔细追踪权重 rmsprop梯度下降算法的特点 梯度下降算法实现_梯度下降算法_33的微小变化是如何传播导致成本 C 的微小变化的。如果我们能做到这一点,并且在途中谨慎地用易于计算的量表达一切,那么我们应该能够计算出 rmsprop梯度下降算法的特点 梯度下降算法实现_梯度下降算法_41

让我们试着实现这一点。权重 rmsprop梯度下降算法的特点 梯度下降算法实现_rmsprop梯度下降算法的特点_34导致了第 rmsprop梯度下降算法的特点 梯度下降算法实现_算法_45 层中第 rmsprop梯度下降算法的特点 梯度下降算法实现_算法_46 个神经元的激活 rmsprop梯度下降算法的特点 梯度下降算法实现_网络_47的微小变化。这一变化可以表示为:
rmsprop梯度下降算法的特点 梯度下降算法实现_网络_48

激活变化rmsprop梯度下降算法的特点 梯度下降算法实现_网络_47会导致下一层(即第(rmsprop梯度下降算法的特点 梯度下降算法实现_反向传播_50)层)中所有激活的变化。我们将重点关注其中的一个激活受到影响的方式,比如 rmsprop梯度下降算法的特点 梯度下降算法实现_rmsprop梯度下降算法的特点_51

rmsprop梯度下降算法的特点 梯度下降算法实现_算法_52


事实上,它会导致以下变化:

rmsprop梯度下降算法的特点 梯度下降算法实现_算法_53

将方程(48)中的表达式代入,我们得到:

rmsprop梯度下降算法的特点 梯度下降算法实现_rmsprop梯度下降算法的特点_54

当然,变化rmsprop梯度下降算法的特点 梯度下降算法实现_网络_55又会导致下一层中激活的变化。事实上,我们可以想象一条从 rmsprop梯度下降算法的特点 梯度下降算法实现_梯度下降算法_33rmsprop梯度下降算法的特点 梯度下降算法实现_网络_57 的网络路径,每个激活的变化都会导致下一个激活的变化,最终导致输出处成本的变化。如果路径通过激活 rmsprop梯度下降算法的特点 梯度下降算法实现_网络_58,则结果表达式为:
rmsprop梯度下降算法的特点 梯度下降算法实现_算法_59

也就是说,对于我们经过的每个额外的神经元,我们都会得到一个 ∂a/∂a 类型的项,以及末端的 rmsprop梯度下降算法的特点 梯度下降算法实现_网络_60 项。这表示了由于网络中沿着这条特定路径的激活变化而引起的 C 的变化。当然,一个变化可以通过许多路径传播以影响成本,我们只考虑了一条路径。要计算 C 的总变化,很可能我们应该对连接权重和最终成本之间的所有可能路径求和,即:
rmsprop梯度下降算法的特点 梯度下降算法实现_算法_61

现在,方程(53)看起来很复杂。然而,它有一个很好的直观解释。我们正在计算成本 C 关于网络中某个权重的变化率。这个方程告诉我们,网络中两个神经元之间的每条边都与一个速率因子相关联,这个速率因子就是一个神经元的激活对另一个神经元的激活的偏导数。从第一个权重到第一个神经元的边具有速率因子 rmsprop梯度下降算法的特点 梯度下降算法实现_rmsprop梯度下降算法的特点_62。一条路径的速率因子就是沿着路径的速率因子的乘积。而总变化率 rmsprop梯度下降算法的特点 梯度下降算法实现_算法_63 就是从初始权重到最终成本的所有路径的速率因子的总和。这个过程在这里用一个单独的路径进行了说明:

rmsprop梯度下降算法的特点 梯度下降算法实现_反向传播_64


到目前为止,我提供的是一种启发式的论证,一种思考在网络中扰动权重时发生的情况的方法。让我勾勒一下你可以用来进一步发展这个论点的思路。首先,你可以推导出方程(53)中所有单个偏导数的明确表达式。这在一点微积分的帮助下很容易做到。在做到这一点之后,你可以尝试弄清楚如何将所有索引求和写成矩阵乘法。这事实上是一件烦人的事情,需要一些毅力,但并不需要超凡的洞察力。在做完所有这些,然后尽可能简化后,你会发现你最终得到了完全就是反向传播算法!因此,你可以将反向传播算法看作是一种计算所有这些路径的速率因子的总和的方法。或者,稍微换一种说法,反向传播算法是一种聪明的方法,可以跟踪权重(和偏差)在网络中传播时的微小扰动,达到输出,然后影响成本。

现在,我不会在这里详细介绍所有这些。这很凌乱,需要非常谨慎地处理所有细节。如果你愿意接受挑战,你可能会喜欢尝试。即使不愿意,我也希望这种思路能让你对反向传播的实现有一些了解。

那么另一个谜团呢——反向传播如何被首次发现?实际上,如果你按照我刚才勾勒的方法,你将会发现一个反向传播的证明。不幸的是,这个证明比我在本章前面描述的证明要长得多,而且更加复杂。那么那个简短(但更神秘)的证明是如何被发现的呢?当你写出长证明的所有细节时,事实上,事后有几个明显的简化摆在你面前。你做出这些简化,得到一个更短的证明,并写下来。然后你会发现更多的明显的简化。所以你再重复一次。经过几次迭代,结果就是我们之前看到的证明——简短,但有些晦涩,因为所有指向它的标志都已被删除!当然,我要求你相信我,但其实,关于早期证明的起源并没有什么大的秘密。这只是一个艰苦的工作,简化我在上面勾勒出的证明。