【深度学习基础】简单易懂理解BP算法

前言

提起BP算法(Back Propagation),相信学过深度学习的人都不陌生,在深层的网络中对权重参数的更新免不了要使用这个算法,所以BP算法也是入门深度学习的一个必须理解的算法。

写这篇文章的缘由是我自己对BP算法在此之前也是属于半懂(知道工作原理,不明白处理细节)的状态,看了网上许多文章,觉得讲的都不够简单,让刚入门的小白难以接受,产生劝退效果。因此,打算通过写一篇简单理解BP算法的文章,一方面提升自己对BP的理解,另一方面希望看到这篇文章的小伙伴能够掌握BP算法的原理,为以后的学习铺路。
【深度学习基础】简单易懂理解BP算法

1. 单层网络参数优化

BP算法是针对深层次网络进行参数更新的算法,因此需要先理解单层网络下,权重是如何被更新的。

1.1 模型定义

为了理解简便,我们采用最简单的线性分类模型
y=f(x)=wTx+b(1)\mathbf y = f(\mathbf x) = \mathbf w^T\mathbf x+b \tag{1}
从公式容易看出,输入是一个向量x\mathbf xwT\mathbf w^T是参数矩阵,bb是偏置(对这些不了解的可以先看我这篇机器学习:线性分类问题(基础知识))。转换成网络图如下:
【深度学习基础】简单易懂理解BP算法

可以看出经过网络后,一个三维的输入向量转换成了一个二维的输出向量

例如得到输出向量为y=[0.7,0.3]\mathbf y = [0.7,0.3]而真实的数据是y^=[1,0]\hat{\mathbf y} = [1,0],那么说明模型的参数还不符合预期,存在误差,这时候就要定义损失函数将误差计算出来。

1.2 损失函数

损失函数是用于衡量预测输出和真实输出之间差距的,通常我们采用均方误差(有时候也用熵):
loss(y,y^)=12i=1Nyy^2(2)loss(\mathbf y,\hat{\mathbf y}) = \frac{1}{2}\sum_{i=1}^N||\mathbf y-\hat{\mathbf y}||^2 \tag{2}

这里NN是输入的样本数量,每个样本输入都会得到一组y,y^\mathbf y,\hat{\mathbf y}

有了损失相当于告诉我们模型还不够完善,要对模型优化(就是对权重更新),如何更新的步骤就是参数优化算法要干的事了

1.3 参数优化

相信大家也很熟悉参数优化采用的方式是梯度下降算法(更一般地是随机梯度下降),梯度下降的含义在于我们知道了误差,现在想要将误差减小,注意这里的参数是w,b\mathbf w,b,你可以简单理解为x,yx,y一元函数优化的过程,下图展示了梯度下降:
【深度学习基础】简单易懂理解BP算法
如图,我们需要从实际的loss降到期望的最小loss,很显然,最快的方法就是验证导数最大的反方向下降,但是我们下降了一会,到了一个新的loss点的时候,原理的导数最大方向不是当前的导数最大方向了。因此,在梯度下降算法中常常有一个超参数叫做学习率(α\alpha),它控制了梯度下降的步长,告诉我们走一段之后重新计算梯度,再往下走。当基本稳定在某个点的时候就不需要再继续下降了。

上面是梯度下降的过程,让我们理解参数是如何被优化的,但同时也可引出三个常见问题的答案

  • 梯度下降得到的最小loss是极值点,不是最值点
  • 学习率不能太小,会使得收敛速度过慢,训练时间过长
  • 学习率不能太大,会达不到区域最优,在附近震荡
    【深度学习基础】简单易懂理解BP算法

到此一轮参数更新已经完成,实际训练中,往往要进行多个周期的更新达到更好的效果,即重复上面的步骤即可。

2. 多层网络的参数优化

上面的方法仅适用于单层(只有输入输出层)的参数优化,当遇到多层网络时,最后一层的参数依然可根据输出的损失,但如何将最后一层的损失传递到前一层,再更新前一层的参数就需要BP算法出马了。

2.1 多层网络模型设计

同样为了简便理解,我们采用一个稍微简单一些的多层网络描述BP算法,网络结构如下:
【深度学习基础】简单易懂理解BP算法

需要解释的地方是f(a1)=o1f(a_1) = o_1是,a1a_1是前面x\mathbf xw,b\mathbf w,b计算得到结果,ff表示**函数,用于添加非线性操作,使得网络能够处理非线性问题。o1o_1是该神经元的输出,也是下一层的输入

因此我们可以得到第ll层和l1l-1层的关系公式如下所示:
al=wlol1+bol=f(al)(3)\mathbf a_l = \mathbf w_l \mathbf o_{l-1}+b\\ \mathbf o_l = f(\mathbf a_l) \tag{3}

其中前面一条与单层的时候类似,只是后面多了一个**函数。因此我们可以将整个网络写成下面的方式(为了简便,参数bb省略):
f3(w3f2(w2f1(w1x)))=y(4)f_3(\mathbf w_3f_2(\mathbf w_2f_1(\mathbf w_1\mathbf x))) = y\tag{4}

说白了就是输入转换成输出再当输入,进行多次(可以理解成多个单层叠加)

2.2 BP算法

我们用CC表示损失函数,依据公式(3)(3),对任意层ll,将公式形式表达如下(将w\mathbf w扩展成增广矩阵W\mathbf W,b放到增广矩阵中):
al=Wlol1=g(Wl)ol=f(al)(5)\mathbf a_l = \mathbf W_l \mathbf o_{l-1} = g(\mathbf W_l)\\ \mathbf o_l = f(\mathbf a_l) \tag{5}

最后一层输出oL\mathbf o_L会被用于求损失函数CC

按照梯度下降原则,即LLll层参数求导,结合(5)采用求导链式法则
CWl=CalalWl=CalWlol1Wl=Calol1T(6)\frac{\partial C}{\partial \mathbf W_l} =\frac{\partial C}{\partial \mathbf a_l}\frac{\partial \mathbf a_l}{\partial \mathbf W_l} = \frac{\partial C}{\partial \mathbf a_l} \frac{\partial \mathbf W_l \mathbf o_{l-1}}{\partial \mathbf W_l} = \frac{\partial C}{\partial \mathbf a_l}\mathbf o_{l-1}^T \tag{6}

这里用到了矩阵求导法则AxA=xT\frac{\partial A\mathbf x}{\partial A} = \mathbf x^T

注意这部分出来了一个Cal\frac{\partial C}{\partial \mathbf a_l}就是误差向量,我们用ξl\bm\xi_l表示,所谓误差反向传播,就是要将最后一层计算得到的误差传到前面层去,来更新前面的参数。因此我们考虑l+1l+1层和ll层的关系,利用链式法则将lll+1l+1联系,并根据公式(5)(5)引入ol\mathbf o_l,最终的目的是要ξl\bm \xi_l的表达式中只包含ξl+1,Wl+1\bm \xi_{l+1},\mathbf W_{l+1},如下推导:

ξl=Cal=Cal+1al+1al=Cal+1al+1ololal=ξl+1Wl+1ololf(al)=ξl+1Wl+1Tf(al)(7)\bm \xi_l = \frac{\partial C}{\partial \mathbf a_{l}} = \frac{\partial C}{\partial \mathbf a_{l+1}} \frac{\partial \mathbf a_{l+1}}{\partial \mathbf a_{l}} \\ = \frac{\partial C}{\partial \mathbf a_{l+1}} \frac{\partial \mathbf a_{l+1}}{\partial \mathbf o_l}\frac{\partial \mathbf o_{l}}{\partial \mathbf a_{l}} \\ = \bm \xi_{l+1}\frac{\partial \mathbf W_{l+1}\mathbf o_l}{\partial \mathbf o_l} f'(\mathbf a_l)\\ = \bm \xi_{l+1}\mathbf W_{l+1}^Tf'(\mathbf a_l) \tag{7}

这部分的推导可能稍微难理解,需要结合公式(5)对三个部分分别计算求导,将al+1,ol\mathbf a_{l+1},\mathbf o_l分别替换再求导。矩阵对向量求导公式Axx=AT\frac{\partial A\mathbf x}{\partial \mathbf x} = A^T

现在Wl+1T,f(al)\mathbf W_{l+1}^T,f'(\mathbf a_l)都是确定的,只要我们求得最后一层的误差向量ξL\bm \xi_{L}就可以推出倒数第二层的误差向量,再推出倒数第三层…

ξL=CaL=12oLy2aL=12f(aL)y2aL=(f(aL)y)f(aL)(8)\bm \xi_{L} = \frac{\partial C}{\partial \mathbf a_L} = \frac{\partial \frac{1}{2}||\mathbf o_L-\mathbf y||^2}{\partial \mathbf a_L} = \frac{\partial \frac{1}{2}|| f(\mathbf a_L)-\mathbf y||^2}{\partial \mathbf a_L} \\ = (f(\mathbf a_L)-\mathbf y)f'(\mathbf a_L) \tag{8}

下面我们就用LL层的误差向量去更新L层参数权重:

由公式(6)和梯度下降法知
WL=WLαCWL=WLαξLf(aL)=WLα(f(aL)y)f(aL)f(aL)(9)\mathbf W_L = \mathbf W_L-\alpha\frac{\partial C}{\partial \mathbf W_L} = \mathbf W_L - \alpha \bm \xi_Lf(\mathbf a_L)\\ =\mathbf W_L - \alpha (f(\mathbf a_L)-\mathbf y)f(\mathbf a_L) f'(\mathbf a_L) \tag{9}

LL层的参数被更新后,计算倒数第二层的误差向量ξL1\bm \xi_{L-1},再去更新L1L-1层的参数,一直到第一层。

在这里我们也可以发现一个常见问题,为什么当网络层数过深的时候,会出现梯度消失的情况。注意到每一次的误差都是要乘上**函数的导数的-8公式(8),因此,当采用sigmod这类当自变量较大,导数趋近于0的**函数时,经过不断的累乘,会使得误差向量趋近于0,使得误差没办法传到前面几层。这种时候可以选用RELU这类梯度较为稳定的**函数替换。

3. 总结

BP算法涉及到较为复杂的求导和更新,一遍读下来可能会觉得晕,不过不用急,多读几遍,将几个不理解的地方自己手动推导一下,我们总结一下BP的全过程,主要有以下几个步骤:

  1. 正向传播得到预测输出
  2. 将预测输出与真实输出根据损失函数计算误差
  3. 根据误差更新参数权重-(9)公式(9)
  4. 根据本层误差和本层的参数矩阵和**函数的导数-(7)公式(7)求传递给前一层的误差
  5. 重复3-4,直到第一层

这篇文章写了5k+字,主要是公式编辑很多,码字不易,如果觉得对你理解BP算法有帮助,不妨点个赞吧~

4. 参考资料

感谢前人的总结和详细的阐述,本文主要还是在前人基础上做了更细微一些的讲解,其中有些地方是我自己的理解,可能有错误或不到之处,欢迎指正批评~