BP神经网络算法
2018/07/12/09:59:19 阅读:3947 来源:谷歌算法 标签:
百度细雨算法
今天来讲BP神经网络,神经网络在机器学习中应用比较广泛,比如函数逼近,模式识别,分类,数据压缩,数据挖掘等领域。接下来介绍BP神经网络的原理及实现。Contents
1.BP神经网络的认识
2.隐含层的选取
3.正向传递子过程
4.反向传递子过程
5.BP神经网络的注意点
6.BP神经网络的C++实现
1.BP神经网络的认识
BP(BackPropagation)神经网络分为两个过程
(1)工作信号正向传递子过程
(2)误差信号反向传递子过程
在BP神经网络中,单个样本有m个输入,有n个输出,在输入层和输出层之间通常还有若干个隐含层。实际上,1989年RobertHecht-Nielsen证明了对于任何闭区间内的一个连续函数都可以用一个隐含层的BP网络来逼近,这就是万能逼近定理。所以一个三层的BP网络就可以完成任意的m维到n维的映射。即这三层分别是输入层(I),隐含层(H),输出层(O)。如下图示
2.隐含层的选取
在BP神经网络中,输入层和输出层的节点个数都是确定的,而隐含层节点个数不确定,那么应该设置为多少
才合适呢?实际上,隐含层节点个数的多少对神经网络的性能是有影响的,有一个经验公式可以确定隐含层
节点数目,如下
其中
为隐含层节点数目,为输入层节点数目,为输出层节点数目,为之间的调节常数。
3.正向传递子过程
现在设节点
和节点之间的权值为,节点的阀值为,每个节点的输出值为,而每个节点的输出
值是根据上层所有节点的输出值、当前节点与上一层所有节点的权值和当前节点的阀值还有激活函数来实现
的。具体计算方法如下
其中
为激活函数,一般选取S型函数或者线性函数。
正向传递的过程比较简单,按照上述公式计算即可。在BP神经网络中,输入层节点没有阀值。
4.反向传递子过程
在BP神经网络中,误差信号反向传递子过程比较复杂,它是基于Widrow-Hoff学习规则的。假设输出层
的所有结果为
,误差函数如下
而BP神经网络的主要目的是反复修正权值和阀值,使得误差函数值达到最小。Widrow-Hoff学习规则
是通过沿着相对误差平方和的最速下降方向,连续调整网络的权值和阀值,根据梯度下降法,权值矢量
的修正正比于当前位置上E(w,b)的梯度,对于第
个输出节点有
假设选择激活函数为
对激活函数求导,得到
那么接下来针对
有
其中有
同样对于
有
这就是著名的
学习规则,通过改变神经元之间的连接权值来减少系统实际输出和期望输出的误差,这个规
则又叫做Widrow-Hoff学习规则或者纠错学习规则。
上面是对隐含层和输出层之间的权值和输出层的阀值计算调整量,而针对输入层和隐含层和隐含层的阀值调
整量的计算更为复杂。假设
是输入层第k个节点和隐含层第i个节点之间的权值,那么有
其中有
这样对
学习规则理解更为深刻了吧。
有了上述公式,根据梯度下降法,那么对于隐含层和输出层之间的权值和阀值调整如下
而对于输入层和隐含层之间的权值和阀值调整同样有
至此BP神经网络的原理基本讲完。
5.BP神经网络的注意点
BP神经网络一般用于分类或者逼近问题。如果用于分类,则激活函数一般选用Sigmoid函数或者硬极限函
数,如果用于函数逼近,则输出层节点用线性函数,即
。
BP神经网络在训练数据时可以采用增量学习或者批量学习。
增量学习要求输入模式要有足够的随机性,对输入模式的噪声比较敏感,即对于剧烈变化的输入模式,训
练效果比较差,适合在线处理。批量学习不存在输入模式次序问题,稳定性好,但是只适合离线处理。
标准BP神经网络的缺陷:
(1)容易形成局部极小值而得不到全局最优值。
BP神经网络中极小值比较多,所以很容易陷入局部极小值,这就要求对初始权值和阀值有要求,要使
得初始权值和阀值随机性足够好,可以多次随机来实现。
(2)训练次数多使得学习效率低,收敛速度慢。
(3)隐含层的选取缺乏理论的指导。
(4)训练时学习新样本有遗忘旧样本的趋势。
BP算法的改进:
(1)增加动量项
引入动量项是为了加速算法收敛,即如下公式
动量因子a一般选取0.1-0.8。
(2)自适应调节学习率
(3)引入陡度因子
通常BP神经网络在训练之前会对数据归一化处理,即将数据映射到更小的区间内,比如[0,1]或[-1,1]。
6.BP神经网络的C++实现
BP神经网络的C++文件如下
BP.h:
#ifndef_BP_H_#define_BP_H_#include<vector>#defineLAYER3//三层神经网络#defineNUM10//每层的最多节点数#defineA30.0#defineB10.0//A和B是S型函数的参数#defineITERS1000//最大训练次数#defineETA_W0.0035//权值调整率#defineETA_B0.001//阀值调整率#defineERROR0.002//单个样本允许的误差#defineACCU0.005//每次迭代允许的误差#defineTypedouble#defineVectorstd::vectorstructData{Vector<Type>x;//输入数据Vector<Type>y;//输出数据};classBP{public:voidGetData(constVector<Data>);voidTrain();Vector<Type>ForeCast(constVector<Type>);private:voidInitNetWork();//初始化网络voidGetNums();//获取输入、输出和隐含层节点数voidForwardTransfer();//正向传播子过程voidReverseTransfer(int);//逆向传播子过程voidCalcDelta(int);//计算w和b的调整量voidUpdateNetWork();//更新权值和阀值TypeGetError(int);//计算单个样本的误差TypeGetAccu();//计算所有样本的精度TypeSigmoid(constType);//计算Sigmoid的值private:intin_num;//输入层节点数intou_num;//输出层节点数inthd_num;//隐含层节点数Vector<Data>data;//输入输出数据Typew[LAYER][NUM][NUM];//BP网络的权值Typeb[LAYER][NUM];//BP网络节点的阀值Typex[LAYER][NUM];//每个神经元的值经S型函数转化后的输出值,输入层就为原值Typed[LAYER][NUM];//记录delta学习规则中delta的值};#endif//_BP_H_
BP.cpp:
#include<string.h>#include<stdio.h>#include<math.h>#include<assert.h>#include"BP.h"//获取训练所有样本数据voidBP::GetData(constVector<Data>_data){data=_data;}//开始进行训练voidBP::Train(){printf("BegintotrainBPNetWork!\n");GetNums();InitNetWork();intnum=data.size();for(intiter=0;iter<=ITERS;iter++){for(intcnt=0;cnt<num;cnt++){//第一层输入节点赋值for(inti=0;i<in_num;i++)x[0][i]=data.at(cnt).x[i];while(1){ForwardTransfer();if(GetError(cnt)<ERROR)//如果误差比较小,则针对单个样本跳出循环break;ReverseTransfer(cnt);}}printf("Thisisthe%dthtrainningNetWork!\n",iter);Typeaccu=GetAccu();printf("AllSamplesAccuracyis%lf\n",accu);if(accu<ACCU)break;}printf("TheBPNetWorktrainEnd!\n");}//根据训练好的网络来预测输出值Vector<Type>BP::ForeCast(constVector<Type>data){intn=data.size();assert(n==in_num);for(inti=0;i<in_num;i++)x[0][i]=data[i];ForwardTransfer();Vector<Type>v;for(inti=0;i<ou_num;i++)v.push_back(x[2][i]);returnv;}//获取网络节点数voidBP::GetNums(){in_num=data[0].x.size();//获取输入层节点数ou_num=data[0].y.size();//获取输出层节点数hd_num=(int)sqrt((in_num+ou_num)*1.0)+5;//获取隐含层节点数if(hd_num>NUM)hd_num=NUM;//隐含层数目不能超过最大设置}//初始化网络voidBP::InitNetWork(){memset(w,0,sizeof(w));//初始化权值和阀值为0,也可以初始化随机值memset(b,0,sizeof(b));}//工作信号正向传递子过程voidBP::ForwardTransfer(){//计算隐含层各个节点的输出值for(intj=0;j<hd_num;j++){Typet=0;for(inti=0;i<in_num;i++)t+=w[1][i][j]*x[0][i];t+=b[1][j];x[1][j]=Sigmoid(t);}//计算输出层各节点的输出值for(intj=0;j<ou_num;j++){Typet=0;for(inti=0;i<hd_num;i++)t+=w[2][i][j]*x[1][i];t+=b[2][j];x[2][j]=Sigmoid(t);}}//计算单个样本的误差TypeBP::GetError(intcnt){Typeans=0;for(inti=0;i<ou_num;i++)ans+=0.5*(x[2][i]-data.at(cnt).y[i])*(x[2][i]-data.at(cnt).y[i]);returnans;}//误差信号反向传递子过程voidBP::ReverseTransfer(intcnt){CalcDelta(cnt);UpdateNetWork();}//计算所有样本的精度TypeBP::GetAccu(){Typeans=0;intnum=data.size();for(inti=0;i<num;i++){intm=data.at(i).x.size();for(intj=0;j<m;j++)x[0][j]=data.at(i).x[j];ForwardTransfer();intn=data.at(i).y.size();for(intj=0;j<n;j++)ans+=0.5*(x[2][j]-data.at(i).y[j])*(x[2][j]-data.at(i).y[j]);}returnans/num;}//计算调整量voidBP::CalcDelta(intcnt){//计算输出层的delta值for(inti=0;i<ou_num;i++)d[2][i]=(x[2][i]-data.at(cnt).y[i])*x[2][i]*(A-x[2][i])/(A*B);//计算隐含层的delta值for(inti=0;i<hd_num;i++){Typet=0;for(intj=0;j<ou_num;j++)t+=w[2][i][j]*d[2][j];d[1][i]=t*x[1][i]*(A-x[1][i])/(A*B);}}//根据计算出的调整量对BP网络进行调整voidBP::UpdateNetWork(){//隐含层和输出层之间权值和阀值调整for(inti=0;i<hd_num;i++){for(intj=0;j<ou_num;j++)w[2][i][j]-=ETA_W*d[2][j]*x[1][i];}for(inti=0;i<ou_num;i++)b[2][i]-=ETA_B*d[2][i];//输入层和隐含层之间权值和阀值调整for(inti=0;i<in_num;i++){for(intj=0;j<hd_num;j++)w[1][i][j]-=ETA_W*d[1][j]*x[0][i];}for(inti=0;i<hd_num;i++)b[1][i]-=ETA_B*d[1][i];}//计算Sigmoid函数的值TypeBP::Sigmoid(constTypex){returnA/(1+exp(-x/B));}
Test.cpp:
#include<iostream>#include<string.h>#include<stdio.h>#include"BP.h"usingnamespacestd;doublesample[41][4]={{0,0,0,0},{5,1,4,19.020},{5,3,3,14.150},{5,5,2,14.360},{5,3,3,14.150},{5,3,2,15.390},{5,3,2,15.390},{5,5,1,19.680},{5,1,2,21.060},{5,3,3,14.150},{5,5,4,12.680},{5,5,2,14.360},{5,1,3,19.610},{5,3,4,13.650},{5,5,5,12.430},{5,1,4,19.020},{5,1,4,19.020},{5,3,5,13.390},{5,5,4,12.680},{5,1,3,19.610},{5,3,2,15.390},{1,3,1,11.110},{1,5,2,6.521},{1,1,3,10.190},{1,3,4,6.043},{1,5,5,5.242},{1,5,3,5.724},{1,1,4,9.766},{1,3,5,5.870},{1,5,4,5.406},{1,1,3,10.190},{1,1,5,9.545},{1,3,4,6.043},{1,5,3,5.724},{1,1,2,11.250},{1,3,1,11.110},{1,3,3,6.380},{1,5,2,6.521},{1,1,1,16.000},{1,3,2,7.219},{1,5,3,5.724}};intmain(){Vector<Data>data;for(inti=0;i<41;i++){Datat;for(intj=0;j<3;j++)t.x.push_back(sample[i][j]);t.y.push_back(sample[i][3]);data.push_back(t);}BP*bp=newBP();bp->GetData(data);bp->Train();while(1){Vector<Type>in;for(inti=0;i<3;i++){Typev;scanf("%lf",&v);in.push_back(v);}Vector<Type>ou;ou=bp->ForeCast(in);printf("%lf\n",ou[0]);}return0;}
Makefile:
Test:BP.hBP.cppTest.cppg++BP.cppTest.cpp-oTestclean:rmTest
热门评论