原文地址:http://suanfazu.com/t/topic/13742
上一次的分享我们提到了神经网络的几个基本概念,其中提到了随机梯度下降(SGD)算法是神经网络学习(或者更通用的,一般性参数优化问题)的主流方法。概念上,神经网络的学习非常简单,可以被归纳为下面的步骤:
(a) 构造神经网络结构(选择层数、激活函数等)
(b) 初始化构造出的神经网络参数![]()
(c) 对于给定的训练样本
与当前的
,计算梯度![]()
(d) 通过(随机)梯度下降算法更新
。
例如,不考虑任何正则化因子情况的最简单参数更新为![]()
神经网络的初学者往往会发现,上述四个步骤当中,对于给定样本
,计算其梯度是最 不直观 的一个步骤。本文我们玻森(bosonnlp.com)的讨论就围绕解决梯度
的核心算法:后向传播 算法来展开。
首先理清一个概念,步骤(d)的梯度下降算法是一种优化算法,而我们要讨论的后向传播算法,是计算步骤(c)中所需要梯度
的一种算法。下面的讨论,我们首先完成单参数(即只有一个参数
需要学习)的特例情况下的推导,进而通过 动态规划(Dynamic programming) 思想,将其推导泛化到多变量的情况。需要注意的是,虽然后向传播概念上并不复杂,所用到的数学工具也很基本,但由于所涉及的变量较多、分层等特点,在推导的时候需要比较仔细,类似绣花。
单参数情况
特例
在讨论后向传播算法之前,我们简单回顾一下单变量微积分中的求导规则。来看个例子,假设我们有一个极端简化的网络,其中只有一个需要学习的参数
,形式如下
![]()
并且假设损失函数Cost为平方误差(MSE)。
![]()
假设我们只有一个训练样本
。因为这个形式非常简单,我们试试将样本直接带入损失函数:
![]()
显然当
时,我们可以让损失函数为0,达到最优。下面让我们 假装 不知道最优解,考虑如何用梯度下降方法来求解。假设我们猜
为最优,带入计算得到![]()
嗯,不算太坏的一个初始值。让我们计算其梯度,或者损失函数关于
的导数。
![]()
设置学习率参数
,我们可以通过梯度下降方法来不断改进
,以达到降低损失函数的目的。三十个迭代的损失函数变化如下:
生成上图采用的是如下Python代码
import matplotlib.pyplot as plt
w0, eta, n_iter = 2, 0.02, 30
gradient_w = lambda w: 2*(w**3)
cost = lambda w: 0.5*(w**4)
costs = []
w = w0
for i in range(n_iter):
costs.append(cost(w))
w = w – eta*gradient_w(w) # SGD
plt.plot(range(n_iter), costs)
可以发现,经过30次迭代后,我们的参数
从初始的2改进到了0.597,正在接近我们的最优目标
。
对于一般
的情况
回忆一下,上面的结果是基于我们给定
下得到的,注意这里我们假设输入信号
为常量。我们将上面的求解步骤做一点点泛化。
![]()
重复上面的求解
![]()
关于w求导,
![]()
注意,上面求导用到了 链式法则(Chain Rule),即
![]()
或者写成偏导数形式:
![]()
对于一般性损失函数的情况
上式推导基于损失函数为平方最小下得出,那么我们再泛化一点,对于任意给定的可导损失函数,其关于
的梯度:
![]()
其中
是损失函数关于
的导数。实际上这个形式很通用,对于 任意 特定的损失函数和神经网络的激活函数,都可以通过这个式子进行梯度计算。譬如,对于一个有三层的神经网络
![]()
同样通过链式法则,
![]()
上式看上去比较复杂,我们可以在符号上做一点简化。令每一层网络得到的激活函数结果为
,即
, 那么:
![]()
即:不论复合函数
本身有多么复杂,我们都可以将其导数拆解成每一层函数
的导数的乘积。

上面的推导我们给出了当神经网络仅仅被一个可学习参数
所刻画的情况。一句话总结,在单参数的网络推导中,我们真正用到的唯一数学工具就是 链式法则。实际问题中,我们面对的参数往往是数以百万计的,这也就是为什么我们无法采用直觉去“猜”到最优值,而需要用梯度下降方法的原因。下面我考虑在多参数情况下,如何求解梯度。
多参数情况
首先,不是一般性的,我们假设所构建的为一个
层的神经网络,其中每一层神经网络都经过线性变换和非线性变换两个步骤(为简化推导,这里我们略去对bias项的考虑):

定义网络的输入
,而
作为输出层。一般的,我们令网络第
层具有
个节点,那么
。注意此时我们网络共有
个参数需要优化。
为了求得梯度
,我们关心参数
关于损失函数的的导数:
,但似乎难以把
简单地与损失函数联系起来。问题在哪里呢?事实上,在单参数的情况下,我们通过链式法则,成功建立第一层网络的
参数与最终损失函数的联系。譬如,
,
的改变影响
函数的值,而连锁反应影响到
的函数结果。那么,对于
值的改变,会影响
,从而影响
。通过
的线性变换(因为
),
的改变将会影响到每一个
。

将上面的过程写下来:

可以通过上式不断展开进行其梯度计算。这个方式相当于我们枚举了 每一条
改变对最终损失函数影响的 路径。通过简单使用链式法则,我们得到了一个 指数级 复杂度的梯度计算方法。稍仔细观察可以发现,这个是一个典型的递归结构(为什么呢?因为
定义的是一个递归结构),可以采用动态规划(Dynamic programming)方法,通过记录子问题答案的进行快速求解。设
用于动态规划的状态记录。我们先解决最后一层的边界情况:

上式为通用形式。对于Sigmoid, Tanh等形式的element-wise激活函数,因为可以写成
的形式,所示上式可以简化为:
![]()
即该情况下,最后一层的关于
的导数与损失函数在
导数和最后一层激活函数在
的导数相关。注意当选择了具体的损失函数和每层的激活函数后,
与
也被唯一确定了。下面我们看看动态规划的 状态转移 情况:

成功建立
与
的递推关系,所以整个网络的
可以被计算出。在确定了
后,我们的对于任意参数
的导数可以被简单表示出:

至此,我们通过链式法则和动态规划的思想,不失一般性的得到了后向传播算法的推导。

微信公众号: 架构师日常笔记 欢迎关注!
浙公网安备 33010602011771号