当前位置 博文首页 > LucyGill的博客:详解反向传播算法--转自知乎

    LucyGill的博客:详解反向传播算法--转自知乎

    作者:[db:作者] 时间:2021-09-05 19:16

    本文转自知乎,作者晓雷。原文链接:

    https://zhuanlan.zhihu.com/p/25081671 (上篇)

    https://zhuanlan.zhihu.com/p/25416673 (下篇)

    作为数学系妹子,一直在试图弄懂反向传播算法,先后看了数篇论文、博客,以及著名的“西瓜书”,感觉还是不甚清晰。下面的文章讲解得很详细,图把反向传播过程清晰地展现了出来。另外,如果想进一步详细了解反向传播算法,建议斯坦福大学的这篇文章:http://deeplearning.stanford.edu/wiki/index.php/%E5%8F%8D%E5%90%91%E4%BC%A0%E5%AF%BC%E7%AE%97%E6%B3%95

    上篇:

    目录:

    1 用计算图来解释几种求导方法:

    1.1 计算图

    1.2 两种求导模式:前向模式求导( forward-mode differentiation) 反向模式求导(reverse-mode differentiation)

    1.3 反向求导模式(反向传播算法)的重要性

    声明:本文内容来自?Calculus on Computational Graphs: Backpropagation?。算是翻译加上自己理解。水平有限,理解错误欢迎指正。

    反向传播算法(Backpropagation)已经是神经网络模型进行学习的标配。但是有很多问题值得思考一下:

    反向传播算法的作用是什么??神经网络模型的学习算法一般是SGD。SGD需要用到损失函数C关于各个权重参数w_{jk},b_j的偏导数\frac{ \partial C }{ \partial w_{jk} } , \frac{ \partial C }{ \partial b_j} 。一个模型的参数w,b是非常多的,故而需要反向传播算法快速计算\frac{ \partial C }{ \partial w_{jk} } , \frac{ \partial C }{ \partial b_j} 。也就是说反向传播算法是一种计算偏导数的方法。

    为什么要提出反向传播算法? 在反向传播算法提出之前人们应该想到了使用SGD学习模型,也想到了一些办法求解网络模型的偏导数,但这些算法求解效率比较低,所以提出反向传播算法来更高效的计算偏导数。(那时的网络模型还比较浅只有2-3层,参数少。估计即便不适用反向传播这种高效的算法也能很好的学习。一旦有人想使用更深的网络自然会遇到这个偏导数无法高效计算的问题,提出反向传播也就势在必行了)

    反向传播怎么样实现高效计算偏导数的? 请先回顾一下当初我们学习微积分时是如何计算偏导数的? (链式法则,具体看下面)

    1 用计算图来解释几种求导方法:

    1.1 计算图

    式子?e=(a+b)*(b+1)?可以用如下计算图表达:

    令a=2,b=1则有:

    如何在计算图上表达“求导”呢? 导数的含义是 因变量随自变量的变化率,例如?\frac{\partial y }{\partial x} = 3 ?表示当x变化1个单位,y会变化3个单位。 微积分中已经学过:加法求导法则是?\frac{\partial}{\partial a}(a+b) = \frac{\partial a}{\partial a} + \frac{\partial b}{\partial a} = 1?乘法求导法则是?\frac{\partial}{\partial u}uv = u\frac{\partial v}{\partial u} + v\frac{\partial u}{\partial u} = v?。 我们在计算图的边上表示导数或偏导数:\frac{ \partial e }{ \partial c } , \frac{ \partial e }{ \partial d }, \frac{ \partial c }{ \partial a }, \frac{ \partial c }{ \partial b }, \frac{ \partial d }{ \partial b }?如下图

    那么?\frac{ \partial e  }{ \partial b }?如何求呢??\frac{\partial c }{ \partial b} = 1 告诉我们1个单位的b变化会引起1个单位的c变换,\frac{\partial e }{ \partial c} = 2告诉我们 1 个单位的c变化会引起2个单位的e变化。所以?\frac{ \partial e  }{ \partial b } =   \frac{ \partial c }{ \partial b } * \frac{ \partial e  }{ \partial c }   = 1*2 =2?吗? 答案必然是错误。因为这样做只考虑到了下图橙色的路径,所有的路径都要考虑:\frac{ \partial e  }{ \partial b } =   \frac{ \partial c }{ \partial b } * \frac{ \partial e  }{ \partial c }  +  \frac{ \partial d  }{ \partial b }  *  \frac{ \partial e  }{ \partial d }  =1*2 + 1 * 3 = 5

    所以上面的求导方法总结为一句话就是: 路径上所有边相乘,所有路径相加。不过这里需要补充一条很有用的合并策略:

    例如:下面的计算图若要计算\frac{\partial Z}{\partial X}就会有9条路径:\frac{\partial Z}{\partial X} = \alpha\delta + \alpha\epsilon + \alpha\zeta + \beta\delta + \beta\epsilon + \beta\zeta + \gamma\delta + \gamma\epsilon + \gamma\zeta

    如果计算图再复杂一些,层数再多一些,路径数量就会呈指数爆炸性增长。但是如果采用合并策略:\frac{\partial Z}{\partial X} = (\alpha + \beta + \gamma)(\delta + \epsilon + \zeta)?就不会出现这种问题。这种策略不是 对每一条路径都求和,而是 “合并同类路径”,“分阶段求解”。先求X对Y的总影响?(\alpha + \beta + \gamma)?再求Y对Z的总影响?(\delta + \epsilon + \zeta)?最后综合在一起。


    1.2 两种求导模式:前向模式求导( forward-mode differentiation) 反向模式求导(reverse-mode differentiation)

    上面提到的求导方法都是前向模式求导( forward-mode differentiation)?:从前向后。先求X对Y的总影响?(\alpha + \beta + \gamma)?再乘以Y对Z的总影响?(\delta + \epsilon + \zeta)?。


    另一种,反向模式求导(reverse-mode differentiation)?则是从后向前。先求Y对Z的影响再乘以X对Y的影响。

    前向求导模式追踪一个输入如何影响每一个节点(对每一个节点进行?\frac{\partial}{\partial X}操作)反向求导模式追踪每一个节点如何影响一个输出(对每一个节点进行?\frac{\partial Z}{\partial}操作)。

    1.3 反向求导模式(反向传播算法)的重要性

    让我们再次考虑前面的例子:

    如果用前向求导模式:关于b向前求导一次如果用反向求导模式:向后求导
    前向求导模式只得到了关于输入b的偏导 \frac{\partial e}{\partial b}?,还需要再次求解关于输入a的偏导\frac{\partial e}{\partial a}?(运算2遍)。而反向求导一次运算就得到了e对两个输入a,b的偏导\frac{\partial e}{\partial a}, \frac{\partial e}{\partial b}?(运算1遍)。上面的比较只看到了2倍的加速。但如果有1亿个输入1个输出,意味着前向求导需要操作1亿遍才得到所有关于输入的偏导,而反向求导则只需一次运算,1亿倍的加速。

    当我们训练神经网络时,把“损失“ 看作 ”权重参数“ 的函数,需要计算”损失“关于每一个”权重参数“的偏导数(然后用梯度下降法学习)。 神经网络的权重参数可以是百万甚至过亿级别。因此 反向求导模式(反向传播算法)可以极大的加速学习。

    参考:

    • Calculus on Computational Graphs: Backpropagation
    • Neural networks and deep learning

    下篇:

    神经网络结构图:

    示例网络图

    其中C是损失函数,例如C可以取:

    梯度下降(SGD)进行学习时,核心问题是求解损失函数C关于所有网络参数w_{jk},b_j的偏导数\frac{\partial C}{\partial w_{jk}} ,\frac{\partial C}{\partial b_j}  。 根据详解反向传播算法(上)?我们已经知道用反向传播算法可以“一次反向计算”得到损失函数C关于网络中所有参数的偏导数。模仿详解反向传播算法(上)?的推理过程,我们首先画出上面网络图的详细计算图:再看看具体怎么样反向传播求偏导数。

    神经网络计算图

    对应计算图如下:(只展开了最后两层的计算图):

    绿色代表权重参数w_{jk},橙色代表基底参数b_j。可见虽然网络图上只是简单几条线,计算图还是蛮复杂的。

    现在我们在计算图箭头上标出对应的偏导数(只标出了一部分)。

    反向传播四公式


    上面计算图上每一个节点关于前一个节点的偏导数都可以求得,根据求导的链式法则,想要求损失函数C关于某一节点的偏导数,只需要“把该节点每条反向路径上的偏导数做乘积,再求和”即可。(w_{jk},b_j分别对应绿色和橙色的节点)

    现在我们已经可以在计算图上求得损失函数C关于模型参数的偏导数\frac{\partial C}{\partial w_{jk}} ,\frac{\partial C}{\partial b_j}  。但是还不够优雅,反向传播算法要优雅的很多,它通过定义一个损失(\delta_j^l),先逐层向后传播得到每一层节点的损失(\delta_j^l),再通过每一个节点的损失(\delta_j^l)来求解该节点的\frac{\partial C}{\partial w_{jk}} ,\frac{\partial C}{\partial b_j}

    首先记损失函数C关于l层的第j个元素的偏导为:\delta_j^l \equiv \frac{\partial C}{\partial z_j^l}

    最后一层

    对于最后一层(L层)的元素j会有:

    \delta_j^L = \frac{\partial C}{\partial z_j^L}=\frac{\partial C}{\partial a_j^L} \cdot \frac{\partial a_j^L}{\partial z_j^L} = \frac{\partial C}{\partial a_j^L} \cdot \sigma^{'}(z_j^L)

    向量化为:

    \bm \delta^L = \begin{pmatrix} \delta_1^L \\\vdots \\ \delta_j^L \\   \vdots\\  \delta_n^L \end{pmatrix}= \begin{pmatrix} \frac{\partial C}{\partial a_1^L} \cdot \sigma^{'}(z_1^L) \\\vdots \\ \frac{\partial C}{\partial a_j^L} \cdot \sigma^{'}(z_j^L) \\   \vdots\\  \frac{\partial C}{\partial a_n^L} \cdot \sigma^{'}(z_n^L) \end{pmatrix}= \begin{pmatrix} \frac{\partial C}{\partial a_1^L} \\\vdots \\ \frac{\partial C}{\partial a_j^L} \\   \vdots\\  \frac{\partial C}{\partial a_n^L} \end{pmatrix}\odot  \begin{pmatrix} \sigma^{'}(z_1^L) \\\vdots \\ \sigma^{'}(z_j^L) \\   \vdots\\  \sigma^{'}(z_n^L) \end{pmatrix} =  \bm \nabla_aC \odot  \sigma^{'}(\bm z^L)?(BP1)

    其中\odot的操作是把两个向量对应元素相乘组成新的元素。

    后一层传播到前一层

    由前面计算图中L和L-1层所标注的偏导数,可得到倒数第一层(L-1)元素j的损失为:(请仔细对照前面的计算图)\delta_j^{L-1} = (\sum_{j=1}^n{\frac{\partial z_j^L}{\partial a_{k}^{L-1}}  \delta_j^L }) \cdot \sigma_{'}(z_j^{L-1}) = (\sum_{j=1}^n{w_{jk}^L \delta_j^L } ) \cdot  \sigma_{'}(z_j^{L-1})  =\begin{pmatrix} w_{1k}^L \cdots w_{jk}^L \cdots  w_{nk}^L\\  \end{pmatrix} \begin{pmatrix} \delta_1^L \\  \vdots \\  \delta_j^L\\\vdots\\\delta_n^L \end{pmatrix}\cdot \sigma^{'}(z_j^{L-1})

    向量化:\delta^{L-1} = ((w^{L})^T\delta^{L} \odot \sigma^{'}(z^{L-1}) )

    这启发我们后一层(l+1层)的损失\delta^{l+1}?如何传播到前一层(l层)得到\delta^l。(只需要把L用l+1替换,L-1l替换)就得到了逐层传播损失的公式:

    \bm \delta^{l} = ((\bm w^{l+1})^T \bm \delta^{l+1} \odot \sigma^{'}(\bm z^{l}) )(BP2)

    关于b_j^l的偏导数

    \frac{\partial C}{\partial b_j^l} =\frac{ \partial C}{ \partial z_j^l} \frac{\partial z_j^l}{\partial b_j^l} = \delta_j^l \cdot 1 = \delta_j^l(BP3)

    向量化: