简介
反向传播算法是一种可使深度学习模型计算上可行的重要算法。对现代的神经网络来说,该算法实现的梯度下降训练要比那些幼稚实现快上百万倍,就像是一个要用一周训练而另一个要用200,000年
除了在深度学习的应用外,在其他方面像天气预告到分析数值稳定性上都是强大的计算工具,只是说换了个名字而已。实际上该算法就在不同的领域被多次重复发现过。撇开那些不同应用,该算法的通用的名称为“反向模式求导”。该算法最主要的功能是能够快速计算求导,并且是你不光在深度学习还有其他数值计算领域都非常重要的技能包
计算图
计算图是一种好理解数学表达式的方式,比如,表达式$e=(a+b) * (b+1)$. 这有三个算式:两个加法一个乘法。为了方便理解,引入两个中间变量$c$和$d$,这样每个算式都有输出结果变量。如下:
$$c = a + b$$
$$d = b + 1$$
$$e = c * d$$
为了创建计算图,将每个算式和输入变量放于每个节点中,箭头的指向关系为一个节点计算结果指向该节点作为输入结果的节点
这种类型的图经常出现在计算机科学中,特别是在谈及函数编程,这很接近依赖图和调用图的概念,同时也是现流行深入学习框架Theano的核心抽象
我们可以设置初始数值,从图自底向上计算每个算式,例如 $a = 2$ 和 $b = 1$
该算式最终结果6
计算图的求导
如果想理解计算图的求导,重要的是理解图边上的求导。如果$a$直接影响$c$,我们想知道$a$怎么影响$c$,如果$a$变化一点,$c$会怎么改变呢?我们称$c$对$a$的偏导
为了计算该计算图的偏导,我们得需下面的两个规则:
$$\frac{\partial (a+b)}{\partial a} = \frac{\partial a}{\partial a} + \frac{\partial b}{\partial b} = 1$$
$$\frac{\partial uv}{\partial u} = u\frac{\partial v}{\partial u} + v\frac{\partial u}{\partial u} = v$$
如图,每个偏导都在图边标出
如果我们想了解不是直接相连节点间的影响,比如如图中的$e$是怎么被$a$影响的。如图可知,我们若以速率1(即斜率1)改变$a$,$c$也会跟随以相同的速率改变。相对应的$c$以速率1改变,那么$e$就以速率2改变。所以$e$相对$a$就以$1 * 2$的速率改变
总结来说就是将两节点间可能的连接偏导乘积相加求和。例如,求对$e$求$b$的偏导:
$$\frac{\partial e}{\partial b} = 1 \times 2 + 1 \times 3$$
这表明的是b怎么通过影响c和d来间接引起e的变化的
这通用的”边加和”规律只是multivariate chain rule的不同理解方式
考虑边间
上面的问题可以很容易获得一个各个边的加和展开式
上图中,从$X$到$Y$,$Y$到$Z$各有三条路径,如果我们想求$\frac{\partial Z}{\partial X}$就要将所有的路径相加,要有九条路径
$$\frac{\partial Z}{\partial X} = \alpha\delta + \alpha\varepsilon +\alpha\zeta +\beta\delta + \beta\varepsilon + \beta\zeta + \gamma\delta + \gamma\varepsilon + \gamma\zeta$$
上面的展开式有9路径,当图更复杂的时候,如上的展开式就会暴涨
展开项合并因式,比起简单的相加各边会更好些
$$\frac{\partial Z}{\partial X} = (\alpha + \beta + \gamma)(\delta + \varepsilon + \zeta)$$
这就是“正向求导”和“反向求导”的来源。利用求各个边的因素和来使得计算效率化,不是显式地去求各个路径的和,而是将每个路劲求和反向合并获取每个节点值,并且两个算法都是只用到一次节点的边。
“正向求导”从计算图的输入开始,直到到结束点。在每个节点,都算一次输入该节点的加和,每个路径表示一条影响该节点的方式,将其求和演化出每个节点被引起变化的各个方面之和
也许你会不以图的方式思考也能和你在数值计算班上了解的介绍相似。
另一方面 “反向求导”是从图的输出开始,向前移动。每个节点将其产生的路径合并
顺序模式求导记录一个输入怎么影响每个节点。反序模式记录每个节点怎么影响一个输入,也就是说顺序模式对每个节点$\frac{\partial }{\partial X}$,而反序模式是对每个节点求$\frac{\partial Z}{\partial }$
计算上的优势
至此,你可能疑惑,反序模式求导做的事情貌似和正序模式是一样的,为什么还有人会对反序模式求导那么上心呢?
让我们考虑最开始的例子吧:
我们利用正序求导自底向上求解。下图展示了每个节点对$b$的求导
我们已经计算出输出对输入的求偏导:$\frac{\partial e}{\partial b}$,
如果我们从$e$往下求反序偏导呢?下图给出了每个$e$对其他每个节点的求导
当说到反序模式求导列出输入$e$对每个节点的偏导,的的确确是指的是每个节点。我们已经获得了两个输出对输入的求导:$\frac{\partial e}{\partial a}$和$\frac{\partial e}{\partial b}$。正序模式只返回一个,反序模式却给出所有的!
对此文来说,这只是两倍的加速,但是想象下,如果有百万个输入且只有一个的输入的情况下,正序模式求导要进行百万次的遍历计算图来获取输出对每个输入点的偏导,但是反序模式只需要一次就能做到。百万倍的加速还是很可观的吧!!
当进行神经网络训练,我们要考虑代价(一描述神经网络差劲的指数)作为一个各个参数的函数(描述该神经网络行为的参数)。为了梯度下降算法,我们要计算代价指数对每个参数的偏导。现在由于每个神经网络要有数百万甚至上千万的参数,所以反序模式的求导,在神经网络里被称为“反向传播算法”就可以使计算得到很大加速。
(有没有其他的场景使得正序求导算法更适用些呢?有的,由于反序模式求导一次给出一个输出对所有的输入的偏导,而正序模式求导一次给出的其实是所有输入对一个输入的求偏导,所以有一个函数伴随多个输出,正序模式求导会更快速)
这不很简单吗?
当我第一次理解反向传播算法的时候,我第一反应是:”😯,这个不就是链式规则吗!怎么会花费我们那么多时间弄清楚呢?”我不是第一个有这中反应的人。确实的, 如果你要问:”在前向神经网络是否存在一个聪明点的算法计算偏导?“答案确实没那么难。
但是我觉得这比它表面上要困难的多。如你所知,在反向传播算法发现前,人们也没有把焦点放在前向神经网络上。很显然,在神经网络训练上求导这个方法都不是很明晰。这个只有你意识到你可以快速计算偏导后才能使得该训练方法明晰,这其实是个认识循坏依赖。
更甚至,我们很容易将一些循环依赖的项钩成做不到的。并且很显然的,计算这些偏导是很耗时的。只是我们知道这个方法是可行的所以我们不去想为什么不去那样子做。
当然这些都是事后才晓得的,一旦你发现了问题,你就完成了最困难的问题了。
结论
求导比你想象的代价小的多。这是这边文章带来的最主要的经验。现实是我们这些傻傻的人们总是要重复地认清这个事实。这是在理解深入学习一个重要的事情。在其他领域如果这个不是普遍晓得的话,那认清这个事实更是有意的。
是否有其他的经验呢?我想有
反向传播算法是理解偏导怎么流经模型的有用的透镜。在指出有些模型很难实现是及其有用的。经典的问题就是在神经网络里梯度消失的问题。
最后,我认为还有很多算法的经验教训值得我们去获取。反向传播算法和正向模式用了两个强大的技巧(线性化和动态规划)来效率地计算偏导。
这篇文章只给出了很简要的反向传播算法的讨论(treatment==既然有讨论的意思),我强烈推荐阅读Michael Nielsen’s chapter on it去获取更精彩更准确的有关神经网络的内容