BP神经网络算法

今天来讲BP神经网络,神经网络在机器学习中应用比较广泛,比如函数逼近,模式识别,分类,数据压缩,数据挖掘等领域。接下来介绍BP神经网络的原理及实现。

Contents

1. BP神经网络的认识

2. 隐含层的选取

3. 正向传递子过程

4. 反向传递子过程

5. BP神经网络的注意点

6. BP神经网络的C++实现

1. BP神经网络的认识

BP(Back Propagation)神经网络分为两个过程

(1)工作信号正向传递子过程

(2)误差信号反向传递子过程

在BP神经网络中,单个样本有m个输入,有n个输出,在输入层和输出层之间通常还有若干个隐含层。实际上,1989年Robert Hecht-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> #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

时间: 2025-01-21 14:56:35

BP神经网络算法的相关文章

BP神经网络算法与实践概述

神经网络曾经很火,有过一段低迷期,现在因为深度学习的原因继续火起来了.神经网络有很多种:前向传输网络.反向传输网络.递归神经网络.卷积神经网络等.本文介绍基本的反向传输神经网络(Backpropagation 简称BP),主要讲述算法的基本流程和自己在训练BP神经网络的一些经验. BP神经网络的结构p>神经网络就是模拟人的大脑的神经单元的工作方式,但进行了很大的简化,神经网络由很多神经网络层构成,而每一层又由许多单元组成,第一层叫输入层,最后一层叫输出层,中间的各层叫隐藏层,在BP神经网络中,只

/*c语言实现*/遗传算法改进BP神经网络原理和算法实现怎么弄?

问题描述 /*c语言实现*/遗传算法改进BP神经网络原理和算法实现怎么弄? 我现在在研究遗传算法改进BP神经网络,大概明白原理,但算法程序还有很多问题,求大神们教教我具体怎么把GA和BP结合,算法如何实现,给出代码或伪代码行吗? 解决方案 一般都是matlab去写,然后用C调用,用C语言得写到什么时候. 参考:http://www.docin.com/p-788836632.html

elasticsearch-请教:BP神经网络分词算法怎么整合到Elasticsearch中?急求解

问题描述 请教:BP神经网络分词算法怎么整合到Elasticsearch中?急求解 我想在Elasticsearch中使用BP神经网络分词算法,应该怎么样的步骤把算法嵌入到Elasticsearch的代码中,请大侠们指教

卷积神经网络算法的简单实现

前言 从理解卷积神经到实现它,前后花了一个月时间,现在也还有一些地方没有理解透彻,CNN还是有一定难度的,不是看哪个的博客和一两篇论文就明白了,主要还是靠自己去专研,阅读推荐列表在末尾的参考文献.目前实现的CNN在MINIT数据集上效果还不错,但是还有一些bug,因为最近比较忙,先把之前做的总结一下,以后再继续优化. 卷积神经网络CNN是Deep Learning的一个重要算法,在很多应用上表现出卓越的效果,[1]中对比多重算法在文档字符识别的效果,结论是CNN优于其他所有的算法.CNN在手写体

神经网络算法可能存在的问题?

问题描述 神经网络算法可能存在的问题? 关于神经网络算法梯度下降法和随机梯度下降法可能存在的问题: (小白刚注册第一次发帖,原谅没有多少金币...还请各路大神多指点,非常感谢!!!) 用梯度下降法修改权值,是当训练集里所有的样本都输入完之后,将所有样本的误差加和来修改权值.总觉得哪地方不对,这样是不是放大了误差对权值的作用?得到的网络不仅仅是不容易找到全局最小值的问题,而且是很不精确?由于不是每个样本输入后都进行一次权值修改,相对于随机梯度下降法优点是计算速度快? 随机梯度下降法每一个样本的输入

神经网络算法能拟合所有函数吗?

问题描述 神经网络算法能拟合所有函数吗? 单层的神经网络几乎等价于N元一次方程多层的神经网络有什么意义?能拟合所有函数吗? 如果能能介绍一下相关的证明过程/研究成果吗?(证明它能拟合所有函数)如果不能是不是说神经网络是基本上没用的 解决方案 http://wenku.baidu.com/link?url=fBV2jF23VDKQVDJ_D_RJyHxhA37ZwqlIoheY71MM9GK0LbknOxlid1zK47_Tm6AtfXmCLzZuw23w3O4unjJCxl6V6Vlu8HKSx

计算机视觉怎么给图像分类?KNN、SVM、BP神经网络、CNN、迁移学习供你选(附开源代码)

图像分类问题就是从固定的一组分类中,给输入图像分配标签的任务.这是计算机视觉的核心问题之一,尽管它看似简单,却在实际生活中有着各种各样的应用. 传统方式:功能描述和检测. 也许这种方法对于一些样本任务来说是比较好用的,但实际情况却要复杂得多. 因此,我们将使用机器学习来为每个类别提供许多示例,然后开发学习算法来查看这些示例,并了解每个类的视觉外观,而不是试图直接在代码中指定每一个大家感兴趣的类别是什么样的.  然而,图像分类问题就是一个非常复杂的工作,它总是借用诸如卷积神经网络(CNN)这样的深

backp-用java写可以识别数字的BP神经网络

问题描述 用java写可以识别数字的BP神经网络 现在学校要求写一个可以识别数字的神经网络,要求最好用bp, 也可以用别的算法,语言可以任意,最好是java.数据事uci的optical recognition of handwritten digits 想知道有没有指导怎么一步一步建立的的教程,不知道如何建立这样的网络,代码里需要几层方法? 解决方案 参考:http://download.csdn.net/download/u012828028/6546885 解决方案二: BP神经网络应用于

极简反传(BP)神经网络

   一.两层神经网络(感知机) import numpy as np '''极简两层反传(BP)神经网络''' # 样本 X = np.array([[0,0,1],[0,1,1],[1,0,1],[1,1,1]]) y = np.array([0,0,1,1]) # 权值矩阵 初始化 Wi = 2 * np.random.random(3) - 1 for iter in range(10000): # 前向传播,计算误差 li = X lo = 1 / (1 + np.exp(-np.do