摘要:
关于BP算法(误差反向传播)网上的学习、参考资料已经有很多了。每篇文章都差不多,都是在阐述BP的数学原理或者实践。本篇文章其实也和网上大部分文章一样,但从一个初学者的角度来分享学习这个算法的过程的一些体会。
参考资料:
知乎-BP算法
清晰的数学推导
预先定义:
网上许多资料都是以单层的神经网络为例推导BP算法,本文用一个两个隐藏层的神经网络为例推导BP算法。(其实结果是一样的)。网络结构如下图:
接着,我们定义一下一些参数。(请不要跳过这个环节)
假设输入层的每一个输入表示为xm,m=1,2...Ninput
假设输入层第m个输入与第一个隐藏层的第i个神经元的连接权重为uim。
那么,第一层隐藏层的第i神经元输入为neti,neti=∑Ninputm=1uimxm。
因此,经过**函数f(x)后,第i个神经元的输出为y(1)i
y(1)i=f(neti)
同理可以得到第二个隐藏层:
第二层隐藏层的第j个神经元输入为netj,netj=∑N(1)m=1vjmy(1)m,其中N(1)是第二层的神经元个数。vjm是权重。经过**函数后其值为y(2)j,y(2)j=f(netj)
最后,可以得到输出层:
netk=∑N(2)m=1wkmy(2)m,经过**函数后其值为ok,ok=f(netk)。
另外,假设目前的损失函数为E=12∑N(o)m=1(ok−tk)2。(这里的0.5系数是为了方便计算)
这里需要提一下的是,很多文章都是把损失函数定义成均方误差,这个主要是为是好解释BP算法。在后面推导完后就可以发现,其实任意的可导损失函数都可以,本文也会简单的说明。
另外,一些读者可能发现上面在求net的值时没有写明计算偏置项,其实这不影响我们的公式推导,后面也会做说明(当然,你也可以把公式理解成已经把偏置项包含进去了,此时输入总是1)
推导
BP算法解决的是什么问题?解决的是如何更新网络中的权重值。
一般来说,对于任意一个权重我们都有:
wafter:=wbefore+Δw,第二项其实就是我们熟知的学习率和梯度的乘积。
在这个多层的网络中,需要学习的参数有wkm,vjm,uim。
其于目标函数E的梯度值分别为:
∂E∂wkm,∂E∂vjm,∂E∂uim。只要能够求出上面这3个式子,我们就完成了更新权重的任务。所以下面,一个一个求解。
1.先求∂E∂wkm:
由链式法则,我们可以得到下式:
∂E∂wkm=∂E∂netk∗∂netk∂wkm。
(这里简单解释一下为什么会想到这一步。回头看我们的一些参数定义,我们可以看到,wkm和netk是最直接的函数关系,所以我们选择引入中间变量netk,另一方面,我们如果把神经元输入作为中间变量引入到链式法则中后面推导得到式子会很整齐,很有规律性)
接着,我们定义一个误差信号:
所谓误差信号即:δ(o)k=∂E∂netk,那么上式就可以写成:
∂E∂wkm=∂E∂netk∗∂netk∂wkm=δ(o)k∗∂netk∂wkm。
对于∂netk∂wkm其实很好求解。
因为netk=∑N(2)m=1wkmy(2)m,故∂netk∂wkm=y(2)m(注意,求和式子里只有一个wkm)
下面求解δ(o)k:
δ(o)k=∂E∂netk=∂E∂ok∗∂ok∂netk
(会想到这个是因为和netk构成最直接的函数关系的是ok)
∂ok∂netk=f′(netk)
由于E=12∑N(o)m=1(ok−tk)2,
故∂E∂ok=(ok−tk)。
OK,至此解决了∂E∂wkm。其值具体为:
∂E∂wkm=δ(o)k∗y(2)m,
其中δ(o)k=(ok−tk)∗f′(netk)
(注意,解释一下为什么我推导到这里就算推导完成,我们推导的目的就是为了让程序能构计算。上式中的任何一个值都是可以通过计算得到的)
2.再求∂E∂vjm:
求解的思路其实和上面的差不多,有一点区别,具体看推导过程。
∂E∂vjm=∂E∂netj∗∂netj∂vjm
(同样,这里引入netj的原因和上面netk是一样的)
同样定义一个误差信号δ(2)j,δ(2)j=∂E∂netj。
而∂netj∂vjm=y(1)m
(由netj=∑N(1)m=1vjmy(1)m可得)
那么上式就变成了:
∂E∂vjm=∂E∂netj∗∂netj∂vjm=δ(2)j∗y(1)m
(在这里,我通过和前面的∂E∂wkm对比,我们就能够发现,两者的形式非常像,其形式可以基本归纳为 误差信号δ *上一层的某一个输入x)
那么δ(2)j如何求解?和上面类似,我们容易想到再引入一个中间变量。
δ(2)j=∂E∂netj=∂E∂y(2)j∗∂y(2)j∂netj.
容易得到:∂y(2)j∂netj=f′(netj)
对于∂E∂y(2)j稍微比较复杂,但是也是整个BP最核心的一部分。
我们沿用上面的想法,和y(2)j直接构成函数关系的是netk,自然有:
∂E∂y(2)j=∂E∂netk∗∂netk∂y(2)j。
这个想法是对的,但是结果是错误的。这里其实是没有看到对于y(2)j,其不单单只影响了一个netk。而是影响了整个输出层的net。可以参考下面这个图。
蓝线所指的神经元输出是y(2)j,其红线连接的神经元是受其影响的神经元(这个也是BP算法的思想)
所以∂E∂y(2)j=∑N(o)k=1(∂E∂netk∗∂netk∂y(2)j)。
进一步的,我们发现其中包含有一个误差信号,替换可以得到:
∂E∂y(2)j=∑N(o)k=1(∂E∂netk∗∂netk∂y(2)j)=∑N(o)k=1(δok∗∂netk∂y(2)j)。
最后由函数直接关系得到∂netk∂y(2)j=wkj。
带入所有式子可以有:
∂E∂y(2)j=∑N(o)k=1(δok∗wkj)
δ(2)j=∂E∂netj=∂E∂y(2)j∗∂y(2)j∂netj=∑N(o)k=1(δok∗wkj)∗f′(netj)。
上式的任何一个数都可以计算得到。故推导完毕。
即∂E∂vjm=δ(2)j∗y(1)m,
其中δ(2)j=∑N(o)k=1(δok∗wkj)∗f′(netj)。
3.最后求∂E∂uim:
(其实到这里,读者可以自己尝试一下求解)
同样地,沿用上面的思路有:
∂E∂uim=∂E∂neti∗∂neti∂uim。
∂neti∂uim=xm
定义误差信号δ(1)i=∂E∂neti
故上式也可写为:
∂E∂uim=∂E∂neti∗∂neti∂uim=δ(1)i∗xm。(和上面的式子也是类似的)
下面也是求解δ(1)i。
δ(1)i=∂E∂neti=∂E∂y(1)i∗∂y(1)i∂neti
而∂y(1)i∂neti=f′(neti)
∂E∂y(1)i=∑N(2)j=1(∂E∂netj∗∂netj∂y(1)i)。
(这里和前面是类似的。原因参考上面的图)
这里同样存在误差信号,替换后可以得到:
∂E∂y(1)i=∑N(2)j=1(∂E∂netj∗∂netj∂y(1)i)=∑N(2)j=1(δ(2)j∗∂netj∂y(1)i)。
且∂netj∂y(1)i=vji。
故:∂E∂y(1)i=∑N(2)j=1(δ(2)j∗vji)。
最终我们可以得到误差信号:
δ(1)i=∂E∂neti=∂E∂y(1)i∗∂y(1)i∂neti=∑N(2)j=1(δ(2)j∗vji)∗f′(neti)。
因此对于梯度∂E∂uim=δ(1)i∗xm,
其中δ(1)i=∑N(2)j=1(δ(2)j∗vji)∗f′(neti)。
至此,所有的推导工作已经完成了,我们来对比一下我们最后得到这三个式子:
对于输出层:
∂E∂wkm=δ(o)k∗y(2)m,其中δ(o)k=(ok−tk)∗f′(netk)
第二个隐藏层
∂E∂vjm=δ(2)j∗y(1)m,
其中δ(2)j=∑N(o)k=1(δok∗wkj)∗f′(netj)。
第一个隐藏层
∂E∂uim=δ(1)i∗xm,
其中δ(1)i=∑N(2)j=1(δ(2)j∗vji)∗f′(neti)。
相信,大家看到这三个式子就能够得出自己的结论了。
其实对于求解梯度来说,最为关键的就是求解误差信号,而各层之间的误差信号其实存在稳定的关系
关于损失函数
在上面的推导中,我们一直指定了损失函数E=12∗(ok−tk)2。也就说我们采用MSE作为我们的loss function。
如果我们希望使用其他的损失函数,那么BP的推导有哪些部分会发生变化呢?
比如采用softmax作为最后一层的**函数,交叉熵作为loss function,整个推导过程有什么变化的?
具体可以参考: BP算法推导-softmax层+交叉熵(logloss)。文中虽然以RNN为例,但总体的思路是相似的。
关于最后一层的**函数问题
也许有人会困惑,如果最后一层也就是输出层不经过**函数处理,其推导结果会有什么不同呢?
回头看推导过程,其实这个也只会影响输出层的信号误差这一步,具体表现为:δ(o)k=∂E∂ok∗∂ok∂netk中的∂ok∂netk。若ok=netk,那么其偏导的结果就为1。
故:δ(o)k=∂E∂ok
关于偏置项
其实是否显式的写出偏置项不影响公式的推导。在这里,我们可以尝试加上看对我们的结果有何影响。
假设:
netk=∑N(2)m=1wkmy(2)m+bk,经过**函数后其值为ok,ok=f(netk)。
那么对于∂E∂wkm的求法不变,
下面来看∂E∂bk:
∂E∂bk=∂E∂netk∗∂netk∂bk
可以看到,对于∂E∂bk和∂E∂wkm区别仅仅在于第二项∂netk∂bk。
对于∂netk∂bk,其值为1
对于∂E∂wkm,其值为y(2)m。
所以,只要把偏置项目看做是其中一个wkm,但其输入恒定为1即可。