您的位置:首页 > 理论基础 > 计算机网络

[置顶] 基于梯度下降法的三层神经网络的C++实现(支持保存和读取)

2017-08-12 13:26 771 查看
前言:这也是我根据很久之前看到一本关于游戏AI设计的书里的代码写的,改了一点点,支持保存和读取神经网络。关于什么是神经网络已经有很多博客都有详细的介绍了,这里主要是写写代码的实现。把整个神经网络封装成类,用起来超方便!


BP-Neural-Network


一个三层神经网络模板


采用梯度下降算法,和sigmoid单元,因此输出应限定在0~1


支持保存和读取训练好的网络


main.cpp里有详细的用法介绍


main.cpp里的例子是训练出一个可以识别线性不可分的数据,即

┇
1      ┇       0
┇
┇


┅┅┅┅┅┅┅┅┅┅┅┅┅
┇
┇
0      ┇       1
┇
┇


这里简单介绍几个重要的函数和用法。

首先我们要定义训练数据的输入向量和输出向量。我的代码采用vector<float>的形式,如。

vector<float> input1;

input1.push_back(1.0);

input1.push_back(1.0);

vector<float> output1;

output1.push_back(0.0);

vector<float> input4;

input4.push_back(-1.0);

input4.push_back(1.0);

vector<float> output4;

output4.push_back(1.0);

然后我们要新建一个数据类,把该训练数据插入进去

Data* MyData=new Data(2,1);//2个输入,一个输出

MyData->AddData(input1,output1);//添加数据,第一个参数是输入向量,第二个数据是输出向量,向量大小必须和定义的一样!

MyData->AddData(input4,output4);

然后我们就可以新建一个神经网络

NeuralNet* Brain=new NeuralNet(2,1,4,0.1,NeuralNet::ERRORSUM,true);//新建一个神经网络,输入神经元个数,输出神经元个数,隐藏层神经元个数,学习率,停止训练方法(次数或误差最小),是否输出误差值(用于观察是否收敛)

然后可以设置我们的网络

Brain->SetErrorThrehold(0.01);//设置误差,默认0.01

 //Brain->SetCount(10000);设置次数,默认10000

接下来我们就开始训练,训练函数需要一个数据类

Brain->Train(MyData);//通过数据,开始训练

训练完毕后我们就可以输入我们的测试数据了。我们自己定义好一个输入向量后,通过前向函数,我们会返回一个输出向量,然后我们就可以读取里面的结果了。

cout<<Brain->Update(input1)[0]<<endl;//通过输入得到输出

本代码还支持保存和读取网络,我们可以将训练好的网络保存,和通过重载的构造函数读取。

Brain->saveNet("D:\\1.txt");//保存网络

NeuralNet* Brain2=new NeuralNet("D:\\1.txt");//通过文件读取网络

cout<<Brain2->Update(input1)[0]<<endl;

以上就是最基本的用法。当然这只是个最简单的模板,跟那些专门的库肯定没法比,但是对于初学者来说,帮助很大,也可以用于完成一些简单的识别任务。

附上GitHub地址:一个三层神经网络模板,采用梯度下降算法,支持保存和读取训练好的网络(记得给小星星支持下喔^_^)

附上代码

NeuralNet.h

#ifndef NEURALNET_H
#define NEURALNET_H
#define ACTIVATION_RESPONSE 1.0
#include<string>
#include<vector>
#include<math.h>
#include<stdlib.h>
#include<iostream>
using namespace std;
class Data{
private:
vector<vector<float> > SetIn;//记录训练用的数据
vector<vector<float> > SetOut;//记录训练用数据的目标输出
int NumPatterns;//数据量
int InVectorSize;//数据大小
int OutVectorSize;
public:
Data(int invectorSize,int outvectorsize):InVectorSize(invectorSize),OutVectorSize(outvectorsize){

}
void AddData(vector<float> indata,vector<float> outdata){
SetIn.push_back(indata);
++NumPatterns;
SetOut.push_back(outdata);
}
vector<vector<float> > GetInputSet() {return SetIn;}
vector<vector<float> > GetOutputSet(){return SetOut;}
};

struct Neuron{
int NumInputs;//神经元的输入量
vector<float> vecWeight;//权重
float Activation;//这里是根据输入,通过某个线性函数确定,相当于神经元的输出
float Error;//误差值
float RandomClamped(){
return -1+2*(rand()/((float)RAND_MAX+1));
}
Neuron(int inputs){
NumInputs=inputs+1;
Activation=0;
Error=0;
for(int i=0;i<NumInputs+1;i++)
vecWeight.push_back(RandomClamped());//初始化权重
}
};

struct NeuronLayer{
int	NumNeurons;//每层拥有的神经元数
vector<Neuron>	vecNeurons;
NeuronLayer(int neurons, int perNeuron):NumNeurons(neurons){
for(int i=0;i<NumNeurons;i++)
vecNeurons.push_back(Neuron(perNeuron));
}
};

typedef vector<float> iovector;
class NeuralNet{
private:
int NumInputs;//输入量
int NumOutputs;//输出量
int NumHiddenLayers;//隐藏层数
int NeuronsPerHiddenLayer;//隐藏层拥有的神经元
float LearningRate;//学习率
float ErrorSum;//误差总值
bool Trained;//是否已经训练过
int NumEpochs;//代数
float ERROR_THRESHOLD;     //误差阈值(什么时候停止训练)
long int Count;     //训练次数(什么时候停止训练)
vector<NeuronLayer> vecLayers;//层数
bool NetworkTrainingEpoch(vector<iovector > &SetIn,vector<iovector > &SetOut);//训练神经网络
void CreateNet();//生成网络
void InitializeNetwork();//初始化
inline float Sigmoid(float activation, float response);
float RandomClamped(){
return -1+2*(rand()/((float)RAND_MAX+1));
}
bool Debug;//是否输出误差值
public:
bool Train(Data* data);//开始训练
enum STOPTYPE{COUNT,ERRORSUM}StopType;
NeuralNet(int inputs,int outputs,int hiddenneurons,float learningRate,STOPTYPE type,bool debug=0);//初始化网络
void SetErrorThrehold(float num){ERROR_THRESHOLD=num;}//设置误差
void SetCount(long int num){Count=num;}//设置训练次数
vector<float> Update(vector<float> inputs);//得到输出
NeuralNet(string filename);//通过文件地址打开一个已经训练好的网络
void saveNet(string filename);//保存已经训练的网络
};

#endif // NEURALNET_H


NeuralNet.cpp

#include "NeuralNet.h"
bool NeuralNet::NetworkTrainingEpoch(vector<iovector> &SetIn, vector<iovector> &SetOut){
vector<float>::iterator curWeight;//指向某个权重
vector<Neuron>::iterator curNrnOut,curNrnHid;//指向输出神经元和某个隐藏神经元
ErrorSum=0;//置零
//对每一个输入集合调整权值
for(unsigned int vec=0;vec<SetIn.size();vec++){
vector<float> outputs=Update(SetIn[vec]);//通过神经网络获得输出
//根据每一个输出神经元的输出调整权值
for(int op=0;op<NumOutputs;op++){
float err=(SetOut[vec][op]-outputs[op])*outputs[op]*(1-outputs[op]);//误差的平方
ErrorSum+=(SetOut[vec][op]-outputs[op])*(SetOut[vec][op]-outputs[op]);//计算误差总和,用于暂停训练
vecLayers[1].vecNeurons[op].Error=err;//更新误差(输出层)
curWeight=vecLayers[1].vecNeurons[op].vecWeight.begin();//标记第一个权重
curNrnHid=vecLayers[0].vecNeurons.begin();//标记隐藏层第一个神经元
//对该神经元的每一个权重进行调整
while(curWeight!=vecLayers[1].vecNeurons[op].vecWeight.end()-1){
*curWeight+=err*LearningRate*curNrnHid->Activation;//根据误差和学习率和阈值调整权重
curWeight++;//指向下一个权重
curNrnHid++;//指向下一个隐藏层神经元
}
*curWeight+=err*LearningRate*(-1);//偏移值
}
curNrnHid=vecLayers[0].vecNeurons.begin();//重新指向隐藏层第一个神经元
int n=0;
//对每一个隐藏层神经元
while(curNrnHid!=vecLayers[0].vecNeurons.end()-1){
float err=0;
curNrnOut=vecLayers[1].vecNeurons.begin();//指向第一个输出神经元
//对每一个输出神经元的第一个权重
while(curNrnOut!=vecLayers[1].vecNeurons.end()){
err+=curNrnOut->Error*curNrnOut->vecWeight
;//某种计算误差的方法(BP)
curNrnOut++;
}
err*=curNrnHid->Activation*(1-curNrnHid->Activation);//某种计算误差的方法(BP)
for(int w=0;w<NumInputs;w++)
curNrnHid->vecWeight[w]+=err*LearningRate*SetIn[vec][w];//根据误差更新隐藏层的权重
curNrnHid->vecWeight[NumInputs]+=err*LearningRate*(-1);//偏移值
curNrnHid++;//下一个隐藏层神经元
n++;//下一个权重
}
}
return 1;
}

void NeuralNet::CreateNet(){
if(NumHiddenLayers>0){
vecLayers.push_back(NeuronLayer(NeuronsPerHiddenLayer,NumInputs));
for(int i=0;i<NumHiddenLayers-1;++i)
vecLayers.push_back(NeuronLayer(NeuronsPerHiddenLayer,NeuronsPerHiddenLayer));
vecLayers.push_back(NeuronLayer(NumOutputs,NeuronsPerHiddenLayer));
}
else{
vecLayers.push_back(NeuronLayer(NumOutputs,NumInputs));
}
}

void NeuralNet::InitializeNetwork(){
for(int i=0;i<NumHiddenLayers+1;++i)
for(int n=0;n<vecLayers[i].NumNeurons;++n)
for(int k=0;k<vecLayers[i].vecNeurons
.NumInputs;++k)
vecLayers[i].vecNeurons
.vecWeight[k]=RandomClamped();//随机生成权重
ErrorSum=9999;
NumEpochs=0;
}

float NeuralNet::Sigmoid(float activation, float response){
return ( 1 / ( 1 + exp(-activation / response)));
}

NeuralNet::NeuralNet(int inputs, int outputs, int hiddenneurons, float learningRate, STOPTYPE type, bool debug):
NumInputs(inputs),
NumOutputs(outputs),
NumHiddenLayers(1),
NeuronsPerHiddenLayer(hiddenneurons),
LearningRate(learningRate),
ERROR_THRESHOLD(0.01),
Count(10000),
StopType(type),
Debug(debug),
ErrorSum(9999),
Trained(false),
NumEpochs(0){
CreateNet();
}

vector<float> NeuralNet::Update(vector<float> inputs){
vector<float> outputs;
int cWeight = 0;
if (inputs.size()!=NumInputs)
return outputs;
for(int i=0;i<NumHiddenLayers+1;++i){
if(i>0)
inputs=outputs;
outputs.clear();
cWeight = 0;
for(int n=0;n<vecLayers[i].NumNeurons;++n){
float netinput=0;
int	numInputs=vecLayers[i].vecNeurons
.NumInputs;
for (int k=0;k<numInputs-1;++k)
netinput+=vecLayers[i].vecNeurons
.vecWeight[k]*inputs[cWeight++];
netinput+=vecLayers[i].vecNeurons
.vecWeight[numInputs-1]*(-1);
vecLayers[i].vecNeurons
.Activation=Sigmoid(netinput,ACTIVATION_RESPONSE);
outputs.push_back(vecLayers[i].vecNeurons
.Activation);//即输出
cWeight = 0;
}
}
return outputs;
}

NeuralNet::NeuralNet(string filename){
FILE *sourcefile=fopen(filename.c_str(),"rb");
fread(&this->NumInputs,sizeof(int),1,sourcefile);
fread(&this->NumOutputs,sizeof(int),1,sourcefile);
fread(&this->NumHiddenLayers,sizeof(int),1,sourcefile);
fread(&this->NeuronsPerHiddenLayer,sizeof(int),1,sourcefile);
fread(&this->LearningRate,sizeof(float),1,sourcefile);
fread(&this->ErrorSum,sizeof(float),1,sourcefile);
fread(&this->Trained,sizeof(bool),1,sourcefile);
fread(&this->NumEpochs,sizeof(int),1,sourcefile);
fread(&this->ERROR_THRESHOLD,sizeof(float),1,sourcefile);
fread(&this->Count,sizeof(long int),1,sourcefile);
this->CreateNet();
for(int i=0;i<this->vecLayers.size();i++){
fread(&this->vecLayers[i].NumNeurons,sizeof(int),1,sourcefile);
for(int j=0;j<this->vecLayers[i].vecNeurons.size();j++){
fread(&this->vecLayers[i].vecNeurons[j].NumInputs,sizeof(int),1,sourcefile);
fread(&this->vecLayers[i].vecNeurons[j].Activation,sizeof(float),1,sourcefile);
fread(&this->vecLayers[i].vecNeurons[j].Error,sizeof(float),1,sourcefile);
for(int k=0;k<this->vecLayers[i].vecNeurons[j].vecWeight.size();k++)
fread(&this->vecLayers[i].vecNeurons[j].vecWeight[k],sizeof(float),1,sourcefile);
}
}
fclose(sourcefile);
}

void NeuralNet::saveNet(string filename){
FILE *sourcefile=fopen(filename.c_str(),"wb");
fwrite(&this->NumInputs,sizeof(int),1,sourcefile);
fwrite(&this->NumOutputs,sizeof(int),1,sourcefile);
fwrite(&this->NumHiddenLayers,sizeof(int),1,sourcefile);
fwrite(&this->NeuronsPerHiddenLayer,sizeof(int),1,sourcefile);
fwrite(&this->LearningRate,sizeof(float),1,sourcefile);
fwrite(&this->ErrorSum,sizeof(float),1,sourcefile);
fwrite(&this->Trained,sizeof(bool),1,sourcefile);
fwrite(&this->NumEpochs,sizeof(int),1,sourcefile);
fwrite(&this->ERROR_THRESHOLD,sizeof(float),1,sourcefile);
fwrite(&this->Count,sizeof(long int),1,sourcefile);
for(int i=0;i<this->vecLayers.size();i++){
fwrite(&this->vecLayers[i].NumNeurons,sizeof(int),1,sourcefile);
for(int j=0;j<this->vecLayers[i].vecNeurons.size();j++){
fwrite(&this->vecLayers[i].vecNeurons[j].NumInputs,sizeof(int),1,sourcefile);
fwrite(&this->vecLayers[i].vecNeurons[j].Activation,sizeof(float),1,sourcefile);
fwrite(&this->vecLayers[i].vecNeurons[j].Error,sizeof(float),1,sourcefile);
for(int k=0;k<this->vecLayers[i].vecNeurons[j].vecWeight.size();k++)
fwrite(&this->vecLayers[i].vecNeurons[j].vecWeight[k],sizeof(float),1,sourcefile);
}
}
fclose(sourcefile);
}

bool NeuralNet::Train(Data *data){
cout<<"Training..."<<endl;
if(Trained==1)
return false;
vector<vector<float> > SetIn=data->GetInputSet();
vector<vector<float> > SetOut=data->GetOutputSet();
InitializeNetwork();
if(StopType==COUNT){
long int i=Count;
while(i--){
if(Debug)
cout<<"ErrorSum:"<<ErrorSum<<endl;
NetworkTrainingEpoch(SetIn,SetOut);
}
}
else{
while(ErrorSum>ERROR_THRESHOLD){
if(Debug)
cout<<"ErrorSum:"<<ErrorSum<<endl;
NetworkTrainingEpoch(SetIn,SetOut);
}
}
Trained=true;
cout<<"Done!!!"<<endl;
return true;
}


main.cpp

#include"NeuralNet.h"
int main(){

//训练数据
vector<float> input1;
input1.push_back(1.0);
input1.push_back(1.0);
vector<float> output1;
output1.push_back(0.0);

vector<float> input2;
input2.push_back(1.0);
input2.push_back(-1.0);
vector<float> output2;
output2.push_back(1.0);

vector<float> input3;
input3.push_back(-1.0);
input3.push_back(-1.0);
vector<float> output3;
output3.push_back(0.0);

vector<float> input4;
input4.push_back(-1.0);
input4.push_back(1.0);
vector<float> output4;
output4.push_back(1.0);

//建立一个数据类
Data* MyData=new Data(2,1);//2个输入,一个输出
MyData->AddData(input1,output1);//添加数据
MyData->AddData(input2,output2);
MyData->AddData(input3,output3);
MyData->AddData(input4,output4);

NeuralNet* Brain=new NeuralNet(2,1,4,0.1,NeuralNet::ERRORSUM,true);//新建一个神经网络,输入神经元个数,输出神经元个数,隐藏层神经元个数,学习率,停止训练方法(次数或误差最小),是否输出误差值(用于观察是否收敛)
Brain->SetErrorThrehold(0.01);//设置误差,默认0.01
//Brain->SetCount(10000);设置次数,默认10000
Brain->Train(MyData);//通过数据,开始训练
cout<<Brain->Update(input1)[0]<<endl;//通过输入得到输出
cout<<Brain->Update(input2)[0]<<endl;
cout<<Brain->Update(input3)[0]<<endl;
cout<<Brain->Update(input4)[0]<<endl;

Brain->saveNet("D:\\1.txt");//保存网络

NeuralNet* Brain2=new NeuralNet("D:\\1.txt");//通过文件读取网络
cout<<Brain2->Update(input1)[0]<<endl;
cout<<Brain2->Update(input2)[0]<<endl;
cout<<Brain2->Update(input3)[0]<<endl;
cout<<Brain2->Update(input4)[0]<<endl;

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: