目录
一、误差逆传播算法
二、BP算法实现流程图
三、BP神经网络推导
3.1 前向传播
3.2 反向传播
四、Python实现BP神经网络
4.1 激活函数sigmod
4.2 构造三层BP神经网络
4.2.1 BP神经网络初始化
4.2.2 前向传播
4.2.3 反向传播
4.2.4 迭代训练
4.3 算法检验——预测数据
4.4 图解总结
五、运行结果
六、整体总结
七、总代码
写在前面:
本篇文章关于BP神经网络的算法实现相对来说通俗易懂,讲解下至关于函数变量的解释,上至关于BP算法中涉及的前向传播及反向传播的原理、再至其过程中涉及的公式详细推导都有笔者的一些思考笔记。本文也对标准的BP神经网络进行几处改进,目的都是为了提高训练速度。另外,若文章存在一些理论错误请各位不吝赐教,当然,在阅读过程中若有些地方不明白可以在评论区留言!
误差逆传播算法的大概流程为:
1、初始化网络中所有连接权和阈值;
2、进入迭代训练:
(1)根据当前参数计算当前样本的输出;
(2)根据下面公式计算输出层神经元的梯度项gj:
(3)根据下面公式计算隐层神经元的梯度项eh:
(4)根据下面公式更新连接权whj、vih与阈值θj、γh:
3、训练完成后输出连接权和阈值确定的多层前馈神经网络;
标准的BP流程图属于单样本训练,假设总共有样本个数为P,其中第i个样本为p,训练次数用q表示,训练完成来源是设置的误差阈值和实际误差对比,达到要求时结束。
左面是标准BP的算法流程,权值调整方法是是基于单样本训练的,但是单样本训练遵循的是只顾眼前的这个数据产生的误差进行调节权值调整,这样的后果是当训练数据很多时,计算量就会急剧增加,导致收敛速度过慢。为了改变这些缺点,我们采用另外一种方法就是在所有样本输入以后,计算网络的总误差,然后根据总误差计算各层的误差信号并调整权值,这种累积的误差的批处理方式称为批(batch)训练或者(epoch)训练。由于批训练遵循了以减小全局误差为目标的“集体主义”原则也就是所有的训练样本。在保证总误差向减小方向变化时,即使训练样本很多,训练时的收敛速度也是很快的。
我们只讨论三层的BP神经网络,又称三层感知机。所谓三层,即包括输入层、隐层、输出层。三层感知机的神经元连接形式:
图解:
此处要对三层BP网络中不同符号所代表的意思理解,下面进行符号说明:
符号说明:
输入层、隐层、输出层神经元的个数分别为d、q、l;
xi :最初第i个神经元的输入;
vih :输入层第i个神经元和隐层第h个神经元的权值;
αh:隐层第h个神经元的输入;
γh:隐层第h个神经元的阈值;
bh :隐层第h个神经元的输出;
whj :隐层第h个神经元与输出层第j个神经元的权值;
βj:输出层第j个神经元的输入;
θj:输出层第j个神经元的阈值;
yj :输出层第j个神经元的输出;
激活函数f(x)一般有对数几率函数sigmoid、双曲正切函数tanh等等:
在前向传播计算完成后,需要通过误差逆传播算法对误差进行反向传播修改权值和阈值,相关公式推导如下:
下面通过python代码实现上述BP网络的流程。
激活函数采用双曲正切函数tanh:
#? 激活函数sigmoid(x)、及其导数DS(x)
import numpy as np
# 双曲正切函数tanh
def sigmoid(x):
return np.tanh(x)
def DS(x):
return 1 - (np.tanh(x)) ** 2
# 第90次迭代 误差0.00005
#? 构造3层BP神经网络架构
class BP:
#? 初始化函数:各层结点数、激活结点、权重矩阵、偏差、动量因子
def __init__(self,num_in,num_hidden,num_out):
# 输入层、隐藏层、输出层 的结点数
self.num_in=num_in+1 # 输入层结点数 并增加一个偏置结点(阈值)
self.num_hidden=num_hidden+1 # 隐藏层结点数 并增加一个偏置结点(阈值)
self.num_out=num_out # 输出层结点数
# 激活BP神经网络的所有结点(向量)
self.active_in=np.array([-1.0]*self.num_in)
self.active_hidden=np.array([-1.0]*self.num_hidden)
self.active_out=np.array([1.0]*self.num_out)
# 创建权重矩阵
self.weight_in=makematrix(self.num_in,self.num_hidden) # in*hidden 的0矩阵
self.weight_out=makematrix(self.num_hidden,self.num_out) # hidden*out的0矩阵
# 对权重矩阵weight赋初值
for i in range(self.num_in): # 对weight_in矩阵赋初值
for j in range(self.num_hidden):
self.weight_in[i][j]=random_number(0.1,0.1)
for i in range(self.num_hidden): # 对weight_out矩阵赋初值
for j in range(self.num_out):
self.weight_out[i][j]=random_number(0.1,0.1)
# 偏差
for j in range(self.num_hidden):
self.weight_in[0][j]=0.1
for j in range(self.num_out):
self.weight_out[0][j]=0.1
# 建立动量因子(矩阵)
self.ci=makematrix(self.num_in,self.num_hidden) # num_in*num_hidden 矩阵
self.co=makematrix(self.num_hidden,self.num_out) # num_hidden*num_out矩阵
图解:初始化后各个变量的存在形式:
#? 构造3层BP神经网络架构
class BP:
#? 信号正向传播
def update(self,inputs):
if len(inputs)!=(self.num_in-1):
raise ValueError("与输入层结点数不符")
# 数据输入 输入层
self.active_in[1:self.num_in]=inputs
# 数据在隐藏层处理
self.sum_hidden=np.dot(self.weight_in.T,self.active_in.reshape(-1,1)) # 叉乘
# .T操作是对于array操作后的数组进行转置操作
# .reshape(x,y)操作是对于array操作后的数组进行重新排列成一个x*y的矩阵,参数为负数表示无限制,如(-1,1)转换成一列的矩阵
self.active_hidden=sigmoid(self.sum_hidden) # active_hidden[]是处理完输入数据之后处理,作为输出层的输入数据
self.active_hidden[0]=-1
# 数据在输出层处理
self.sum_out=np.dot(self.weight_out.T,self.active_hidden)
self.active_out=sigmoid(self.sum_out)
# 返回输出层结果
return self.active_out
图解:正向传播的过程:计算出active_in、active_hidden、active_out:
#? 构造3层BP神经网络架构
class BP:
#? 误差反向传播
def errorbackpropagate(self,targets,lr,m): # lr 学习效率
if self.num_out==1:
targets=[targets]
if len(targets)!=self.num_out:
raise ValueError("与输出层结点数不符")
# 误差
error=(1/2)*np.dot((targets.reshape(-1,1)-self.active_out).T,
(targets.reshape(-1,1)-self.active_out))
# 输出层 误差信号
self.error_out=(targets.reshape(-1,1)-self.active_out)*DS(self.sum_out) # DS(self.active_out)
# 隐层 误差信号
self.error_hidden=np.dot(self.weight_out,self.error_out)*DS(self.sum_hidden) # DS(self.active_hidden)
# 更新权值
# 隐层
self.weight_out=self.weight_out+lr*np.dot(self.error_out,self.active_hidden.reshape(1,-1)).T+m*self.co
self.co=lr*np.dot(self.error_out,self.active_hidden.reshape(1,-1)).T
# 输入层
self.weight_in=self.weight_in+lr*np.dot(self.error_hidden,self.active_in.reshape(1,-1)).T+m*self.ci
self.ci=lr*np.dot(self.error_hidden,self.active_in.reshape(1,-1)).T
return error
图解:反向传播计算出error_hidden(即eh)、error_out(即gj):
反向传播更新权值,计算出weight_in、weight_out:
更新权值时对标准的BP算法有改进——增加了动量项ci、co:
co(t)=co(t) ==> co(t)=co(t)+m*co(t-1)
ci(t)=ci(t) ==> ci(t)=ci(t)+m*ci(t-1)
m为动量系数,0
标准BP算法在调整权值时,只按t时刻误差的梯度降方向调整,而没有考虑t时刻以前的梯度方向,从而常使训练过程发生振荡,收敛缓慢;
增加动量项,可以从前一次权值调整量中取出一部分迭加到本次权值调整量中;动量项反映了以前积累的调整经验,对于t时刻的调整起阻尼作用,当误差曲面出现骤然起伏时,可减小振荡趋势,提高训练速度;
#? 构造3层BP神经网络架构
class BP:
def train(self,pattern,itera=100,lr=0.2,m=0.1):
for i in range(itera):
error=0.0 # 每一次迭代将error置0
for j in pattern: # j为传入数组的第一维数据 #! *2?(1次迭代里面重复两次?)
inputs=j[0:self.num_in-1] # 根据输入层结点的个数确定传入结点值的个数
targets=j[self.num_in-1:] # 剩下的结点值作为输出层的值
self.update(inputs) # 正向传播 更新了active_out
error=error+self.errorbackpropagate(targets,lr,m) # 误差反向传播 计算总误差
if i%10==0:
print("########################误差 %-.5f ######################第%d次迭代" %(error, i))
默认迭代训练100次。
#? 算法检验——预测数据D
# X 输入数据;D 目标数据
X = list(np.arange(-1, 1.1, 0.1)) # -1~1.1 步长0.1增加
D = [-0.96, -0.577, -0.0729, 0.017, -0.641, -0.66, -0.11, 0.1336, -0.201, -0.434, -0.5,
-0.393, -0.1647, 0.0988, 0.3072,0.396, 0.3449, 0.1816, -0.0312, -0.2183, -0.3201]
A = X + D # 数据合并 方便处理
patt = np.array([A] * 2) # 2*42矩阵 #! 为什么*2?
# 创建神经网络,21个输入节点,13个隐藏层节点,21个输出层节点
bp = BP(21, 13, 21)
# 训练神经网络
bp.train(patt)
# 测试神经网络
d = bp.test(patt)
# 查阅权重值
bp.weights()
import matplotlib.pyplot as plt
plt.plot(X, D, label="source data") # D为真实值
plt.plot(X, d, label="predict data") # d为预测值
plt.legend()
plt.show()
输入数据为X数据集、预测目标数据为D数据集,经过BP网络后输出的预测数据为d数据集。
最后结果输出形式为终端数据呈现、与可视化图像呈现。
综上图解,各个变量的存在形式如下:
1、权值矩阵:
2、激活矩阵:
3、动量矩阵:
1、运行后在终端输出的一部分结果:
“->”左侧为输入的数据xi,“->”右侧为这些数据经过三层感知机后的输出结果yj;
集合D为标准值,可以看到“->”右侧的预测结果和真实值相差很小,说明预测效果较好;
2、下面看一下一些迭代后的误差数据:
我们一共迭代训练了100次,可以看到当训练到90次后,误差已经减小到0.00005,误差极小,也说明预测效果较好;比较这些误差数据发现误差减小的速度很快,说明我们使用改进后的BP算法比较不错;
3、可视化预测值和真实值的差距:
图中其实有两根线,分别为黄线和蓝线,因为预测效果较好导致蓝线不太明显;黄线代表经过三层感知机预测出的数据,蓝线代表真实值,两条曲线拟合度很高说明三层感知机训练效果较好;
1、上述为三层的BP神经网络,即三层感知机;
2、此BP网络对标准BP网络进行改进:
(1)标准BP网络属于单样本训练,训练速度满;改进后可以进行批训练,将所有样本输入后计算网络的总误差,并根据总误差调整权值,这样训练时的收敛速度很快;
(2)标准BP网络在调整权值时只按t时刻误差的梯度降方向调整,而没有考虑t时刻之前的梯度方向,在训练过程中可能会出现振荡,收敛速度慢;改进后添加了动量因子考虑了t时刻之前的梯度变化,可以提高训练速度;
#! BP神经网络(误差逆传播算法)
#! 三层BP神经网络/三层感知机
#? 激活函数sigmoid(x)、及其导数DS(x)
import numpy as np
# 双曲正切函数tanh
def sigmoid(x):
return np.tanh(x)
def DS(x):
return 1 - (np.tanh(x)) ** 2
# 第90次迭代 误差0.00005
#? 生成区间[a,b]内的随机数
import random
def random_number(a,b):
return (b-a)*random.random()+a # random.random()随机生成[0,1)内浮点数
#? 生成一个m*n矩阵,并且设置默认零矩阵
def makematrix(m,n,fill=0.0):
a = []
for i in range(m):
a.append([fill]*n) # 列表1*n会得到一个新列表,新列表元素为列表1元素重复n次。[fill]*3==[fill fill fill]
return np.array(a)
#? 构造3层BP神经网络架构
class BP:
#? 初始化函数:各层结点数、激活结点、权重矩阵、偏差、动量因子
def __init__(self,num_in,num_hidden,num_out):
# 输入层、隐藏层、输出层 的结点数
self.num_in=num_in+1 # 输入层结点数 并增加一个偏置结点(阈值)
self.num_hidden=num_hidden+1 # 隐藏层结点数 并增加一个偏置结点(阈值)
self.num_out=num_out # 输出层结点数
# 激活BP神经网络的所有结点(向量)
self.active_in=np.array([-1.0]*self.num_in)
self.active_hidden=np.array([-1.0]*self.num_hidden)
self.active_out=np.array([1.0]*self.num_out)
# 创建权重矩阵
self.weight_in=makematrix(self.num_in,self.num_hidden) # in*hidden 的0矩阵
self.weight_out=makematrix(self.num_hidden,self.num_out) # hidden*out的0矩阵
# 对权重矩阵weight赋初值
for i in range(self.num_in): # 对weight_in矩阵赋初值
for j in range(self.num_hidden):
self.weight_in[i][j]=random_number(0.1,0.1)
for i in range(self.num_hidden): # 对weight_out矩阵赋初值
for j in range(self.num_out):
self.weight_out[i][j]=random_number(0.1,0.1)
# 偏差
for j in range(self.num_hidden):
self.weight_in[0][j]=0.1
for j in range(self.num_out):
self.weight_out[0][j]=0.1
# 建立动量因子(矩阵)
self.ci=makematrix(self.num_in,self.num_hidden) # num_in*num_hidden 矩阵
self.co=makematrix(self.num_hidden,self.num_out) # num_hidden*num_out矩阵
#? 信号正向传播
def update(self,inputs):
if len(inputs)!=(self.num_in-1):
raise ValueError("与输入层结点数不符")
# 数据输入 输入层
self.active_in[1:self.num_in]=inputs
# 数据在隐藏层处理
self.sum_hidden=np.dot(self.weight_in.T,self.active_in.reshape(-1,1)) # 叉乘
# .T操作是对于array操作后的数组进行转置操作
# .reshape(x,y)操作是对于array操作后的数组进行重新排列成一个x*y的矩阵,参数为负数表示无限制,如(-1,1)转换成一列的矩阵
self.active_hidden=sigmoid(self.sum_hidden) # active_hidden[]是处理完输入数据之后处理,作为输出层的输入数据
self.active_hidden[0]=-1
# 数据在输出层处理
self.sum_out=np.dot(self.weight_out.T,self.active_hidden)
self.active_out=sigmoid(self.sum_out)
# 返回输出层结果
return self.active_out
#? 误差反向传播
def errorbackpropagate(self,targets,lr,m): # lr 学习效率
if self.num_out==1:
targets=[targets]
if len(targets)!=self.num_out:
raise ValueError("与输出层结点数不符")
# 误差
error=(1/2)*np.dot((targets.reshape(-1,1)-self.active_out).T,
(targets.reshape(-1,1)-self.active_out))
# 输出层 误差信号
self.error_out=(targets.reshape(-1,1)-self.active_out)*DS(self.sum_out) # DS(self.active_out)
# 隐层 误差信号
self.error_hidden=np.dot(self.weight_out,self.error_out)*DS(self.sum_hidden) # DS(self.active_hidden)
# 更新权值
# 隐层
self.weight_out=self.weight_out+lr*np.dot(self.error_out,self.active_hidden.reshape(1,-1)).T+m*self.co
self.co=lr*np.dot(self.error_out,self.active_hidden.reshape(1,-1)).T
# 输入层
self.weight_in=self.weight_in+lr*np.dot(self.error_hidden,self.active_in.reshape(1,-1)).T+m*self.ci
self.ci=lr*np.dot(self.error_hidden,self.active_in.reshape(1,-1)).T
return error
#? 测试
def test(self,patterns):
for i in patterns: # i为传入数组的第一维数据
print(i[0:self.num_in-1],"->",self.update(i[0:self.num_in-1]))
return self.update(i[0:self.num_in-1]) # 返回测试结果,用于作图
#? 权值
def weights(self):
print("输入层的权值:")
print(self.weight_in)
print("输出层的权值:")
print(self.weight_out)
def train(self,pattern,itera=100,lr=0.2,m=0.1):
for i in range(itera):
error=0.0 # 每一次迭代将error置0
for j in pattern: # j为传入数组的第一维数据
inputs=j[0:self.num_in-1] # 根据输入层结点的个数确定传入结点值的个数
targets=j[self.num_in-1:] # 剩下的结点值作为输出层的值
self.update(inputs) # 正向传播 更新了active_out
error=error+self.errorbackpropagate(targets,lr,m) # 误差反向传播 计算总误差
if i%10==0:
print("########################误差 %-.5f ######################第%d次迭代" %(error, i))
#? 算法检验——预测数据D
# X 输入数据;D 目标数据
X = list(np.arange(-1, 1.1, 0.1)) # -1~1.1 步长0.1增加
D = [-0.96, -0.577, -0.0729, 0.017, -0.641, -0.66, -0.11, 0.1336, -0.201, -0.434, -0.5,
-0.393, -0.1647, 0.0988, 0.3072,0.396, 0.3449, 0.1816, -0.0312, -0.2183, -0.3201]
A = X + D # 数据合并 方便处理
patt = np.array([A] * 2) # 2*42矩阵
# 创建神经网络,21个输入节点,13个隐藏层节点,21个输出层节点
bp = BP(21, 13, 21)
# 训练神经网络
bp.train(patt)
# 测试神经网络
d = bp.test(patt)
# 查阅权重值
bp.weights()
import matplotlib.pyplot as plt
plt.plot(X, D, label="source data") # D为真实值
plt.plot(X, d, label="predict data") # d为预测值
plt.legend()
plt.show()