BP网络模型处理信息的基本原理是:输入信号Xi通过中间节点(隐层点)作用于输出节点,经过非线形变换,产生输出信号Yk,网络训练的每个样本包括输入向量X和期望输出量t,网络输出值Y与期望输出值t之间的偏差,通过调整输入节点与隐层节点的联接强度取值Wij和隐层节点与输出节点之间的联接强度Tjk以及阈值,使误差沿梯度方向下降,经过反复学习训练,确定与最小误差相对应的网络参数(权值和阈值),训练即告停止。此时经过训练的神经网络即能对类似样本的输入信息,自行处理输出误差最小的经过非线形转换的信息。
一 BP神经网络模型
BP网络模型包括其输入输出模型、作用函数模型、误差计算模型和自学习模型。
(1)节点输出模型
隐节点输出模型:Oj=f(∑Wij×Xi-q j) (1)
输出节点输出模型:Yk=f(∑Tjk×Oj-q k) (2)
f-非线形作用函数;q -神经单元阈值。
(2)作用函数模型
作用函数是反映下层输入对上层节点刺激脉冲强度的函数又称刺激函数,一般取为(0,1)内连续取值Sigmoid函数: f(x)=1/(1+e-x) (3)
(3)误差计算模型
误差计算模型是反映神经网络期望输出与计算输出之间误差大小的函数:
Ep=1/2×∑(tpi-Opi)2 (4)
tpi- i节点的期望输出值;Opi-i节点计算输出值。
(4)自学习模型
神经网络的学习过程,即连接下层节点和上层节点之间的权重拒阵Wij的设定和误差修正过程。BP网络有师学习方式-需要设定期望值和无师学习方式-只需输入模式之分。自学习模型为
△Wij(n+1)= h ×Фi×Oj+a×△Wij(n) (5)
h -学习因子;Фi-输出节点i的计算误差;Oj-输出节点j的计算输出;a-动量因子。
二 BP网络模型的缺陷分析及优化策略
(1)学习因子h 的优化
采用变步长法根据输出误差大小自动调整学习因子,来减少迭代次数和加快收敛速度。
h =h +a×(Ep(n)- Ep(n-1))/ Ep(n) a为调整步长,0~1之间取值(6)
(2)隐层节点数的优化
隐 节点数的多少对网络性能的影响较大,当隐节点数太多时,会导致网络学习时间过长,甚至不能收敛;而当隐节点数过小时,网络的容错能力差。利用逐步回归分析法并进行参数的显著性检验来动态删除一些线形相关的隐节点,节点删除标准:当由该节点出发指向下一层节点的所有权值和阈值均落于死区(通常取±0.1、±0.05等区间)之中,则该节点可删除。最佳隐节点数L可参考下面公式计算:
L=(m+n)1/2+c (7)
m-输入节点数;n-输出节点数;c-介于1~10的常数。
(3)输入和输出神经元的确定
利用多元回归分析法对神经网络的输入参数进行处理,删除相关性强的输入参数,来减少输入节点数。
(4)算法优化
由于BP算法采用的是剃度下降法,因而易陷于局部最小并且训练时间较长。用基于生物免疫机制地既能全局搜索又能避免未成熟收敛的免疫遗传算法IGA取代传统BP算法来克服此缺点。
该程序实现神经网络的BP算法,输入节点数,输出节点数,隐层数,隐层节点数任意,由用户决定。其中隐层数指的是总共曾数包含输出层,比如说异或算法为2层,第一层节点数为2,第二层也即输出层节点数为1,输入点数为2 。但是该程序对异或算法实现并不理想,对多层多节点的神经网络有较好的结果。#include
"iostream.h"
#include <time.h>
#include <stdlib.h>
#include<fstream>
#include <math.h>
#include "stdio.h "
#define MAXCOUNT 1e5 //迭代训练次数上限// 精度0.001的随机浮点数,范围在-0.5——0.5
float randf()
{
return (float)((rand() % 1001) * 0.001f-0.5);
}
//高斯随机数产生函数
double gaussrand()
{ static double V1, V2, S;
static int phase = 0;
double X; if(phase == 0) {
do {
double U1 = (double)rand() / RAND_MAX;
double U2 = (double)rand() / RAND_MAX;
V1 = 2 * U1 - 1;
V2 = 2 * U2 - 1;
S = V1 * V1 + V2 * V2;
} while(S >= 1 || S == 0);
X = V1 * sqrt(-2 * log(S) / S);
} else
X = V2 * sqrt(-2 * log(S) / S);
phase = 1 - phase;
return X;
}
//定义一个多层前向BP网络class BP
{public:
double ***p;//记录所有的权值
double ***ddp;//记录所有的权值增量
int *pnode;//记录每一层的节点数
double **pnodey;//记录每组每一层的节点的输出值
double **ddlj;//记录每组每一层的节点的ddlj
double **pX;//记录输入样本
double **pY;//记录输入理想输出值
int Sidenum;
int Inputnodenum;
int outputnodenum;
int yangbenzushu;
BP()
{ Sidenum=0;
Inputnodenum=0;
outputnodenum=0;
yangbenzushu=0;}
~BP()
{
for(int m=0;m<Sidenum;m++)
{
for(int n=0;n<pnode[m+1];n++)
{delete[] p[m][n];
delete[] ddp[m][n];}
delete[] p[m];
delete[] ddp[m];
}
delete[] p;
delete[] ddp;
p=NULL;
ddp=NULL;
if(p==NULL)
delete [] pnode;
for(int M=0;M<Sidenum;M++)
{delete[] pnodey[M];
delete[] ddlj[M];
}
delete[] pnodey;
delete[] ddlj;
pnodey=NULL;
ddlj=NULL;
}
//完成所有权值的初始化
void getW(int sidenum,int inputnodenum,int outputnodenum1,int yangbenzu)
{ Sidenum=sidenum;
yangbenzushu= yangbenzu;
Inputnodenum=inputnodenum;
outputnodenum=outputnodenum1;
p=new double **[sidenum];
ddp=new double **[sidenum];
pnode=new int [sidenum+1];//包含输入层输出层每一层的节点数
for(int i=0;i<sidenum+1;i++)
{
int data=0;
cout<<"请输入第"<<i<<"层节点数"<<endl;
cin>>data;
pnode[i]=data;
}
for (int j=0;j<sidenum;j++)
{ p[j]=new double* [pnode[j+1]];
ddp[j]=new double*[pnode[j+1]];
for (int k=0;k<pnode[j+1];k++)
{ ddp[j][k]=new double[pnode[j]+1];
p[j][k]=new double[pnode[j]+1];
for (int t=0;t<pnode[j]+1;t++)
{ ddp[j][k][t]=0;//每一层的权值初始化为0
if(t==0)p[j][k][t]=-fabs(randf());//每一层的阀值初始化
else p[j][k][t]=randf();//每一层的权值初始化
}
}
}
//为记录每一层的节点的输出值和ddlj的指针开辟内存
pnodey=new double *[Sidenum];
ddlj=new double *[Sidenum];for(int p=0;p<Sidenum;p++)
{
pnodey[p] = new double [pnode[p+1]+1];
ddlj[p]=new double [pnode[p+1]];
pnodey[p][0]=1;//每组每层的首值为1
}
}
/**********************/
//每个节点输出函数
double fas(double s)
{ double t;
t=1.0/(exp(-s)+1);
return t;
}
/************************************************/
//该函数用来记录样本值和理想输出值
void INPUT(int yangbenzushu1 )
{ pY=new double*[yangbenzushu1];
pX=new double*[yangbenzushu1];
for(int yu=0;yu<yangbenzushu1;yu++)
{ pX[yu]=new double[Inputnodenum+1];
pY[yu]=new double[outputnodenum+1];
}
//每组样本的首值赋为1
for(int yu1=0;yu1<yangbenzushu1;yu1++)
{ pX[yu1][0]=1;
pY[yu1][0]=1;
}
cout<<"请输出样本输入值"<<endl;
for(int yuy=0;yuy<yangbenzushu1;yuy++)
for(int yy=1;yy<=Inputnodenum;yy++)
{ if(yy==Inputnodenum) cout<<endl;
cout<<"X["<<yuy<<"]"<<"["<<yy<<"]="<<' ';
cin>>pX[yuy][yy];
}
cout<<"请输出样本理想输出值"<<endl;
for(int yuy1=0;yuy1<yangbenzushu1;yuy1++)
for(int yy1=1;yy1<=outputnodenum;yy1++)
{ //if(yy==Inputnodenum) cout<<endl;
cout<<"Y["<<yuy1<<"]"<<"["<<yy1<<"]="<<' ';
cin>>pY[yuy1][yy1];
}}
/****************************************************************************/
//计算每个节点的输出值函数
double computeYl(int KK)//KK代表第几组组号
{ double sum1=0;
//把所有的层的每一个节点的输出值算出来并记录在 pnodey里,不包含输入点值
for(int y=0;y<Sidenum;y++)//层数
{
for(int r=1;r<pnode[y+1]+1;r++)//节点数
{ double sum=0;
for(int z=0;z<pnode[y]+1;z++)//前一层的节点数
{if(y==0)sum+= pX[KK][z]*p[y][r-1][z];
else
sum+=pnodey[y-1][z]*p[y][r-1][z];
}
pnodey[y][r]=fas(sum);
}
}
for(int j=1;j<=outputnodenum;j++)
sum1+=pow(pY[KK][j]-pnodey[Sidenum-1][j],2);
return sum1;
}
/**********************************************************/
//Compute Back-Propagation-Errors
void ComputeBackPropagationErrors(int gf)//gf代表组号
{//计算所有的ddlj[][]
//for(int gf=0;gf<yangbenzushu;gf++)//组数
for(int q=Sidenum-1;q>=0;q--)//从最后一层开始
{
if (q==Sidenum-1)//如果是最外一层的话
{ for(int rt=0;rt<pnode[q+1];rt++)//每层的节点数
ddlj[q][rt]=pnodey[q][rt+1]*(1-pnodey[q][rt+1])*(pY[gf][rt+1]-pnodey[q][rt+1]) ;
}
else
{
for(int ry=0;ry<pnode[q+1];ry++)
{ double sumtemp=0;
for(int fg=0;fg<pnode[q+2];fg++)
sumtemp+=ddlj[q+1][fg]*p[q+1][fg][ry+1];
ddlj[q][ry] = pnodey[q][ry+1]*(1-pnodey[q][ry+1])* sumtemp;
}
}
}
//计算所有的ddp[][]
//for(int gf1=0;gf1<yangbenzushu;gf1++)//组数
for(int l=0;l<Sidenum;l++)//层数
for(int JJ=0;JJ<pnode[l+1];JJ++)//每一层的节点数
for(int i=0;i<pnode[l]+1;i++)//前一层的节点数
{ if(l==0)//如果是第一层的话,y值为输入的X值
ddp[l][JJ][i]=ddlj[l][JJ]*pX[gf][i];
else
ddp[l][JJ][i]=ddlj[l][JJ]*pnodey[l-1][i];
}}
/*************************************************************************/
void UpdatetheWeightsusingBPAlgorithm()
{ for(int cent=0;cent<Sidenum;cent++)//层数
for(int J=0;J<pnode[cent+1];J++)//每一层的节点数
for(int i=0;i<pnode[cent]+1;i++)//前一层的节点数
p[cent][J][i]+=0.2*ddp[cent][J][i];}
/***************************************************************************/
double xunlianErrors()//定义训练误差函数
{ double error=0;
double sum=0;
double temp=0;
double temp1=0;
for(int gf1=0;gf1<yangbenzushu;gf1++)//组数
{temp= computeYl(gf1);
//temp1=zhengquelv(gf1);
//sum+=temp1;
for(int jj=1;jj<=outputnodenum;jj++)
cout<<pnodey[Sidenum-1][jj];
error+=temp;
}
// sum=sum/yangbenzushu;
cout<<"用训练集所得到的正确率:"<<sum<<endl;
return error/yangbenzushu;
}
/****************************************************************************/
double jiaoyanErrors(int yangbenzushu1 )//定义校验误差函数
{double error=0;
double sum=0;
double temp=0;
double temp1=0;
for(int gf1=0;gf1<yangbenzushu1;gf1++)//组数
{temp= computeYl(gf1);
for(int jj=1;jj<=outputnodenum;jj++)
cout<<pnodey[Sidenum-1][jj];
//temp1=zhengquelv(gf1);
//sum+=temp1;
error+=temp;
}
//sum=sum/yangbenzushu1;
// cout<<"用校验集所得到的正确率:"<<sum<<endl;
return error/yangbenzushu1;}
/********************************************************************/
double zhengquelv(int KK)
{int count=0;
double av=0;
//for(int gf1=0;gf1<yangbenzushu;gf1++)//组数
for(int jj=1;jj<=outputnodenum;jj++)
{if (pnodey[Sidenum-1][jj]>0) pnodey[Sidenum-1][jj]=1;
else pnodey[Sidenum-1][jj]=0;
if(pY[KK][jj]==pnodey[Sidenum-1][jj])count++;
}
av=(double)count/outputnodenum;
return av;
}
/***********************************************************************/
void freeINput()
{
if(pX!=NULL)
{for(int u=0;u<yangbenzushu;u++)
delete []pX[u];
delete []pX;
pX=NULL;
}
if(pY!=NULL)
{for(int u1=0;u1<yangbenzushu;u1++)
delete []pY[u1];
delete []pY;
pY=NULL;
}
}
/***************************************************************/
//输出所有的权值
void wputout()
{ for (int j=0;j<Sidenum;j++)
{ cout<<"第["<<j+1<<"]层权值为:"<<endl;
for (int k=0;k<pnode[j+1];k++)
{ //if(k==pnode[j+1]-1) cout<<endl;
for (int t=0;t<pnode[j]+1;t++)
{
cout<<p[j][k][t]<<' ';
if(t==pnode[j]) cout<<endl;
}
}
}}
/**********************************************************/
};
void main()
{
BP bp;
int count=0;//用来统计所用的迭代次数
//FILE *fp;
int inputnodenum,outnodenum,sidenum,yangbenzunum;
double error;
cout<<"请输入输入点数,输出点数,隐层数"<<endl;
cin>>inputnodenum>>outnodenum>>sidenum;cout<<"请输入样本组数"<<endl;cin>>yangbenzunum;
//第一步初始化所有的权值
bp.getW(sidenum,inputnodenum,outnodenum,yangbenzunum);
//第二步输入样本组
bp.INPUT(yangbenzunum);
for(;;count++)
{
double sum=0;
double temp=0;
for(int fuzu=0;fuzu<yangbenzunum;fuzu++)
{
//第三步计算所有y值
temp=bp.computeYl(fuzu);
//第四步Compute Back-Propagation-Errors
bp.ComputeBackPropagationErrors(fuzu);
//第五步Update the Weights using BP Algorithm
bp.UpdatetheWeightsusingBPAlgorithm();
sum+=temp;
}
//第六步判断是否收敛
error=sum/2*yangbenzunum;
//freopen("debug\\out.txt","w",stdout);
//fp=freopen( "out.txt", "w", stdout) ;
// cout<<count<<' '<<error<<endl;
// fclose(stdout);//关闭文件
/*if(count==1000)cout<<error<<endl;
if(count==1500)cout<<error<<endl;
if(count==1600)cout<<error<<endl;*/
//if(count==10000)cout<<error<<endl;
if(error<1.02)
{
cout<<"循环收敛"<<"迭代次数为:"<<count<<endl;
//bp.freeINput();//释放X Y空间
break;
}}
cout<<"权值为:"<<endl;bp.wputout();
double XUNLIANER=bp.xunlianErrors();
//cout<<"训练误差为:"<<XUNLIANER<<endl;
bp.freeINput();//释放X Y空间
/*
cout<<"请输入校验样本: "<<endl;
int jiaoyannum=0;
cin>>jiaoyannum;
bp.INPUT(jiaoyannum);
double jiaoyanER=bp.jiaoyanErrors(jiaoyannum);
cout<<"校验误差为:"<<jiaoyanER<<endl;
//fclose( stdout ) ;*/
简介:BP(Back Propagation)网络是1986年由Rumelhart和McCelland为首的科学家小组提出,是一种按误差逆传播算法训练的多层前馈网络,是目前应用最广泛的神经网络模型之一。BP网络能学习和存贮大量的输入-输出模式映射关系,而无需事前揭示描述这种映射关系的数学方程。它的学习规则是使用最速下降法,通过反向传播来不断调整网络的权值和阈值,使网络的误差平方和最小。BP神经网络模型拓扑结构包括输入层(input)、隐层(hide layer)和输出层(output layer)
摘 要:BP神经网络算法是在BP神经网络现有算法的基础上提出的,是通过任意选定一组权值,将给定的目标输出直接作为线性方程的代数和来建立线性方程组,解得待求权,不存在传统方法的局部极小及收敛速度慢的问题,且更易理解。
关键词:固定权值;gauss消元法;BP算法
人工神经网络(artificial neural networks,ANN)系统是20世纪40年代后出现的,它是由众多的神经元可调的连接权值连接而成,具有大规模并行处理、分布式信息存储、良好的自组织自学习能力等特点,在信息处理、模式识别、智能控制及系统建模等领域得到越来越广泛的应用。尤其误差反向传播算法(ErrorBack-propagation Training,简称BP网络)可以逼近任意连续函数,具有很强的非线性映射能力,而且网络的中间层数、各层的处理单元数及网络的学习系数等参数可根据具体情况设定,灵活性很大,所以它在许多应用领域中起到重要作用。近年来,为了解决BP神经网络收敛速度慢、不能保证收敛到全局最小点,网络的中间层及它的单元数选取无理论指导及网络学习和记忆的不稳定性等缺陷,提出了许多改进算法。
1 传统的BP算法简述
BP算法是一种有监督式的学习算法,其主要思想是:输入学习样本,使用反向传播算法对网络的权值和偏差进行反复的调整训练,使输出的向量与期望向量尽可能地接近,当网络输出层的误差平方和小于指定的误差时训练完成,保存网络的权值和偏差。具体步骤如下:
(1)初始化,随机给定各连接权[w],[v]及阀值θi,rt。
(2)由给定的输入输出模式对计算隐层、输出层各单元输出
bj=f(■wijai-θj) ct=f(■vjtbj-rt)
式中:bj为隐层第j个神经元实际输出;ct为输出层第t个神经元的实际输出;wij为输入层至隐层的连接权;vjt为隐层至输出层的连接权。
dtk=(ytk-ct)ct(1-ct) ejk=[■dtvjt] bj(1-bj)
式中:dtk为输出层的校正误差;ejk为隐层的校正误差。
(3)计算新的连接权及阀值,计算公式如下:
vjt(n+1)=vjt(n)+?琢dtkbj wij(n+1)=wij(n)+?茁ejkaik
rt(n+1)=rt(n)+?琢dtk θj(n+1)=θj(n)+?茁ejk
式中:?琢,?茁为学习系数(0<?琢<1,0<?茁<1)。
(4)选取下一个输入模式对返回第2步反复训练直到网络设输出误差达到要求结束训练。
传统的BP算法,实质上是把一组样本输入/输出问题转化为一个非线性优化问题,并通过负梯度下降算法,利用迭代运算求解权值问题的一种学习方法,但其收敛速度慢且容易陷入局部极小,为此提出了一种新的算法,即高斯消元法。
2 改进的BP网络算法
2.1 改进算法概述
此前有人提出:任意选定一组自由权,通过对传递函数建立线性方程组,解得待求权。本文在此基础上将给定的目标输出直接作为线性方程等式代数和来建立线性方程组,不再通过对传递函数求逆来计算神经元的净输出,简化了运算步骤。没有采用误差反馈原理,因此用此法训练出来的神经网络结果与传统算法是等效的。其基本思想是:由所给的输入、输出模式对通过作用于神经网络来建立线性方程组,运用高斯消元法解线性方程组来求得未知权值,而未采用传统BP网络的非线性函数误差反馈寻优的思想。
2.2 改进算法的具体步骤
对给定的样本模式对,随机选定一组自由权,作为输出层和隐含层之间固定权值,通过传递函数计算隐层的实际输出,再将输出层与隐层间的权值作为待求量,直接将目标输出作为等式的右边建立方程组来求解。
现定义如下符号(见图1):x (p)输入层的输入矢量;y (p)输入层输入为x(p)时输出层的实际输出矢量;t (p)目标输出矢量;n,m,r分别为输入层、隐层和输出层神经元个数;W为隐层与输入层间的权矩阵;V为输出层与隐层间的权矩阵。具体步骤如下:
(1)随机给定隐层和输入层间神经元的初始权值wij。
(2)由给定的样本输入xi(p)计算出隐层的实际输出aj(p)。为方便起见将图1网络中的阀值写入连接权中去,令:隐层阀值θj=wnj,x(n)=-1,则:
aj(p)=f(■wijxi(p)) (j=1,2…m-1)。
(3)计算输出层与隐层间的权值vjr。以输出层的第r个神经元为对象,由给定的输出目标值tr(p)作为等式的多项式值建立方程,用线性方程组表示为:
a0(1)v1r+a1(1)v2r+…+am(1)vmr=tr(1)a0(2)v1r+a1(2)v2r+…+am(2)vmr=tr(2) ……a0(p)v1r+a1(p)v2r+…+am(p)vmr=tr(p) 简写为:Av=T
为了使该方程组有唯一解,方程矩阵A为非奇异矩阵,其秩等于其增广矩阵的秩,即:r(A)=r(A┊B),且方程的个数等于未知数的个数,故取m=p,此时方程组的唯一解为:Vr=[v0r,v2r,…vmr](r=0,1,2…m-1)
(4)重复第三步就可以求出输出层m个神经元的权值,以求的输出层的权矩阵加上随机固定的隐层与输入层的权值就等于神经网络最后训练的权矩阵。
3 计算机运算实例
现以神经网络最简单的XOR问题用VC编程运算进行比较(取神经网络结构为2-4-1型),传统算法和改进BP算法的误差(取动量因子α=0.001 5,步长η=1.653)