前一段时间做了一个数字识别的小系统,基于 BP 神经网络算法的,用 MFC 做的交互。在实现过程中也试着去找一些源码,总体上来讲,这些源码的可移植性都不好,多数将交互部分和核心算法代码杂糅在一起,这样不仅代码阅读困难,而且重要的是核心算法不具备可移植性。设计模式,设计模式的重要性啊!于是自己将 BP 神经网络的核心算法用标准 C++ 实现,这样可移植性就有保证的,然后在核心算法上实现基于不同 GUI 库的交互( MFC , QT )是能很快的搭建好系统的。下面边介绍 BP 算法的原理(请看《数字图像处理与机器视觉》非常适合做工程的伙伴),边给出代码的实现,最后给出基于核心算法构建交互的例子。
1. 感知器
感知器是一种具有简单的两种输出的人工神经元,如下图所示。
2. 线性单元
只有 1 和 -1 两种输出的感知器实际上限制了其处理和分类的能力,下图是一种简单的推广,即不带阈值的感知器。
3. 误差准则
使用的是一个常用的误差度量标准,平方误差准则。公式如下。
其中 D 为训练样本, t d 为训练观测值 d 的训练输出, o t 为观测值 d 的实际观测值。如果是个凸函数就好了(搞数学的,一听到凸函数就很高兴,呵呵!),但还是可以用梯度下降的方法求其参数 w 。
4. 梯度下降推导
在高等数学中梯度的概念实际上就是一个方向向量,也就是方向导数最大的方向,也就是说沿着这个方向,函数值的变化速度最快。我们这里是做梯度下降,那么就是沿着梯度的负方向更新参数 w 的值来快速达到 E 函数值的最小了。这样梯度下降算法的步骤基本如下:
1) 初始化参数 w (随机,或其它方法)。
2) 求梯度。
3) 沿梯度方向更新参数 w ,可以添加一个学习率,也就是按多大的步子下降。
4) 重复 1 ), 2 ), 3 )直到达到设置的条件(迭代次数,或者 E 的减小量小于某个阈值)。
梯度的表达式如下:
那么如何求梯度呢?就是复合函数求导的过程,如下:
其中 x id 为样本中第 d 个观测值对应的一个输入分量 x i 。这样,训练过程中参数 w 的更新表达式如下(其中添加了一个学习率,也就是下降的步长):
于是参数 w i 的更新增量为:
对于学习率选择的问题,一般较小是能够保证收敛的,看下图吧。
5. 增量梯度下降
对于 4 中的梯度下降算法,其缺点是有时收敛速度慢,如果在误差曲面上存在多个局部极小值,算法不能保证能够找到全局极小值。为了改善这些缺点,提出了增量梯度下降算法。增量梯度下降,与 4 中的梯度下降的不同之处在于, 4 中对参数 w 的更新是根据整个样本中的观测值的误差来计算的, 而增量梯度下降算法是根据样本中单个观测值的误差来计算 w 的更新。
6. 梯度检验
这是一个比较实用的内容,如何确定自己的代码就一定没有错呢?因为在求梯度的时候是很容易犯错误的,我就犯过了,嗨,调了两天才找出来,一个数组下表写错了,要是早一点看看斯坦福大学的深度学习基础教程就好了,这里只是截图一部分,有时间去仔细看看吧。
好了有了前面的基础,我们现在就可以进行实战了,构造多层神经网络。
1.Sigmoid 神经元
Sigmoid 神经元可由下图表示:
2. 神经网络层
一个三层的 BP 神经网络可由下图表示:
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 重新定义如下:
其中 outputs 是网络中输出层单元的集合, t kd , o kd 是训练样本中第 d 个观测值在第 k 个输出单元的而输出值。
1 ) BP 算法推导
先引入下列符号:
增量梯度下降算法中,对于每个训练样本中第 d 个观测的一个输入权重 w ij 的增量如下表示:
其中 E d 是训练样本中第 d 个观测的误差,通过对输出层所有单元的求和得到:
这里说明一下,神经网络输出层的所有单元联合一起表示一个样本观测的训练值的。假设样本观测值为 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 等。
下面我们就要求梯度了(要分层求解,输出层,隐藏层),梯度向量中的各元素求解如下:
1 )当单元 j 是一个输出单元时:
于是得到:
2 )当单元 j 是一个隐藏层单元时,有如下推导:
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;
}