深度网络现代实践 - 深度前馈网络之反向传播和其他的微分算法篇-续

序言

反向传播(Backpropagation,简称backprop)是神经网络训练过程中最关键的技术之一,尤其在多层神经网络中广泛应用。它是一种与优化方法(如梯度下降法)结合使用的算法,用于计算网络中各参数的梯度,进而通过调整这些参数来最小化损失函数,从而提高模型的预测准确性和泛化能力。微分算法在机器学习中占据核心地位,主要用于计算复杂函数的梯度。反向传播作为其中的一种,特别适用于神经网络中的梯度计算。其基本原理是利用链式法则,通过计算图中每个节点的梯度来逐步反向传播误差信号,从而实现对网络参数的优化。

反向传播和其他的微分算法

  • 当我们使用前馈神经网络接收输入 x \boldsymbol{x} x并产生输出 y ^ \hat{\boldsymbol{y}} y^时,信息通过网络向前流动。

  • 前向传播(forward propagation)

    • 输入 x \boldsymbol{x} x提供初始信息,然后传播到每一层的隐藏单元,最终产生输出 y ^ \hat{\boldsymbol{y}} y^
  • 反向传播(back propagation)

    • 在训练过程中,前向传播可以持续向前直到它产生一个标量代价函数 J ( θ ) J(\boldsymbol{\theta}) J(θ)反向传播(back propagation),经常简称为backprop,允许来自代价函数的信息通过网络向后流动,以便计算梯度。
    • 计算梯度的解析表达式是很直观的,但是数值化地求解这样的表达式在计算上可能是昂贵的。反向传播算法使用简单和廉价的程序来实现这个目标。
    • 反向传播这个术语经常被误解为用于多层神经网络的整个学习算法。实际上,反向传播仅指用于计算梯度的方法,而另一种算法,例如随机梯度下降,使用该梯度来进行学习。
    • 此外,反向传播仅适用于多层神经网络,但原则上它可以计算任何函数的导数(对于一些函数,正确的响应是报告函数的导数是未定义的)。
    • 特别地,我们会描述如何计算一个任意函数 f f f的梯度 ∇ x f ( x , y ) \nabla_x f(\boldsymbol{x,y}) xf(x,y)
      • 其中 x \boldsymbol{x} x是一组变量,我们需要它们的导数
      • y \boldsymbol{y} y是另外一组函数的输入变量,但我们并不需要它们的导数。
    • 在学习算法中,我们最常需要的梯度是成本函数关于参数的梯度,即 ∇ θ J ( θ ) \nablaθJ(θ) θJ(θ)。许多机器学习任务涉及计算其他导数,作为学习过程的一部分,或者用来分析学习的模型。反向传播算法也适用于这些任务,并且不限于计算成本函数关于参数的梯度。通过网络传播信息来计算导数的想法是非常通用的,并且可以用于计算诸如具有多个输出的函数 f f f Jacobi \text{Jacobi} Jacobi矩阵的值。我们这里描述的是最常用的情况, f f f只有单个输出。
  • 算法3


    典型深度神经网络中的前向传播和代价函数的计算。
    损失函数 L ( y ^ , y ) L(\boldsymbol{\hat{y}},\boldsymbol{y}) L(y^,y)取决于输出 y ^ \boldsymbol{\hat{y}} y^和目标 y \boldsymbol{y} y(参见:基于梯度的学习篇中损失函数的示例)。
    为了获得总代价 J J J,损失函数可以加上正则项 Ω ( θ ) \Omega(\theta) Ω(θ),其中 θ \theta θ包含所有参数(权重和偏置)。
    算法4说明了如何计算 J J J对参数 W \boldsymbol{W} W b \boldsymbol{b} b的梯度。
    为简单起见,该演示仅使用单个输入样例 x \boldsymbol{x} x。实际应用应该使用minibatch。请参见用于MLP训练的反向传播算法以获得更加真实的演示。


    伪代码
    R e q u i r e \bold{Require} Require: l l l, Network depth
    R e q u i r e \bold{Require} Require: W ( i ) \boldsymbol{W}^{(i)} W(i), i ∈ { 1 , … , l } i\in\{1,\dots,l\} i{1,,l}, the weight matrices of the model
    R e q u i r e \bold{Require} Require: b ( i ) \boldsymbol{b}^{(i)} b(i), i ∈ { 1 , … , l } i\in\{1,\dots,l\} i{1,,l}, the bias parameters of the model
    R e q u i r e \bold{Require} Require: x \boldsymbol{x} x, the input to process
    R e q u i r e \bold{Require} Require: y \boldsymbol{y} y, the target output
    h ( 0 ) = x \boldsymbol{h}^{(0)} = x h(0)=x
    f o r \bold{for} for k k k = 1 , … , l 1,\dots,l 1,,l d o \bold{do} do
    \quad a ( k ) = b ( k ) + W ( k ) h ( k − 1 ) \boldsymbol{a}^{(k)} = \boldsymbol{b}^{(k)} + \boldsymbol{W}^{(k)}\boldsymbol{h}^{(k-1)} a(k)=b(k)+W(k)h(k1)
    \quad h ( k ) = f ( a ( k ) ) \boldsymbol{h}^{(k)} = f(\boldsymbol{a}^{(k)}) h(k)=f(a(k))
    e n d \bold{end} end f o r \bold{for} for
    y ^ = h ( l ) \boldsymbol{\hat{y}} = \boldsymbol{h}^{(l)} y^=h(l)
    J = L ( y ^ , y ) + λ Ω ( θ ) J = L(\boldsymbol{\hat{y}},\boldsymbol{y}) + \lambda\Omega(\theta) J=L(y^,y)+λΩ(θ)


  • 算法4


    深度神经网络中算法3的反向计算,它使用了不止输入 x \boldsymbol{x} x和目标 y \boldsymbol{y} y
    该计算对于每一层 k k k都产生了对激活 a ( k ) \boldsymbol{a}^{(k)} a(k)的梯度,从输出层开始向后计算一直到第一个隐藏层。
    这些梯度可以看作是对每层的输出应如何调整以减小误差的指导,根据这些梯度可以获得对每层参数的梯度。
    权重和偏置上的梯度可以立即用作随机梯度更新的一部分(梯度算出后即可执行更新),或者与其他基于梯度的优化方法一起使用。


    伪代码
    After the forward computation, compute the gradient on the output layer:
    g ← ∇ y ^ J = ∇ y ^ L ( y ^ , y ) \boldsymbol{g} \gets \nabla_{\boldsymbol{\hat{y}}}J= \nabla_{\boldsymbol{\hat{y}}}L(\boldsymbol{\hat{y}},\boldsymbol{y}) gy^J=y^L(y^,y)
    f o r \bold{for} for k k k = l , l − 1 , … , 1 l,l-1,\dots,1 l,l1,,1 d o \bold{do} do
    \quad Convert the gradient on the layer’s output into a gradient into the prenonlinearity activation (element-wise multiplication if f is element-wise):
    \quad g ← ∇ a ( k ) J = g ⊙ f ′ ( a ( k ) ) \boldsymbol{g} \gets \nabla_{\boldsymbol{a}^{(k)}}J=\boldsymbol{g}\odot f'(\boldsymbol{a}^{(k)}) ga(k)J=gf(a(k))
    \quad Compute gradients on weights and biases (including the regularization term,
    where needed):
    \quad ∇ b ( k ) J = g + λ ∇ b ( k ) Ω ( θ ) \nabla_{\boldsymbol{b}^{(k)}}J=\boldsymbol{g}+\lambda\nabla_{\boldsymbol{b}^{(k)}}\Omega(\theta) b(k)J=g+λb(k)Ω(θ)
    \quad ∇ W ( k ) J = g h ( k − 1 ) ⊤ + λ ∇ W ( k ) Ω ( θ ) \nabla_{\boldsymbol{W}^{(k)}}J=\boldsymbol{g}\boldsymbol{h}^{(k-1)\top}+\lambda\nabla_{\boldsymbol{W}^{(k)}}\Omega(\theta) W(k)J=gh(k1)+λW(k)Ω(θ)
    \quad Propagate the gradients w.r.t. the next lower-level hidden layer’s activations:
    \quad g ← ∇ h ( k − 1 ) J = W ( k ) ⊤ g \boldsymbol{g} \gets \nabla_{\boldsymbol{h}^{(k-1)}}J=\boldsymbol{W}^{(k)\top}\boldsymbol{g} gh(k1)J=W(k)g
    e n d \bold{end} end f o r \bold{for} for


一般化的反向传播

  • 反向传播算法非常简单。

  • 为了计算某个标量 z z z对图中它的一个祖先 x \boldsymbol{x} x的梯度,我们首先观察到对 z z z的梯度由 ∂ z ∂ z = 1 \frac{\partial z}{\partial z}=1 zz=1给出。我们然后可以计算对图中 z z z的操作的 Jacobi \text{Jacobi} Jacobi矩阵。我们继续乘以 Jacobi \text{Jacobi} Jacobi矩阵,以这种方式向后穿过图,直到我们到达 x \boldsymbol{x} x。对于从 z z z触发可以经过两个或更多路径向后行进而到达的任意节点,我们简单的对该节点来自不同路径上的梯度进行求和。

  • 更正式地,图 G \mathcal{G} G中的每个节点对应着一个变量。为了实现最大的一般化,我们将这个变量描述为一个张量 V \bold{V} V。张量通常可以具有任意维度,并且包含标量、向量和矩阵。

  • 我们假设每个变量 V \bold{V} V与下列子程序相关联:

    • get_operation( V \bold{V} V):
      • 它返回用于计算 V \bold{V} V的操作,代表了在计算图中流入 V \bold{V} V的边。
      • 例如,可能有一个Python或者C++的类表示矩阵乘法操作,以及get_operation函数。
      • 假设我们的一个变量是由矩阵乘法产生的, C = A B C=AB C=AB
      • 那么,get_operation( V \bold{V} V)返回一个指向相应C++类的实力的指针。
    • get_consumers( V , G \bold{V},\mathcal{G} V,G):
      • 它返回一组变量,是计算图 G \mathcal{G} G V \bold{V} V的孩子节点。
    • get_inputs( V , G \bold{V},\mathcal{G} V,G):
      • 它返回一组变量,是计算图 G \mathcal{G} G V \bold{V} V的父节点。
  • 重要:每个操作op也与bprop操作相关联。该bprop操作可以计算如公式: ∇ X z = ∑ j ( ∇ X Y j ) ∂ z ∂ Y j \nabla_{\bold{X}}z=\sum\limits_j(\nabla_{\bold{X}}\bold{Y}_j)\displaystyle\frac{\partial z}{\partial \bold{Y}_j} Xz=j(XYj)Yjz描述的 Jacobi \text{Jacobi} Jacobi向量积。这是反向传播算法能够实现很大通用性的原因。

  • 每个操作负责了解如何通过它参与的图中的边来反向传播。例如,我们可以使用矩阵乘法操作来产生变量 C = A B C = AB C=AB

    • 假设标量 z z z关于 C C C的梯度是 G G G
    • 矩阵乘法操作负责定义两个反向传播规则,每个规则对应于一个输入变量。
    • 如果我们调用bprop方法来请求 A A A 的梯度,在给定输出的梯度为 G G G的情况下,那么矩阵乘法操作的bprop方法必须说明对 A A A的梯度是 G B ⊤ GB^\top GB
    • 类似的,如果我们调用bprop方法来请求 B B B的梯度,那么矩阵操作负责实现bprop方法并指定期望的梯度是 A ⊤ G A^\top G AG
    • 反向传播算法本身并不需知道任何微分法则。它只需要使用正确的参数调用每个操作的bprop 方法即可。正式地, op.bprop(inputs, X , G \bold{X},G X,G)必须返回: ∑ i ( ∇ X op . f ( inputs ) i ) G i \sum\limits_i(\nabla_{\bold{X}} \text{op}.f(\text{inputs})_i)G_i i(Xop.f(inputs)i)Gi
    • 这只是如公式: ∇ X z = ∑ j ( ∇ X Y j ) ∂ z ∂ Y j \nabla_{\bold{X}}z=\sum\limits_j(\nabla_{\bold{X}}\bold{Y}_j)\displaystyle\frac{\partial z}{\partial \bold{Y}_j} Xz=j(XYj)Yjz所表达的链式法则的实现。
      • 说明:
        • inputs是提供给操作的一组输入
        • op.f是操作实现的数学函数
        • X \bold{X} X是输入,我们想要计算关于它的梯度
        • G G G是操作对输出的梯度
    • op.bprop方法应该总是假装它的所有输入彼此不同,即使它们不是。
      • 例如,如果mul操作传递两个 x x x来计算 x 2 x^2 x2,op.bprop方法应该仍然返回 x x x作为对于两个输入的导数。
      • 反向传播算法后面会将这些变量加起来获得 2 x 2x 2x,这是 x x x上总的正确导数。
  • 反向传播算法的软件实现通常提供操作和其bprop 两种方法,所以深度学习软件库的用户能够对使用诸如矩阵乘法、指数运算、对数运算等等常用操作构建的图进行反向传播。构建反向传播新实现的软件工程师或者需要向现有库添加自己的操作的高级用户通常必须手动为新操作推导op.bprop方法。

  • 反向传播算法的正式描述参见算法5

  • 算法5


    反向传播算法最外围的骨架。这部分做简单的设置和清理工作。大多数重要的工作在发生在算法6的子程序build_grad中。


    伪代码
    R e q u i r e \bold{Require} Require: T \mathbb{T} T, 计算梯度的变量的目标集
    R e q u i r e \bold{Require} Require: G \mathbb{G} G, 计算图
    R e q u i r e \bold{Require} Require: z z z, 要求导的变量
    \quad Let G ′ \mathcal{G}' G be G \mathcal{G} G pruned to contain only nodes that are ancestors of z z z and descendents of nodes in T \mathbb{T} T.
    \quad Initialize grad_table, a data structure associating tensors to their gradients
    \quad grad_table[ z z z] ← \gets 1
    \quad f o r \bold{for} for V \bold{V} V in T \mathbb{T} T d o \bold{do} do
    \quad\quad build_grad( V \bold{V} V, G \mathcal{G} G, G ′ \mathcal{G}' G, grad_table)
    \quad e n d \bold{end} end f o r \bold{for} for
    \quad Return grad_table restricted to T \mathbb{T} T


  • 算法6


    反向传播算法的内循环子程序build_grad( V \bold{V} V, G \mathcal{G} G, G ′ \mathcal{G}' G, grad_table),被在算法5中定义的反向传播算法调用。


    伪代码
    R e q u i r e \bold{Require} Require: V \bold{V} V, the variable whose gradient should be added to G \mathcal{G} G and grad_table.
    R e q u i r e \bold{Require} Require: G \mathcal{G} G, the graph to modify.
    R e q u i r e \bold{Require} Require: G ′ \mathcal{G}' G, the restriction of G \mathcal{G} G to nodes that participate in the gradient.
    R e q u i r e \bold{Require} Require: grad_table, a data structure mapping nodes to their gradients
    \quad i f \bold{if} if V \mathbb{V} V is in grad_table t h e n \bold{then} then
    \quad\quad Return grad_table[ V \bold{V} V]
    e n d \quad\bold{end} end i f \bold{if} if
    i ← 1 \quad i\gets1 i1
    \quad f o r \bold{for} for C \bold{C} C in get_consumers( V \bold{V} V, G ′ \mathcal{G}' G) d o \bold{do} do
    \quad \quad op get_operation( C \bold{C} C)
    \quad D \bold{D} D build_grad( C \bold{C} C, G \mathcal{G} G, G ′ \mathcal{G}' G, grad_table)
    \quad \quad G ( i ) \bold{G}^{(i)} G(i) ← \gets op:bprop(get_inputs( C \bold{C} C, G ′ \mathcal{G}' G), V \bold{V} V, D \bold{D} D)
    \quad i ← i + 1 \quad i \gets i + 1 ii+1
    \quad e n d \bold{end} end f o r \bold{for} for
    \quad G ← ∑ i G ( i ) \bold{G} \gets \sum_i \bold{G}^{(i)} GiG(i)
    \quad grad_table[ V \bold{V} V] = G \bold{G} G
    \quad Insert G \bold{G} G and the operations creating it into G \mathcal{G} G
    \quad Return G \bold{G} G


  • 在微积分中的链式法则中,我们使用反向传播作为一种策略来避免多次计算链式法则中的相同子表达式。

  • 由于这些重复子表达式的存在,简单的算法可能具有指数运行时间。现在我们已经详细说明了反向传播算法,我们可以去理解它的计算成本。

  • 如果我们假设每个操作的执行都有大致相同的开销,那么我们可以依据执行操作的数量来分析计算成本。注意这里我们将一个操作记为计算图的基本单位,它实际可能包含许多算术运算(例如,我们可能将矩阵乘法视为单个操作)。

  • 在具有 n n n个节点的图中计算梯度,将永远不会执行超过 O ( n 2 ) \Omicron(n^2) O(n2)个操作,或者存储超过 O ( n 2 ) \Omicron(n^2) O(n2)个操作的输出。这里我们是对计算图中的操作进行计数,而不是由底层硬件执行的单独操作,所以重要的是要记住每个操作的运行时间可能是高度可变的。

  • 例如,两个矩阵相乘可能对应着图中的一个单独的操作,但这两个矩阵可能每个都包含数百万个元素。我们可以看到,计算梯度至多需要 O ( n 2 ) \Omicron(n^2) O(n2)的操作,因为在最坏的情况下,前向传播的步骤将执行原始图的全部 n n n个节点(取决于我们想要计算的值,我们可能不需要执行整个图)。

  • 反向传播算法在原始图的每条边添加一个 Jacobi \text{Jacobi} Jacobi向量积,可以用 O ( 1 ) \Omicron(1) O(1)个节点来表达。因为计算图是有向无环图,它至多有 O ( n 2 ) \Omicron(n^2) O(n2)条边。对于实践中常用的图的类型,情况会更好。

  • 大多数神经网络的代价函数大致是链式结构的,使得反向传播只有 O ( n ) \Omicron(n) O(n)的成本。这远远胜过简单的方法,简单方法可能需要执行指数级的节点。

  • 这种潜在的指数级代价可以通过非递归地扩展和重写递归链式法则(公式: ∂ u ( n ) ∂ u ( j ) = ∑ i : j ∈ P a ( u ( i ) ) ∂ u ( n ) ∂ u ( i ) ∂ u ( i ) ∂ u ( j ) \displaystyle\frac{\partial u^{(n)}}{\partial u^{(j)}}=\sum\limits_{i:j\in Pa(u^{(i)})}\frac{\partial u^{(n)}}{\partial u^{(i)}} \frac{\partial u^{(i)}}{\partial u^{(j)}} u(j)u(n)=i:jPa(u(i))u(i)u(n)u(j)u(i))来看出:
    ∂ u ( n ) ∂ u ( j ) = ∑ path ( u ( π 1 ) , u ( π 2 ) , … , u ( π t ) ) , from π i = j t o π t = n ∏ k = 2 t ∂ u ( π k ) ∂ u ( π k − 1 ) \frac{\partial u^{(n)}}{\partial u^{(j)}}=\sum\limits_{\text{path}(u^{(\pi_1)},u^{(\pi_2)},\dots,u^{(\pi_t)}), \hspace{2pt}\text{from}\hspace{2pt}\pi_i=j\hspace{2pt}to\hspace{2pt}\pi_t=n} \prod\limits_{k=2}^t \frac{\partial u^{(\pi_k)}}{\partial u^{(\pi_{k-1})}} u(j)u(n)=path(u(π1),u(π2),,u(πt)),fromπi=jtoπt=nk=2tu(πk1)u(πk)

    • 由于节点 j j j到节点 n n n的路径数目可以在这些路径的长度上指数地增长,所以上述求和符号中的项数(这些路径的数目),可能以前向传播图的深度的指数级增长。
    • 会产生如此大的成本是因为对于 ∂ u ( i ) ∂ u ( j ) \frac{\partial u^{(i)}}{\partial u^{(j)}} u(j)u(i),相同的计算会重复进行很多次。
    • 为了避免这种重新计算,我们可以将反向传播看作一种表填充算法,利用存储的中间结果 ∂ u ( n ) ∂ u ( i ) \frac{\partial u^{(n)}}{\partial u^{(i)}} u(i)u(n)来对表进行填充。
    • 图中的每个节点对应着表中的一个位置,这个位置存储对该节点的梯度。
    • 通过顺序填充这些表的条目,反向传播算法避免了重复计算许多公共子表达式。
    • 这种表填充策略有时被称为动态规划(dynamic programming)。

用于MLP训练的反向传播算法实例

  • 作为一个例子,我们利用反向传播算法来训练多层感知器(MLP)。
  • 这里,我们考虑具有单个隐藏层的一个非常简单的多层感知机。为了训练这个模型,我们将使用小批量(minibatch)随机梯度下降算法。反向传播算法用于计算单个minibatch上的代价的梯度。
    • 具体来说,我们使用训练集上的一组minibatch实例,将其规范化为一个设计矩阵 X \boldsymbol{X} X以及相关联的类标签的向量 y \boldsymbol{y} y

    • 网络计算隐藏特征层: H = max ⁡ { 0 , X W ( 1 ) } \boldsymbol{H}=\max\{0,\boldsymbol{XW^{(1)}}\} H=max{0,XW(1)}。为了简化表示,我们在这个模型中不使用偏置。

    • 假设我们的图语言包含relu操作,该操作可以对 max ⁡ { 0 , Z } \max\{0,\boldsymbol{Z}\} max{0,Z}表达式的每个元素分别进行计算。

    • 类的非归一化对数概率的预测将随后由 H W ( 2 ) \boldsymbol{HW^{(2)}} HW(2) 给出。假设我们的图语言包含cross_entropy操作,用以计算目标 y \boldsymbol{y} y和由这些未归一化对数概率定义的概率分布间的交叉熵。所得到的交叉熵定义了代价函数 J MLE J_{\text{MLE}} JMLE

    • 最小化这个交叉熵将执行对分类器的最大似然估计。然而,为了使得这个例子更加真实,我们也包含一个正则项。总的代价函数为:
      J = J MLE + λ ( ∑ i , j ( W i , j ( 1 ) ) 2 + ∑ i , j ( W i , j ( 2 ) ) 2 ) J=J_{\text{MLE}}+\lambda \left(\sum\limits_{i,j}(W_{i,j}^{(1)})^2+\sum\limits_{i,j}(W_{i,j}^{(2)})^2 \right) J=JMLE+λ(i,j(Wi,j(1))2+i,j(Wi,j(2))2)

    • 包含了交叉熵和系数为 λ \lambda λ的权重衰减项。它的计算图在下图中给出。

    • 例如,用于计算代价函数的计算图,这个代价函数是使用交叉熵损失以及权重衰减训练我们的单层MLP示例所产生的。
      在这里插入图片描述

    • 说明:

      • 这个示例的梯度计算图实在太大,以至于绘制或者阅读都将是乏味的。这显示出了反向传播算法的优点之一,即它可以自动生成梯度,而这种计算对于软件工程师来说需要进行直观但冗长的手动推导.
      • 我们可以通过观察图中的正向传播图来粗略地描述反向传播算法的行为。为了训练,我们希望计算 ∇ W ( 1 ) J \nabla_{\boldsymbol{W}^{(1)}}J W(1)J ∇ W ( 2 ) J \nabla_{\boldsymbol{W}^{(2)}}J W(2)J
      • 有两种不同的路径从 J J J后退到权重:
        • 一条通过交叉熵成本
        • 另一条通过权重衰减成本
      • 权重衰减成本相对简单,它总是对 W ( i ) \boldsymbol{W}^{(i)} W(i)上的梯度贡献 2 λ W ( i ) 2\lambda\boldsymbol{W}^{(i)} 2λW(i)
      • 另一条通过交叉熵成本的路径稍微复杂一些。
        • G \boldsymbol{G} G是cross_entropy操作提供的对未归一化对数概率 U ( 2 ) \boldsymbol{U}^{(2)} U(2)的梯度。
        • 反向传播算法现在需要探索两个不同的分支。
        • 在较短的分支上,它使用对矩阵乘法的第二个变量的反向传播规则,将 H ⊤ G \boldsymbol{H}^\top\boldsymbol{G} HG加到 W ( 2 ) \boldsymbol{W}^{(2)} W(2)的梯度上。
        • 另一条更长些的路径沿着网络逐步下降。
          • 首先,反向传播算法使用对矩阵乘法的第一个变量的反向传播规则,计算 ∇ H J = G W ( 2 ) ⊤ \nabla_{\boldsymbol{H}}J=\boldsymbol{GW^{(2)\top}} HJ=GW(2)
          • 接下来,relu操作使用期反向传播规则来对关于 U ( 1 ) \boldsymbol{U}^{(1)} U(1)的梯度中小于 0 0 0的部分清零。
      • 记上述结果为 G ′ \boldsymbol{G}' G。反向传播算法的最后一步是使用对matmul操作的第二个变量的反向传播规则,将 X ⊤ G ′ \boldsymbol{X}^\top\boldsymbol{G}' XG加到 W ( 1 ) \boldsymbol{W}^{(1)} W(1)的梯度上。
      • 在计算了这些梯度以后,梯度下降算法或者其他的优化算法的责任就是使用这些梯度来更新参数。
  • 对于MLP,计算成本主要来源于矩阵乘法matmul操作
    • 前向传播阶段,我们乘以每个权重矩阵,得到了 O ( ω ) \Omicron(\omega) O(ω)数量的乘-加,其中 ω \omega ω是权重的数量。
    • 反向传播阶段,我们乘以每个权重矩阵的转置,具有相同的计算成本。
    • 算法主要的存储成本是我们需要将输入存储到隐藏层的非线性中去。这些值从被计算时开始存储,直到反向过程回到了同一点。
    • 因此存储成本是 O ( m n h ) \Omicron(mn_h) O(mnh),其中 m m m是minibatch中样例的数目, n h n_h nh是隐藏单元的数量。

复杂化

  • 我们这里描述的反向传播算法要比现实中实际使用的实现要简单。
  • 正如前面提到的,我们将操作的定义限制为返回单个张量的函数。大多数软件实现需要支持可以返回多个张量的操作。例如,如果我们希望计算张量中的最大值和该值的索引,则最好在单次运算中计算两者,因此将该过程实现为具有两个输出的操作效率更高。
  • 我们还没有描述如何控制反向传播的内存消耗。反向传播经常涉及将许多张量加在一起。在朴素方法中,将分别计算这些张量中的每一个,然后在第二步中对所有这些张量求和。朴素方法具有过高的存储瓶颈,可以通过保持一个缓冲器,并且在计算时将每个值加到该缓冲器中来避免该瓶颈。
  • 反向传播的现实实现还需要处理各种数据类型,例如,32位浮点数,64位浮点数和整型。处理这些类型的策略需要特别的设计考虑。
  • 一些操作具有未定义的梯度,并且重要的是跟踪这些情况并且确定用户请求的梯度是否是未定义的。
  • 各种其他技术的特性使现实世界的微分更加复杂。这些技术性并不是不可逾越的,本章已经描述了计算微分所需的关键智力工具,但重要的是要知道还有许多的精妙之处存在。

深度学习界以外的微分(注:晦涩难懂则可暂且略过)

  • 深度学习界在某种程度上已经与更广泛的计算机科学界隔离开来,并且在很大程度上发展了自己关于如何进行微分的文化态度。
  • 更一般地, 自动微分(automatic differentiation) 领域关心如何以算法方式计算导数。这里描述的反向传播算法只是自动微分的一种方法。它是一种称为反向模式累加 (reverse mode accumulation) 的更广泛类型的技术的特殊情况。其他方法以不同的顺序来计算链式法则的子表达式。一般来说,确定一种计算的顺序使得计算开销最小,是困难的问题。找到计算梯度的最优操作序列是 NP 完全问题 (Naumann, 2008),在这种意义上,它可能需要将代数表达式简化为它们最廉价的形式。
  • 例如,假设我们有变量 p 1 , p 2 , … , p n p_1,p_2,\dots,p_n p1,p2,,pn表示概率,和变量 z i , z 2 , … , z n z_i,z_2,\dots,z_n zi,z2,,zn表示未归一化的对数概率。假设我们定义: q i = e z i ∑ i e z i q_i=\frac{e^{z_i}}{\sum_i e^{z_i}} qi=ieziezi
    • 其中我们通过指数化、求和与除法运算构建 softmax \text{softmax} softmax函数,并构造交叉熵损失函数 J = − ∑ i p i log ⁡ q i J=-\sum_i p_i\log q_i J=ipilogqi
    • 人类数学家可以观察到 J J J z i z_i zi的导数采用了非常简单的形式: p i q i − p i p_iq_i-p_i piqipi
    • 反向传播算法不能够以这种方式来简化梯度,而是会通过原始图中的所有对数和指数操作显示地传播梯度。一些软件库如 Theano(Bergstra et al., 2010b;Bastien et al., 2012b) 能够执行某些种类的代数替换来改进由纯反向传播算法提出的图。
  • 当前向图 G \mathcal{G} G具有单个输出节点,并且每个偏导数 ∂ u ( i ) ∂ u ( j ) \frac{\partial u^{(i)}}{\partial u^{(j)}} u(j)u(i)都可以用恒定的计算量来计算时,反向传播保证梯度计算的计算数目和前向计算的计算数目是同一个量级:这可以在算法2中看出,因为每个局部偏导数 ∂ u ( i ) ∂ u ( j ) \frac{\partial u^{(i)}}{\partial u^{(j)}} u(j)u(i)以及递归链式公式(公式 ∂ u ( n ) ∂ u ( j ) = ∑ i : j ∈ P a ( u ( i ) ) ∂ u ( n ) ∂ u ( i ) ∂ u ( i ) ∂ u ( j ) \displaystyle\frac{\partial u^{(n)}}{\partial u^{(j)}}=\sum\limits_{i:j\in Pa(u^{(i)})}\frac{\partial u^{(n)}}{\partial u^{(i)}} \frac{\partial u^{(i)}}{\partial u^{(j)}} u(j)u(n)=i:jPa(u(i))u(i)u(n)u(j)u(i))中相关的乘和加都只需计算一次。因此,总的计算量是 O ( #edges ) \Omicron(\text{\#edges}) O(#edges)。然而,可能通过对反向传播算法构建的计算图进行简化来减少这些计算量,并且这是NP完全问题。诸如 Theano \text{Theano} Theano TensorFlow \text{TensorFlow} TensorFlow的实现使用基于匹配已知简化模式的试探法,以便重复地尝试去简化图。我们定义反向传播仅用于计算标量输出的梯度,但是反向传播可以扩展到计算 Jacobi \text{Jacobi} Jacobi 矩阵(该 Jacobi \text{Jacobi} Jacobi矩阵或者来源于图中的 k k k个不同标量节点,或者来源于包含 k k k个值的张量值节点)。朴素的实现可能需要 k k k倍的计算:对于原始前向图中的每个内部标量节点,朴素的实现计算 k k k个梯度而不是单个梯度。当图的输出数目大于输入的数目时,有时更偏向于使用另外一种形式的自动微分,称为前向模式累加 (forward mode accumulation)。前向模式计算已经被提出用于循环神经网络梯度的实时计算,例如 (Williams and Zipser, 1989)。
  • 这也避免了存储整个图的值和梯度的需要,是计算效率和内存使用的折中。前向模式和后向模式的关系类似于左乘和右乘一系列矩阵之间的关系,例如 A B C D \boldsymbol{ABCD} ABCD,其中的矩阵可以认为是 Jacobi \text{Jacobi} Jacobi矩阵。例如,如果 D \boldsymbol{D} D是列向量,而 A \boldsymbol{A} A有很多行,那么这对应于一幅具有单个输出和多个输入的图,并且从最后开始乘,反向进行,只需要矩阵-向量的乘积。这对应着反向模式。相反,从左边开始乘将涉及一系列的矩阵-矩阵乘积,这使得总的计算变得更加昂贵。然而,如果 A \boldsymbol{A} A的行数小于 D \boldsymbol{D} D的列数,则从左到右乘更为便宜,这对应着前向模式。
  • 在机器学习以外的许多社区中,更常见的是使用传统的编程语言来直接实现微分软件,例如用 Python \text{Python} Python或者 C \text{C} C来编程,并且自动生成使用这些语言编写的不同函数的程序。在深度学习界中,计算图通常使用由专用库创建的明确的数据结构表示。专用方法的缺点是需要库开发人员为每个操作定义bprop方法,并且限制了库的用户仅使用定义好的那些操作。然而,专用方法也允许定制每个操作的反向传播规则,允许开发者以非显而易见的方式提高速度或稳定性,对于这种方式自动的过程可能不能复制。
  • 因此,反向传播不是计算梯度的唯一方式或最佳方式,但它是一个非常实用的方法,继续为深度学习社区服务。在未来,深度网络的微分技术可能会提高,因为深度学习的从业者更加懂得了更广泛的自动微分领域的进步。

高阶微分

  • 一些软件框架支持使用高阶导数。在深度学习软件框架中,这至少包括 Theano \text{Theano} Theano TensorFlow \text{TensorFlow} TensorFlow。这些库使用一种数据结构来描述要被微分的原始函数,它们使用相同类型的数据结构来描述这个函数的导数表达式。这意味着符号微分机制可以应用于导数(从而产生高阶导数)。
  • 在深度学习的相关领域,很少会计算标量函数的单个二阶导数。相反,我们通常对 Hessian \text{Hessian} Hessian矩阵的性质比较感兴趣。如果我们有函数 f : R n → R f:\mathbb{R}^n\rightarrow\mathbb{R} f:RnR,那么 Hessian \text{Hessian} Hessian矩阵的大小是 n × n n\times n n×n。在典型的深度学习应用中, n n n将是模型的参数数量,可能很容易达到数十亿。完整的 Hessian \text{Hessian} Hessian矩阵因此甚至不能表示。
  • 代替明确地计算 Hessian \text{Hessian} Hessian矩阵,典型的深度学习方法是使用Krylov方法(Krylov method)。Krylov方法是用于执行各种操作的一组迭代技术,这些操作包括像近似求解矩阵的逆、或者近似矩阵的特征值或特征向量等,而不使用矩阵-向量乘法以外的任何操作。
  • 为了在 Hessian \text{Hessian} Hessian矩阵上使用Krylov方法,我们只需要能够计算 Hessian \text{Hessian} Hessian矩阵 H \boldsymbol{H} H和一个任意向量 v \boldsymbol{v} v间的乘积即可。实现这一目标的一种直观方法 (Christianson,1992)是: H v = ∇ x [ ( ∇ x f ( x ) ) ⊤ v ] \boldsymbol{Hv}=\nabla_{\boldsymbol{x}}\left[(\nabla_{\boldsymbol{x}}f(x))^\top\boldsymbol{v}\right] Hv=x[(xf(x))v]
  • 该表达式中两个梯度的计算都可以由适当的软件库自动完成。注意,外部梯度表达式是内部梯度表达式的函数的梯度。
  • 如果 v \boldsymbol{v} v本身是由计算图产生的一个向量,那么重要的是指定自动微分软件不要对产生 v \boldsymbol{v} v的图进行微分。
  • 虽然计算 Hessian \text{Hessian} Hessian通常是不可取的,但是可以使用 Hessian \text{Hessian} Hessian向量积。可以对所有的 i = 1 , … , n i = 1,\dots,n i=1,,n简单地计算 H e ( i ) \boldsymbol{H}_{e}^{(i)} He(i),其中 e ( i ) e^{(i)} e(i) e i ( i ) = 1 e_i^{(i)}=1 ei(i)=1并且其他元素都为 0 0 0的one-hot向量。

总结

反向传播算法和自动微分技术是神经网络训练过程中不可或缺的组成部分。它们通过高效地计算梯度,使得神经网络的参数优化成为可能。反向传播算法利用链式法则,通过反向传播误差信号来逐层调整网络参数,而自动微分技术则通过构建计算图来自动完成这一复杂的计算过程。这些技术的结合,极大地简化了神经网络的训练过程,提高了模型的训练效率和性能。

本章涉及知识点参考往期内容

应用数学与机器学习基础 - 线性代数篇
深度网络现代实践 - 深度前馈网络之基于梯度的学习篇
深度网络现代实践 - 深度前馈网络之反向传播和其他的微分算法篇

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/42786.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

瑞萨RA6M3开发实践-UART实践-亲测有效 || 过程中遇到的问题

目录 写在前面 一、开发环境 二、编译下载 三、遇到的错误及解决方法 四、开启串口uart4的打印结果 写在前面 在看这编之前要是不太懂可参考HMI-Board (rt-thread.org),本文章为在此基础上进行相应开发。 一、开发环境 rtthread studio版本2.2.6,…

原生Ajax技术的执行流程,用火山写作创作的,总感觉差点意思。

启动原生Ajax操作可遵循以下步骤: 首先,我们需要构建一个XMLHttpRequest对象以实现与服务器的有效互动。这个过程往往是借助于调用XMLHttpRequest对象的构造函数得以实现。 接下来,我们有必要对请求细节进行设定,包括明确请求方法…

叹为观止|四款让人赞不绝口的优质软件,越用越上瘾

不说闲话直接上狠货,下面神仙软件,都值得使用。 Smart Defrag 说起电脑运行慢或者抽风,磁盘碎片就是让电脑变得又卡又不稳定的元凶之一。 不过Smart Defrag就算是新手小白也能操作,它里面藏着一个超强的碎片整理引擎&#xff0…

一.2.(5)共射、共集、共基三种基本放大电路的静态及动态分析;

共什么的问题:共什么取决于输入输出,共剩下的那一极 1.基本共射放大电路 见前面章节,不做累述 2.基本共集放大电路 列KVL方程,求解 AU1,所以又叫射极跟随器 Ib是流入基极的电流,Ii是从输入交流信号源流出的…

SpringBoot源码阅读(1)——环境搭建

SpringBoot官网 官网 https://spring.io/projects/spring-boot 代码仓库 github:https://github.com/spring-projects/spring-boot gitee: https://gitee.com/mirrors/spring-boot 下载代码 git clone https://gitee.com/mirrors/spring-boot.git下载的代码中有些…

CnosDB:深入理解时序数据修复函数

CnosDB是一个专注于时序数据处理的数据库。CnosDB针对时序数据的特点设计并实现了三个强大的数据修复函数: timestamp_repair – 对时间戳列进行有效修复,支持插入、删除、不变等操作。value_repair – 对值列进行智能修复,根据时间戳间隔和…

【源代码】srm供应商管理系统,招标管理系统,在线询价管理系统

前言: 随着互联网和数字技术的不断发展,企业采购管理逐渐走向数字化和智能化。数字化采购平台作为企业采购管理的新模式,能够提高采购效率、降低采购成本、优化供应商合作效率,已成为企业实现效益提升的关键手段。系统获取平台私…

干冰运输与存储中的温度监测:确保药品安全与合规性

在制药行业,干冰对于运输和储存对温度敏感的药品,如原料药API、疫苗、冻干物质和人体组织样本等至关重要。虹科ELPRO LIBERO系列干冰温度记录仪,能够为您提供专业的解决方案,定期监测和记录干冰运输和存储过程中的温度&#xff0c…

基于Java+SpringMvc+Vue技术的就医管理系统设计与实现系统(源码+LW+部署讲解)

目录 界面展示 第六章 部分代码实现 6.1 Spring boot 配置代码 6.2 用户管理及登录登出代码 6.3 Md5 加密算法代码 6.4 部分数据库代码 六、论文参考: 七、其他案例: 系统介绍: 就医管理系统,也称为医院管理系统&#…

14-21 剑和远方1 - AI历史及简单神经网络的工作原理

初始 “我们需要走得更深”这句台词出自电影《盗梦空间》。这是在讨论深入梦境更深层次时说的,暗示需要探索梦境的更深层次。虽然这似乎是不可能的,但它传达的理念是,要创造一个新的世界,就必须冒险进入更深的层次。 电影《盗梦空…

docker安装oracle 11g

最近把一些常用数据库都移到docker了,而且是windows下,很是方便。偶尔还是要用一下Oracle,今天就试一下安装oracle 11g 在docker上。 一、搜索并拉取镜像 docker search oracle_11gdocker pull ![在这里插入图片描述](https://i-blog.csdni…

CDGA|数据治理:突破“采集难、应用难”的困境

随着数字化时代的来临,数据已成为企业最宝贵的资产之一。然而,如何有效地采集和应用这些数据,却成为众多企业面临的一大挑战。数据治理作为一种全面的数据管理框架,为解决数据采集难、应用难等问题提供了有效途径。 数据采集难的挑…

FuTalk设计周刊-Vol.064

#AI漫谈 热点捕手 1.可灵视频模型Web 端功能上线 文生视频:画质升级、单次10s视频生成;图生视频:画质提升、支持自定义首尾帧;运镜控制:提供丰富的镜头控制选项,预设多种大师级镜头模式。在限免期间&…

CUTS 多粒度分割 + 局部图像块对比学习: 无需大量标注数据 + 多尺度病变识别 + 解决医学图像不同仪器成像差异

CUTS 多粒度分割 局部图像块对比学习: 无需大量标注数据 多尺度病变识别 解决医学图像不同仪器成像差异 提出背景CUTS 框架(A) 总览(B) 像素中心的图像块嵌入(C) 图像内对比图像块选择(D) 扩散凝结粗粒化(E) 多粒度分割 解法拆解子解法1:多粒度分割子…

应用层协议原理——因特网提供的运输服务

我们已经考虑了计算机网络能够一般性地提供的运输服务。现在我们要更为具体地考察由因特网提供的运输服务类型。因特网(更一般的是TCP/IP网络)为应用程序提供两个运输层协议,即UDP和TCP。当软件开发者为因特网创建一个新的应用时,首先要做出的决定是&…

我是售前工程师转大模型了,不装了我摊牌了

有无售前工程师的朋友,心里的苦谁懂呀,售前工程师是项目开发人员与业务销售人员的桥梁,在业务销售人员眼中,他们是技术人员,在项目实施中的开发人员眼中,他们是专注技术的销售人员,在用户眼中&a…

【运算放大器学习】

运算放大器学习 运放的选型一般主要需要观察以下几个参数,下面一起来理解一下几个核心参数的意义;今天说 输入失调电压 、失调电压温漂 、 偏置电流 、 失调电流几个参数; 放大器的几个主要参数 输入失调电压失调电压温漂偏置电流失调电流…

GPU 张量核心(Tensor Core)技术解读

一文理解 GPU 张量核心(Tensor Core) 引言 最新一代Nvidia GPU搭载Tensor Core技术,本指南深度解读其卓越性能,为您带来极致体验。 Nvidia最新GPU微架构中的核心技术——Tensor Core,自Volta起每代均获突破&#xf…

C语言求10进制转2进制(除2取余法)

1.思路:除2取余法,也就是说用除以2取余来将10进制数转换为二进制 2.两种代码实现,这里用了两,一个递归一个非递归。 递归是一种编程技术,其中一个函数直接或间接地调用自己。递归通常用于解决那些可以被分解为更小的、…

Check if a fine-tuned OpenAI model was successfully deleted

题意:检查微调后的OpenAI模型是否已成功删除 问题背景: I am doing some work with the OpenAI API with Python. Im working with fine-tuning and I am working on deleting an existing model and starting over again. I want to be able to check …