最近在学习神经网络算法,用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神经网络中,单个样本有
个输入,有
个输出,在输入层和输出层之间通常还有若干个隐含层。
实际
上,
1989
年
Robert Hecht-Nielsen
证明了对于任何闭区间内的一个连续函数都可以用一个隐含层的BP网
络来逼近,这就是
万能逼近定理
。所以一个三层的BP网络就可以完成任意的
维到
维的映射。即这三层分
别是
输入层(I),隐
含层(H),
输出层(O)。
算法
计算过程:输入层开始,从左往右计算,逐层往前直到输出层产生结果。如果结果值和目标值有差距,再从右往左算,逐层向后计算每个节点的误差,并且调整每个节点的所有权重,反向到达输入层后,又重新向前计算,重复迭代以上步骤,直到所有权重参数收敛到一个合理值。由于计算机程序求解方程参数和数学求法不一样,一般是先随机选取参数,然后不断调整参数减少误差直到逼近正确值,所以大部分的
机器学习
都是在不断迭代训练,下面我们从程序上详细看看该过程实现就清楚了。
隐含层的选取
在BP神经网络中,输入层和输出层的节点个数都是确定的,而隐含层节点个数不确定,那么应该设置为多少
才
合适呢?实际上,隐含层节点个数的多少对神经网络的性能是有影响的,有一个经验公式可以确定隐含层
节点数目,如下
其中
为隐含层节点数目,
为输入层节点数目,
为输出层节点数目,
为
之间的调节常数。
初始化
BP神经网络初始化主要是对学习率,初始权重,动量系数等参数的设置。
前向计算
按照公式一层一层的计算隐层神经元和输出层神经元的输入和输出。这里
采用S函数1/(1+Math.exp(-z))将每个节点的值统一到0-1之间,再逐层向前计算直到输出层。
反向传播
反向传播时,把误差信号按原来正向传播的通路反向传回,并对每个隐层的各个神经元的权系数进行修改,以望误差信号趋向最小。
在BP神经网络中,误差信号反向传递子过程比较复杂,它是基于
Widrow-Hoff
学习规则的。
假设输出层
的所有结果为
,
误差函数
如下
而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[b]=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)增加动量项
动量因子
一般选取
。
(2)自适应调节学习率
(3)引入陡度因子
通常BP神经网络在训练之前会对数据
归一化处理
,即将数据映射到更小的区间内,比如[0,1]或[-1,1]。