反向传播是使训练深度学习模型能够计算自如的关键算法。 对于现代神经网络,相对于传统的执行,它可以使基于梯度下降的训练相对于传统的执行快一千万倍。 这就是一个模型需要一个星期训练和花费20万年的区别。
除了在深度学习中的使用,反向传播是许多其他领域的强大计算工具,从天气预报到分析数值稳定性 – 它只是以不同的名字出现。 实际上,该算法在不同领域已被重新发明了数十次。 一般,作为独立的应用程序,名称是“反向模式求导”。
从根本上说,这是一种快速计算导数的技术。 这不仅仅是在深度学习中,而且在各种各样的数值计算环境中,这是很有必要掌握的一项技巧。
计算图
计算图是考虑数学表达式的好方法。 例如,对于表达式e =(a + b)*(b + 1)。 有三个操作:两个加法和一个乘法。 为了帮助我们讨论这个问题,我们来引入两个中间变量c和d,以便每个函数的输出都有一个变量。 我们现在有:
c=a+b
d=b+1
e=c∗d
要创建一个计算图,我们将这些操作和输入变量一起放入节点中。 当一个节点的值是另一个节点的输入时,箭头从一个指向另一个。
这种图形一直在计算机科学中出现,特别是在讨论函数式编程时。 它们与依赖关系图和调用图的概念非常密切相关。,同时也是Theano的热门深度学习框架背后的核心抽象。
我们可以通过将输入变量设置为某些值并通过图形向上计算节点来评估表达式。 例如,我们设置a = 2和b = 1:
表达式计算结果为6。
计算图上的导数
如果想要理解计算图中的导数,关键是理解在边缘上的导数。 如果a 直接影响c,那么我们想知道它怎样影响c。 如果a 有一点改变,相应的c怎么变化? 我们把这叫做c相对于a的偏导数。
为了评估该图中的偏导数,我们需要导数运算的加法规则和乘法规则:
下图中,每个边上都有导数:
如果我们想了解没有直接连接的节点之间如何影响? 我们考虑一下e如何受到影响。 如果我们以1的速度改变a,c也以1的速度变化。然后,以1的速度变化的c导致e以2的速度改变。所以e以1 * 2的速率变化, 相对于a来说。
一般规则是将从一个节点到另一个节点的所有可能路径相加,将路径的每个边缘的导数相乘在一起。 例如,得到e相对于b的偏导数:
这说明b如何通过c影响e,以及如何通过d影响它。
这种一般的“对所有路径求和”的规则只是对多变量链式法则的另一种思考方式。
因式分解路径
对路径求和的规则在遇到相关的路径呈爆炸式增长时会存在问题。
在上图中,从X到Y有三条路径,还有从Y到Z的三条路径。如果要通过求和所有路径得到导数∂Z/∂X,我们需要求和3 * 3 = 9条路径:
以上只有九条路径,但是随着图形变得越来越复杂,路径的数量增长将变得非常多。
对路径上的结果进行求和后对它们进行因式分解:
在这里引入“正向模式求导”和“反向模式求导”,它们是通过分解路径求和来进行有效计算的算法。 不是明确地对所有路径求和,它们通过将路径合并在每个节点上来更有效地计算。 事实上,这两种算法都会触摸每个边缘一次!
正向模式求导从图形的输入开始,向着最后方向移动。 在每个节点,它将所有进入的路径相加。这些路径中的每一个表示输入影响该节点的一种方式。 通过添加它们,我们得到节点受输入影响的总体方式,它就是导数。
尽管从图形上你可能想不到它的样子,但是如果你上了微积分课程,它与你所学到的东西很相似。
另一方面,反向模式区分从图形的输出开始,并朝向开始移动。 在每个节点,它合并起始于该节点的所有路径。
前向模式求导跟踪一个输入如何影响每个节点。 反向模式求导跟踪每个节点如何影响一个输出。 也就是说,前向模式求导将运算符∂/∂X应用于每个节点,而反向模式求导则将运算符∂Z/∂应用于每个节点。
计算上的优势
此时,你可能会想知道为什么有人都会关心反向模式求导。 与正向模式求导相比,这看起来像一个奇怪的方式做同样的事情。 这样做有什么优势吗?
我们再来看一下之前的例子:
figure1
我们可以使用前向模式求导。 下面给出了每个节点相对于b的导数:
我们已经计算出∂e/∂b,我们的输出相对于一个输入的导数。
如果我们从 e向下进行反向模式求导呢? 下面给出了我们关于每个节点的e的导数:
当我说反向模式求导给出了我们e相对于每个节点的导数,我说的确实是每一个节点。 我们得到∂e/∂a和∂e/∂b,e相对于两个输入的导数。 前向模式求导给出了我们输出相对于单个输入的导数,但反向模式求导给了我们所有的导数。
对于这个图表,这仅仅是两个因子的加速,但是想象一个具有一百万个输入和一个输出的函数。 正向模式求导需要我们通过数百万次计算得到导数。 反向模式求导可以一下子全部得到他们! 一次一百万的加速是相当不错的!
在神经网络中的应用
当训练神经网络时,我们考虑成本(一个描述神经网络表现如何的值)作为参数(描述神经网络行为的数字)的函数。 我们要计算相对于所有参数的成本的导数,用于梯度下降。 现在,神经网络中通常有数百万甚至数千万个参数。 因此,在神经网络背景下称为反向传播的反向模式求导给了我们巨大的提速!
这里先直观理解多层神经网络的训练:
机器学习可以看做是数理统计的一个应用,在数理统计中一个常见的任务就是拟合,也就是给定一些样本点,用合适的曲线揭示这些样本点随着自变量的变化关系。
深度学习同样也是为了这个目的,只不过此时,样本点不再限定为(x, y)点对,而可以是由向量、矩阵等等组成的广义点对(X,Y)。而此时,(X,Y)之间的关系也变得十分复杂,不太可能用一个简单函数表示。然而,人们发现可以用多层神经网络来表示这样的关系,而多层神经网络的本质就是一个多层复合的函数。借用网上找到的一幅图[1],来直观描绘一下这种复合关系。
其对应的表达式如下:
上面式中的Wij就是相邻两层神经元之间的权值,它们就是深度学习需要学习的参数,也就相当于直线拟合y=k*x+b中的待求参数k和b。
和直线拟合一样,深度学习的训练也有一个目标函数,这个目标函数定义了什么样的参数才算一组“好参数”,不过在机器学习中,一般是采用成本函数(cost function),然后,训练目标就是通过调整每一个权值Wij来使得cost达到最小。cost函数也可以看成是由所有待求权值Wij为自变量的复合函数,而且基本上是非凸的,即含有许多局部最小值。但实际中发现,采用我们常用的梯度下降法就可以有效的求解最小化cost函数的问题。
梯度下降法需要给定一个初始点,并求出该点的梯度向量,然后以负梯度方向为搜索方向,以一定的步长进行搜索,从而确定下一个迭代点,再计算该新的梯度方向,如此重复直到cost收敛。那么如何计算梯度呢?
假设我们把cost函数表示为
那么它的梯度向量[2]就等于
其中eij表示正交单位向量。为此,我们需求出cost函数H对每一个权值Wij的偏导数。而BP算法正是用来求解这种多层复合函数的所有变量的偏导数的利器。
回到之前的例子e = c * d ,大家也许已经注意到,正向求导是十分冗余的,因为很多路径被重复访问了。比如图figure1中,a-c-e和b-c-e就都走了路径c-e。对于权值动则数万的深度模型中的神经网络,这样的冗余所导致的计算量是相当大的。
同样是利用链式法则,BP算法则机智地避开了这种冗余,它对于每一个路径只访问一次就能求顶点对所有下层节点的偏导值。正如反向传播(BP)算法的名字说的那样,BP算法是反向(自上往下)来寻找路径的。
从最上层的节点e开始,初始值为1,以层为单位进行处理。对于e的下一层的所有子节点,将1乘以e到某个节点路径上的偏导值,并将结果“堆放”在该子节点中。等e所在的层按照这样传播完毕后,第二层的每一个节点都“堆放”些值,然后我们针对每个节点,把它里面所有“堆放”的值求和,就得到了顶点e对该节点的偏导。然后将这些第二层的节点各自作为起始顶点,初始值设为顶点e对它们的偏导值,以”层”为单位重复上述传播过程,即可求出顶点e对每一层节点的偏导数。
以图figure1为例,节点c接受e发送的12并堆放起来,节点d接受e发送的13并堆放起来,至此第二层完毕,求出各节点总堆放量并继续向下一层发送。节点c向a发送21并对堆放起来,节点c向b发送21并堆放起来,节点d向b发送31并堆放起来,至此第三层完毕,节点a堆放起来的量为2,节点b堆放起来的量为21+3*1=5, 即顶点e对b的偏导数为5.
举个不太恰当的例子,如果把图中的箭头表示欠钱的关系,即c→e表示e欠c的钱。以a, b为例,直接计算e对它们俩的偏导相当于a, b各自去讨薪。a向c讨薪,c说e欠我钱,你向他要。于是a又跨过c去找e。b先向c讨薪,同样又转向e,b又向d讨薪,再次转向e。可以看到,追款之路,充满艰辛,而且还有重复,即a, b 都从c转向e。而BP算法就是主动还款。e把所欠之钱还给c,d。c,d收到钱,乐呵地把钱转发给了a,b,皆大欢喜。
本文由“猿助猿”发布,2017年04月05日