BP算法简介
前言
在上一篇感知器算法中只有一层功能神经元,其只能解决对于与或非等简单线性可分的问题。对于简单的非线性可分的问题,在解决非线性可分的问题时,感知器的学习过程无法找到一个合适的分割平面对数据进行分类。为了解决感知器无法解决异或问题,可以使用多层神经元的感知器模型(一般来说三层就够了,神经网络的层数不是越多越好)。其结构模型如图所示,图片来自于西瓜书。
BP算法数学模型
在BP算法的模型中,与感知器算法相似,
- 在每个输入与隐藏层的每个神经元都相连并且存在权重 v i h v_{ih} vih
- 每个隐藏层神经元中都存在阈值 γ h \gamma_{h} γh和激活函数f(x)
- 在每个隐藏层与输出层直接都相连并存在权重 ω h j \omega_{hj} ωhj
- 在每个输出层神经元中都存在阈值 θ j \theta_{j} θj和激活函数f(x)
数据的正向传递
- 我们首先将从输入层到隐藏层的权值设为
V
=
[
v
11
v
1
h
.
.
.
v
1
q
.
.
.
.
.
.
.
.
.
.
.
.
v
i
1
v
i
h
.
.
.
v
i
q
.
.
.
.
.
.
.
.
.
.
.
.
v
d
1
v
d
h
.
.
.
v
d
q
]
d
×
q
V= {\left[ \begin{matrix} v_{11} & v_{1h} & ...&v_{1q} \\ ...&...&...&...\\ v_{i1} & v_{ih} & ...&v_{iq} \\ ...&...&...&...\\ v_{d1}& v_{dh} &...& v_{dq} \end{matrix} \right] }_{d\times q}
V=⎣⎢⎢⎢⎢⎡v11...vi1...vd1v1h...vih...vdh...............v1q...viq...vdq⎦⎥⎥⎥⎥⎤d×q
其中 v i h v_{ih} vih的值为从输入 x i x_{i} xi到隐藏层神经元 b h b_{h} bh的权重 - 当一个d维数据 X = [ x 1 , . . . , x d ] X=[x_{1},...,x_{d}] X=[x1,...,xd]输入到BP算法模型中时,首先能获得隐藏层神经元的输入 α = X V = [ α 1 , . . . , α q ] \alpha=XV=[\alpha_{1},...,\alpha_{q}] α=XV=[α1,...,αq]
- 隐藏层的输出 b = [ f ( α 1 − γ 1 ) , . . . , f ( α h − γ h ) , . . . , f ( α q − γ q ) ] = [ b 1 , . . . , b h , . . . , b q ] b=[f(\alpha_{1}-\gamma_{1}),...,f(\alpha_{h}-\gamma_{h}),...,f(\alpha_{q}-\gamma_{q})]=[b_{1},...,b_{h},...,b_{q}] b=[f(α1−γ1),...,f(αh−γh),...,f(αq−γq)]=[b1,...,bh,...,bq]
- 同理从隐藏层到输出层的权值设为
V
=
[
ω
11
ω
1
j
.
.
.
ω
1
l
.
.
.
.
.
.
.
.
.
.
.
.
ω
h
1
ω
h
j
.
.
.
ω
h
l
.
.
.
.
.
.
.
.
.
.
.
.
ω
q
1
ω
q
j
.
.
.
ω
q
l
]
q
×
l
V= {\left[ \begin{matrix} \omega_{11} & \omega_{1j} & ...&\omega_{1l} \\ ...&...&...&...\\ \omega_{h1} & \omega_{hj} & ...&\omega_{hl} \\ ...&...&...&...\\ \omega_{q1}& \omega_{qj} &...& \omega_{ql} \end{matrix} \right] }_{q\times l}
V=⎣⎢⎢⎢⎢⎡ω11...ωh1...ωq1ω1j...ωhj...ωqj...............ω1l...ωhl...ωql⎦⎥⎥⎥⎥⎤q×l
其中 ω h j \omega_{hj} ωhj的值为从隐藏层输出到输出层神经元的权重 - 输出层的输入 β j = b V = [ β 1 , . . . , β j , . . . , β l ] \beta_{j}=bV=[\beta_{1},...,\beta_{j},...,\beta_{l}] βj=bV=[β1,...,βj,...,βl]
- 输出层的输出 o u t p u t = [ f ( θ 1 − β 1 ) , . . . , f ( θ h − β h ) , . . . , f ( θ q − γ q ) ] = [ o 1 , . . . , o h , . . . , o q ] output=[f(\theta_{1}-\beta_{1}),...,f(\theta_{h}-\beta_{h}),...,f(\theta_{q}-\gamma_{q})]=[o_{1},...,o_{h},...,o_{q}] output=[f(θ1−β1),...,f(θh−βh),...,f(θq−γq)]=[o1,...,oh,...,oq]
误差反向传播
在上面我们获得了这一组数据的输出
o
u
t
p
u
t
output
output,我们需要根据均方误差公式调整
B
P
BP
BP算法中的参数:
输出层阈值
θ
\theta
θ 隐藏层到输出层的权重
ω
\omega
ω 隐藏层阈值
γ
\gamma
γ 输入层到隐藏层
v
v
v。
均方误差计算公式:
E
k
=
1
2
Σ
j
=
1
l
(
o
j
−
t
a
r
g
e
t
j
)
2
E_{k}=\frac{1}{2}{\Sigma}^{l}_{j=1}(o_{j}-target_{j})^2
Ek=21Σj=1l(oj−targetj)2
因此对于给定的学习率
η
\eta
η,更新
B
P
BP
BP算法的参数。
η
∈
(
0
,
1
)
\eta \in(0,1)
η∈(0,1),
η
\eta
η值 过大则容易引起震荡,过小则容易收敛速度过慢。通常设置
η
=
0.1
\eta=0.1
η=0.1,在更新参数
θ
\theta
θ、
ω
\omega
ω与参数
γ
\gamma
γ、
v
v
v中的
η
\eta
η值并不相同
算法中所有的参数
p
a
r
a
m
e
t
e
r
s
parameters
parameters都是同样的更新方式
p
a
r
a
m
e
t
e
r
s
=
p
a
r
a
m
e
t
e
r
s
+
Δ
p
a
r
a
m
e
t
e
r
s
parameters = parameters+\Delta parameters
parameters=parameters+Δparameters
更新输出层阈值 θ \theta θ
θ
j
=
θ
j
+
Δ
θ
j
\theta_{j}=\theta_{j}+\Delta\theta_{j}
θj=θj+Δθj
其中
Δ
θ
j
=
−
η
1
g
j
\Delta\theta_{j}=-\eta_{1} g_{j}
Δθj=−η1gj
其中
g
j
=
o
j
(
1
−
o
j
)
(
t
a
r
g
e
t
j
−
o
j
)
(1)
g_{j}=o_{j}(1-o_{j})(target_{j}-o_{j}) \tag1
gj=oj(1−oj)(targetj−oj)(1)
更新隐藏层到输出层的权重值 ω \omega ω
ω
h
j
=
ω
h
j
+
Δ
ω
h
j
\omega_{hj}=\omega_{hj}+\Delta\omega_{hj}
ωhj=ωhj+Δωhj
其中
Δ
ω
h
j
=
η
1
g
j
b
h
\Delta\omega_{hj}=\eta_{1} g_{j} b_{h}
Δωhj=η1gjbh
其中
g
j
g_{j}
gj与公式(1)中的
g
j
g_{j}
gj为同一个,
b
h
b_{h}
bh为隐藏层中第
h
h
h个神经元的输出
更新隐藏层神经元的阈值 γ \gamma γ
γ
h
=
γ
h
+
Δ
γ
h
\gamma_{h}=\gamma_{h}+\Delta\gamma_{h}
γh=γh+Δγh
其中
Δ
γ
h
=
−
η
2
e
h
\Delta\gamma_{h}=-\eta_{2} e_{h}
Δγh=−η2eh
其中
e
h
=
b
h
(
1
−
b
h
)
Σ
j
=
1
l
ω
h
j
g
j
(2)
e_{h}=b_{h}(1-b_{h})\Sigma^{l}_{j=1}\omega_{hj}g_{j}\tag2
eh=bh(1−bh)Σj=1lωhjgj(2)
最后更新输入层到隐藏层的权重值 v v v
v
i
h
=
v
i
h
+
Δ
v
i
h
v_{ih}=v_{ih}+\Delta v_{ih}
vih=vih+Δvih
其中
Δ
v
i
h
=
η
2
e
h
x
i
\Delta v_{ih}=\eta_{2} e_{h}x_{i}
Δvih=η2ehxi
其中
e
h
e_{h}
eh为公式(2)中的
e
h
e_{h}
eh为同一个,
x
i
x_{i}
xi为输入层中的第
i
i
i个输入值。
最后经过训练反复迭代,使得 B P BP BP算法中的各项参数能够有效的作用于数据集上。以上的内容总结都是基于西瓜书,更有详细信息,请参阅西瓜书。本文如有错误,希望不吝指教。
代码实现
code by young_monkeysun 2019
import numpy
import matplotlib.pyplot as plt
import csv
import pandas
import random
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False
#定义画图函数
def draw_Eks(array):
plt.plot(range(1,len(array)+1),array,label = '累计均方误差')
plt.xlabel("训练次数")
plt.ylabel("数据集单次训练累计均方误差")
plt.legend()
plt.show()
#BP神经网络学习算法
class BP_net(object):
def __init__(self , eta , n_inter , q ):
self.eta = eta
self.inter = n_inter
self.q = q
def Sigmoid(self,x):
return 1.0/(1+numpy.exp(-x))
#隐藏层输入
def input_hide(self,x):
return numpy.dot(x , self.v.T)
#隐藏层输出
def output_hide(self,x):
Alpha = self.input_hide(x)
Al_Gam=Alpha - self.Gamma
self.bh = self.Sigmoid(Al_Gam)
return self.bh
#输出层输入
def input_out(self,x):
bh = self.output_hide(x)
return numpy.dot(bh,self.W.T)
#输出层输出
def predict(self, x):
beta = self.input_out(x)
bet_Thet = beta - self.Theta
return self.Sigmoid(bet_Thet)
def fit(self , x , y):
'''初始化输入层到隐藏层的权值
假设单个训练样本xi有3个属性,隐藏层神经元有4个
Vih = [
[v1 , v2 , v3 ]
[v4 , v5 , v6 ]
[v7 , v8 , v9 ]
[v10 , v11 , v12]
]
X = [
[x1 , x2 , x3]
...........
[Xn-2 , Xn-1 , Xn ]
]
隐藏层输入
Alpha = [X[0]*Vih[0] , X[0]*Vih[1] , X[0]*Vih[2] , X[0]*Vih[3]]
隐藏层输出
bh = Sigmoid(Alpha - Gamma)
隐藏层到输出层权值,设隐藏层神经元为4个
W = [
[w1 , w2 , w3 , w4]
[w5 , w6 , w7 , w8]
[w9 , w10 , w11 , w12]
]
输出层输入
beta = [W[0]*bh , w[1]*bh , w[2]*bh ]
输出层输出
y = Sigmoid[beta - Theta]
'''
self.v = numpy.random.rand(self.q , x.shape[1])
#初始化隐藏层神经元阈值
self.Gamma = numpy.random.rand(self.q)
#初始化隐藏层到输出层权值
self.W = numpy.random.rand(1,self.q)
#初始化输出层神经元阈值
self.Theta = numpy.random.rand(1)
#创建总均方误差统计数组
self.Eks = []
#执行self.inter次训练
for N in range(self.inter):
#单次训练错误次数初始化为0
Ek= []
#从训练集中抽取单个训练样本
for xi , target in zip(x,y):
#获得输出层输出
self.y=self.predict(xi)
#计算单次输出均方误差
Ek_ = self.y-target
Ek.append(Ek_)
#调整隐藏层到输出层权值和阈值
gj = self.y*(1-self.y)*(target-self.y)
#计算Δθ
Delta_Theta = (-1)*self.eta*gj
#更新θ
self.Theta +=Delta_Theta
#计算Δw
Delta_w = self.eta*gj*self.bh
#更新W
self.W += Delta_w
#调整输入层到隐藏层的权值和阈值
#计算各个ΣWhj_gj的值
Sigma_W_g = self.W*gj
Sigma_W_g = Sigma_W_g[0]
#计算隐藏层各个神经元的eh
eh=[]
for n in range(len(Sigma_W_g)):
eh_ = self.bh[n]*(1-self.bh[n])*Sigma_W_g[n]
eh.append(eh_)
#计算Δγ
Delta_Gamma = [self.eta*eh[i] for i in range(len(eh))]
Delta_Gamma = numpy.asarray(Delta_Gamma)
Delta_Gamma = Delta_Gamma[0]
#更新阈值γ
self.Gamma -=Delta_Gamma
#计算Δv
for n in range(len(self.v)):
Delta_v = self.eta*eh[n]*self.v[n]
self.v[n] +=Delta_v
self.Eks.append(numpy.average(0.5*sum(numpy.square(Ek))))
iris = load_iris()
x= iris['data']
y = iris['target']
y = y.reshape(-1,1)
MinMaxScaler = MinMaxScaler(feature_range=(0,1))
x = MinMaxScaler.fit_transform(x)
y = MinMaxScaler.fit_transform(y)
x_train ,x_test,y_train ,y_test = train_test_split(x,y,test_size = 0.3)
#设置学习率,训练次数,隐藏层神经元个数
bp_net =BP_net(eta = 0.01 , n_inter = 2000 , q =8)
#训练样本
bp_net.fit(x_train,y_train )
draw_Eks(bp_net.Eks)
#仿真输出和实际输出对比图
netout_target = []
for i in range(len(y_test)):
netout_target.append(bp_net.predict(x_test[i]))
y_test = MinMaxScaler.inverse_transform(y_test)
netout_target = MinMaxScaler.inverse_transform(netout_target)
for i in range(len(y_test)):
if netout_target[i]<=0.66:
netout_target[i]=0
elif netout_target[i]>0.66 and netout_target[i]<=1.32:
netout_target[i]=1
else:
netout_target[i]=2
#画出实际值和预测值折线图
plt.plot(netout_target, marker = 'o', label = '预测值')
plt.plot(y_test, marker = 'x', label = '实际值')
plt.xlabel('样本')
plt.ylabel('类别')
plt.title('预测类别与实际类别对比图')
plt.legend()
plt.show()
#画出误差率饼图
correctN = 0
wrongN =0
for i in range(len(y_test)):
if netout_target[i]==y_test[i]:
correctN+=1
else:
wrongN+=1
cor_and_wrong=[]
cor_and_wrong.append(correctN)
cor_and_wrong.append(wrongN)
label=["正确","错误"]
plt.pie(cor_and_wrong,labels=label,autopct="%.2f%%")
plt.title('错误率饼图')
plt.legend(loc="best")
plt.show()
总结
算法都是基于数学,能把其中的数学推导理解,码代码只是花时间的问题。话虽如此但这毕竟是简单的机器学习算法,以后可能就不会这么想了