本教程由 Datawhale 开源社区 编译,与对应的英文原版均开源免费 5.6 反向传播与自动微分 在许多机器学习的应用中,我们通过计算学习目标关于模型参数的梯度,然后执行梯度下降(见 7.1 节)找好的模型参数。对于给定的目标函数,我们可以利用微积分的链式法则得到其对模型参数的梯度(见 5.2.2 节)。我们在 5.3 节已经尝试对平方损失结果关于线性回归模型参数求梯度。 考虑下面的函数: $$ f(x) = \sqrt{ x^{2} + \exp(x^{2}) } + \cos \Big[ x^{2} + \exp(x^{2}) \Big]. \tag{5.
在许多机器学习的应用中,我们通过计算学习目标关于模型参数的梯度,然后执行梯度下降(见 7.1 节)找好的模型参数。对于给定的目标函数,我们可以利用微积分的链式法则得到其对模型参数的梯度(见 5.2.2 节)。我们在 5.3 节已经尝试对平方损失结果关于线性回归模型参数求梯度。
考虑下面的函数:
由链式法则,并注意到微分的线性性,我们可以得到:
像这样显式求解得到这样冗长的导数表达往往不切实际。在实践中这意味着若不小心处理,梯度的实现可能比计算函数值要昂贵得多,这增加了不必要的开销。对于神经网络模型,反向传播算法(Kelley, 1960; Bryson, 1961; Dreyfus, 1962; Rumelhart et al., 1986)是一种计算误差对模型参数梯度的有效方法。
深度学习领域将链式法则的功用发挥到了极致,输入 \boldsymbol{x} 通过多层复合的函数得到函数值 \boldsymbol{y} :
其中,\boldsymbol{x} 是输入(如图像),\boldsymbol{y} 是观测值(如类标签),每个函数 f_{i}, i = 1, \dots, K,有各自的参数。

在一般的多层神经网络中,第 i 层中有函数 f_{i}(\boldsymbol{x}_{i-1}) = \sigma(\boldsymbol{A}_{i-1}\boldsymbol{x}_{i-1} + \boldsymbol{b}_{i-1}) 。其中 x_{i-1} 是 i=1 层的输出和一个激活函数 \sigma,例如 sigmoid 函数 \displaystyle \frac{1}{1-e^{-x}},\tanh 或修正线性单元(rectified linear unit, ReLU)。训练这样的模型,我们需要一个损失函数 L,对其值求关于所有模型参数 \boldsymbol{A}_j, \boldsymbol{b}_{j}, j=1, \dots, K 的梯度。这同时要求我们求其对模型中各层的输入的梯度。例如,如果我们有输入\boldsymbol{x}和观测值\boldsymbol{y}和一个网络结构(如图 5.8):
我们关心找到使得下面的平方损失最小的 \boldsymbol{A}_{j}, \boldsymbol{b}_{j}, j=1, \dots, K:
其中 \boldsymbol{\theta} = \{ \boldsymbol{A}_{0}, \boldsymbol{b}_{0}, \dots, \boldsymbol{A}_{K-1}, \boldsymbol{b}_{K-1} \}。
为得到相对于参数集 \boldsymbol{\theta} 的梯度,我们需要得到 L 对每一层参数 \theta_j = \{\boldsymbol{A}_j, \boldsymbol{b}_{j}\}, j=0, \dots, K-1 的偏导数。根据链式法则,我们得到
其中橙色的项是某层输出相对于其输入的偏导数,而蓝色的项是某层的输出相对于其参数的偏导数。假设我们已经计算出了 \displaystyle \frac{\partial L}{\partial \boldsymbol{\theta}_{i+1}},那么我们可以在计算 \displaystyle \frac{\partial L}{\partial \boldsymbol{\theta}_{i}} 中省去大量的工作,因为我们只需计算方框中的项。图 5.9 中表示了像这样在网络中反向传递梯度的图示。

对此更深入的讨论见 Justin Domke 的 Lecture Notes
事实上,反向传播是数值分析中常采用采用的自动微分 (automatic differentiation) 的一种特殊情况。我们可将其看作是一组通过中间变量和链式法则,计算一个函数之(直到机器精度的)精确数值(而非符号)梯度。自动微分始于一系列初等算术运算(如加法、乘法)和初等函数(如 \sin、\cos、\exp、\log)。通过将链式法则应用于这些操作,我们可以自动计算出相当复杂的函数的梯度。自动微分适用于一般的程序,具有正向和反向两种模式。Baydin et al.(2018)对机器学习中的自动微分进行了很好的概述。
图5.10显示了一个简单的描述数据流动的图。数据流从输入节点 x 开始,通过中间变量 a,b 最后得到输出 y。如果我们要计算导数 \displaystyle \frac{\mathrm{d}y}{\mathrm{d}x},我们可以用链式法则:
直观来讲,正向模式和反向模式的自动微分在处理多重嵌套梯度的乘积顺序上有所不同。由于矩阵乘法有结合律,我们可以采用下面两种不同的方法计算梯度:
式(5.120)就是反向自动微分,因为梯度通过计算图向后传播(即与数据流流向相反)。式(5.121)是正向自动微分,其中梯度与数据的流向都是从左到右。
下面,我们将重点关注反向自动微分,即反向传播。在神经网络中,输入的维数通常比标签的维数高得多,反向自动微分在计算上比正向的计算消耗低得多。让我们从一个典型的的例子开始理解它。
示例 5.14 反向自动微分
考虑函数f(x) = \sqrt{ x^{2} + \exp\{ x^{2} \} } + \cos \Big( x^{2} + \exp\{ x^{2} \} \Big) \tag{5.122}这个函数就是(5.109)。如果我们要在计算机上实现这个函数,我们将使用一些中间变量来节省一些计算:\begin{align}a &= x^{2}, \tag{5.123}\\b &= \exp\{ a \}, \tag{5.124}\\c &= a + b, \tag{5.125}\\d &= \sqrt{ c }, \tag{5.126}\\e &= \cos(c), \tag{5.127}\\f &= d + e. \tag{5.128}\\\end{align}图 5.11 计算图。输入为 x,输出为函数值 f,并有中间变量 a ~ e
计算该函数的梯度和我们使用链式法则的思想类似。请注意,前面一组方程所需的操作比(5.109)中定义的函数的直接实现要少。图 5.11 中对应的计算图显示了得到函数值 f 所需的数据流和计算。
包含中间变量的方程组可以被认为是一个计算图,它被广泛应用于神经网络库的实现。回顾初等函数导数的定义,我们可以直接计算中间变量与其相应输入的导数,就得到了下面这些式子:\begin{align}\displaystyle \frac{ \partial a }{ \partial x } &= 2x \tag{5.129}\\\displaystyle \frac{ \partial b }{ \partial a } &= \exp\{ a \}\tag{5.130}\\\displaystyle \frac{ \partial c }{ \partial a } &= 1 = \displaystyle \frac{ \partial c }{ \partial b } \tag{5.131}\\\displaystyle \frac{ \partial d }{ \partial c } &= \frac{1}{2\sqrt{ c }}\tag{5.132}\\\displaystyle \frac{ \partial e }{ \partial c } &= -\sin(c)\tag{5.133}\\\displaystyle \frac{ \partial f }{ \partial d } &= 1 = \displaystyle \frac{ \partial f }{ \partial e }. \tag{5.134}\end{align}此时我们看图 5.11 中的计算图,我们可以通过从输出逆向地计算以得到 \displaystyle \frac{\partial f}{\partial x}:\begin{align}\displaystyle \frac{ \partial f }{ \partial c } &= \displaystyle \frac{ \partial f }{ \partial d } \displaystyle \frac{ \partial d }{ \partial c } + \displaystyle \frac{ \partial f }{ \partial e } \displaystyle \frac{ \partial e }{ \partial c } \tag{5.135}\\\displaystyle \frac{ \partial f }{ \partial b } &= \displaystyle \frac{ \partial f }{ \partial c } \displaystyle \frac{ \partial c }{ \partial b } \tag{5.136}\\\displaystyle \frac{ \partial f }{ \partial a } &= \displaystyle \frac{ \partial f }{ \partial b } \displaystyle \frac{ \partial b }{ \partial a } + \displaystyle \frac{ \partial f }{ \partial c } \displaystyle \frac{ \partial c }{ \partial a } \tag{5.137}\\\displaystyle \frac{ \partial f }{ \partial x } &= \displaystyle \frac{ \partial f }{ \partial a } \displaystyle \frac{ \partial a }{ \partial x }. \tag{5.138}\\\end{align}注意,我们在上面隐式地应用了链式法则。最后我们用上前面求得的初等函数导数代入上面的式子,得到\begin{align}\displaystyle \frac{ \partial f }{ \partial c } &= 1 \cdot \frac{1}{2\sqrt{ c }} + 1 \cdot \big[ -\sin(c) \big] \tag{5.139}\\\displaystyle \frac{ \partial f }{ \partial b } &= \displaystyle \frac{ \partial f }{ \partial c } \cdot 1\tag{5.140}\\\displaystyle \frac{ \partial f }{ \partial a } &= \displaystyle \frac{ \partial f }{ \partial b } \exp\{ a \} + \displaystyle \frac{ \partial f }{ \partial c } \cdot 1 \tag{5.141}\\\displaystyle \frac{ \partial f }{ \partial x } &= \displaystyle \frac{ \partial f }{ \partial a } \cdot 2x. \tag{5.142}\end{align}r如果把上面的每个偏导数看做一个变量,我们可以观察到,计算导数所需的计算量与函数值本身的计算量相似。这非常违反直觉,因为式(5.110)中 \displaystyle \frac{\partial f}{\partial x} 的比式(5.109)中的函数 f (x) 要复杂得多。
一般的自动微分是示例 5.14 的形式化。设 x_{1}, \dots, x_{d} 是函数的输入变量,x_{d+1}, \dots, x_{D-1} 是中间变量,x_{D} 是输出变量。则计算图可以表示为:
其中,g_{i}(\cdot) 是初等函数,x_{\text{Pa}(x_{i})} 是图中变量 x_i 的所有父节点。给定一个以这种方式定义的函数,我们可以使用链式法则逐步计算该函数的导数。回想一下,根据定义,f=x_{D},因此
对于其他变量 x_{i},我们应用链式法则
其中,x_{\text{Pa}(x_{i})} 是计算图中 x_j 的父节点的集合。式(5.143)是一个函数的正向传播,而(5.145)是梯度通过计算图的反向传播。在神经网络的训练中,我们将标签的预测误差反向传播。
自动微分应用于可表示为计算图,且组成计算图的基本的函数是可微时的情形。事实上,这个函数甚至可能不是一个数学意义上的函数,而是一个程序。然而并不是所有的程序都能自动微分,例如当我们找不到可微的初等函数时。程序结构中,如循环和if语句,在涉及自动微分的处理时需要更为小心。