BP神经网络原理及C++实战

前一段时间做了一个数字识别的小系统,基于 BP 神经网络算法的,用 MFC 做的交互。在实现过程中也试着去找一些源码,总体上来讲,这些源码的可移植性都不好,多数将交互部分和核心算法代码杂糅在一起,这样不仅代码阅读困难,而且重要的是核心算法不具备可移植性。设计模式,设计模式的重要性啊!于是自己将 BP 神经网络的核心算法用标准 C++ 实现,这样可移植性就有保证的,然后在核心算法上实现基于不同 GUI 库的交互( MFC , QT )是能很快的搭建好系统的。下面边介绍 BP 算法的原理(请看《数字图像处理与机器视觉》非常适合做工程的伙伴),边给出代码的实现,最后给出基于核心算法构建交互的例子。

1. 感知器

感知器是一种具有简单的两种输出的人工神经元,如下图所示。

wKiom1Rgu7GSltIFAAB3QPoYEJo250.jpg

2. 线性单元

只有 1 和 -1 两种输出的感知器实际上限制了其处理和分类的能力,下图是一种简单的推广,即不带阈值的感知器。

wKiom1RgvBCxfEINAABTuPlzwCA823.jpg

3. 误差准则

使用的是一个常用的误差度量标准,平方误差准则。公式如下。

wKioL1RgvN3zBlELAAAfmaBDSoM954.jpg

其中 D 为训练样本, t d 为训练观测值 d 的训练输出, o t 为观测值 d 的实际观测值。如果是个凸函数就好了(搞数学的,一听到凸函数就很高兴,呵呵!),但还是可以用梯度下降的方法求其参数 w 。

4. 梯度下降推导

在高等数学中梯度的概念实际上就是一个方向向量,也就是方向导数最大的方向,也就是说沿着这个方向,函数值的变化速度最快。我们这里是做梯度下降,那么就是沿着梯度的负方向更新参数 w 的值来快速达到 E 函数值的最小了。这样梯度下降算法的步骤基本如下:

1) 初始化参数 w (随机,或其它方法)。

2) 求梯度。

3) 沿梯度方向更新参数 w ,可以添加一个学习率,也就是按多大的步子下降。

4) 重复 1 ), 2 ), 3 )直到达到设置的条件(迭代次数,或者 E 的减小量小于某个阈值)。

梯度的表达式如下:

wKiom1RgvMKyZz0PAAAsXJjofww067.jpg

那么如何求梯度呢?就是复合函数求导的过程,如下:

wKiom1RgvPfQfLmrAACfvrKC9P0674.jpg

其中 x id 为样本中第 d 个观测值对应的一个输入分量 x i 。这样,训练过程中参数 w 的更新表达式如下(其中添加了一个学习率,也就是下降的步长):

wKiom1RgvU_zGpB8AAAflmrdQpw857.jpg

于是参数 w i 的更新增量为:

wKiom1RgvaDSzn56AAAa9yFtAlw606.jpg

对于学习率选择的问题,一般较小是能够保证收敛的,看下图吧。

wKioL1RgvmnTsw05AADqfaynL2I685.jpg

5. 增量梯度下降

对于 4 中的梯度下降算法,其缺点是有时收敛速度慢,如果在误差曲面上存在多个局部极小值,算法不能保证能够找到全局极小值。为了改善这些缺点,提出了增量梯度下降算法。增量梯度下降,与 4 中的梯度下降的不同之处在于, 4 中对参数 w 的更新是根据整个样本中的观测值的误差来计算的, 而增量梯度下降算法是根据样本中单个观测值的误差来计算 w 的更新。

6. 梯度检验

这是一个比较实用的内容,如何确定自己的代码就一定没有错呢?因为在求梯度的时候是很容易犯错误的,我就犯过了,嗨,调了两天才找出来,一个数组下表写错了,要是早一点看看斯坦福大学的深度学习基础教程就好了,这里只是截图一部分,有时间去仔细看看吧。

wKioL1RgvrOQvn2fAAIm7AJietY265.jpg

好了有了前面的基础,我们现在就可以进行实战了,构造多层神经网络。

1.Sigmoid 神经元

Sigmoid 神经元可由下图表示:

wKioL1RgvvKATld2AABv9nesXzY799.jpg

2. 神经网络层

一个三层的 BP 神经网络可由下图表示:

wKiom1Rgvr6hpCAgAAFpop9khJM994.jpg

3. 神经元和神经网络层的标准 C++ 定义

由 2 中的三层 BP 神经网络的示意图中可以看出,隐藏层和输出层是具有类似的结构的。神经元和神经网络层的定义如下:

////////////////////////////////////////////////////////
// Neuron.h
#ifndef __SNEURON_H__
#define __SNEURON_H__
 
#define NEED_MOMENTUM //if you want to addmomentum, remove the annotation
 
#define MOMENTUM 0.6 //momentumcoefficient, works on when defined NEED_MOMENTUM
 
typedef double WEIGHT_TYPE; // definedatatype of the weight
 
 
struct SNeuron{//neuron cell
  /******Data*******/
  intm_nInput; //number of inputs
  WEIGHT_TYPE*m_pWeights;  //weights array of inputs
#ifdef NEED_MOMENTUM
  WEIGHT_TYPE*m_pPrevUpdate; //record last weights update when momentum is needed
#endif
  doublem_dActivation; //output value, through Sigmoid function
  doublem_dError; //error value of neuron
 
  /********Functions*************/
  voidInit(int nInput){
     m_nInput= nInput + 1; //add a side term,number of inputs is actual number of actualinputs plus 1
     m_pWeights= new WEIGHT_TYPE[m_nInput];//allocate for weights array
#ifdef NEED_MOMENTUM
     m_pPrevUpdate= new WEIGHT_TYPE[m_nInput];//allocate for the last weights array
#endif
     m_dActivation= 0; //output value, through SIgmoid function
     m_dError= 0;  //error value of neuron
  }
 
  ~SNeuron(){
     //releasememory
     delete[]m_pWeights;
#ifdef NEED_MOMENTUM
     delete[]m_pPrevUpdate;
#endif
  }
};//SNeuron
 
 
struct SNeuronLayer{//neuron layer
  /************Data**************/
  intm_nNeuron; //Neuron number of this layer
  SNeuron*m_pNeurons; //Neurons array
 
  /*************Functions***************/
  SNeuronLayer(intnNeuron, int nInputsPerNeuron){
     m_nNeuron= nNeuron;
     m_pNeurons= new SNeuron[nNeuron];  //allocatememory for nNeuron neurons
 
     for(inti=0; i<nNeuron; i++){
       m_pNeurons[i].Init(nInputsPerNeuron);  //initialize neuron
     }
  }
  ~SNeuronLayer(){
     delete[]m_pNeurons;  //release neurons array
  }
};//SNeuronLayer
 
#endif//__SNEURON_H__

代码中定义了一个 NEED_MOMENTUM ,它是用来解决局部极小值的问题的,其含义是本次权重的更新是依赖于上一次权重更新的。另外还有一种解决局部极小值问题的方法是,将 w 初始化为接近于 0 的随机数。

4. 反向传播( BP )算法

前面虽然有了神经元和神经网络层的定义,但要构造一个三层的 BP 神经网络之前,还要搞清楚 BP 算法是如何来学习神经网络的参数的。它仍采用梯度下降算法,但不同的是这里的输出是整个网络的输出,而不再是一个单元的输出,所有对误差函数 E 重新定义如下:

wKiom1Rgv6HgAO8fAAApeDEAOfM978.jpg

其中 outputs 是网络中输出层单元的集合, t kd , o kd 是训练样本中第 d 个观测值在第 k 个输出单元的而输出值。

1 ) BP 算法推导

先引入下列符号:

wKioL1RgwE3iRiDYAADpzb0k1yg980.jpg

增量梯度下降算法中,对于每个训练样本中第 d 个观测的一个输入权重 w ij 的增量如下表示:

wKioL1RgwJnwNhTTAAAW7EB7v0w899.jpg

其中 E d 是训练样本中第 d 个观测的误差,通过对输出层所有单元的求和得到:

wKioL1RgwLuDQ4olAAAiDrGsF8E752.jpg

这里说明一下,神经网络输出层的所有单元联合一起表示一个样本观测的训练值的。假设样本观测值为 5 种,即 5 种类别,那么先验训练数据的类别表示为:1,0,0,0,0 ; 0,1,0,0,0 ; 0,0,1,0,0 ; 0,0,0,1,0 ; 0,0,0,0,1 。这样在对神经网络训练时,我们的训练输出值的表示也就是类似的,当然基于神经元的结构表示,我们也可以将先验训练数据的类别表示中的 1 换成 0.9 等。

下面我们就要求梯度了(要分层求解,输出层,隐藏层),梯度向量中的各元素求解如下:

wKiom1RgwL6QeOimAABWAfEi0B8959.jpg

1 )当单元 j 是一个输出单元时:

wKiom1RgwOug-XAiAAAlk5dFJ-o518.jpg

wKioL1RgwXvw4gEWAAChKLD6rzc918.jpg

于是得到:

wKiom1RgwZrSlN_6AACB4pw7ljs023.jpg

2 )当单元 j 是一个隐藏层单元时,有如下推导:

wKioL1Rgwm_xivUmAAE7AtHd_xM921.jpg

5 .标准 C++ 构建三层 BP 神经网络

该神经网络提供了重要的两个接口。一个是一次训练训练接口 TrainingEpoch ,可用于上层算法构建训练函数时调用;另一个是计算给定一个输入神经网络的输出接口CalculateOutput ,它在一次训练函数中被调用,更重要的是,在上层算法中构建识别函数调用。

头文件:

// NeuralNet.h: interface for theCNeuralNet class.
//
//
 
#ifndef __NEURALNET_H__
#define __NEURALNET_H__
 
#include <vector>
#include <math.h>
#include "Neuron.h"
using namespace std;
 
typedef vector<double> iovector;
#define BIAS 1 //bias term's coefficient w0
 
/*************Random functions initializingweights*************/
#define WEIGHT_FACTOR 0.1 //used to confineinitial weights
 
/*Return a random float between 0 to 1*/
inline double RandFloat(){ return(rand())/(RAND_MAX+1.0); }
 
/*Return a random float between -1 to 1*/
inline double RandomClamped(){ returnWEIGHT_FACTOR*(RandFloat() - RandFloat()); }
 
class CNeuralNet{
private:
  /*Initialparameters, can not be changed throghout the whole training.*/
  intm_nInput;  //number of inputs
  intm_nOutput; //number of outputs
  intm_nNeuronsPerLyr; //unit number of hidden layer
  intm_nHiddenLayer; //hidden layer, not including the output layer
 
  /***Dinamicparameters****/
  doublem_dErrorSum;  //one epoch's sum-error
 
  SNeuronLayer*m_pHiddenLyr;  //hidden layer
  SNeuronLayer*m_pOutLyr;     //output layer
 
public:
  /*
  *Constructorand Destructor.
  */
  CNeuralNet(intnInput, int nOutput, int nNeuronsPerLyr, int nHiddenLayer);
  ~CNeuralNet();
  /*
  *Computeoutput of network, feedforward.
  */
  bool CalculateOutput(vector<double> input,vector<double>& output);
  /*
  *Trainingan Epoch, backward adjustment.
  */
  bool TrainingEpoch(vector<iovector>& SetIn,vector<iovector>& SetOut, double LearningRate);
  /*
  *Geterror-sum.
  */
  doubleGetErrorSum(){ return m_dErrorSum; }
  SNeuronLayer*GetHiddenLyr(){ return m_pHiddenLyr; }
  SNeuronLayer*GetOutLyr(){ return m_pOutLyr; }
 
private:
  /*
  *Biuldnetwork, allocate memory for each layer.
  */
  voidCreateNetwork();
  /*
  *Initializenetwork.
  */
  voidInitializeNetwork();
  /*
  *Sigmoidencourage fuction.
  */
  doubleSigmoid(double netinput){
     doubleresponse = 1.0;  //control steep degreeof sigmoid function
     return(1 / ( 1 + exp(-netinput / response) ) );
  }       
};
 
#endif //__NEURALNET_H__

实现文件:

// NeuralNet.cpp: implementation of theCNeuralNet class.
//
//
#include "stdafx.h"
#include "NeuralNet.h"
#include <assert.h>
 
CNeuralNet::CNeuralNet(int nInput, intnOutput, int nNeuronsPerLyr, int nHiddenLayer){
  assert(nInput>0 && nOutput>0 && nNeuronsPerLyr>0 &&nHiddenLayer>0 );
  m_nInput= nInput;
  m_nOutput= nOutput;
  m_nNeuronsPerLyr= nNeuronsPerLyr;
  if(nHiddenLayer!= 1)
     m_nHiddenLayer= 1;
  else
     m_nHiddenLayer= nHiddenLayer; //temporarily surpport only one hidden layer
 
  m_pHiddenLyr= NULL;
  m_pOutLyr= NULL;
 
  CreateNetwork();   //allocate for each layer
  InitializeNetwork();  //initialize the whole network
}
 
CNeuralNet::~CNeuralNet(){
  if(m_pHiddenLyr!= NULL)
     deletem_pHiddenLyr;
  if(m_pOutLyr!= NULL)
     deletem_pOutLyr;
}
 
void CNeuralNet::CreateNetwork(){
  m_pHiddenLyr= new SNeuronLayer(m_nNeuronsPerLyr, m_nInput);
  m_pOutLyr= new SNeuronLayer(m_nOutput, m_nNeuronsPerLyr);
}
 
void CNeuralNet::InitializeNetwork(){
  inti, j;  //variables for loop
 
  /*usepresent time as random seed, so every time runs this programm can producedifferent random sequence*/
  //srand((unsigned)time(NULL) );
 
  /*initializehidden layer's weights*/
  for(i=0;i<m_pHiddenLyr->m_nNeuron; i++){
     for(j=0;j<m_pHiddenLyr->m_pNeurons[i].m_nInput; j++){
       m_pHiddenLyr->m_pNeurons[i].m_pWeights[j]= RandomClamped();
       #ifdefNEED_MOMENTUM
       /*whenthe first epoch train started, there is no previous weights update*/
       m_pHiddenLyr->m_pNeurons[i].m_pPrevUpdate[j]= 0;
       #endif
     }       
  }
  /*initializeoutput layer's weights*/
  for(i=0;i<m_pOutLyr->m_nNeuron; i++){
     for(intj=0; j<m_pOutLyr->m_pNeurons[i].m_nInput; j++){
       m_pOutLyr->m_pNeurons[i].m_pWeights[j]= RandomClamped();
       #ifdefNEED_MOMENTUM
       /*whenthe first epoch train started, there is no previous weights update*/
       m_pOutLyr->m_pNeurons[i].m_pPrevUpdate[j]= 0;
       #endif
     }
  }
 
  m_dErrorSum= 9999.0;  //initialize a large trainingerror, it will be decreasing with training
}
 
boolCNeuralNet::CalculateOutput(vector<double> input,vector<double>& output){
  if(input.size()!= m_nInput){ //input feature vector's dimention not equals to input of network
     returnfalse;
  }
  inti, j;
  doublenInputSum;  //sum term
 
  /*computehidden layer output*/
  for(i=0;i<m_pHiddenLyr->m_nNeuron; i++){
     nInputSum= 0;
     for(j=0;j<m_pHiddenLyr->m_pNeurons[i].m_nInput-1; j++){
       nInputSum+= m_pHiddenLyr->m_pNeurons[i].m_pWeights[j] * input[j];
     }
     /*plusbias term*/
     nInputSum+= m_pHiddenLyr->m_pNeurons[i].m_pWeights[j] * BIAS;
     /*computesigmoid fuction's output*/
     m_pHiddenLyr->m_pNeurons[i].m_dActivation= Sigmoid(nInputSum);
  }
 
  /*computeoutput layer's output*/
  for(i=0;i<m_pOutLyr->m_nNeuron; i++){
     nInputSum= 0;
     for(j=0;j<m_pOutLyr->m_pNeurons[i].m_nInput-1; j++){
       nInputSum+= m_pOutLyr->m_pNeurons[i].m_pWeights[j]
         *m_pHiddenLyr->m_pNeurons[j].m_dActivation;
     }
     /*plusbias term*/
     nInputSum+= m_pOutLyr->m_pNeurons[i].m_pWeights[j] * BIAS;
     /*computesigmoid fuction's output*/
     m_pOutLyr->m_pNeurons[i].m_dActivation= Sigmoid(nInputSum);
     /*saveit to the output vector*/
     output.push_back(m_pOutLyr->m_pNeurons[i].m_dActivation);
  }
  returntrue;
}
 
bool CNeuralNet::TrainingEpoch(vector<iovector>&SetIn, vector<iovector>& SetOut, double LearningRate){
  inti, j, k;
  doubleWeightUpdate;  //weight's update value
  doubleerr;  //error term
 
  /*increment'sgradient decrease(update weights according to each training sample)*/
  m_dErrorSum= 0;  // sum of error term
  for(i=0;i<SetIn.size(); i++){
     iovectorvecOutputs;
     /*forwardlyspread inputs through network*/
     if(!CalculateOutput(SetIn[i],vecOutputs)){
       returnfalse;
     }
 
     /*updatethe output layer's weights*/
     for(j=0;j<m_pOutLyr->m_nNeuron; j++){
 
       /*computeerror term*/
       err= ((double)SetOut[i][j]-vecOutputs[j])*vecOutputs[j]*(1-vecOutputs[j]);
       m_pOutLyr->m_pNeurons[j].m_dError= err;  //record this unit's error
 
       /*updatesum error*/
       m_dErrorSum+= ((double)SetOut[i][j] - vecOutputs[j]) * ((double)SetOut[i][j] -vecOutputs[j]);
 
       /*updateeach input's weight*/
       for(k=0;k<m_pOutLyr->m_pNeurons[j].m_nInput-1; k++){
         WeightUpdate= err * LearningRate * m_pHiddenLyr->m_pNeurons[k].m_dActivation;
#ifdef NEED_MOMENTUM
         /*updateweights with momentum*/
         m_pOutLyr->m_pNeurons[j].m_pWeights[k]+=
            WeightUpdate+ m_pOutLyr->m_pNeurons[j].m_pPrevUpdate[k] * MOMENTUM;
         m_pOutLyr->m_pNeurons[j].m_pPrevUpdate[k]= WeightUpdate;
#else
         /*updateunit weights*/
         m_pOutLyr->m_pNeurons[j].m_pWeights[k]+= WeightUpdate;
#endif
       }
       /*biasupdate volume*/
       WeightUpdate= err * LearningRate * BIAS;
#ifdef NEED_MOMENTUM
       /*updatebias with momentum*/
       m_pOutLyr->m_pNeurons[j].m_pWeights[k]+=
         WeightUpdate+ m_pOutLyr->m_pNeurons[j].m_pPrevUpdate[k] * MOMENTUM;
       m_pOutLyr->m_pNeurons[j].m_pPrevUpdate[k]= WeightUpdate;
#else
       /*updatebias*/
       m_pOutLyr->m_pNeurons[j].m_pWeights[k]+= WeightUpdate;
#endif
     }//for out layer
 
     /*updatethe hidden layer's weights*/
     for(j=0;j<m_pHiddenLyr->m_nNeuron; j++){
 
       err= 0;
       for(intk=0; k<m_pOutLyr->m_nNeuron; k++){
         err+= m_pOutLyr->m_pNeurons[k].m_dError *m_pOutLyr->m_pNeurons[k].m_pWeights[j];
       }
       err*= m_pHiddenLyr->m_pNeurons[j].m_dActivation * (1 -m_pHiddenLyr->m_pNeurons[j].m_dActivation);
       m_pHiddenLyr->m_pNeurons[j].m_dError= err;  //record this unit's error
 
       /*updateeach input's weight*/
       for(k=0;k<m_pHiddenLyr->m_pNeurons[j].m_nInput-1; k++){
         WeightUpdate= err * LearningRate * SetIn[i][k];
#ifdef NEED_MOMENTUM
         /*updateweights with momentum*/
         m_pHiddenLyr->m_pNeurons[j].m_pWeights[k]+=
            WeightUpdate+ m_pHiddenLyr->m_pNeurons[j].m_pPrevUpdate[k] * MOMENTUM;
         m_pHiddenLyr->m_pNeurons[j].m_pPrevUpdate[k]= WeightUpdate;
#else
         m_pHiddenLyr->m_pNeurons[j].m_pWeights[k]+= WeightUpdate;
#endif
       }
       /*biasupdate volume*/
       WeightUpdate= err * LearningRate * BIAS;
#ifdef NEED_MOMENTUM
       /*updatebias with momentum*/
       m_pHiddenLyr->m_pNeurons[j].m_pWeights[k]+=
         WeightUpdate+ m_pHiddenLyr->m_pNeurons[j].m_pPrevUpdate[k] * MOMENTUM;
       m_pHiddenLyr->m_pNeurons[j].m_pPrevUpdate[k]= WeightUpdate;
#else
       /*updatebias*/
       m_pHiddenLyr->m_pNeurons[j].m_pWeights[k]+= WeightUpdate;
#endif
     }//forhidden layer
  }//forone epoch
  returntrue;
}

6. 基于 BP 核心算法构建 MVC 框架

到此为止我们的核心算法已经构建出来了,再应用两次 Strategy 设计模式,我们就很容易构建出一个 MVC 框架( see also :http://remyspot.blog.51cto.com/8218746/1574484 )。下面给出一个应用Strategy 设计模式基于 CNeuralNet 类构建一个 Controller ,在 Controller 中我们就可以开始依赖特定的 GUI 库了。下面的这个 Controller 是不能直接使用的,你所要做的是参考该代码( 重点参看

boolTrain(vector<iovector>& SetIn, vector<iovector>& SetOut);

bool SaveTrainResultToFile(const char* lpszFileName, boolbCreate);

bool LoadTrainResultFromFile(const char* lpszFileName, DWORDdwStartPos);

int Recognize(CString strPathName, CRect rt, double&dConfidence);

接口的实现 ), 然后基于前面的核心 BP 算构建你自己的 Controller ,然后在该 Controller 的上层实现你自己的交互功能。

说明一下, Train 接口中的 SetIn 是训练数据的特征, SetOut 是训练数据的类别表示。

头文件:

#ifndef __OPERATEONNEURALNET_H__
#define __OPERATEONNEURALNET_H__
 
#include "NeuralNet.h"
#define NEURALNET_VERSION 1.0
#define RESAMPLE_LEN 4
 
class COperateOnNeuralNet{
private:
  /*network*/
  CNeuralNet*m_oNetWork;
 
  /*network'sparameter*/
  intm_nInput;
  intm_nOutput;
  intm_nNeuronsPerLyr;
  intm_nHiddenLayer;
 
  /*trainingconfiguration*/
  intm_nMaxEpoch;  // max training epoch times
  doublem_dMinError; // error threshold
  doublem_dLearningRate;
 
  /*dinamiccurrent parameter*/
  intm_nEpochs;
  doublem_dErr;  //mean error of oneepoch(m_dErrorSum/(num-of-samples * num-of-output))
  boolm_bStop; //control whether stop or not during the training
  vector<double> m_vecError;   //record each epoch'straining error, used for drawing error curve
 
public:
  COperateOnNeuralNet();
  ~COperateOnNeuralNet();
 
  voidSetNetWorkParameter(int nInput, int nOutput, int nNeuronsPerLyr, intnHiddenLayer);
  boolCreatNetWork();
  voidSetTrainConfiguration(int nMaxEpoch, double dMinError, double dLearningRate);
  voidSetStopFlag(bool bStop) { m_bStop = bStop; }
 
  doubleGetError(){ return m_dErr; }
  intGetEpoch(){ return m_nEpochs; }
  intGetNumNeuronsPerLyr(){ return m_nNeuronsPerLyr; }
 
  boolTrain(vector<iovector>& SetIn, vector<iovector>& SetOut);
  bool SaveTrainResultToFile(const char* lpszFileName, boolbCreate);
  bool LoadTrainResultFromFile(const char* lpszFileName, DWORDdwStartPos);
  int Recognize(CString strPathName, CRect rt, double&dConfidence);
};
/*
* Can be used when saving or readingtraining result.
*/
struct NEURALNET_HEADER{
  DWORDdwVersion;  //version imformation
        
  /*initialparameters*/
  intm_nInput;  //number of inputs
  intm_nOutput; //number of outputs
  intm_nNeuronsPerLyr; //unit number of hidden layer
  intm_nHiddenLayer; //hidden layer, not including the output layer
 
  /*trainingconfiguration*/
  intm_nMaxEpoch;  // max training epoch times
  doublem_dMinError; // error threshold
  doublem_dLearningRate;
 
  /*dinamiccurrent parameter*/
  intm_nEpochs;
  doublem_dErr;  //mean error of oneepoch(m_dErrorSum/(num-of-samples * num-of-output))
};
#endif //__OPERATEONNEURALNET_H__

实现文件:

// OperateOnNeuralNet.cpp: implementationof the COperateOnNeuralNet class.
//
//
 
#include "stdafx.h"
#include "MyDigitRec.h"
#include "OperateOnNeuralNet.h"
#include "Img.h"
#include <assert.h>
 
/*
*Handle message during waiting.
*/
void WaitForIdle()
{
  MSGmsg; 
  while(::PeekMessage(&msg,NULL, 0, 0, PM_REMOVE))
  {
     ::TranslateMessage(&msg);
     ::DispatchMessage(&msg);
  }
}
 
COperateOnNeuralNet::COperateOnNeuralNet(){
  m_nInput= 0;
  m_nOutput= 0;
  m_nNeuronsPerLyr= 0;
  m_nHiddenLayer= 0;
 
  m_nMaxEpoch= 0;
  m_dMinError= 0;
  m_dLearningRate= 0;
 
  m_oNetWork= 0;
 
  m_nEpochs= 0;
  m_dErr= 0;
  m_bStop= false;
}
 
COperateOnNeuralNet::~COperateOnNeuralNet(){
  if(m_oNetWork)
     deletem_oNetWork;
}
 
voidCOperateOnNeuralNet::SetNetWorkParameter(int nInput, int nOutput, intnNeuronsPerLyr, int nHiddenLayer){
  assert(nInput>0 && nOutput>0 && nNeuronsPerLyr>0 &&nHiddenLayer>0 );
  m_nInput= nInput;
  m_nOutput= nOutput;
  m_nNeuronsPerLyr= nNeuronsPerLyr;
  m_nHiddenLayer= nHiddenLayer;
}
 
bool COperateOnNeuralNet::CreatNetWork(){
  assert(m_nInput>0 && m_nOutput>0 && m_nNeuronsPerLyr>0&& m_nHiddenLayer>0 );
  m_oNetWork= new CNeuralNet(m_nInput, m_nOutput, m_nNeuronsPerLyr, m_nHiddenLayer);
  if(m_oNetWork)
     returntrue;
  else
     returnfalse;
}
 
voidCOperateOnNeuralNet::SetTrainConfiguration(int nMaxEpoch, double dMinError,double dLearningRate){
  assert(nMaxEpoch>0&& !(dMinError<0) && dLearningRate!=0);
  m_nMaxEpoch= nMaxEpoch;
  m_dMinError= dMinError;
  m_dLearningRate= dLearningRate;
}
 
boolCOperateOnNeuralNet::Train(vector<iovector>& SetIn, vector<iovector>&SetOut){
  m_bStop= false;  //no stop during training
  CStringstrOutMsg;
 
  do{
     /*trainone epoch*/
     if(!m_oNetWork->TrainingEpoch(SetIn, SetOut, m_dLearningRate) ){
       strOutMsg.Format("Erroroccured at training %dth epoch!",m_nEpochs+1);
       AfxMessageBox(strOutMsg);
       returnfalse;
     }else{
       m_nEpochs++;
     }
    
     /*computemean error of one epoch(m_dErrorSum/(num-of-samples * num-of-output))*/
     intsum = m_oNetWork->GetErrorSum();
     m_dErr= m_oNetWork->GetErrorSum() / ( m_nOutput * SetIn.size() );
     m_vecError.push_back(m_dErr);
     if(m_dErr< m_dMinError){
       break;
     }
 
     /*stopin loop to chech wether user's action made or message sent, mostly for changem_bStop */
     WaitForIdle();
 
     if(m_bStop){
       break;
     }
  }while(--m_nMaxEpoch> 0);
 
  returntrue;
}
 
boolCOperateOnNeuralNet::SaveTrainResultToFile(const char* lpszFileName, boolbCreate){
  CFilefile;
  if(bCreate){
     if(!file.Open(lpszFileName,CFile::modeWrite|CFile::modeCreate))
       returnfalse;
  }else{
     if(!file.Open(lpszFileName,CFile::modeWrite))
       returnfalse;
     file.SeekToEnd();  //add to end of file
  }
 
  /*createnetwork head information*/
  /*initialparameter*/
  NEURALNET_HEADERheader = {0};
  header.dwVersion= NEURALNET_VERSION;
  header.m_nInput= m_nInput;
  header.m_nOutput= m_nOutput;
  header.m_nNeuronsPerLyr= m_nNeuronsPerLyr;
  header.m_nHiddenLayer= m_nHiddenLayer;
  /*trainingconfiguration*/
  header.m_nMaxEpoch= m_nMaxEpoch;
  header.m_dMinError= m_dMinError;
  header.m_dLearningRate= m_dLearningRate;
  /*dinamiccurrent parameter*/
  header.m_nEpochs= m_nEpochs;
  header.m_dErr= m_dErr;
 
  file.Write(&header,sizeof(header));
 
  /*writeweight information to file*/
  inti, j;
  /*hiddenlayer weight*/
  for(i=0;i<m_oNetWork->GetHiddenLyr()->m_nNeuron; i++){
     file.Write(&m_oNetWork->GetHiddenLyr()->m_pNeurons[i].m_dActivation,
       sizeof(m_oNetWork->GetHiddenLyr()->m_pNeurons[i].m_dActivation));
     file.Write(&m_oNetWork->GetHiddenLyr()->m_pNeurons[i].m_dError,
       sizeof(m_oNetWork->GetHiddenLyr()->m_pNeurons[i].m_dError));
     for(j=0;j<m_oNetWork->GetHiddenLyr()->m_pNeurons[i].m_nInput; j++){
       file.Write(&m_oNetWork->GetHiddenLyr()->m_pNeurons[i].m_pWeights[j],
         sizeof(m_oNetWork->GetHiddenLyr()->m_pNeurons[i].m_pWeights[j]));
     }
  }
  /*outputlayer weight*/
  for(i=0;i<m_oNetWork->GetOutLyr()->m_nNeuron; i++){
     file.Write(&m_oNetWork->GetOutLyr()->m_pNeurons[i].m_dActivation,
       sizeof(m_oNetWork->GetOutLyr()->m_pNeurons[i].m_dActivation));
     file.Write(&m_oNetWork->GetOutLyr()->m_pNeurons[i].m_dError,
       sizeof(m_oNetWork->GetOutLyr()->m_pNeurons[i].m_dError));
     for(j=0;j<m_oNetWork->GetOutLyr()->m_pNeurons[i].m_nInput; j++){
       file.Write(&m_oNetWork->GetOutLyr()->m_pNeurons[i].m_pWeights[j],
         sizeof(m_oNetWork->GetOutLyr()->m_pNeurons[i].m_pWeights[j]));
     }
  }
 
  file.Close();
  returntrue;
}
 
boolCOperateOnNeuralNet::LoadTrainResultFromFile(const char* lpszFileName, DWORDdwStartPos){
  CFilefile;
  if(!file.Open(lpszFileName,CFile::modeRead)){
     returnfalse;
  }
 
  file.Seek(dwStartPos,CFile::begin);  //point to dwStartPos
 
  /*readin NeuralNet_Head infomation*/
  NEURALNET_HEADERheader = {0};
  if(file.Read(&header, sizeof(header)) != sizeof(header) ){
     returnfalse;
  }
 
  /*chechversion*/
  if(header.dwVersion!= NEURALNET_VERSION){
     returnfalse;
  }
 
  /*checkbasic NeuralNet's structure*/
  if(header.m_nInput!= m_nInput
     ||header.m_nOutput != m_nOutput
     ||header.m_nNeuronsPerLyr != m_nNeuronsPerLyr
     ||header.m_nHiddenLayer != m_nHiddenLayer
     ||header.m_nMaxEpoch != m_nMaxEpoch
     ||header.m_dMinError != m_dMinError
     ||header.m_dLearningRate != m_dLearningRate ){
     returnfalse;
  }
        
  /*dynamicparameters*/
  m_nEpochs= header.m_nEpochs;  //update trainingepochs
  m_dErr= header.m_dErr;		  //update training error
 
  /*readin NetWork's weights*/
  inti,j;
  /*readin hidden layer weights*/
  for(i=0;i<m_oNetWork->GetHiddenLyr()->m_nNeuron; i++){
     file.Read(&m_oNetWork->GetHiddenLyr()->m_pNeurons[i].m_dActivation,
       sizeof(m_oNetWork->GetHiddenLyr()->m_pNeurons[i].m_dActivation));
     file.Read(&m_oNetWork->GetHiddenLyr()->m_pNeurons[i].m_dError,
       sizeof(m_oNetWork->GetHiddenLyr()->m_pNeurons[i].m_dError));
 
     for(j=0;j<m_oNetWork->GetHiddenLyr()->m_pNeurons[i].m_nInput; j++){
       file.Read(&m_oNetWork->GetHiddenLyr()->m_pNeurons[i].m_pWeights[j],
         sizeof(m_oNetWork->GetHiddenLyr()->m_pNeurons[i].m_pWeights[j]));
     }
  }
 
  /*readin out layer weights*/
  for(i=0;i<m_oNetWork->GetOutLyr()->m_nNeuron; i++){
     file.Read(&m_oNetWork->GetOutLyr()->m_pNeurons[i].m_dActivation,
       sizeof(m_oNetWork->GetOutLyr()->m_pNeurons[i].m_dActivation));
     file.Read(&m_oNetWork->GetOutLyr()->m_pNeurons[i].m_dError,
       sizeof(m_oNetWork->GetOutLyr()->m_pNeurons[i].m_dError));
 
     for(j=0;j<m_oNetWork->GetOutLyr()->m_pNeurons[i].m_nInput; j++){
       file.Read(&m_oNetWork->GetOutLyr()->m_pNeurons[i].m_pWeights[j],
         sizeof(m_oNetWork->GetOutLyr()->m_pNeurons[i].m_pWeights[j]));
     }
  }
        
  returntrue;
}
 
int COperateOnNeuralNet::Recognize(CStringstrPathName, CRect rt, double &dConfidence){
  intnBestMatch;  //category number
  doubledMaxOut1 = 0; //max output
  doubledMaxOut2 = 0; //second max output
 
  CImggray;
  if(!gray.AttachFromFile(strPathName)){
     return-1;
  }
 
  /*convert the picture waitiong for being recognized to vector*/
  vector<double>vecToRec;
  for(intj=rt.top; j<rt.bottom; j+= RESAMPLE_LEN){
     for(inti=rt.left; i<rt.right; i+=RESAMPLE_LEN){
       intnGray = 0;
       for(intmm=j; mm<j+RESAMPLE_LEN; mm++){
         for(intnn=i; nn<i+RESAMPLE_LEN; nn++)
            nGray+= gray.GetGray(nn, mm);
       }
       nGray/= RESAMPLE_LEN*RESAMPLE_LEN;
       vecToRec.push_back(nGray/255.0);
     }
  }
 
  /*computethe output result*/
  vector<double>outputs;
  if(!m_oNetWork->CalculateOutput(vecToRec,outputs)){
     AfxMessageBox("Recfailed!");
     return-1;
  }
 
  /*findthe max output unit, and its unit number is the category number*/
  nBestMatch= 0;
  for(intk=0; k<outputs.size(); k++){
     if(outputs[k]> dMaxOut1){
       dMaxOut2= dMaxOut1;
       dMaxOut1= outputs[k];
       nBestMatch= k;
     }
  }
  dConfidence= dMaxOut1 - dMaxOut2;  //compute beliefdegree
  returnnBestMatch;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值