BP神经网络。今天来讲BP神经网络,神经网络在机器学习中应用比较广泛,比如函数逼近,模式识别,分类,数据压缩,数据挖掘等领域。接下来介绍BP神经网络的原理及实现。
总结:简单的描述就是,输入层将刺激传递给隐藏层,隐藏层通过神经元之间联系的强度(权重)和传递规则(激活函数)将刺激传到输出层,输出层整理隐藏层处理后的刺激产生最终结果。若有正确的结果,那么将正确的结果(相当于知道了精确答案,与这个网络算出的结果相对比,来看这个网络是否能正确计算)和产生的结果进行比较,得到误差,再逆推对神经网中的链接权重进行反馈修正,从而来完成学习的过程,这就是BP(Back Propagation)神经网的反馈机制,也正是BP名字的来源。
Contents
1. BP神经网络的认识
2. 隐含层的选取
3. 正向传递子过程
4. 反向传递子过程
5. BP神经网络的注意点
6. BP神经网络的C++实现
1. BP神经网络的认识
BP(Back Propagation)神经网络分为两个过程
(1)工作信号正向传递子过程
(2)误差信号反向传递子过程
在BP神经网络中,单个样本有
个输入,有
个输出,在输入层和输出层之间通常还有若干个隐含层。实际
上,1989年Robert Hecht-Nielsen证明了对于任何闭区间内的一个连续函数都可以用一个隐含层的BP网
络来逼近,这就是万能逼近定理。所以一个三层的BP网络就可以完成任意的
维到
维的映射。即这三层分
别是输入层(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)增加动量项
引入动量项是为了加速算法收敛,即如下公式
动量因子
一般选取
。
(2)自适应调节学习率
(3)引入陡度因子
通常BP神经网络在训练之前会对数据归一化处理,即将数据映射到更小的区间内,比如[0,1]或[-1,1]。
6. BP神经网络的C++实现
BP神经网络的C++文件如下
BP.h:
view plaincopy
- #ifndef_BP_H_
- #define_BP_H_
-
- #include
-
- #defineLAYER3//三层神经网络
- #defineNUM10//每层的最多节点数
-
- #defineA30.0
- #defineB10.0//A和B是S型函数的参数
- #defineITERS1000//最大训练次数
- #defineETA_W0.0035//权值调整率
- #defineETA_B0.001//阀值调整率
- #defineERROR0.002//单个样本允许的误差
- #defineACCU0.005//每次迭代允许的误差
-
- #defineTypedouble
- #defineVectorstd::vector
-
- structData
- {
- Vectorx;//输入数据
- Vectory;//输出数据
- };
-
- classBP{
-
- public:
-
- voidGetData(constVector);
- voidTrain();
- VectorForeCast(constVector);
-
- private:
-
- voidInitNetWork();//初始化网络
- voidGetNums();//获取输入、输出和隐含层节点数
- voidForwardTransfer();//正向传播子过程
- voidReverseTransfer(int);//逆向传播子过程
- voidCalcDelta(int);//计算w和b的调整量
- voidUpdateNetWork();//更新权值和阀值
- TypeGetError(int);//计算单个样本的误差
- TypeGetAccu();//计算所有样本的精度
- TypeSigmoid(constType);//计算Sigmoid的值
-
- private:
- intin_num;//输入层节点数
- intou_num;//输出层节点数
- inthd_num;//隐含层节点数
-
- Vectordata;//输入输出数据
-
- Typew[LAYER][NUM][NUM];//BP网络的权值
- Typeb[LAYER][NUM];//BP网络节点的阀值
-
- Typex[LAYER][NUM];//每个神经元的值经S型函数转化后的输出值,输入层就为原值
- Typed[LAYER][NUM];//记录delta学习规则中delta的值
- };
-
- #endif//_BP_H_
BP.cpp:
view plaincopy
- #include
- #include
- #include
- #include
- #include"BP.h"
-
- //获取训练所有样本数据
- voidBP::GetData(constVector_data)
- {
- data=_data;
- }
-
- //开始进行训练
- voidBP::Train()
- {
- printf("BegintotrainBPNetWork!\n");
- GetNums();
- InitNetWork();
- intnum=data.size();
-
- for(intiter=0;iter<=ITERS;iter++)
- {
- for(intcnt=0;cnt<>
- {
- //第一层输入节点赋值
- for(inti=0;i<>
- x[0][i]=data.at(cnt).x[i];
-
- while(1)
- {
- ForwardTransfer();
- if(GetError(cnt) break;
- ReverseTransfer(cnt);
- }
- }
- printf("Thisisthe%dthtrainningNetWork!\n",iter);
-
- Typeaccu=GetAccu();
- printf("AllSamplesAccuracyis%lf\n",accu);
- if(accu<>
- }
- printf("TheBPNetWorktrainEnd!\n");
- }
-
- //根据训练好的网络来预测输出值
- VectorBP::ForeCast(constVectordata)
- {
- intn=data.size();
- assert(n==in_num);
- for(inti=0;i<>
- x[0][i]=data[i];
-
- ForwardTransfer();
- Vectorv;
- for(inti=0;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<>
- {
- Typet=0;
- for(inti=0;i<>
- t+=w[1][i][j]*x[0][i];
- t+=b[1][j];
- x[1][j]=Sigmoid(t);
- }
-
- //计算输出层各节点的输出值
- for(intj=0;j<>
- {
- Typet=0;
- for(inti=0;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<>
- 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<>
- {
- intm=data.at(i).x.size();
- for(intj=0;j<>
- x[0][j]=data.at(i).x[j];
- ForwardTransfer();
- intn=data.at(i).y.size();
- for(intj=0;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<>
- 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<>
- {
- Typet=0;
- for(intj=0;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<>
- {
- for(intj=0;j<>
- w[2][i][j]-=ETA_W*d[2][j]*x[1][i];
- }
- for(inti=0;i<>
- b[2][i]-=ETA_B*d[2][i];
-
- //输入层和隐含层之间权值和阀值调整
- for(inti=0;i<>
- {
- for(intj=0;j<>
- w[1][i][j]-=ETA_W*d[1][j]*x[0][i];
- }
- for(inti=0;i<>
- b[1][i]-=ETA_B*d[1][i];
- }
-
- //计算Sigmoid函数的值
- TypeBP::Sigmoid(constTypex)
- {
- returnA/(1+exp(-x/B));
- }
Test.cpp:
view plaincopy
- #include
- #include
- #include
-
- #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()
- {
- Vectordata;
- 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)
- {
- Vectorin;
- for(inti=0;i<3;i++)
- {
- Typev;
- scanf("%lf",&v);
- in.push_back(v);
- }
- Vectorou;
- ou=bp->ForeCast(in);
- printf("%lf\n",ou[0]);
- }
- return0;
- }
Makefile:
view plaincopy
- Test:BP.hBP.cppTest.cpp
- g++BP.cppTest.cpp-oTest
-
- clean:
- rmTest