神经网络的反向传播公式的推导
前言:
早该开始入坑CNN,RNN的博主总觉得要先能用python加numpy手撸一个神经网络,才能更好理解其他神经网络的原理(强迫症)。于是…这一拖就是快两月(懒),最近填坑的时候才发现以为自己很通透的反向传播过程,写起代码推起来就…。
光看西瓜书觉得反向传播就是损失函数反向对每一层参数求偏导的过程。但西瓜书推导仅在三层网络上,各层参数符号定义也不统一(博主太笨)。于是又结合吴恩达《machine learning》课程才算推导出能够写成代码迭代形式的公式。同时发现吴恩达老师视频有关键步骤是简略带过,让基础较差初学者推导起来也很生硬。于是,博主在他网络课基础上再推导一遍记成笔记。水平有限,有疏漏错误欢迎指正。
正文
神经网络的前向传播和符号
在《machine learning》里神经网络的前向传播部分的公式被定义如下:
输入层是x
,中间有两层隐藏层,输出层是
a
(
4
)
a^{(4)}
a(4)。
a
(
1
)
=
x
z
(
2
)
=
Θ
(
1
)
a
(
1
)
a
(
2
)
=
g
(
z
(
2
)
)
z
(
3
)
=
Θ
(
2
)
a
(
2
)
a
(
3
)
=
g
(
z
(
3
)
)
z
(
4
)
=
Θ
(
3
)
a
(
3
)
a
(
4
)
=
h
Θ
(
x
)
=
g
(
z
(
4
)
)
a^{(1)} = x \newline z^{(2)} = \Theta^{(1)}a^{(1)} \newline a^{(2)} = g(z^{(2)}) \newline z^{(3)} = \Theta^{(2)}a^{(2)} \newline a^{(3)} = g(z^{(3)}) \newline z^{(4)} = \Theta^{(3)}a^{(3)} \newline a^{(4)} = h_\Theta(x) = g(z^{(4)})
a(1)=xz(2)=Θ(1)a(1)a(2)=g(z(2))z(3)=Θ(2)a(2)a(3)=g(z(3))z(4)=Θ(3)a(3)a(4)=hΘ(x)=g(z(4))
在这个神经网络里面的g(x)是每个神经元的激活函数,采用sigmoid模型:
g
(
x
)
=
1
1
+
e
(
−
x
)
g(x) = \frac{1}{1+e^{(-x)}}
g(x)=1+e(−x)1
而sigmoid函数有一个在推导过程使用到性质,对它求导有:
g
′
(
x
)
=
g
(
x
)
∗
(
1
−
g
(
x
)
)
g'(x) = g(x)*(1-g(x))
g′(x)=g(x)∗(1−g(x))
吴老师讲解神经网络给的损失函数模型是交叉熵:
J
(
Θ
)
=
−
1
m
∑
i
=
1
m
∑
k
=
1
K
[
y
k
(
i
)
log
(
(
h
Θ
(
x
(
i
)
)
)
k
)
+
(
1
−
y
k
(
i
)
)
log
(
1
−
(
h
Θ
(
x
(
i
)
)
)
k
)
]
+
λ
2
m
∑
l
=
1
L
−
1
∑
i
=
1
s
l
∑
j
=
1
s
l
+
1
(
Θ
j
,
i
(
l
)
)
2
J(\Theta) = - \frac{1}{m} \sum_{i=1}^m \sum_{k=1}^K \left[y^{(i)}_k \log ((h_\Theta (x^{(i)}))_k) + (1 - y^{(i)}_k)\log (1 - (h_\Theta(x^{(i)}))_k)\right] + \frac{\lambda}{2m}\sum_{l=1}^{L-1} \sum_{i=1}^{s_l} \sum_{j=1}^{s_{l+1}} ( \Theta_{j,i}^{(l)})^2
J(Θ)=−m1i=1∑mk=1∑K[yk(i)log((hΘ(x(i)))k)+(1−yk(i))log(1−(hΘ(x(i)))k)]+2mλl=1∑L−1i=1∑slj=1∑sl+1(Θj,i(l))2
化成矩阵的同时方便下面推导形式有:
J
(
Θ
)
=
−
1
m
∑
(
y
∗
l
o
g
(
a
(
4
)
)
)
+
(
1
−
y
)
∗
l
o
g
(
1
−
a
(
4
)
)
)
J(\Theta) = - \frac{1}{m}\sum(y * log(a^{(4)})) + (1-y)*log(1-a^{(4)}))
J(Θ)=−m1∑(y∗log(a(4)))+(1−y)∗log(1−a(4)))
而在周志华老师西瓜书的推导里,给出神经网络的损失函数是均方误差模型:
E
k
=
1
2
∑
(
h
Θ
(
x
)
−
y
)
2
Ek=\frac{1}{2}\sum(h_\Theta(x)- y)^2
Ek=21∑(hΘ(x)−y)2
这里y是标记的正确输出结果。
但其无论该神经网络损失函数是什么,他们反向传播的原理都是一致的。(埋伏笔,下面有解释)
我们先和吴恩达视频里一样先定义 δ ( i ) \delta^{(i)} δ(i)来表示神经网络里第i层的误差。(先不要纠结为什么这么定义,继续看下去)。
同时在吴恩达视频里最难理解一步就是误差
δ
(
4
)
\delta^{(4)}
δ(4)只给出较为抽象的解释。
实际上他这里定义的误差
δ
(
i
)
\delta^{(i)}
δ(i)有公式:
δ ( i ) = ∂ J ∂ z ( i ) \delta^{(i)} = \frac{\partial J}{\partial z^{(i)}} δ(i)=∂z(i)∂J
回到误差定义,从字面理解,上面神经网络输出层误差可以看做神经网络的输出和标记结果的差值:
δ
(
4
)
=
a
(
4
)
−
y
\delta^{(4)} = a^{(4)} - y
δ(4)=a(4)−y
但实际上这个公式是经过误差 δ ( 4 ) \delta^{(4)} δ(4) 的公式对 z ( 4 ) z^{(4)} z(4) 求偏导而来!
由上面
δ
(
i
)
\delta^{(i)}
δ(i)的公式定义及高数里链式求导法则:
δ
(
4
)
=
∂
J
∂
z
(
4
)
=
∂
J
∂
a
(
4
)
∗
∂
a
(
4
)
∂
z
(
4
)
\delta^{(4)} = \frac{\partial J}{\partial z^{(4)}} = \frac{\partial J}{\partial a^{(4)}}*\frac{\partial a^{(4)}}{\partial z^{(4)}}
δ(4)=∂z(4)∂J=∂a(4)∂J∗∂z(4)∂a(4)
而
∂
J
∂
a
(
4
)
=
−
(
y
a
(
4
)
-
(
1
−
y
)
1
−
a
(
4
)
)
\frac{\partial J}{\partial a^{(4)}}=-( \frac{y}{a^{(4)}}- \frac{(1-y)}{1-a^{(4)}})
∂a(4)∂J=−(a(4)y-1−a(4)(1−y))
由上面sigmoid函数求导性质:
∂
a
(
4
)
∂
z
(
4
)
=
g
′
(
z
(
4
)
)
=
(
1
−
a
(
4
)
)
∗
a
(
4
)
\frac{\partial a^{(4)}}{\partial z^{(4)}}=g'(z^{(4)}) =(1-a^{(4)})*a^{(4)}
∂z(4)∂a(4)=g′(z(4))=(1−a(4))∗a(4)
所以两者相乘有:
δ
(
4
)
=
a
(
4
)
−
y
\delta^{(4)} = a^{(4)} - y
δ(4)=a(4)−y
接着关于前面几层的误差 δ ( 3 ) \delta^{(3)} δ(3), δ ( 2 ) \delta^{(2)} δ(2)部分的推导才是重点,请继续阅读。
推导反向传播
所以对于
δ
(
3
)
\delta^{(3)}
δ(3)同样经过导数的链试法则有:
δ
(
3
)
=
Δ
z
(
3
)
=
∂
J
∂
z
(
4
)
∗
∂
z
(
4
)
∂
a
(
3
)
∗
∂
a
(
3
)
∂
z
(
3
)
\delta^{(3)} = \Delta z^{(3)} = \frac{\partial J}{\partial z^{(4)}}*\frac{\partial z^{(4)}}{\partial a^{(3)}} * \frac{\partial a^{(3)}}{\partial z^{(3)}}
δ(3)=Δz(3)=∂z(4)∂J∗∂a(3)∂z(4)∗∂z(3)∂a(3)
对于:
∂
z
(
4
)
∂
a
(
3
)
=
θ
(
3
)
\frac{\partial z^{(4)}}{\partial a^{(3)}} = \theta^{(3)}
∂a(3)∂z(4)=θ(3)
∂
a
(
3
)
∂
z
(
3
)
=
g
’
(
z
(
3
)
)
\frac{\partial a^{(3)}}{\partial z^{(3)}} = g’(z(3))
∂z(3)∂a(3)=g’(z(3))
所以:
δ
(
3
)
=
δ
(
4
)
∗
θ
(
3
)
∗
g
’
(
z
(
3
)
)
\delta^{(3)} =\delta^{(4)}* \theta^{(3)} * g’(z(3))
δ(3)=δ(4)∗θ(3)∗g’(z(3))
高冷预警,同理
δ
(
2
)
\delta^{(2)}
δ(2) :
δ
(
2
)
=
Δ
z
(
2
)
=
∂
J
∂
z
(
3
)
∗
∂
z
(
3
)
∂
a
(
2
)
∗
∂
a
(
2
)
∂
z
(
2
)
\delta^{(2)} = \Delta z^{(2)} = \frac{\partial J}{\partial z^{(3)}}*\frac{\partial z^{(3)}}{\partial a^{(2)}} * \frac{\partial a^{(2)}}{\partial z^{(2)}}
δ(2)=Δz(2)=∂z(3)∂J∗∂a(2)∂z(3)∗∂z(2)∂a(2)
观察一下发现没 ?求
δ
(
i
)
\delta^{(i)}
δ(i) 过程已经可以写成迭代形式,不信你再看下面,很直接根据正向传递公式有:
∂
z
(
i
+
1
)
∂
a
(
i
)
=
θ
(
i
)
\frac{\partial z^{(i+1)}}{\partial a^{(i)}} = \theta^{(i)}
∂a(i)∂z(i+1)=θ(i)
∂
a
(
i
)
∂
z
(
i
)
=
g
’
(
z
(
i
)
)
\frac{\partial a^{(i)}}{\partial z^{(i)}} = g’(z(i))
∂z(i)∂a(i)=g’(z(i))
所以:
δ
(
i
)
=
Δ
z
(
i
)
=
δ
(
i
+
1
)
∗
θ
(
i
)
∗
g
’
(
z
(
i
)
)
\delta^{(i)} = \Delta z^{(i)} = \delta^{(i+1)}* \theta^{(i)} * g’(z(i))
δ(i)=Δz(i)=δ(i+1)∗θ(i)∗g’(z(i))
ok! 但是我们实际最想求得是J对参数
θ
\theta
θ的偏导,因为梯度下降更新就是它。
依然是利用导数链式求导法则有:
∂
J
∂
θ
(
i
)
=
Δ
θ
(
i
)
=
∂
J
∂
z
(
i
+
1
)
∗
∂
z
(
i
+
1
)
∂
θ
(
i
)
\frac{\partial J}{\partial \theta^{(i)}} = \Delta \theta^{(i)} = \frac{\partial J}{\partial z^{(i+1)}}*\frac{\partial z^{(i+1)}}{\partial \theta^{(i)}}
∂θ(i)∂J=Δθ(i)=∂z(i+1)∂J∗∂θ(i)∂z(i+1)
=
δ
(
i
+
1
)
∗
a
(
i
)
= \delta^{(i+1)}* a^{(i)}
=δ(i+1)∗a(i)
这里我们就可以迭代向前求 ∂ J ∂ θ ( i ) \frac{\partial J}{\partial \theta^{(i)}} ∂θ(i)∂J去更新 θ ( i ) \theta^{(i)} θ(i)。
代码
深度学习已经被很多人诟病是黑盒学习原因之一就是层数多了之后每层正向传播还好理解,但参数矫正(反向更新)的过程看着很清晰,但写出一个神经网络还是让很多初学者学的望而止步。
很多时候看了很多优秀博客知乎,明明知道就是利用链式法则方向对每一层参数求偏导的过程,但就是推不出一个可以写程序迭代的公式。
所以如果阅读完上面我总结关于Bp神经网络方向传播的笔记有收获的话,最好能写一个程序来检验一下自己学习成果。
Talk is cheap,show me your code!
我的代码放在github。