基于C++实现简单的BP神经网络算法

最近在学习神经网络算法,用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神经网络中,单个样本有《基于C++实现简单的BP神经网络算法》个输入,有《基于C++实现简单的BP神经网络算法》个输出,在输入层和输出层之间通常还有若干个隐含层。实际

   上,1989Robert Hecht-Nielsen证明了对于任何闭区间内的一个连续函数都可以用一个隐含层的BP网

   络来逼近,这就是万能逼近定理。所以一个三层的BP网络就可以完成任意的《基于C++实现简单的BP神经网络算法》维到《基于C++实现简单的BP神经网络算法》维的映射。即这三层分

   别是输入层(I),隐含层(H),输出层(O)。

《基于C++实现简单的BP神经网络算法》


算法计算过程:输入层开始,从左往右计算,逐层往前直到输出层产生结果。如果结果值和目标值有差距,再从右往左算,逐层向后计算每个节点的误差,并且调整每个节点的所有权重,反向到达输入层后,又重新向前计算,重复迭代以上步骤,直到所有权重参数收敛到一个合理值。由于计算机程序求解方程参数和数学求法不一样,一般是先随机选取参数,然后不断调整参数减少误差直到逼近正确值,所以大部分的机器学习都是在不断迭代训练,下面我们从程序上详细看看该过程实现就清楚了。

隐含层的选取

在BP神经网络中,输入层和输出层的节点个数都是确定的,而隐含层节点个数不确定,那么应该设置为多少

   才合适呢?实际上,隐含层节点个数的多少对神经网络的性能是有影响的,有一个经验公式可以确定隐含层

   节点数目,如下

 

                《基于C++实现简单的BP神经网络算法》

 

   其中《基于C++实现简单的BP神经网络算法》为隐含层节点数目,《基于C++实现简单的BP神经网络算法》为输入层节点数目,《基于C++实现简单的BP神经网络算法》为输出层节点数目,《基于C++实现简单的BP神经网络算法》《基于C++实现简单的BP神经网络算法》之间的调节常数。

初始化

BP神经网络初始化主要是对学习率,初始权重,动量系数等参数的设置。

前向计算

按照公式一层一层的计算隐层神经元和输出层神经元的输入和输出。这里采用S函数1/(1+Math.exp(-z))将每个节点的值统一到0-1之间,再逐层向前计算直到输出层。

反向传播

反向传播时,把误差信号按原来正向传播的通路反向传回,并对每个隐层的各个神经元的权系数进行修改,以望误差信号趋向最小。在BP神经网络中,误差信号反向传递子过程比较复杂,它是基于Widrow-Hoff学习规则的。

假设输出层的所有结果为《基于C++实现简单的BP神经网络算法》误差函数如下

 

                    《基于C++实现简单的BP神经网络算法》

  

而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;
}

输出结果:

《基于C++实现简单的BP神经网络算法》

这里可以用二维坐标来直观表示分类结果,

《基于C++实现简单的BP神经网络算法》

补充:BP算法的改进:

 

   (1)增加动量项

 

       《基于C++实现简单的BP神经网络算法》

 

       动量因子《基于C++实现简单的BP神经网络算法》一般选取《基于C++实现简单的BP神经网络算法》

 

   (2)自适应调节学习率

   (3)引入陡度因子

 

   通常BP神经网络在训练之前会对数据归一化处理,即将数据映射到更小的区间内,比如[0,1]或[-1,1]。


    原文作者:神经网络算法
    原文地址: https://blog.csdn.net/xuanwo11/article/details/54233512
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞