十一行Python代码实现一个误差逆传播(BP)神经网络
标签(空格分隔): BP 神经网络 机器学习
通过一个例子,来学习BP神经网络。这个例子来源于十一行Python代码实现一个神经网络(第一部分),好像也是翻译别人的博客。算法的推导来自周志华的《机器学习》。
样本定义
假设训练样本如下:
输入1 | 输入2 | 输入3 | 输出 |
---|---|---|---|
0 | 0 | 1 | 0 |
1 | 1 | 1 | 1 |
1 | 0 | 1 | 1 |
0 | 1 | 1 | 0 |
这是一个拥有3个输入节点1个输出节点,总共4个样例的训练样本。所以,可以画出下图这样的两层神经网络图,y点为输出节点,
根据《机器学习》(周志华的西瓜书)记述,可以设输出节点
输出节点的输出值是
模型推导
因为BP神经网络是通过输出节点输出值与实际值比较,每次得到的误差推导出输入节点的权值
模型其他参数都是已知量(初始权重为任意假设给定值,阈值
根据周志华的《机器学习》P99页论述,
这个两层神经网络的均方误差为
其中,设
根据sigmoid函数的性质,
每次改变的
python编程
import numpy as np
# sigmoid function 定义激活函数sigmoid(x),nonlin(x,True)就是计算sigmoid的导数。
def nonlin(x,deriv=False):
if(deriv==True):
return x*(1-x)
return 1/(1+np.exp(-x))
#生成样本训练模型,输入4*3矩阵,4个样本,每个样本3个输入值。
X = np.array([ [0,0,1],
[1,1,1],
[1,0,1],
[0,1,1] ])
#生成输出节点数值,输入4*1矩阵,4个样本,每个样本1个输出值。
y = np.array([[0,1,1,0]]).T
#设置随机种子,这个主要是为了每次生成的随机量一样,可以使程序重复试验。
np.random.seed(1)
#设置第一层各输入点权重,权重是随机生成的,均值是0
syn0 = 2*np.random.random((3,1)) - 1
for iter in xrange(10000):
# forward propagation l0是第一层
l0 = X
# l1是输出量\hat{y},l0是输入量,与权值相乘作为输出节点激励函数的自变量
l1 = nonlin(np.dot(l0,syn0))
# how much did we miss?
l1_error = y - l1
l1_delta = l1_error * nonlin(l1,True)
# update weights
syn0 += np.dot(l0.T,l1_delta)
print "Output After Training:"
print l1
程序解读
l0是神经网络第一层,即输入层
l0 = X
l1是输出量
l1 = nonlin(np.dot(l0,syn0))
计算误差
l1_error = y - l1
利用梯度下降法,计算权重改变量。l1作为输出量,nonlin(l1,True)表示对输出量求导即
l1_delta = l1_error * nonlin(l1,True)
对权重进行更新 np.dot(l0.T,l1_delta) 为
syn0 += np.dot(l0.T,l1_delta)
在10000次训练之后,syn0为更新后的权重,则算出的l1为训练后输出值。
运行结果
通过10000次训练,获得权重syn0,在此权重下,输出节点的输出值:
[[ 0.00966449]
[ 0.99211957]
[ 0.99358898]
[ 0.00786506]]
误差为:
[[-0.00966449]
[ 0.00788043]
[ 0.00641102]
[-0.00786506]]
通过100000次训练,获得权重syn0,在此权重下,输出节点的输出值:
[[ 0.00301758]
[ 0.99753723]
[ 0.99799161]
[ 0.00246109]]
误差为:
[[-0.00301758]
[ 0.00246277]
[ 0.00200839]
[-0.00246109]]
可以发现,随着训练步骤的增多,误差越来越小。
三层神经网络
还是刚才的例子,我们这次假定网络有三层,即输入层l0,隐藏层l1,输出层l2,建立如下图的神经网络结构:
图中隐层节点
网络在输出节点的均方误差为:
对其求导,得到:
设
因为是梯度下降,要减小误差,因此
所以,
我们求得隐层到输出层的权值,现在要求输入层到隐层的权值
其中,
所以,
第一层节点权重的该变量
假设
import numpy as np
def nonlin(x,deriv=False):
if(deriv==True):
return x*(1-x)
return 1/(1+np.exp(-x))
X = np.array([[0,0,1],
[0,1,1],
[1,0,1],
[1,1,1]])
y = np.array([[0],
[1],
[1],
[0]])
np.random.seed(1)
# randomly initialize our weights with mean 0
syn0 = 2*np.random.random((3,4)) - 1
syn1 = 2*np.random.random((4,1)) - 1
for j in range(60000):
# Feed forward through layers 0, 1, and 2
l0 = X
l1 = nonlin(np.dot(l0,syn0))#计算隐层节点的输出值
l2 = nonlin(np.dot(l1,syn1))#计算输出节点输出值
# how much did we miss the target value?
l2_error = y - l2 #计算输出与实际值误差
if (j% 10000) == 0:
print("Error:" + str(np.mean(np.abs(l2_error))))
# in what direction is the target value?
# were we really sure? if so, don't change too much.
l2_delta = l2_error*nonlin(l2,deriv=True)
# how much did each l1 value contribute to the l2 error (according to the weights)?
l1_error = l2_delta.dot(syn1.T)
# in what direction is the target l1?
# were we really sure? if so, don't change too much.
l1_delta = l1_error * nonlin(l1,deriv=True)
syn1 += l1.T.dot(l2_delta)
syn0 += l0.T.dot(l1_delta)
程序解读
三层神经网络与二层相似,增加了隐层后需要计算隐层节点的误差,即程序35行
l1_error = l2_delta.dot(syn1.T)
l2_delta=l2_error*nonlin(l2,deriv=True),这步计算了隐层到输出层节点的权重调节值。即理论推导中的
这里公式符号跟编程代码的对应关系如下(因为编码中使用的是向量或者矩阵,这里只是简单的列出对应关系。)
公式符号 | 代码 | 公式符号 | 代码 |
---|---|---|---|
|
l2_error |
|
l1 |
|
l2 |
|
nonlin(l2,deriv=True) |
|
l1.T.dot(l2_delta) |
|
l2_delta |
|
l1_error |
|
l1_delta |
|
l0.T.dot(l1_delta) |
|
l0.T |
通过对应关系表,可以读懂程序中每一步代码的含义。
后记
神经网络是建立输入和输出之间的映射关系,如果输入是一张猫的照片,输出结果则为猫。
绝大多数时候,我们不能建立线性映射关系,建立映射关系需要复杂的函数,神经网络可以以任意精度逼近任一连续可微函数(这个是被证明过得),因此只要层数、神经元够多,我们就能建立关系,但是复杂性会随之增加。机器学习就是通过各种关系,来建立这一关系。
参考
- 神经网络之BP神经网络(Python实现):主要是公式推导
- 一个 11 行 Python 代码实现的神经网络:本文代码的主要参考
- 周志华的《机器学习》:本文公式推导的参考来源