【转载】BP神经网络
原文地址:http://blog.csdn.net/acdreamers/article/details/44657439
今天来讲BP神经网络,神经网络在机器学习中应用比较广泛,比如函数逼近,模式识别,分类,数据压缩,数据
挖掘等领域。接下来介绍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:
- #ifndef _BP_H_
- #define _BP_H_
- #include <vector>
- #define LAYER 3 //三层神经网络
- #define NUM 10 //每层的最多节点数
- #define A 30.0
- #define B 10.0 //A和B是S型函数的参数
- #define ITERS 1000 //最大训练次数
- #define ETA_W 0.0035 //权值调整率
- #define ETA_B 0.001 //阀值调整率
- #define ERROR 0.002 //单个样本允许的误差
- #define ACCU 0.005 //每次迭代允许的误差
- #define Type double
- #define Vector std::vector
- struct Data
- {
- Vector<Type> x; //输入数据
- Vector<Type> y; //输出数据
- };
- class BP{
- public:
- void GetData(const Vector<Data>);
- void Train();
- Vector<Type> ForeCast(const Vector<Type>);
- private:
- void InitNetWork(); //初始化网络
- void GetNums(); //获取输入、输出和隐含层节点数
- void ForwardTransfer(); //正向传播子过程
- void ReverseTransfer(int); //逆向传播子过程
- void CalcDelta(int); //计算w和b的调整量
- void UpdateNetWork(); //更新权值和阀值
- Type GetError(int); //计算单个样本的误差
- Type GetAccu(); //计算所有样本的精度
- Type Sigmoid(const Type); //计算Sigmoid的值
- private:
- int in_num; //输入层节点数
- int ou_num; //输出层节点数
- int hd_num; //隐含层节点数
- Vector<Data> data; //输入输出数据
- Type w[LAYER][NUM][NUM]; //BP网络的权值
- Type b[LAYER][NUM]; //BP网络节点的阀值
- Type x[LAYER][NUM]; //每个神经元的值经S型函数转化后的输出值,输入层就为原值
- Type d[LAYER][NUM]; //记录delta学习规则中delta的值
- };
- #endif //_BP_H_
BP.cpp:
- #include <string.h>
- #include <stdio.h>
- #include <math.h>
- #include <assert.h>
- #include "BP.h"
- //获取训练所有样本数据
- void BP::GetData(const Vector<Data> _data)
- {
- data = _data;
- }
- //开始进行训练
- void BP::Train()
- {
- printf("Begin to train BP NetWork!\n");
- GetNums();
- InitNetWork();
- int num = data.size();
- for(int iter = 0; iter <= ITERS; iter++)
- {
- for(int cnt = 0; cnt < num; cnt++)
- {
- //第一层输入节点赋值
- for(int i = 0; i < in_num; i++)
- x[0][i] = data.at(cnt).x[i];
- while(1)
- {
- ForwardTransfer();
- if(GetError(cnt) < ERROR) //如果误差比较小,则针对单个样本跳出循环
- break;
- ReverseTransfer(cnt);
- }
- }
- printf("This is the %d th trainning NetWork !\n", iter);
- Type accu = GetAccu();
- printf("All Samples Accuracy is %lf\n", accu);
- if(accu < ACCU) break;
- }
- printf("The BP NetWork train End!\n");
- }
- //根据训练好的网络来预测输出值
- Vector<Type> BP::ForeCast(const Vector<Type> data)
- {
- int n = data.size();
- assert(n == in_num);
- for(int i = 0; i < in_num; i++)
- x[0][i] = data[i];
- ForwardTransfer();
- Vector<Type> v;
- for(int i = 0; i < ou_num; i++)
- v.push_back(x[2][i]);
- return v;
- }
- //获取网络节点数
- void BP::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; //隐含层数目不能超过最大设置
- }
- //初始化网络
- void BP::InitNetWork()
- {
- memset(w, 0, sizeof(w)); //初始化权值和阀值为0,也可以初始化随机值
- memset(b, 0, sizeof(b));
- }
- //工作信号正向传递子过程
- void BP::ForwardTransfer()
- {
- //计算隐含层各个节点的输出值
- for(int j = 0; j < hd_num; j++)
- {
- Type t = 0;
- for(int i = 0; i < in_num; i++)
- t += w[1][i][j] * x[0][i];
- t += b[1][j];
- x[1][j] = Sigmoid(t);
- }
- //计算输出层各节点的输出值
- for(int j = 0; j < ou_num; j++)
- {
- Type t = 0;
- for(int i = 0; i < hd_num; i++)
- t += w[2][i][j] * x[1][i];
- t += b[2][j];
- x[2][j] = Sigmoid(t);
- }
- }
- //计算单个样本的误差
- Type BP::GetError(int cnt)
- {
- Type ans = 0;
- for(int i = 0; i < ou_num; i++)
- ans += 0.5 * (x[2][i] - data.at(cnt).y[i]) * (x[2][i] - data.at(cnt).y[i]);
- return ans;
- }
- //误差信号反向传递子过程
- void BP::ReverseTransfer(int cnt)
- {
- CalcDelta(cnt);
- UpdateNetWork();
- }
- //计算所有样本的精度
- Type BP::GetAccu()
- {
- Type ans = 0;
- int num = data.size();
- for(int i = 0; i < num; i++)
- {
- int m = data.at(i).x.size();
- for(int j = 0; j < m; j++)
- x[0][j] = data.at(i).x[j];
- ForwardTransfer();
- int n = data.at(i).y.size();
- for(int j = 0; j < n; j++)
- ans += 0.5 * (x[2][j] - data.at(i).y[j]) * (x[2][j] - data.at(i).y[j]);
- }
- return ans / num;
- }
- //计算调整量
- void BP::CalcDelta(int cnt)
- {
- //计算输出层的delta值
- for(int i = 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(int i = 0; i < hd_num; i++)
- {
- Type t = 0;
- for(int j = 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网络进行调整
- void BP::UpdateNetWork()
- {
- //隐含层和输出层之间权值和阀值调整
- for(int i = 0; i < hd_num; i++)
- {
- for(int j = 0; j < ou_num; j++)
- w[2][i][j] -= ETA_W * d[2][j] * x[1][i];
- }
- for(int i = 0; i < ou_num; i++)
- b[2][i] -= ETA_B * d[2][i];
- //输入层和隐含层之间权值和阀值调整
- for(int i = 0; i < in_num; i++)
- {
- for(int j = 0; j < hd_num; j++)
- w[1][i][j] -= ETA_W * d[1][j] * x[0][i];
- }
- for(int i = 0; i < hd_num; i++)
- b[1][i] -= ETA_B * d[1][i];
- }
- //计算Sigmoid函数的值
- Type BP::Sigmoid(const Type x)
- {
- return A / (1 + exp(-x / B));
- }
Test.cpp:
- #include <iostream>
- #include <string.h>
- #include <stdio.h>
- #include "BP.h"
- using namespace std;
- double sample[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}
- };
- int main()
- {
- Vector<Data> data;
- for(int i = 0; i < 41; i++)
- {
- Data t;
- for(int j = 0; j < 3; j++)
- t.x.push_back(sample[i][j]);
- t.y.push_back(sample[i][3]);
- data.push_back(t);
- }
- BP *bp = new BP();
- bp->GetData(data);
- bp->Train();
- while(1)
- {
- Vector<Type> in;
- for(int i = 0; i < 3; i++)
- {
- Type v;
- scanf("%lf", &v);
- in.push_back(v);
- }
- Vector<Type> ou;
- ou = bp->ForeCast(in);
- printf("%lf\n", ou[0]);
- }
- return 0;
- }
Makefile:
- Test : BP.h BP.cpp Test.cpp
- g++ BP.cpp Test.cpp -o Test
- clean:
- rm Test
【转载】BP神经网络的更多相关文章
- [转载] bp神经网络推导
https://blog.csdn.net/fanxin_i/article/details/80212906 看了这么多就这个推的清楚,转嘞
- 转载——关于bp神经网络
一.BP神经网络的概念 BP神经网络是一种多层的前馈神经网络,其主要的特点是:信号是前向传播的,而误差是反向传播的.具体来说,对于如下的只含一个隐层的神经网络模型: (三层BP神经网络模型) ...
- BP神经网络—java实现(转载)
神经网络的结构 神经网络的网络结构由输入层,隐含层,输出层组成.隐含层的个数+输出层的个数=神经网络的层数,也就是说神经网络的层数不包括输入层.下面是一个三层的神经网络,包含了两层隐含层,一个输出层. ...
- 数据挖掘系列(9)——BP神经网络算法与实践
神经网络曾经很火,有过一段低迷期,现在因为深度学习的原因继续火起来了.神经网络有很多种:前向传输网络.反向传输网络.递归神经网络.卷积神经网络等.本文介绍基本的反向传输神经网络(Backpropaga ...
- bp神经网络及matlab实现
本文主要内容包含: (1) 介绍神经网络基本原理,(2) AForge.NET实现前向神经网络的方法,(3) Matlab实现前向神经网络的方法 . 第0节.引例 本文以Fisher的Iris数据集 ...
- RBF神经网络和BP神经网络的关系
作者:李瞬生链接:https://www.zhihu.com/question/44328472/answer/128973724来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注 ...
- BP神经网络的公式推导
如果感觉自己看不懂,那就看看我博客的梯度下降法,博文最后的感知机也算最简单的BP神经网络吧,用的也是反馈(w,b):典型梯度下降法 BP网络的结构 BP网络的结构如下图所示,分为输入层(Input), ...
- BP神经网络的数学原理及其算法实现
什么是BP网络 BP网络的数学原理 BP网络算法实现 转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/44514073 上一篇 ...
- BP神经网络-- 基本模型
转载:http://www.cnblogs.com/jzhlin/archive/2012/07/28/bp.html BP 神经网络中的 BP 为 Back Propagation 的简写,最早它 ...
随机推荐
- Good Time 冲刺 六
一.今日完成任务情况 第六天 日期:2018.6.19 王怡镔:今天完善了页面,对部分不足进行改进. 于鑫宇:对界面进行完善. 胡雅馨:今天完成前端页面,并改善后端,完善项目. 黄 鹤:做完最后的打卡 ...
- 深入理解JAVA集合系列三:HashMap的死循环解读
由于在公司项目中偶尔会遇到HashMap死循环造成CPU100%,重启后问题消失,隔一段时间又会反复出现.今天在这里来仔细剖析下多线程情况下HashMap所带来的问题: 1.多线程put操作后,get ...
- Maven解读:强大的依赖体系
Github地址:https://github.com/zwjlpeng/Maven_Detail Maven最大的好处就是能够很方便的管理项目对第三方Jar包的依赖,只需在Pom文件中添加几行配置文 ...
- call()方法和apply()方法
最近又遇到了JacvaScript中的call()方法和apply()方法,而在某些时候这两个方法还确实是十分重要的,那么就让我总结这两个方法的使用和区别吧. 1. 每个函数都包含两个非继承而来的方法 ...
- angular 神坑 ,回调函数无法被监视
原方法,使用一个confirm 点ok然后回调,结果 界面无法刷新,搜索了下 是因为$scope没有监视model,必须使用apply方法 $scope.SelectedRow=row; negAle ...
- MDN & IRC
MDN IRC MDN IRC xgqfrms https://developer.mozilla.org/en-US/docs/Mozilla/QA/Getting_Started_with_IRC ...
- js & click copy to clipboard
js & click copy to clipboard https://www.cnblogs.com/xgqfrms/p/9999061.html https://www.cnblogs. ...
- hdu-3308 LCIS (线段树区间合并)
LCIS Time Limit: 6000/2000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submis ...
- std::string 赋值为nullptr引起程序崩溃
一个错误排查两天,std::string赋初值时最好为"", 如果赋初值为nullptr,因为std::string不能和nullptr作比较,所以后面用的时候会引起崩溃. 佩服我 ...
- H5页面遮罩弹框下层还能滚动的问题
在页面上显示一个遮罩层,这是非常常见的操作,在遮罩层上操作,下层也会默认跟随手指滚动 此处就是要在显示遮罩的时候禁止下层滚动. 首先设置一个全局变量 var canScroll=false; 页面初始 ...