基于C++实现简单的BP神经网络算法
2017-01-08 13:39
429 查看
最近在学习神经网络算法,用C语言写了一个简单的BP神经网络算法,实现了简单的数据分类。
这里主要参考了http://blog.csdn.net/acdreamers/article/details/44657439,
http://blog.csdn.net/bryan__/article/details/51288701这两篇博客,收获良多。
有导师学习算法:BP算法
采用BP学习算法的前馈型神经网络通常被称为BP网络。
BP网络具有很强的非线性映射能力,一个3层BP神经网络能够实现对任意非线性函数进行逼近(根据Kolrnogorov定理)。
BP(Back Propagation)神经网络分为两个过程
(1)工作信号正向传递子过程;
(2)误差信号反向传递子过程。
在BP神经网络中,单个样本有
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/6ff07b3ad84c4423976ba76e6ab14a92.png)
个输入,有
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/3c65b0b3a462824b4b9cf079a53dc3ba.png)
个输出,在输入层和输出层之间通常还有若干个隐含层。实际
上,1989年Robert Hecht-Nielsen证明了对于任何闭区间内的一个连续函数都可以用一个隐含层的BP网
络来逼近,这就是万能逼近定理。所以一个三层的BP网络就可以完成任意的
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/94655affd39b8da55d3ecd497c21ae34.png)
维到
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/05d582ed8e2bde38e73e0f1f10d8098e.png)
维的映射。即这三层分
别是输入层(I),隐含层(H),输出层(O)。
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/caf16499373b0190ec914711406957a2)
算法计算过程:输入层开始,从左往右计算,逐层往前直到输出层产生结果。如果结果值和目标值有差距,再从右往左算,逐层向后计算每个节点的误差,并且调整每个节点的所有权重,反向到达输入层后,又重新向前计算,重复迭代以上步骤,直到所有权重参数收敛到一个合理值。由于计算机程序求解方程参数和数学求法不一样,一般是先随机选取参数,然后不断调整参数减少误差直到逼近正确值,所以大部分的
f54b
机器学习都是在不断迭代训练,下面我们从程序上详细看看该过程实现就清楚了。
隐含层的选取
在BP神经网络中,输入层和输出层的节点个数都是确定的,而隐含层节点个数不确定,那么应该设置为多少
才合适呢?实际上,隐含层节点个数的多少对神经网络的性能是有影响的,有一个经验公式可以确定隐含层
节点数目,如下
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/062938ec240e16c6513b27bca2ef530e.png)
其中
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/0e46b623428fda5b03cf792b06137a01.png)
为隐含层节点数目,
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/e1926790c3bc6a76c86648f7628e2a35.png)
为输入层节点数目,
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/ca329c5bf54ebe20e9e8f9cfa0c2b492.png)
为输出层节点数目,
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/3b6c0e94dce16e2dcd896815f06943c3.png)
为
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/2a622d7cbf852f3806a8471d3e983ec7.png)
之间的调节常数。
BP神经网络初始化主要是对学习率,初始权重,动量系数等参数的设置。
前向计算
按照公式一层一层的计算隐层神经元和输出层神经元的输入和输出。这里采用S函数1/(1+Math.exp(-z))将每个节点的值统一到0-1之间,再逐层向前计算直到输出层。
反向传播
反向传播时,把误差信号按原来正向传播的通路反向传回,并对每个隐层的各个神经元的权系数进行修改,以望误差信号趋向最小。在BP神经网络中,误差信号反向传递子过程比较复杂,它是基于Widrow-Hoff学习规则的。
假设输出层的所有结果为
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/6ea7efa97cdda90077d889657306ba1e.png)
,误差函数如下
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/f1db61b64fe9c7aed0fd38cfdfcdcc80.png)
而BP神经网络的主要目的是反复修正权值和阀值,使得误差函数值达到最小。再根据最小化误差去调整权重。
这里采用动量法调整,将上一次调整的经验考虑进来,避免陷入局部最小值,下面的k代表迭代次数,mobp为动量项,rate为学习步长:
Δw(k+1) = mobp*Δw(k)+rate*Err*Layer
反向计算过程:先将位置定位到倒数第二层(也就是最后一层隐含层)上,然后逐层反向调整,根据L+1层算好的误差来调整L层的权重,同时计算好L层的误差,用于下一次循环到L-1层时计算权重,以此循环下去直到倒数第一层(输入层)结束。
以下是用C语言写的BP神经网络算法程序(这里程序没有优化,写的比较初级,仅当参考,后续补上优化版本):
#include "stdio.h"
#include "stdlib.h"
#include <math.h>
int main(void)
{
int layernum[3]={2,10,2};//神经网络各层节点数
double rate=0.15;//学习率
double mobp=0.8;//动量系数
double data[4][2]={{1,2},{2,2},{1,1},{2,1}};//样本数据
double target[4][2]={{1,0},{0,1},{0,1},{1,0}};//初始目标分类
double layer_weight1[3][10]={{0},{0},{0}};//各层节点初始权值,0
double layer_weight2[11][2]={{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}};
double layer_delta1[3][10]={{0},{0},{0}};//各层节点初始权重动量,0
double layer_delta2[11][2]={{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}};
//神经网络各层节点以及各节点误差,初始值0
double layer1[2]={0,0};
double layer2[10]={0,0,0,0,0,0,0,0,0,0};
double layer3[2]={0,0};
double layerErr1[2]={0,0};
double layerErr2[10]={0,0,0,0,0,0,0,0,0,0};
double layerErr3[2]={0,0};
for( int L=0;L<2;L++)
{
if(L==0)
{
for(int j=0;j<layernum[L]+1;j++)
{
for(int i=0;i<layernum[L+1];i++)
{
layer_weight1[j][i]=rand()%2;//随机初始化权值
}
}
}
if(L==1)
{
for(int j=0;j<layernum[L]+1;j++)
{
for(int i=0;i<layernum[L+1];i++)
{
layer_weight2[j][i]=rand()%2;//这里各层分开写,后续优化可以写一起
}
}
}
}
for(int n=0;n<5000;n++)//迭代训练5000次
{
for(int i=0;i<4;i++)//样本数据量,这里为4
{
for(int j=0;j<10;j++)
{
double z=layer_weight1[2][j];
for(int b=0;b<2;b++)
{
layer1=1==1?data[i][b]:layer1[b];
z+=layer_weight1[b][j]*layer1[b];
}
layer2[j]=1/(1+exp(-z));//逐层向前计算输出(这里又分开写
}
for(int c=0;c<2;c++)
{
double x=layer_weight2[10][c];
for(int d=0;d<10;d++)
{
layer2[d]=2==1?data[i][d]:layer2[d];
x+=layer_weight2[d][c]*layer2[d];
}
layer3[c]=1/(1+exp(-x));//逐层向前计算输出
}
//逐层反向计算误差并修改权重
for(int j=0;j<2;j++)
{
layerErr3[j]=layer3[j]*(1-layer3[j])*(target[i][j]-layer3[j]);
}
for(int j=0;j<10;j++)
{
double z=0.0;
for(int i=0;i<2;i++)
{
z=z+1>0?layerErr3[i]*layer_weight2[j][i]:0;
//隐含层动量调整
layer_delta2[j][i]=mobp*layer_delta2[j][i]+rate*layerErr3[i]*layer2[j];
layer_weight2[j][i]+=layer_delta2[j][i];//隐含层权重调整
if(j==9)
{
//截距动量调整
layer_delta2[j+1][i]=mobp*layer_delta2[j+1][i]+rate*layerErr3[i];
//截距权重调整
layer_weight2[j+1][i]+=layer_delta2[j+1][i];
}
}
//记录误差
layerErr2[j]=z*layer2[j]*(1-layer2[j]);
}
for(int j=0;j<2;j++)
{
double z=0.0;
for(int i=0;i<10;i++)
{
z=z+1>0?layerErr2[i]*layer_weight1[j][i]:0;
layer_delta1[j][i]=mobp*layer_delta1[j][i]+rate*layerErr2[i]*layer1[j];
layer_weight1[j][i]+=layer_delta1[j][i];
if(j==1)
{
layer_delta1[j+1][i]=mobp*layer_delta1[j+1][i]+rate*layerErr2[i];
layer_weight1[j+1][i]+=layer_delta1[j+1][i];
}
}
layerErr1[j]=z*layer1[j]*(1-layer1[j]);
}
}
}
double x[2]={1,1};//预测数据,测试
//测试数据训练
for(int j=0;j<10;j++)
{
double z=layer_weight1[2][j];
for(int i=0;i<2;i++)
{
layer1[i]=1==1?x[i]:layer1[i];
z+=layer_weight1[i][j]*layer1[i];
}
layer2[j]=1/(1+exp(-z));
}
for(int j=0;j<2;j++)
{
double z=layer_weight2[10][j];
for(int i=0;i<10;i++)
{
layer2[i]=2==1?x[i]:layer2[i];
z+=layer_weight2[i][j]*layer2[i];
}
layer3[j]=1/(1+exp(-z));
printf("%f\n",layer3[j]);//输出分类结果
}
system("pause");
return 0;
}
输出结果:
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/dbad291c6b0de80369c5a8c031885409)
这里可以用二维坐标来直观表示分类结果,
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/e7547530498388f23b970f9b46cf0d77)
补充:BP算法的改进:
(1)增加动量项
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/3c3f5570c1b9f19fe12d1d7cd016a68a.png)
动量因子
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/1ee5733318c660e4e3ae3e6b62fb735b.png)
一般选取
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/ac427e5450eed76777e7785530dd7406.png)
。
(2)自适应调节学习率
(3)引入陡度因子
通常BP神经网络在训练之前会对数据[b]归一化处理,即将数据映射到更小的区间内,比如[0,1]或[-1,1]。
这里主要参考了http://blog.csdn.net/acdreamers/article/details/44657439,
http://blog.csdn.net/bryan__/article/details/51288701这两篇博客,收获良多。
有导师学习算法:BP算法
采用BP学习算法的前馈型神经网络通常被称为BP网络。
BP网络具有很强的非线性映射能力,一个3层BP神经网络能够实现对任意非线性函数进行逼近(根据Kolrnogorov定理)。
BP(Back Propagation)神经网络分为两个过程
(1)工作信号正向传递子过程;
(2)误差信号反向传递子过程。
在BP神经网络中,单个样本有
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/6ff07b3ad84c4423976ba76e6ab14a92.png)
个输入,有
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/3c65b0b3a462824b4b9cf079a53dc3ba.png)
个输出,在输入层和输出层之间通常还有若干个隐含层。实际
上,1989年Robert Hecht-Nielsen证明了对于任何闭区间内的一个连续函数都可以用一个隐含层的BP网
络来逼近,这就是万能逼近定理。所以一个三层的BP网络就可以完成任意的
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/94655affd39b8da55d3ecd497c21ae34.png)
维到
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/05d582ed8e2bde38e73e0f1f10d8098e.png)
维的映射。即这三层分
别是输入层(I),隐含层(H),输出层(O)。
算法计算过程:输入层开始,从左往右计算,逐层往前直到输出层产生结果。如果结果值和目标值有差距,再从右往左算,逐层向后计算每个节点的误差,并且调整每个节点的所有权重,反向到达输入层后,又重新向前计算,重复迭代以上步骤,直到所有权重参数收敛到一个合理值。由于计算机程序求解方程参数和数学求法不一样,一般是先随机选取参数,然后不断调整参数减少误差直到逼近正确值,所以大部分的
f54b
机器学习都是在不断迭代训练,下面我们从程序上详细看看该过程实现就清楚了。
隐含层的选取
在BP神经网络中,输入层和输出层的节点个数都是确定的,而隐含层节点个数不确定,那么应该设置为多少
才合适呢?实际上,隐含层节点个数的多少对神经网络的性能是有影响的,有一个经验公式可以确定隐含层
节点数目,如下
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/062938ec240e16c6513b27bca2ef530e.png)
其中
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/0e46b623428fda5b03cf792b06137a01.png)
为隐含层节点数目,
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/e1926790c3bc6a76c86648f7628e2a35.png)
为输入层节点数目,
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/ca329c5bf54ebe20e9e8f9cfa0c2b492.png)
为输出层节点数目,
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/3b6c0e94dce16e2dcd896815f06943c3.png)
为
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/2a622d7cbf852f3806a8471d3e983ec7.png)
之间的调节常数。
初始化
BP神经网络初始化主要是对学习率,初始权重,动量系数等参数的设置。前向计算
按照公式一层一层的计算隐层神经元和输出层神经元的输入和输出。这里采用S函数1/(1+Math.exp(-z))将每个节点的值统一到0-1之间,再逐层向前计算直到输出层。
反向传播
反向传播时,把误差信号按原来正向传播的通路反向传回,并对每个隐层的各个神经元的权系数进行修改,以望误差信号趋向最小。在BP神经网络中,误差信号反向传递子过程比较复杂,它是基于Widrow-Hoff学习规则的。
假设输出层的所有结果为
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/6ea7efa97cdda90077d889657306ba1e.png)
,误差函数如下
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/f1db61b64fe9c7aed0fd38cfdfcdcc80.png)
而BP神经网络的主要目的是反复修正权值和阀值,使得误差函数值达到最小。再根据最小化误差去调整权重。
这里采用动量法调整,将上一次调整的经验考虑进来,避免陷入局部最小值,下面的k代表迭代次数,mobp为动量项,rate为学习步长:
Δw(k+1) = mobp*Δw(k)+rate*Err*Layer
反向计算过程:先将位置定位到倒数第二层(也就是最后一层隐含层)上,然后逐层反向调整,根据L+1层算好的误差来调整L层的权重,同时计算好L层的误差,用于下一次循环到L-1层时计算权重,以此循环下去直到倒数第一层(输入层)结束。
以下是用C语言写的BP神经网络算法程序(这里程序没有优化,写的比较初级,仅当参考,后续补上优化版本):
#include "stdio.h"
#include "stdlib.h"
#include <math.h>
int main(void)
{
int layernum[3]={2,10,2};//神经网络各层节点数
double rate=0.15;//学习率
double mobp=0.8;//动量系数
double data[4][2]={{1,2},{2,2},{1,1},{2,1}};//样本数据
double target[4][2]={{1,0},{0,1},{0,1},{1,0}};//初始目标分类
double layer_weight1[3][10]={{0},{0},{0}};//各层节点初始权值,0
double layer_weight2[11][2]={{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}};
double layer_delta1[3][10]={{0},{0},{0}};//各层节点初始权重动量,0
double layer_delta2[11][2]={{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}};
//神经网络各层节点以及各节点误差,初始值0
double layer1[2]={0,0};
double layer2[10]={0,0,0,0,0,0,0,0,0,0};
double layer3[2]={0,0};
double layerErr1[2]={0,0};
double layerErr2[10]={0,0,0,0,0,0,0,0,0,0};
double layerErr3[2]={0,0};
for( int L=0;L<2;L++)
{
if(L==0)
{
for(int j=0;j<layernum[L]+1;j++)
{
for(int i=0;i<layernum[L+1];i++)
{
layer_weight1[j][i]=rand()%2;//随机初始化权值
}
}
}
if(L==1)
{
for(int j=0;j<layernum[L]+1;j++)
{
for(int i=0;i<layernum[L+1];i++)
{
layer_weight2[j][i]=rand()%2;//这里各层分开写,后续优化可以写一起
}
}
}
}
for(int n=0;n<5000;n++)//迭代训练5000次
{
for(int i=0;i<4;i++)//样本数据量,这里为4
{
for(int j=0;j<10;j++)
{
double z=layer_weight1[2][j];
for(int b=0;b<2;b++)
{
layer1=1==1?data[i][b]:layer1[b];
z+=layer_weight1[b][j]*layer1[b];
}
layer2[j]=1/(1+exp(-z));//逐层向前计算输出(这里又分开写
}
for(int c=0;c<2;c++)
{
double x=layer_weight2[10][c];
for(int d=0;d<10;d++)
{
layer2[d]=2==1?data[i][d]:layer2[d];
x+=layer_weight2[d][c]*layer2[d];
}
layer3[c]=1/(1+exp(-x));//逐层向前计算输出
}
//逐层反向计算误差并修改权重
for(int j=0;j<2;j++)
{
layerErr3[j]=layer3[j]*(1-layer3[j])*(target[i][j]-layer3[j]);
}
for(int j=0;j<10;j++)
{
double z=0.0;
for(int i=0;i<2;i++)
{
z=z+1>0?layerErr3[i]*layer_weight2[j][i]:0;
//隐含层动量调整
layer_delta2[j][i]=mobp*layer_delta2[j][i]+rate*layerErr3[i]*layer2[j];
layer_weight2[j][i]+=layer_delta2[j][i];//隐含层权重调整
if(j==9)
{
//截距动量调整
layer_delta2[j+1][i]=mobp*layer_delta2[j+1][i]+rate*layerErr3[i];
//截距权重调整
layer_weight2[j+1][i]+=layer_delta2[j+1][i];
}
}
//记录误差
layerErr2[j]=z*layer2[j]*(1-layer2[j]);
}
for(int j=0;j<2;j++)
{
double z=0.0;
for(int i=0;i<10;i++)
{
z=z+1>0?layerErr2[i]*layer_weight1[j][i]:0;
layer_delta1[j][i]=mobp*layer_delta1[j][i]+rate*layerErr2[i]*layer1[j];
layer_weight1[j][i]+=layer_delta1[j][i];
if(j==1)
{
layer_delta1[j+1][i]=mobp*layer_delta1[j+1][i]+rate*layerErr2[i];
layer_weight1[j+1][i]+=layer_delta1[j+1][i];
}
}
layerErr1[j]=z*layer1[j]*(1-layer1[j]);
}
}
}
double x[2]={1,1};//预测数据,测试
//测试数据训练
for(int j=0;j<10;j++)
{
double z=layer_weight1[2][j];
for(int i=0;i<2;i++)
{
layer1[i]=1==1?x[i]:layer1[i];
z+=layer_weight1[i][j]*layer1[i];
}
layer2[j]=1/(1+exp(-z));
}
for(int j=0;j<2;j++)
{
double z=layer_weight2[10][j];
for(int i=0;i<10;i++)
{
layer2[i]=2==1?x[i]:layer2[i];
z+=layer_weight2[i][j]*layer2[i];
}
layer3[j]=1/(1+exp(-z));
printf("%f\n",layer3[j]);//输出分类结果
}
system("pause");
return 0;
}
输出结果:
这里可以用二维坐标来直观表示分类结果,
补充:BP算法的改进:
(1)增加动量项
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/3c3f5570c1b9f19fe12d1d7cd016a68a.png)
动量因子
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/1ee5733318c660e4e3ae3e6b62fb735b.png)
一般选取
![](https://oscdn.geek-share.com/Uploads/Images/Content/201701/08/ac427e5450eed76777e7785530dd7406.png)
。
(2)自适应调节学习率
(3)引入陡度因子
通常BP神经网络在训练之前会对数据[b]归一化处理,即将数据映射到更小的区间内,比如[0,1]或[-1,1]。
相关文章推荐
- MFC/基于对话框的MFC上位机串口通信(C++实现)简单例程
- VS2008基于对话框的MFC上位机串口通信(C++实现)简单例程
- 一个C++基于boost简单实现的线程池
- VS2008基于对话框的MFC上位机串口通信(C++实现)简单例程
- 基于C++实现的简单的图像搜索引擎
- C++模板实现动态顺序表(更深层次的深浅拷贝)与基于顺序表的简单栈的实现
- 基于C++实现一个简单的智能指针类
- 基于ros---一个完整的实现topic 发布和监听的类和msg的简单使用(使用c++)
- 基于C++的简单的FSM实现
- 基于VS C++平台的OpenCV设置,实现简单的行人检测
- 基于AD0建立和压缩ACCESS数据库文件的C++简单实现
- 简单队列的实现(基于链表)
- 基于Mysql Connector(C++)的数据库连接池的实现
- C++实现简单的职工信息管理系统
- 基于pdo-mysql实现的简单orm
- java调用c/c++代码简单实现以及遇见的坑
- c++vector简单实现
- 基于Direct3D实现简单的粒子系统
- 基于 libmad 的简单 MP3 流媒体播放器的实现
- 简单数据结构之数组栈(C++实现)