数学 · 神经网络(二)· BP(反向传播)

数学 · 神经网络(二)· BP(反向传播)

这一章要讲的就是可能最让我们头疼的 BP(反向传播)算法了。事实上,如果不是要做理论研究而只是想快速应用神经网络来干活的话,了解如何使用 tensorflow 等帮你处理梯度的成熟的框架可能会比了解算法细节要更好一些。但即使如此,了解神经网络背后的原理总是不亏的,在某种意义上它也能告诉我们应该选择怎样的神经网络结构来进行具体的训练

要想知道 BP 算法的推导,我们需要且仅需要知道两点知识:求导及其链式法则;如果想知道原理的话,还需要知道梯度相关的一些理论。同时我们可以从名字上知道,BP 算法和前向传导算法的“方向”刚好相反:前向传导是由后往前一路传导,反向传播则是由前往后一路传播

先从直观上理解一下 BP 的原理。总体上来说,BP 的目的是利用梯度来更新结构中的变量以使得损失函数最小化。这里面就涉及两个问题:

  • 如何获得梯度?
  • 如何更新?

这一章我们会讲第一个问题如何解决、并说一种第二个问题的解决方案,对第二个问题的详细讨论会放在 Optimizers 章节中

在讲如何获得梯度之前,我们先说说梯度是个什么东西。虽然梯度可以牵扯出许多理论性的东西,不过对于 BP 算法而言,我们完全可以先把梯度直观地理解为:

  • 梯度是函数f在某点x_0上升最快的方向

换言之,当自变量x沿着梯度方向走时,函数的增长最快
其数学定义则是

它是向量函数 f 对 n 个变量的偏导组成的向量(顺带一提,我个人的习惯是在推导向量函数的梯度时,先把它分拆成单个的函数进行普通函数的求偏导计算、最后再把它们合成梯度)

在开始进行 BP 的推导前,我们还需要知道这么两件事:

  • BP 算法的输入是真实的类别向量Y
  • 我们的目标是让模型的输出尽可能拟合Y。为此我们会定义:
    • 预测函数f(X),它是一个向量函数、会根据输入矩阵X输出预测向量Y_{pred}
    • 损失函数C^{*}(X) = C(Y, Y_{pred}) = C(Y, f(X)),它是一个标量函数、其函数值能反映Y_{pred}Y的差异;差异越大、C^{*}(X) = C(Y, Y_{pred})的值越大

接下来我们就可以开始进行 BP 的推导了。如前所述,我们会把求解梯度的过程化为若干个求解偏导数的问题,为此我们需要将我们的神经网络进行拆分:

其中:

代表着第 k 层第 j 个神经元的接收的输入;

代表着对应的激活值

(话说这个人图画得真的好丑)(捂脸

接下来先来看 BP 的思想、再来看具体的步骤


正如前面提到的,BP 是在前向传导之后进行的、从后往前传播的算法,所以我们需要时刻记住这么一个要求:每个 Layer 的梯度除了利用它自身的数据外、仅会利用到:

  • 上一层传过来的激活值和下一层传回来的梯度
  • 该层与下一层之间的线性变换矩阵w

从而我们需要定义一个仅由当前层数据和下一层传回的梯度决定局部梯度。先给定义:


我们接下来证明它符合要求。由链式法则可知:

这就是我们想要的梯度。可以看到只要局部梯度符合要求、则梯度也符合要求,从而问题化为了如何求局部梯度\delta _{j}^{(k)}。这里会有两种情况(不妨设我们的神经网络一共有 n 层)

  • 当前 Layer 是 CostLayer、也就是说最后一层。此时的“下一层”就是Y,从而:
    相当长的一个式子,里面涉及的定义也挺多,不过其本质只是链式法则而已 ( σ'ω')σ
  • 当前 Layer 不是最后一层,此时由:
    再由链式法则可知(注:该层每个神经元对下一层所有神经元都会有影响):
    其中
    一项就是下一层传播回来的局部梯度,且:
    从而可见,局部梯度\delta _{j}^{(k)}确实符合要求

这些就是求梯度的推导了。虽然有点多,但一步一步看下来的话、相信聪明的观众老爷们理解起来不难 ( σ'ω')σ(然而码公式快把我码死了 冷漠)


在得到梯度后该怎么做呢?如我提到过的,梯度当自变量x沿着梯度方向走时,函数的增长最快。现在我们的目的是减少我们的损失函数C*,所以就让我们的自变量w_{ij}^{(k)}梯度方向相反的方向走就行了:

其中\eta叫做学习速率,直观上来说其大小反映的是w_{ij}^{(k)}往梯度反方向走的距离

(感谢评论区 @熊亮 的建议,下补上 BP 算法的矩阵形式)

事实上,BP 算法的优点之一正在于它能利用矩阵运算这个被高度优化的计算过程。由于矩阵运算表达式的推导只是把上面的东西合起来,所以这里就只给出最终结果。为简洁,我们只考虑中间 Layer 的情况,CostLayer 的情况是几乎同理的:

其中\times代表矩阵乘法,v^{(k-1)}{'}w^{(k+1)}{'}代表矩阵的转置,\cdot代表 element wise 操作。相当简洁不是吗?如果所用的编程语言直接支持矩阵操作的话,BP 算法就可以用一行实现 ( σ'ω')σ


以上就是 BP 算法的全过程。由于 BP 算法确实有些繁复、所以我上面的过程可能会有疏漏,观众老爷们如果有什么意见的话还请多多指教 ( σ'ω')σ

此外值得一提的是,很多常用的激活函数的导函数使用函数值来定义会比使用自变量来定义要更好(所谓更好是指形式上更简单、从而计算开销会更小),具体细节可以参见这篇文章中间偏后的部分

下一章我们会介绍一些常用的损失函数,这些损失函数不仅在神经网络中有应用、在其它的一些机器学习算法中也会用到

希望观众老爷们能够喜欢~

猛戳我进入下一章!( σ'ω')σ

编辑于 2017-05-16

文章被以下专栏收录