BP(back propagation)神经网络是1986年由Rumelhart和McClelland为首的科学家提出的概念,是一种按照误差逆向传播算法训练的多层前馈神经网络,是目前应用最广泛的神经网络。
目前,在人工神经网络的实际应用中,绝大部分的神经网络模型都采用BP网络及其变化形式。它也是前向网络的核心部分,体现了人工神经网络的精华。
先举一个简单的例子,带入数值演示反向传播法的过程,掌握BP神经网络。
第一层是输入层,包含两个神经元In1,In2,和截距项b1;第二层是隐含层,包含两个神经元H1、H2和截距项b2,第三层是输出O1、O2,每条线上标的wi是层与层之间连接的权重,激活函数我们默认为sigmoid函数。设输入分别为x1、x2,实际输出分别为y1、y2。
1. 前向传播
- 输入层Ini---->隐含层Hi:
计算神经元H1、H2的输入加权和分别为h1、h2:
h 1 = x 1 . w 1 + x 2 . w 3 + b 1 h_{1} = x_{1} .w_{1}+x_{2}. w_{3}+b_{1} h1=x1.w1+x2.w3+b1
h 2 = x 1 . w 2 + x 2 . w 4 + b 2 h_{2} = x_{1} .w_{2}+x_{2} .w_{4}+b_{2} h2=x1.w2+x2.w4+b2
神经元H1的输出到O1:(此处用到激活函数为sigmoid函数)、H2的输出到O2:
h o 1 = 1 1 + e h 1 ho_{1}=\frac{1}{1+e^{h_{1}}} ho1=1+eh11
h o 2 = 1 1 + e h 2 ho_{2}=\frac{1}{1+e^{h_{2}}} ho2=1+eh21 - 隐含层-Hi—>输出层Oi:
计算输出层神经元o1和o2的值:
o 1 = h o 1 . w 5 + h o 2 . w 7 + b 3 o_{1} = ho_{1}. w_{5}+ho_{2} .w_{7}+b_{3} o1=ho1.w5+ho2.w7+b3
o 2 = h o 1 . w 6 + h o 2 . w 8 + b 4 o_{2} = ho_{1} .w_{6}+ho_{2}. w_{8}+b_{4} o2=ho1.w6+ho2.w8+b4
神经元O1的输出oo1,神经元O2的输出oo2:
o o 1 = 1 1 + e o 1 oo_{1}=\frac{1}{1+e^{o_{1}}} oo1=1+eo11
o o 2 = 1 1 + e o 2 oo_{2}=\frac{1}{1+e^{o_{2}}} oo2=1+eo21
2. 反向传播
- 计算总误差
使用均方误差为总误差(square error),设为E,分别计算O1和O2的误差,求和:
E o 1 = 1 2 ( y 1 − o o 1 ) 2 Eo_{1}=\frac{1}{2}(y_{1}-oo_{1})^{2} Eo1=21(y1−oo1)2
E o 2 = 1 2 ( y 2 − o o 2 ) 2 Eo_{2}=\frac{1}{2}(y_{2}-oo_{2})^{2} Eo2=21(y2−oo2)2
E = E o 1 + E o 2 E = Eo_{1} + Eo_{2} E=Eo1+Eo2 - 隐含层
→
\rightarrow
→输出层的权值更新:
以权重参数w5为例,如果我们想知道w5对整体误差产生了多少影响,可以用整体误差对w5求偏导求出:(链式法则)
∂ E ∂ w 5 = ∂ E ∂ o o 1 ⋅ ∂ o o 1 ∂ o 1 ⋅ ∂ o 1 ∂ w 5 \frac{\partial E}{\partial w_{5}}=\frac{\partial E}{\partial oo_{1}}\cdot \frac{\partial oo_{1}}{\partial o_{1}}\cdot \frac{\partial o_{1}}{\partial w_{5}} ∂w5∂E=∂oo1∂E⋅∂o1∂oo1⋅∂w5∂o1
如下面的图所示,可以更直观的看清楚使用链式求导,体现误差是怎样反向传播的:
现在我们来分别求导三者计算每个式子:
E
=
1
2
(
y
1
−
o
o
1
)
2
+
1
2
(
y
2
−
o
o
2
)
2
E=\frac{1}{2} \left (y_{1} - oo_{1}\right)^{2} + \frac{1}{2} \left (y_{2} - oo_{2}\right)^{2}
E=21(y1−oo1)2+21(y2−oo2)2
δ
E
δ
o
o
1
=
2
⋅
1
2
(
y
1
−
o
o
1
)
2
−
1
⋅
−
1
+
0
\frac{\delta E}{\delta oo_{1}}=2 \cdot \frac {1}{2} \left(y_{1} - oo_{1}\right)^{2-1}\cdot-1+0
δoo1δE=2⋅21(y1−oo1)2−1⋅−1+0
δ
E
δ
o
o
1
=
−
(
y
1
−
o
o
1
)
\frac{\delta E}{\delta oo_{1}}=- \left(y_{1} - oo_{1}\right)
δoo1δE=−(y1−oo1)
已下中间求导过程略。
δ
o
o
1
δ
o
1
=
o
o
1
(
1
−
o
o
1
)
\frac{\delta oo_{1}}{\delta o_{1}}=oo_{1} \left(1 - oo_{1}\right)
δo1δoo1=oo1(1−oo1)
δ
o
1
δ
w
5
=
h
o
1
\frac{\delta o_{1}}{\delta w_{5}}=ho_{1}
δw5δo1=ho1
三者相乘的结果如下:
∂
E
∂
w
5
=
−
(
y
1
−
o
o
1
)
⋅
o
o
1
⋅
(
1
−
o
o
1
)
⋅
h
o
1
\frac{\partial E}{\partial w_{5}}=-(y_{1} - oo_{1}) \cdot oo_{1} \cdot (1-oo_{1})\cdot ho_{1}
∂w5∂E=−(y1−oo1)⋅oo1⋅(1−oo1)⋅ho1
为了表达方便,用
δ
o
1
\delta o_{1}
δo1来表示输出层的误差:
δ
o
1
=
∂
E
∂
o
o
1
⋅
∂
o
o
1
∂
h
o
1
=
∂
E
∂
h
o
1
\delta o_{1}= \frac{\partial E}{\partial oo_{1}}\cdot \frac{\partial oo_{1}}{\partial ho_{1}} = \frac{\partial E}{\partial ho_{1}}
δo1=∂oo1∂E⋅∂ho1∂oo1=∂ho1∂E
δ
o
1
=
−
(
y
1
−
o
o
1
)
⋅
o
o
1
(
1
−
o
o
1
)
\delta o_{1}= -\left ( y_{1} - oo_{1} \right ) \cdot oo_{1}\left ( 1- oo_{1} \right )
δo1=−(y1−oo1)⋅oo1(1−oo1)
因此,整体误差E对w5的偏导公式可以写成:
∂
E
∂
w
5
=
δ
o
1
⋅
h
o
1
\frac{\partial E}{\partial w_{5}} = \delta o_{1}\cdot ho_{1}
∂w5∂E=δo1⋅ho1
假如以
w
5
(
1
)
w_{5}^{(1)}
w5(1)表示反向传播更新w5的值。
w
5
(
1
)
=
w
5
−
η
⋅
∂
E
∂
w
5
w_{5}^{(1)}=w_{5} - \eta \cdot \frac{\partial E}{\partial w_{5}}
w5(1)=w5−η⋅∂w5∂E
其中, η \eta η是学习速率。同理,可更新w6,w7,w8。
- 隐含层
→
\rightarrow
→隐含层的权值更新:
推导过程方法与上面描述原理基本一致,但是有个点需是不一样的,在上文计算总误差对w5的偏导时,是从 o o 1 → h o 1 → w 5 oo_{1} \rightarrow ho_{1} \rightarrow w_{5} oo1→ho1→w5,但是在隐含层之间的权值更新时,过程是 h o 1 → h 1 → w 1 ho_{1}\rightarrow h_{1} \rightarrow w_{1} ho1→h1→w1,而 h o 1 ho_{1} ho1会接受 E o 1 Eo_{1} Eo1和 E o 1 Eo_{1} Eo1两个地方传来的误差,如下图所示,因此,这个点两个都要计算。
首先,计算 ∂ E ∂ h o 1 \frac{\partial E}{\partial ho_{1}} ∂ho1∂E :
∂ E ∂ h o 1 = ∂ E o 1 ∂ h o 1 + ∂ E o 2 ∂ h o 1 \frac{\partial E}{\partial ho_{1}} = \frac{\partial Eo_{1}}{\partial ho_{1}} + \frac{\partial Eo_{2}}{\partial ho_{1}} ∂ho1∂E=∂ho1∂Eo1+∂ho1∂Eo2
需要分别计算
∂
E
o
1
∂
h
o
1
\frac{\partial Eo_{1}}{\partial ho_{1}}
∂ho1∂Eo1 、
∂
E
o
2
∂
h
o
1
\frac{\partial Eo_{2}}{\partial ho_{1}}
∂ho1∂Eo2:
∂
E
o
1
∂
h
o
1
=
∂
E
o
1
∂
o
1
⋅
∂
o
1
∂
h
o
1
\frac{\partial Eo_{1}}{\partial ho_{1}}=\frac{\partial Eo_{1}}{\partial o_{1}}\cdot \frac{\partial o_{1}}{\partial ho_{1}}
∂ho1∂Eo1=∂o1∂Eo1⋅∂ho1∂o1
其中,
∂
E
o
1
∂
o
1
=
∂
E
o
1
∂
o
o
1
⋅
∂
o
o
1
∂
o
1
\frac{\partial Eo_{1}}{\partial o_{1}}=\frac{\partial Eo_{1}}{\partial oo_{1}}\cdot \frac{\partial oo_{1}}{\partial o_{1}}
∂o1∂Eo1=∂oo1∂Eo1⋅∂o1∂oo1
并且,上式中的内容,在前面已经计算过来,直接引用。(注:
∂
E
o
1
∂
o
o
1
\frac{\partial Eo_{1}}{\partial oo_{1}}
∂oo1∂Eo1与
∂
E
∂
o
o
1
\frac{\partial E}{\partial oo_{1}}
∂oo1∂E的结果是一样的?因为对
o
o
1
oo_{1}
oo1求导,则涉及到
E
o
2
Eo_{2}
Eo2的
o
o
2
oo_{2}
oo2部分都为0)
o
1
=
w
5
⋅
h
o
1
+
w
6
⋅
h
o
2
+
b
2
⋅
1
o_{1} = w_{5} \cdot ho_{1} + w_{6} \cdot ho_{2} + b_{2}\cdot 1
o1=w5⋅ho1+w6⋅ho2+b2⋅1
∂
o
1
∂
h
o
1
=
w
5
\frac{\partial o_{1}}{\partial ho_{1}}=w_{5}
∂ho1∂o1=w5
同理,可以求得
∂
E
o
2
∂
h
o
1
=
∂
E
o
2
∂
o
o
2
⋅
∂
o
o
2
∂
h
o
1
\frac{\partial Eo_{2}}{\partial ho_{1}}=\frac{\partial Eo_{2}}{\partial oo_{2}}\cdot \frac{\partial oo_{2}}{\partial ho_{1}}
∂ho1∂Eo2=∂oo2∂Eo2⋅∂ho1∂oo2
接着,计算
∂
h
o
1
∂
h
1
\frac{\partial ho_{1}}{\partial h_{1}}
∂h1∂ho1:
h
o
1
=
1
1
+
e
−
h
1
ho_{1}=\frac{1}{1+e^{ -h_{1}}}
ho1=1+e−h11
∂
h
o
1
∂
h
1
=
h
o
1
⋅
(
1
−
h
o
1
)
\frac{\partial ho_{1}}{\partial h_{1}}=ho_{1} \cdot (1 - ho_{1})
∂h1∂ho1=ho1⋅(1−ho1)
接着,再计算
∂
h
1
∂
w
1
\frac{\partial h_{1}}{\partial w_{1}}
∂w1∂h1:
h
1
=
w
1
⋅
i
n
1
+
w
2
⋅
i
n
2
+
b
1
⋅
1
h_{1}=w_{1} \cdot in_{1} + w_{2} \cdot in_{2} + b_{1} \cdot 1
h1=w1⋅in1+w2⋅in2+b1⋅1
∂
h
1
∂
w
1
=
i
n
1
\frac{\partial h_{1}}{\partial w_{1}}=in_{1}
∂w1∂h1=in1
最后,三者相乘:
∂
E
∂
w
1
=
∂
E
∂
h
o
1
⋅
∂
h
o
1
∂
h
1
⋅
∂
h
1
∂
w
1
\frac{\partial E}{\partial w_{1}}=\frac{\partial E}{\partial ho_{1}}\cdot \frac{\partial ho_{1}}{\partial h_{1}}\cdot \frac{\partial h_{1}}{\partial w_{1}}
∂w1∂E=∂ho1∂E⋅∂h1∂ho1⋅∂w1∂h1
为了简化公式,使用
s
i
g
m
a
(
h
1
)
sigma(h_{1})
sigma(h1)表示隐含层单元h1的误差:
∂
E
∂
w
1
=
(
∑
o
=
1
n
∂
E
∂
o
o
⋅
∂
o
o
∂
o
⋅
∂
o
∂
h
o
1
)
⋅
∂
h
o
1
∂
h
1
⋅
∂
h
1
∂
w
1
\frac{\partial E}{\partial w_{1}}= (\sum_{o=1}^{n} \frac{\partial E}{\partial oo} \cdot \frac{\partial oo}{\partial o} \cdot \frac{\partial o}{\partial ho_{1}}) \cdot \frac{\partial ho_{1}}{\partial h_{1}}\cdot \frac{\partial h_{1}}{\partial w_{1}}
∂w1∂E=(∑o=1n∂oo∂E⋅∂o∂oo⋅∂ho1∂o)⋅∂h1∂ho1⋅∂w1∂h1
∂
E
∂
w
1
=
(
∑
o
=
1
n
δ
o
⋅
w
h
o
)
⋅
h
o
1
⋅
(
1
−
h
o
1
)
⋅
i
n
1
\frac{\partial E}{\partial w_{1}}=(\sum_{o=1}^{n} \delta_o \cdot w_{ho})\cdot ho_{1} \cdot (1-ho1)\cdot in_{1}
∂w1∂E=(∑o=1nδo⋅who)⋅ho1⋅(1−ho1)⋅in1
∂
E
∂
w
1
=
δ
h
1
⋅
i
n
1
\frac{\partial E}{\partial w_{1}}=\delta_{h_{1}}\cdot in_{1}
∂w1∂E=δh1⋅in1
通过上面的推导过程,更新w1的权值:
w
1
(
1
)
=
w
1
−
η
⋅
∂
E
∂
w
1
w_{1}^{(1)}=w_{1} - \eta \cdot \frac{\partial E}{\partial w_{1}}
w1(1)=w1−η⋅∂w1∂E
同理,其他按此方式更w2、w3、w4。
3. BP神经网络代码(Tensorflow)
我们使用随机梯度下降算法和MNIST训练数据,来写一个能够学习识别手写数字的BP神经网络程序。首先我们需要获得MNIST数据。
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import numpy as np
mnist = input_data.read_data_sets('./data/', one_hot = True)
num_classes = 10 # 输出大小
input_size = 784 # 输入大小
hidden_units_size = 30 # 隐藏层节点数量
batch_size = 100
training_iterations = 10000
X = tf.placeholder(tf.float32, shape = [None, input_size])
Y = tf.placeholder(tf.float32, shape = [None, num_classes])
W1 = tf.Variable(tf.random_normal ([input_size, hidden_units_size], stddev = 0.1))
B1 = tf.Variable(tf.constant (0.1), [hidden_units_size])
W2 = tf.Variable(tf.random_normal ([hidden_units_size, num_classes], stddev = 0.1))
B2 = tf.Variable(tf.constant (0.1), [num_classes])
hidden_opt = tf.matmul(X, W1) + B1 # 输入层到隐藏层正向传播
hidden_opt = tf.nn.relu(hidden_opt) # 激活函数,用于计算节点输出值
final_opt = tf.matmul(hidden_opt, W2) + B2 # 隐藏层到输出层正向传播
# 对输出层计算交叉熵损失
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=Y, logits=final_opt))
# 梯度下降算法,这里使用了反向传播算法用于修改权重,减小损失
opt = tf.train.GradientDescentOptimizer(0.05).minimize(loss)
# 初始化变量
init = tf.global_variables_initializer()
# 计算准确率
correct_prediction =tf.equal (tf.argmax (Y, 1), tf.argmax(final_opt, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, 'float'))
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for i in range (training_iterations) :
batch = mnist.train.next_batch (batch_size)
batch_input = batch[0]
batch_labels = batch[1]
# 训练
training_loss = sess.run ([opt, loss], feed_dict = {X: batch_input, Y: batch_labels})
if i % 1000 == 0 :
train_accuracy = accuracy.eval (session = sess, feed_dict = {X: batch_input,Y: batch_labels})
print ("step : %d, training accuracy = %g " % (i, train_accuracy))
总结
在一般的BP神经网络中,单个样本有m个输入和n个输出,在输入层和输出层之间还有若干个隐藏层h,实际上 1989年时就已经有人证明了一个万能逼近定理 :
在任何闭区间的连续函数都可以用一个隐藏层的BP神经网络进行任意精度的逼近。
所以说一个三层的神经网络就可以实现一个任意从m维到n维的一个映射,这三层分别是 输入层、隐藏层、输出层 。
一般来说,在BP神经网络中,输入层和输出层的节点数目都是固定的,关键的就是在于隐藏层数目的选择,隐藏层数目的选择决定了神经网络工作的效果。
这里有一个选择隐藏层数目的经验公式: h = ( m + n ) + a h=\sqrt{(m+n)+a} h=(m+n)+a
- h 隐藏层
- m 输入层
- n 输出层
- a 调节常数数(一般介于1~10之间,如果数据多的话我们可以设a稍微大一点,而数据不是太多的时候就设置的小一点防止过拟合。)
BP神经网络的优缺点,以及应用介绍:
-
优点:具有任意复杂的模式分类能力和优良的多维函数映射能力,解决了简单感知器不能解决的异或或者一些其他的问题。从结构上讲,BP神经网络具有输入层、隐含层和输出层;从本质上讲,BP算法就是以网络误差平方目标函数、采用梯度下降法来计算目标函数的最小值。基本BP算法包括信号的前向传播和误差的反向传播两个过程。
-
缺点:
①学习速度慢,即使是一个简单的过程,也需要几百次甚至上千次的学习才能收敛。
②容易陷入局部极小值。
③网络层数、神经元个数的选择没有相应的理论指导。
④网络推广能力有限。 -
应用:
①函数逼近。
②模式识别。
③分类。
④数据压缩。
参考:
《A Step by Step Backpropagation Example》 Matt Mazur 2015.03
《一文弄懂神经网络中的反向传播法——BackPropagation》 Charlotte77 2016.06