Code
此代码可在任何场合使用 但请保留注释
//**************基于BP的ANN类*******************
// --Feathersky
//***********************************************
#ifndef ANN_BP_H
#define ANN_BP_H
#include "math.h"
#include <stdlib.h>
#include <iostream.h>
#include <fstream.h>
#include <iomanip.h>
#include"MemoryTool1.h"
//-------------------------------------------------------------------------------
class ANN_BP
{
public:
int nMethod; //动量项标志
double Rate; //学习率
int layer1; //输入层结点数
int layer2; //中间层结点数
int layer3; //输出层结点数
double *Y1; //第一层输出
double *Y2; //第二层输出
double *Y3; //第三层输出
double *Dest; //期望输出
double **W1; //权值矩阵
double **W2;
double **SW1; //第一层权值的改变量 动量项作法使用
double **SW2; //第二层权值的改变量 动量项作法使用
double *S3; //辅助变量
double *S2; //辅助变量
double **G1_1; //第一层权值距阵各分量偏导
double **G2_1; //第二层权值距阵各分量偏导
public:
// 类构造函数,输入参数为3层BP网络每层的结点数。
// 在此函数中进行内存分配等初始化工作
ANN_BP()
{
Y1=Y2=Y3=Dest=NULL;
W1=W2=NULL;
SW1=SW2=NULL;
S3=S2=NULL;
G1_1=G2_1=NULL;
SetMethod();
}
ANN_BP(int input,int middle,int output)
{
SetLayerNodeCount(input,middle,output);
SetMethod();
}
// 类析构函数 释放分配的内存
~ANN_BP()
{
FreeMemory();
}
void FreeMemory()
{
delete[] Y1;
delete[] Y2;
delete[] Y3;
delete[] Dest;
free2D(W1);
free2D(W2);
free2D(SW1);
free2D(SW2);
delete[] S2;
delete[] S3;
free2D(G1_1);
free2D(G2_1);
}
void SetLayerNodeCount(int input,int middle,int output)
{
FreeMemory();
layer1=input+1; //输入层结点数
layer2=middle; //中间层结点数
layer3=output; //输出层结点数
Y1 = new double[layer1];
Y2 = new double[layer2];
Y3 = new double[layer3];
Dest= new double[layer3];
W1 =malloc2D(layer2,layer1);
W2 =malloc2D(layer3,layer2);
SW1 =malloc2D(layer2,layer1);
SW2 =malloc2D(layer3,layer2);
S2 = new double[layer2];
S3 = new double[layer3];
G1_1 =malloc2D(layer2,layer1);
G2_1 =malloc2D(layer3,layer2);
initial();
}
void SetMethod(int method=0)
{
nMethod=method; //是否使用动量项标志
}
private:
//用随机数初始化权值
void initial()
{
for(int i=0;i<layer1;i++)
for(int j=0;j<layer2;j++)
{
W1[i][j]=(double)(rand()%2000-1000)/1000;
if(W1[i][j]==0) W1[i][j]=0.1;
}
for( i=0;i<layer2;i++)
for(int j=0;j<layer3;j++)
{
W2[i][j]=(double)(rand()%2000-1000)/1000;
if(W2[i][j]==0) W2[i][j]=0.1;
}
}
int sgn(double i)
{
if(i>0) return 1;
if(i<0) return -1;
return 0;
}
//sigmoid 函数
double func(double i)
{
// return (double)( (1-exp(-i))/(1+exp(-i)) );
if(i<-45) return 0;
if(i>45) return 1;
return 1.0/(1.0+exp(-i));
}
/*
我在程序中调试,曾遇见过此类溢出,故此技巧是必须的
Subject: How to avoid overflow in the logistic function?
The formula for the logistic activation function is often written as:
netoutput = 1 / (1+exp(-netinput));
But this formula can produce floating-point overflow in the exponential function if you program it in this simple form. To avoid overflow, you can do this:
if (netinput < -45) netoutput = 0;
else if (netinput > 45) netoutput = 1;
else netoutput = 1 / (1+exp(-netinput));
The constant 45 will work for double precision on all machines that I know of, but there may be some bizarre machines where it will require some adjustment. Other activation functions can be handled similarly.
*/
void ComputeY() // 计算各层输出Y1[], Y2[],Y3[]
{
int i;
for(i=0; i<layer2; i++)
{
Y2[i]=0;
for(int j=0;j<layer1;j++) Y2[i]+=Y1[j]*W1[j][i];
Y2[i]=func(Y2[i]);
}
for(i=0; i<layer3; i++)
{
Y3[i]=0;
for(int j=0;j<layer2;j++) Y3[i]+=Y2[j]*W2[j][i];
Y3[i]=func(Y3[i]);
}
}
void ComputeG() //计算 误差对各层矩阵的偏导,求得梯度信息
{
int i,j;
for(i=0;i<layer3;i++)
{
S3[i]=(Dest[i]-Y3[i])*(1-Y3[i])*Y3[i];
}
for(i=0;i<layer2;i++)
{
S2[i]=0;
for(int j=0;j<layer3;j++) S2[i]+=W2[i][j]*S3[j];
S2[i]*=(1-Y2[i]) * Y2[i];
}
for( i=0;i<layer1;i++ )
for( j=0;j<layer2;j++ )
G1_1[i][j]=Y1[i]*S2[j];
for( i=0;i<layer2;i++ )
for( j=0;j<layer3;j++ )
G2_1[i][j]=Y2[i]*S3[j];
}
void WeightAdapt() // 按梯度方向修正权值
{
if(nMethod==0) //不使用动量项改进
{
for(int i=0;i<layer2;i++)
for(int j=0;j<layer3;j++)
{
W2[i][j]+=Rate*G2_1[i][j];
}
for(i=0;i<layer1;i++)
for(int j=0;j<layer2;j++)
{
W1[i][j]+=Rate*G1_1[i][j];
}
}
if(nMethod==1) //使用动量项改进 此方式只有对一个样本训练的次数大于1时有用
{
for(int i=0;i<layer2;i++)
for(int j=0;j<layer3;j++)
{
W2[i][j]+=Rate*G2_1[i][j]+0.5*Rate*SW2[i][j];
SW2[i][j]=Rate*G2_1[i][j];
}
for(i=0;i<layer1;i++)
for(int j=0;j<layer2;j++)
{
W1[i][j]+=Rate*G1_1[i][j]+0.5*Rate*SW1[i][j];
SW1[i][j]=Rate*G1_1[i][j];
}
}
}
public:
//LimitTrainCount 最大训练次数
//Error 允许学习误差
double TrainOneCase(double Input[],double Out[],double Error=0.000001,int LimitTrainCount=500)
{
int w,h;
if(nMethod==1)
{
for( h=0; h<layer1; h++)
for( w=0; w<layer2; w++)
{
SW1[h][w]=0; //动量项置为0
}
for( h=0; h<layer2; h++)
for( w=0; w<layer3; w++)
{
SW2[h][w]=0; //动量项置为0
}
}
Y1[layer1-1]=-1; //域值对应的输入
for(int i=0;i<layer1-1;i++) Y1[i] = Input[i];
for( i=0;i<layer3;i++) Dest[i] = Out[i];
double TrainError;
Rate=0.3;
do
{
if(--LimitTrainCount<0) break;
ComputeY();
ComputeG();
WeightAdapt();
TrainError=0;
for(i=0; i<layer3; i++) TrainError+=(Dest[i]-Y3[i])*(Dest[i]-Y3[i])/2;
Rate=0.05+TrainError/5; //根据误差自适应修改学习率
}
while(TrainError>Error); // 误差指标
return TrainError; //返回误差
}
double ComputeError(double Input[],double Out[])
{
double TrainError=0;
Y1[layer1-1]=-1;
for(int i=0;i<layer1-1;i++) Y1[i] = Input[i];
for( i=0;i<layer3;i++) Dest[i] = Out[i];
ComputeY();
for(i=0; i<layer3; i++) TrainError+=(Dest[i]-Y3[i])*(Dest[i]-Y3[i])/2;
return TrainError; //返回误差
}
//Out[]即为输出结果
int DecideOneCase(double Input[],double Out[])
{
Y1[layer1-1]=-1;
for(int i=0;i<layer1-1;i++) Y1[i] = Input[i];
ComputeY();
double max=0;
int index=0;
for(i=0;i<layer3; i++ )
{
Out[i]=Y3[i];
if(Y3[i]>max)
{
max=Y3[i];
index=i;
}
}
return index; //index 为out[]中最大的那个数的索引
}
/********************************************
保存机制 仅保存权重
/*******************************************/
int SaveWeight(char* file) //save the value of weight
{
ofstream in(file);
in.precision(32);
in<<layer1-1<<' '<<layer2<<' '<<layer3<<endl;
for(int i=0;i<layer1;i++)
{
for(int j=0;j<layer2;j++) in<<setw(64)<<W1[i][j]<<' ';
in<<endl;
}
in<<endl;
for(i=0;i<layer2;i++)
{
for(int j=0;j<layer3;j++) in<<setw(64)<<W2[i][j]<<' ';
in<<endl;
}
in.close();
return 1;
}
int LoadWeight(char * file) //load the value of weight
{
//CFileFind finder;
//if(!finder.FindFile(file)) { initial(); return; }
int input, middle, output;
ifstream out(file);
//out.precision(32);
if(out.fail() || out.eof()) return 0;
out>>input>>middle>>output;
SetLayerNodeCount(input,middle,output);
for(int i=0;i<layer1;i++)
for(int j=0;j<layer2;j++)
out>>W1[i][j];
for(i=0;i<layer2;i++)
for(int j=0;j<layer3;j++)
out>>W2[i][j];
out.close();
return 1;
}
};
#endif
//--------------------二维数据内存管理----Feathersky-----------------------------------------------
/*************************************************
二维数据内存空间 每行以DWORD方式对齐
二位数据内存空间是连续的
每行首地址另存于一数组
**************************************************/
#ifndef MEMORYTOOL1_H
#define MEMORYTOOL1_H
#include <malloc.h>
/****************************************************************************
* Funciton: malloc2D
* Puroose: 分配二维数据内存空间
****************************************************************************/
double ** malloc2D(int nW, int nH)
{
double ** pLineHead=(double **)malloc(nH*sizeof(double *));
double * pMem=(double *)malloc(nW*nH*sizeof(double));
for(int i=0; i<nH; i++)
{
pLineHead[i]=(double *)((BYTE *)pMem+nW*i*sizeof(double));
}
return pLineHead;
}
/****************************************************************************
* Funciton: Free2D
* Puroose: 释放二维数据内存空间
****************************************************************************/
inline void free2D(double ** & pLineHead)
{
if(pLineHead)
{
free(pLineHead[0]);
free(pLineHead);
}
}
#endif