文章目录
前向传递与反向传播
引言
神经网络其实就是一个输入 X X X到输出 Y Y Y的映射函数: f ( X ) = Y f(X)=Y f(X)=Y,函数的系数(权重)就是我们所要训练的网络参数 W W W,只要能够确定网络参数 W W W,对于任何的输出 x i x_i xi就能得到与之对应的 y i y_i yi。
问题
当我们手中只有训练集的输入 X X X和输出 Y Y Y,我们应该如何调整网络参数 W W W,使得输出的 f ( X ) = Y ^ f(X)=\hat{Y} f(X)=Y^与训练集中的真实值 Y Y Y尽可能接近?
先对反向传播过程有一个直观上的印象。首先,反向传播的核心是cost fuction
J
J
J对网络中参数(各层的权重
W
W
W以及偏置项
b
b
b)的偏导表达式,如下:
∂
J
∂
w
\frac{\partial{J}}{\partial{w}}
∂w∂J
∂
J
∂
b
\frac{\partial{J}}{\partial{b}}
∂b∂J
上述的表达式描述了代价函数
J
J
J随权重
W
W
W或偏置
b
b
b变化而变化的程度。
BP算法的思路: 如果当前代价函数所得的数值与真实值相差较远,通过调整 w w w和 b b b的值,使得新的代价函数所得的预测值更接近真实值。一直重复该过程,直到最终的代价函数所得的预测值与真实值相差在误差范围之内,则停止迭代。
前向传递过程
前向传播过程:已知输入
X
X
X、权重
W
W
W以及偏置
b
b
b,求得输出
Y
Y
Y。
最普通的DNN可以看作一个多层感知机MLP,
w
24
(
3
)
w_{24}^{(3)}
w24(3)代表神经网络第2层的第4个神经元连接神经网络第3层的第2个神经元的权重。
提问:为什么权重不写成 w 42 ( 3 ) w_{42}^{(3)} w42(3)?
回答:如果写成 w 42 ( 3 ) w_{42}^{(3)} w42(3),则第3层神经元的输入就要通过 w T x + b w^Tx+b wTx+b来计算,而当权重为 w 24 ( 3 ) w_{24}^{(3)} w24(3)时,只需要计算 w x + b wx+b wx+b,省略了矩阵转置的额外工作量。
偏置项
b
b
b的定义也类似,
b
3
(
2
)
b_3^{(2)}
b3(2)表示神经网络第2层的第3个神经元的偏置。
再定义
a
j
i
a_j^i
aji和
z
j
i
z_j^i
zji,分别表示第
i
i
i层神经网络的第
j
j
j个神经元的激活前的输入和激活后的输出,并假设
σ
(
z
)
\sigma(z)
σ(z)是激活函数。
Sigmoid函数:
σ ( x ) = 1 1 + e − x \sigma(x)=\frac{1}{1+e^{-x}} σ(x)=1+e−x1
σ ′ ( x ) = e − x ( 1 + e − x ) 2 = 1 1 + e − x ⋅ ( 1 − 1 1 + e − x ) = σ ( x ) ⋅ ( 1 − σ ( x ) ) {\sigma}'(x)=\frac{e^{-x}}{(1+e^{-x})^2} = \frac{1}{1+e^{-x}}\cdot{(1-\frac{1}{1+e^{-x}})}=\sigma(x)\cdot(1-\sigma(x)) σ′(x)=(1+e−x)2e−x=1+e−x1⋅(1−1+e−x1)=σ(x)⋅(1−σ(x))
举例:
对于第2层第1个节点的输出
a
1
2
a_1^2
a12有:
a
1
2
=
σ
(
z
1
2
)
=
σ
(
w
11
2
x
1
+
w
12
2
x
2
+
w
13
2
x
3
+
b
1
2
)
a_1^2 = \sigma(z_1^2)=\sigma(w_{11}^2x_1+w_{12}^2x_2+w_{13}^2x_3+b^2_1)
a12=σ(z12)=σ(w112x1+w122x2+w132x3+b12)
所以一般化,假设
l
−
1
l-1
l−1层有
m
m
m个神经元,对于
a
j
i
a_j^i
aji有:
a
j
l
=
σ
(
z
j
l
)
=
σ
(
∑
k
=
1
m
w
j
k
l
a
k
l
−
1
+
b
j
l
)
a_j^l=\sigma(z_j^l)=\sigma(\sum_{k=1}^m{w_{jk}^la_k^{l-1}+b_j^l})
ajl=σ(zjl)=σ(k=1∑mwjklakl−1+bjl)
矩阵形式:
a
L
=
σ
(
w
L
a
L
−
1
+
b
L
)
a^L = \sigma(w^La^{L-1}+b^L)
aL=σ(wLaL−1+bL)
反向传播过程
当通过前向传递得到由任意一组随机参数 W W W和 b b b计算出的预测结果后,我们可以利用损失函数相对于每个参数的梯度对他们进行修正,从而得到误差范围内的模型,即目标函数 Y Y Y。事实上,神经网络的训练就是这样一个不停的前向-反向传播的过程,直到网络的预测能力达到我们的预期。
反向传播的四个基本方程
根据上述过程描述,权重
w
w
w和偏置
b
b
b的改变如何影响代价函数
J
J
J是理解反向传播的关键,意味着计算出每个
∂
J
∂
w
j
k
l
\frac{\partial{J}}{\partial{w^l_{jk}}}
∂wjkl∂J和
∂
J
∂
b
j
l
\frac{\partial{J}}{\partial{b_j^l}}
∂bjl∂J。
在讨论之前,引入误差
δ
j
l
\delta_j^l
δjl,表示第
l
l
l层第
j
j
j个神经元的误差。对于这个误差的理解,《Neural Network and Deep Learning》书中给了一个比较形象的例子。
如上图所示,有一个小恶魔在网络的第
l
l
l层第
j
j
j个神经元捣乱,使得这个神经元发生了变化
Δ
z
j
l
\Delta z_j^l
Δzjl,那么这个神经元在激活后即为
σ
(
z
j
l
+
Δ
z
j
l
)
\sigma(z_j^l+\Delta z_j^l)
σ(zjl+Δzjl),然后这个误差向后逐层传播下去,导致最终的代价函数变化了
∂
J
∂
z
j
l
Δ
j
l
\frac{\partial{J}}{\partial{z_j^l}}\Delta_j^l
∂zjl∂JΔjl。然而,小恶魔良心发现,给予我们帮助,使得目标函数
Y
Y
Y输出的值接近groud truth。选择一个和
∂
J
∂
z
j
l
\frac{\partial{J}}{\partial{z_j^l}}
∂zjl∂J方向相反的
Δ
z
j
l
\Delta z_j^l
Δzjl,使得损失函数的值更小(梯度下降法)。随着迭代的进行,
∂
J
∂
z
j
l
\frac{\partial{J}}{\partial{z_j^l}}
∂zjl∂J会逐渐趋向于0,此时有局部最优解了。这就启发我们可以用
∂
J
∂
z
j
l
\frac{\partial{J}}{\partial{z_j^l}}
∂zjl∂J来衡量神经元的误差:
δ
j
l
=
∂
J
∂
z
j
l
\delta_j^l = \frac{\partial{J}}{\partial{z_j^l}}
δjl=∂zjl∂J
1. 输出层的误差方程(计算最后一层神经网络产生的误差)
δ j L = ∂ J ∂ z j L = ∂ J ∂ a j L ∂ a j L ∂ z j L = ∂ J ∂ a j L σ ′ ( z j L ) \delta_j^L = \frac{\partial{J}}{\partial{z_j^L}}=\frac{\partial{J}}{\partial{a_j^L}}\frac{\partial{a_j^L}}{\partial{z_j^L}}=\frac{\partial{J}}{\partial{a_j^L}}{\sigma}'(z_j^L) δjL=∂zjL∂J=∂ajL∂J∂zjL∂ajL=∂ajL∂Jσ′(zjL)
注释:等式的最右边的第一项, ∂ J ∂ a j L \frac{\partial{J}}{\partial{a_j^L}} ∂ajL∂J衡量了代价函数随着网络最终输出的变化快慢,而第二项 σ ′ ( z j L ) {\sigma}'(z_j^L) σ′(zjL)则衡量了激活函数输出随 z j L z_j^L zjL的变化快慢。 L L L指最后一层输出层。
矩阵形式:
假设代价函数为
J
(
w
,
b
,
x
,
y
)
=
1
2
∑
j
=
1
m
(
y
j
−
a
j
L
)
2
J(w, b, x, y) = \frac{1}{2}\sum_{j=1}^m(y_j-a_j^L)^2
J(w,b,x,y)=21∑j=1m(yj−ajL)2,则
∂
J
∂
a
j
L
=
a
j
L
−
y
i
\frac{\partial{J}}{\partial{a_j^L}}=a_j^L-y_i
∂ajL∂J=ajL−yi。
则得到以下公式:
δ
L
=
▽
a
J
⊙
σ
′
(
z
L
)
\delta^L=\bigtriangledown_aJ\odot{\sigma}'(z^L)
δL=▽aJ⊙σ′(zL)
说明:
⊙
\odot
⊙为Hadamard积,即矩阵的点积
2. 误差传递方程(由后往前,计算每一层神经网络产生的误差)
公式:
δ
l
=
(
(
w
l
+
1
)
T
δ
l
+
1
)
⊙
σ
′
(
z
l
)
\delta^l=((w^{l+1})^T\delta^{l+1})\odot{\sigma}'(z^l)
δl=((wl+1)Tδl+1)⊙σ′(zl)
由以上公式可得,可以通过第
l
+
1
l+1
l+1层得误差,求得第
l
l
l层的误差,通过结合输出层误差方程和误差传递方程,就可以计算神经网络中任意一层的误差,先计算
δ
L
\delta^L
δL,再计算
δ
L
−
1
\delta^{L-1}
δL−1、
δ
L
−
2
\delta^{L-2}
δL−2、
δ
L
−
3
\delta^{L-3}
δL−3…,直到输入层。
推导过程:
δ
j
l
=
∂
J
∂
z
j
l
=
∑
k
=
1
m
∂
J
∂
z
k
l
+
1
∂
z
k
l
+
1
∂
a
j
l
∂
a
j
l
∂
z
j
l
=
∑
k
=
1
m
δ
j
l
+
1
⋅
∂
w
k
j
l
+
1
a
j
l
+
b
k
l
+
1
∂
a
j
l
⋅
σ
(
z
j
l
)
′
=
∑
k
=
1
m
δ
j
l
+
1
⋅
w
k
j
l
+
1
⋅
σ
(
z
j
l
)
′
\begin{aligned} \delta_j^l & = \frac{\partial{J}}{\partial{z_j^l}} \\ & = \sum_{k=1}^m\frac{\partial{J}}{\partial{z_k^{l+1}}}\frac{\partial{z_k^{l+1}}}{\partial{a_j^l}}\frac{\partial{a_j^l}}{\partial{z_j^l}} \\ & =\sum_{k=1}^m\delta_j^{l+1}\cdot\frac{\partial{w_{kj}^{l+1}a_j^l+b_k^{l+1}}}{\partial{a_j^l}}\cdot {\sigma(z_j^l)}' \\ & =\sum_{k=1}^{m}\delta_j^{l+1}\cdot w_{kj}^{l+1}\cdot {\sigma(z_j^l)}' \end{aligned}
δjl=∂zjl∂J=k=1∑m∂zkl+1∂J∂ajl∂zkl+1∂zjl∂ajl=k=1∑mδjl+1⋅∂ajl∂wkjl+1ajl+bkl+1⋅σ(zjl)′=k=1∑mδjl+1⋅wkjl+1⋅σ(zjl)′
可以得到以下公式【矩阵形式】
δ
l
=
(
(
w
l
+
1
)
T
δ
l
+
1
)
⊙
σ
′
(
z
l
)
\delta^l=((w^{l+1})^T\delta^{l+1})\odot{\sigma}'(z^l)
δl=((wl+1)Tδl+1)⊙σ′(zl)
3.代价函数对权重的改变率(计算权重的梯度)
∂
J
∂
w
j
k
l
=
a
k
l
−
1
δ
j
l
\frac{\partial{J}}{\partial{w_{jk}^l}}=a_k^{l-1}\delta_j^l
∂wjkl∂J=akl−1δjl
推导过程:
∂
J
∂
w
j
k
l
=
∂
J
∂
z
j
k
l
⋅
∂
z
j
k
l
∂
w
j
k
l
=
δ
j
l
⋅
∂
(
w
j
k
l
a
k
l
−
1
+
b
j
l
)
∂
w
j
k
l
=
a
k
l
−
1
δ
j
l
\begin{aligned} \frac{\partial{J}}{\partial{w_{jk}^l}} & = \frac{\partial{J}}{\partial{z_{jk}^l}} \cdot \frac{\partial{z_{jk}^l}}{\partial{w_{jk}^l}}\\ & = \delta_j^l \cdot \frac{\partial{(w_{jk}^l a_k^{l-1}+b_j^l)}}{\partial{w_{jk}^l}} \\ & = a_k^{l-1}\delta_j^l \end{aligned}
∂wjkl∂J=∂zjkl∂J⋅∂wjkl∂zjkl=δjl⋅∂wjkl∂(wjklakl−1+bjl)=akl−1δjl
可以化简得到下面式子:
∂
J
∂
w
=
a
i
n
δ
o
u
t
\frac{\partial J}{\partial w}=a_{in}\delta_{out}
∂w∂J=ainδout
由上述的公式可得,当上一层激活后的输出接近0的时候,无论返回的误差为多大,
∂
J
∂
w
\frac{\partial J}{\partial w}
∂w∂J的改变都很小,这就是解释了为什么神经元饱和不利于训练。当激活前的神经元没有被激活,或者激活后输出神经元处于饱和状态,权重和偏置会学习的非常慢,这不是我们想要的效果。这也说明了为什么我们平时总是说激活函数的选择非常重要。
4. 代价函数对偏置的改变率(计算偏置的梯度)
公式如下:
∂
J
∂
b
j
l
=
δ
j
l
\frac{\partial J}{\partial{b_j^l}} = \delta_j^l
∂bjl∂J=δjl
推导过程:
∂
J
∂
b
j
l
=
∂
J
∂
z
j
l
⋅
∂
z
j
l
∂
b
j
l
=
δ
j
l
⋅
∂
(
w
j
k
l
a
k
l
−
1
+
b
j
l
)
∂
b
j
l
=
δ
j
l
\begin{aligned} \frac{\partial J}{\partial b_j^l} &=\frac{\partial J}{\partial z_j^l} \cdot \frac{\partial z_j^l}{\partial b_j^l} \\ &=\delta_j^l \cdot \frac{\partial(w_{jk}^l a_k^{l-1}+b_j^l)}{\partial b_j^l} \\ &=\delta_j^l \end{aligned}
∂bjl∂J=∂zjl∂J⋅∂bjl∂zjl=δjl⋅∂bjl∂(wjklakl−1+bjl)=δjl
反向传播算法伪代码
- 输入训练集,共有 m m m个样本
- 对于训练集中的每个样本 x x x,设置输入层(input layer)对应的激活函数 a l a^l al
- 前向传播: a l = σ ( z l ) = σ ( w l a l − 1 + b l ) a^l = \sigma(z^l)=\sigma(w^l a^{l-1}+b^l) al=σ(zl)=σ(wlal−1+bl)
- 计算输出层误差: δ L = ▽ a J ⊙ σ ′ ( z L ) \delta^L=\bigtriangledown_aJ\odot{\sigma}'(z^L) δL=▽aJ⊙σ′(zL)
- 反向传播: δ l = ( ( w l + 1 ) T δ l + 1 ) ⊙ σ ′ ( z l ) \delta^l=((w^{l+1})^T\delta^{l+1})\odot{\sigma}'(z^l) δl=((wl+1)Tδl+1)⊙σ′(zl)
- 使用梯度下降(gradient descent),优化权重和偏置:
w l → w l − η m ∑ x = 1 m δ x , l ( a x , l − 1 ) T w^l \rightarrow w^l-\frac{\eta}{m}\sum_{x=1}^{m}\delta^{x,l}(a^{x,l-1})^T wl→wl−mηx=1∑mδx,l(ax,l−1)T
b l → b l − η m ∑ x = 1 k δ x , l b^l \rightarrow b^l - \frac{\eta}{m}\sum_{x=1}^k\delta^{x,l} bl→bl−mηx=1∑kδx,l
附上代码:
import numpy as np
import random
def sigmoid_func(x):
"""
激活函数sigmoid
"""
return 1/(1 + np.exp(-x))
def sigmoid_derivation(x):
"""
激活函数的导数
"""
y = 1/(1 + np.exp(-x))
return y * (1 - y)
def loss_function(output_arry, groud_truth):
"""
求出与groud truth的损失
"""
diff = (np.sum((output_arry - groud_truth) ** 2)) / 2
# print(diff)
return diff
def backpropagation_func(input_arry, groud_truth, theta):
"""
反向传播优化权重以及偏置
input:
input_arry --> 输入的矩阵
groud_truth --> 真实值
theta ---> 学习率
return:
output ---> 输出的值
"""
# 假定是一个3层的神经网络
# 假设输入的单个样本的特征向量是1个纬度,共有2个样本,即输入的矩阵为2*1
# 第2层的有2个神经元,即w的矩阵为2*2
# 前向传递
w_2 = [[0.15, 0.20], [0.25, 0.30]]
bias_2 = 0.35
w_3 = [[0.40, 0.45], [0.50, 0.55]]
bias_3 = 0.6
# print(bias_2)
# print(w_2)
z_2 = np.dot(w_2, input_arry) + bias_2
a_2 = sigmoid_func(z_2)
# print('z2=', z_2)
# print('a2=', a_2)
z_3 = np.dot(w_3, a_2) + bias_3
a_3 = sigmoid_func(z_3)
# print('z3=', z_3)
# print('a3=', a_3)
loss = loss_function(a_3, groud_truth)
print("前向传播的损失为: {}".format(loss))
for count in range(1000):
if loss > 0.01:
# 反向传播
# 输出层的误差(对z的梯度)
sigmoid_de = sigmoid_derivation(z_3)
# print('梯度:{}'.format(sigmoid_de))
#numpy中 * 表示Hadmard乘积
# print(a_3 - groud_truth)
delta_L = (a_3 - groud_truth) * sigmoid_de
# print("输出层的误差:{}".format(delta_L))
# 计算第二层的神经元的误差
# 切记,这里需要将w_3转置,因为上一层的神经元影响下一层的两个神经元
w_3_transpose = list(np.array(w_3).T)
delta_l = np.dot(w_3_transpose, delta_L) * sigmoid_derivation(z_2)
# print("第二层神经元的误差:{}".format(delta_l))
# print('w_3 = ', w_3[0])
# print('result = \n', theta * delta_L[0] * a_2)
# 权重更新
for i in range(len(w_3)):
w_3[i] -= theta * delta_L[i] * a_2
# print("最后一层权重的更新为: {}".format(w_3))
for i in range(len(w_2)):
w_2[i] -= theta * delta_l[i] * input_arry
# print("非最后一层权重更新为:{}".format(w_2))
# 偏置更新
bias_3 -= theta * delta_L
bias_2 -= theta * delta_l
# 再一次进行前向传播
z_2 = np.dot(w_2, input_arry) + bias_2
a_2 = sigmoid_func(z_2)
# print('z2=', z_2)
# print('a2=', a_2)
z_3 = np.dot(w_3, a_2) + bias_3
a_3 = sigmoid_func(z_3)
# print('z3=', z_3)
# print('a3=', a_3)
# 反向传播
loss = loss_function(a_3, groud_truth)
print("经过反向传播{}次,损失为: {}".format((count+1), loss))
else:
break
return w_2, w_3, bias_3, bias_2, loss
if __name__ == '__main__':
print("=====反向传播start======")
theta = 0.5
input_arry = np.array([0.05, 0.10])
# print(input_arry)
groud_truth = np.array([0.01, 0.99])
# print(groud_truth.shape)
# print(input_arry.shape)
param = backpropagation_func(input_arry, groud_truth, theta)
print('loss = {}\n'.format(param[4]))
print('权重w_2 = {}, \n w_3 = {}\n'.format(param[0], param[1]))
print('偏置bias_2 = {},\n bias_3 = {} \n'.format(param[2], param[3]))
结果:
=====反向传播start======
前向传播的损失为: 0.2983711087600027
经过反向传播1次,损失为: 0.28047144679143016
经过反向传播2次,损失为: 0.2619076230693008
经过反向传播3次,损失为: 0.24293543335380882
经过反向传播4次,损失为: 0.22387321290700182
经过反向传播5次,损失为: 0.20507531776516638
经过反向传播6次,损失为: 0.1868957476561393
......
经过反向传播66次,损失为: 0.010433675583027257
经过反向传播67次,损失为: 0.010230717010390656
经过反向传播68次,损失为: 0.010034748131211645
经过反向传播69次,损失为: 0.009845424426560941
loss = 0.009845424426560941
权重w_2 = [array([0.15686046, 0.21372093]), array([0.25617323, 0.31234645])],
w_3 = [array([-0.6641919 , -0.61741066]), array([0.83781677, 0.88865522])]
偏置bias_2 = [-1.18080352 1.16167516],
bias_3 = [0.48720928 0.47346454]