参考资料:人工神经网络-韩力群PPT
看了一些关于基于神经网络的语言模型, 与传统语言模型相比, 除了计算量让人有点不满意之外, 不需要额外的平滑算法, 感觉它们的效果让人惊讶。 这些网络里面都能看到BP的影子, 可以说BP网络是最基本的, 掌握扎实了, 对其他结构理解会更深刻, 于是早在学习语言模型之前我自己曾经用c++写过一个简单的BP网络,虽然功能简单,只有最基本的三层结构,但让自己对误差反传理解的更深刻。那个时候自己还没开始写博客, 现在把以前的代码放上来吧, 那个时候写代码没考虑任何优化,达到功能即可,所以很多模块没有做成函数,也没有考虑如何加速网络,比如矩阵相乘,我用的就是最老实的多重循环来计算。最后利用这个网络给了一个简单的例子——用这个BP网络来计算异或运算。这样更容易理解,网络先进行训练,由人为给定异或运算的样本规则,然后让其自行学习,最后是测试模块(那个时候我写的函数叫work,当时不知道准确的叫法应该是test).
本文不对BP网络做推导,因为网上随便一搜就很多,下面是一个典型的三层结构,图来自人工神经网络-韩力群PPT
下面是代码,代码组织方式是按照标准的公式推导的,以前写这个代码时参照了网上的,但现在确实找不到是哪儿的了,就没办法引用了。下面直接上代码,因为注释的非常详细,所以过程就不做解释了。
首先BP网络的结构定义在BpNet.h,内容如下:
#include <iostream>
#include <fstream>
#include <ctime>
#include <cmath>
using namespace std;
/*常量区域*/
#define N 4 //样本的个数
#define IN 2 //输入层神经元个数
#define HN 2 //隐层神经元个数
#define ON 1 //输出层神经元个数
//定义存放学习样本的结构
class StudyData
{
public:
float input[IN]; //输入样本
float teach[ON]; //期望输出(教师信号)
StudyData();
virtual ~StudyData();
};
//定义BP神经网络的类结构
class BpNet
{
public:
void GetOutput(); //从键盘输入数据,输出进行保存
void Work(char *weight, char *threshold); //将训练好的数据进行工作
double GetSumErr(); //得到总误差
//进行训练
void Train(char *sampleFileName, char *weight, char *threshold);
void ReadWeight(char *weight, char *threshold); //读取权值阈值到神经网络中
void SaveBpNet(char *weight, char *threshold); //保存权值
void UpdateWeight(int m); //更新权值
void ErrorSignal(int m); //算误差信号
void NetInputOutput(int m); //算各层输出输入
void GetTrainingData(char *sampleFileName); //从外存获取样本集
void StartShow(void);
StudyData studyData[N]; //存放多组学习样本的数组
float W[HN][IN]; //输入层到隐层权值数组
float V[ON][HN]; //隐层到输出层权值数组
float HU_HN[HN]; //隐层神经元阈值数组
float HU_ON[ON]; //输出层神经元阈值数组
float IN_HN[HN]; //隐层的输入
float OUT_HN[HN]; //隐层的输出
float IN_ON[ON]; //输出层的输入
float OUT_ON[ON]; //输出层的输出
float E[N]; //样本组误差数组,每个分量为一组样本的误差
float stuRate1; //输出层至隐层学习效率
float stuRate2; //隐层至输入层学习效率
float errSignalON[ON]; //δk,输出层的误差信号数组
float errSignalHN[HN]; //δj,隐层的误差信号数组
BpNet();
virtual ~BpNet();
};
#include <fstream>
#include <ctime>
#include <cmath>
using namespace std;
/*常量区域*/
#define N 4 //样本的个数
#define IN 2 //输入层神经元个数
#define HN 2 //隐层神经元个数
#define ON 1 //输出层神经元个数
//定义存放学习样本的结构
class StudyData
{
public:
float input[IN]; //输入样本
float teach[ON]; //期望输出(教师信号)
StudyData();
virtual ~StudyData();
};
//定义BP神经网络的类结构
class BpNet
{
public:
void GetOutput(); //从键盘输入数据,输出进行保存
void Work(char *weight, char *threshold); //将训练好的数据进行工作
double GetSumErr(); //得到总误差
//进行训练
void Train(char *sampleFileName, char *weight, char *threshold);
void ReadWeight(char *weight, char *threshold); //读取权值阈值到神经网络中
void SaveBpNet(char *weight, char *threshold); //保存权值
void UpdateWeight(int m); //更新权值
void ErrorSignal(int m); //算误差信号
void NetInputOutput(int m); //算各层输出输入
void GetTrainingData(char *sampleFileName); //从外存获取样本集
void StartShow(void);
StudyData studyData[N]; //存放多组学习样本的数组
float W[HN][IN]; //输入层到隐层权值数组
float V[ON][HN]; //隐层到输出层权值数组
float HU_HN[HN]; //隐层神经元阈值数组
float HU_ON[ON]; //输出层神经元阈值数组
float IN_HN[HN]; //隐层的输入
float OUT_HN[HN]; //隐层的输出
float IN_ON[ON]; //输出层的输入
float OUT_ON[ON]; //输出层的输出
float E[N]; //样本组误差数组,每个分量为一组样本的误差
float stuRate1; //输出层至隐层学习效率
float stuRate2; //隐层至输入层学习效率
float errSignalON[ON]; //δk,输出层的误差信号数组
float errSignalHN[HN]; //δj,隐层的误差信号数组
BpNet();
virtual ~BpNet();
};
其次是BpNet.h的实现,如下:
#include "stdafx.h"
#include "BpNet.h"
//
// Construction/Destruction
//
/*BpNet构造函数,对变量进行初始化*/
BpNet::BpNet()
{
srand(time(NULL)); //随机数种子
int i, j;
for (i=0; i<HN; i++) //输入层到隐层权值初始化为0附近的小数
{
for (j=0; j<IN; j++)
{
W[i][j] = ( float )(((rand()/32767.0)*2-1)/2);
}
}
for (i=0; i<ON; i++) //隐层到输出层权值初始化为0附近的小数
{
for (j=0; j<HN; j++)
{
V[i][j] = ( float )(((rand()/32767.0)*2-1)/2);
}
}
for (i=0; i<HN; i++) //阈值初始化
{
HU_HN[i] = 1.0;
}
for (i=0; i<ON; i++) //阈值初始化
{
HU_ON[i] = 1.0;
}
stuRate1 = 0.5; //输出层到隐层的学习率初始化
stuRate2 = 0.5; //隐层到输入层的学习率初始化
//测试权值的输出
/*
for (i=0; i<HN; i++)
{
for (j=0; j<IN; j++)
{
cout << W[i][j] << " ";
}
cout << endl;
}
cout << endl;
for (i=0; i<ON; i++)
{
for (j=0; j<HN; j++)
{
cout << V[i][j] << " ";
}
cout << endl;
}*/
}
/*BpNet析构函数,对动态申请内存进行释放*/
BpNet::~BpNet()
{
}
/*程序开始*/
void BpNet::StartShow()
{
cout << endl;
cout << "*************BP算法的使用程序(C++版)********" << endl << endl;
}
/**从文件中读取样本, 并显示到屏幕上以便于进行核对**/
void BpNet::GetTrainingData( char *sampleFileName)
{
ifstream infile(sampleFileName, ios::in); //打开样本文件
if (!infile) //如果打开失败
{
cerr << "打开样本文件出错" << endl;
exit(1);
}
int i, j; //循环下标
float temp;
for (i=0; i<N; i++) //i表示每一趟读取一个样本
{
for (j=0; j<IN+ON; j++)
{
if (!(infile >> temp)) //从文件读入一个数
{
cerr << "读入文件元素过程中出错,或以达到文件结尾" << endl;
}
if (j > (IN-1)) //读入教师信号
{
studyData[i].teach[j-IN] = temp;
}
else //读入样本输入
{
studyData[i].input[j] = temp;
}
}
}
infile.close(); //关闭文件
/*文件中的样本信息输出到屏幕*/
cout << "从指定文件中成功载入"
<< N * (IN + ON)
<< "个数据,显示如下:" << endl << endl;
for (i=0; i<IN; i++) //进行排版
{
cout << "输入值" << " " ;
}
for (i=0; i<ON; i++)
{
cout << "期望值" << " " ;
}
cout << endl;
for (i=0; i<N; i++)
{
for (j=0; j<IN+ON; j++)
{
if (j > (IN-1)) //输出教师信号
{
cout <<studyData[i].teach[j-IN] << " " ;
}
else //输出样本输入
{
cout <<studyData[i].input[j] << " " ;
}
}
cout << endl;
}
}
StudyData::StudyData()
{
}
StudyData::~StudyData()
{
}
void BpNet::NetInputOutput( int m)
{
//求BP网络的神经网络各层进输入输出
//断言参数m合法 参数m表示第m组测试样本
int i, j;
float sum = 0.0;
for (i=0; i<HN; i++) //求隐层的净输入输出
{
for (j=0; j<IN; j++)
{
sum += W[i][j] * studyData[m].input[j]; //算隐层第i个神经元不包含阈值的输入
}
IN_HN[i] = sum + HU_HN[i]; //算隐层第i个神经元的净输入
OUT_HN[i] = 1.0 / (1.0 + exp(-IN_HN[i])); //算隐层第i个神经元的输出
}
sum = 0.0;
for (i=0; i<ON; i++) //求输出层的输入输出
{
for (j=0; j<HN; j++)
{
sum += V[i][j] * OUT_HN[j]; //算输出层第i个神经元的输入(不含阈值)
}
IN_ON[i] = sum + HU_ON[i]; //算输出层第i个神经元的净输入
OUT_ON[i] = 1.0 / (1.0 + exp(-IN_ON[i])); //算输出层第i个神经元的输出
}
//测试输入输出 打印到屏幕上
/*
for (i=0; i<ON; i++)
{
cout << "输出层输出" << OUT_ON[i] << endl;
}
for (i=0; i<HN; i++)
{
cout << "隐层输入" << IN_HN[i] << endl;
}*/
}
void BpNet::ErrorSignal( int m)
{
//算误差信号δk,δj
//断言m合法,m表示第m组样本
int k;
float absErr[ON]; //期望-输出即绝对误差
float sqrErr = 0.0; //误差平方和
for (k=0; k<ON; k++)
{
absErr[k] = studyData[m].teach[k] - OUT_ON[k]; //算绝对误差
//算输出层的误差信号
errSignalON[k] = absErr[k] * OUT_ON[k] * (1.0-OUT_ON[k]);
sqrErr += absErr[k] * absErr[k]; //算第m组样本的误差平方和
}
E[m] = sqrErr / 2; //算第m组样本的总误差
int j; //下面算隐层的误差信号δj
float sum;
for (j=0; j<HN; j++)
{
sum = 0.0;
for (k=0; k<ON; k++)
{
sum += errSignalON[k] * V[k][j];
}
errSignalHN[j] = sum * OUT_HN[j] * (1-OUT_HN[j]); //得到隐层的误差信号
}
}
void BpNet::UpdateWeight( int m)
{
//更新两层权值,阈值
//断言m合法,m表示第m组测试样本
int i, j;
float deltaWeight; //权值的改变量
for (i=0; i<ON; i++) //更新隐层到输出层权值
{
for (j=0; j<HN; j++)
{
deltaWeight = stuRate1 * errSignalON[i] * OUT_HN[j]; //计算权值的改变量
V[i][j] += deltaWeight; //更新权值
}
HU_ON[i] += stuRate1 * errSignalON[i]; //更新输出层阈值
}
for (i=0; i<HN; i++) //调整隐层到输出层的权值
{
for (j=0; j<IN; j++)
{
//权值改变量
deltaWeight = stuRate2 * errSignalHN[i] * studyData[m].input[j];
W[i][j] += deltaWeight; //更新权值
}
HU_HN[i] += stuRate2 * errSignalHN[i]; //更新隐层阈值
}
}
void BpNet::SaveBpNet( char *weight, char *threshold)
{
//保存权值到指定的文件名weight内
//保存阈值到指定的文件名threshold内
ofstream outfile(weight, ios::out); //打开保存权值的文件
if (!outfile)
{
cerr << "打开权值文件失败" << endl;
exit(1);
}
int i, j;
for (i=0; i<HN; i++) //输入层到隐层的权值写入
{
for (j=0; j<IN; j++)
{
outfile << W[i][j] << " " ;
}
}
for (i=0; i<ON; i++) //将输出层到隐层的权值写入
{
for (j=0; j<HN; j++)
{
outfile << V[i][j] << " " ;
}
}
outfile.close(); //关闭文件
ofstream outfile2(threshold, ios::out); //打开阈值文件
if (!outfile2)
{
cerr << "阈值文件打开失败" << endl;
exit(1);
}
for (i=0; i<HN; i++) //写入隐层的阈值
{
outfile2 << HU_HN[i] << " " ;
}
for (i=0; i<ON; i++) //写入输出层的阈值
{
outfile2 << HU_ON[i] << " " ;
}
outfile2.close(); //关闭文件
}
void BpNet::ReadWeight( char *weight, char *threshold)
{
//从训练好网络中读取权值、阈值等
ifstream infile1(weight, ios::in); //打开文件
if (!infile1)
{
cerr << "打开权值文件失败" << endl;
exit(1);
}
int i, j;
float temp;
for (i=0; i<HN; i++) //读取输入层权值到隐层权值
{
for (j=0; j<IN; j++)
{
if (infile1 >> temp)
{
W[i][j] = temp;
}
}
}
for (i=0; i<ON; i++) //读取输出层到隐层的权值
{
for (j=0; j<HN; j++)
{
if (infile1 >> temp)
{
V[i][j] = temp;
}
}
}
infile1.close(); //关闭权值文件
ifstream infile2(threshold, ios::in); //打开阈值文件
if (!infile2)
{
cerr << "阈值文件打开失败" << endl;
exit(1);
}
for (i=0; i<HN; i++) //读取隐层阈值
{
if (infile2 >> temp)
{
HU_HN[i] = temp;
/* cout << "隐层的阈值是" << HU_HN[i] << endl;*/
}
}
for (i=0; i<ON; i++) //读取输出层阈值
{
if (infile2 >> temp)
{
HU_ON[i] = temp;
/* cout << "输出层的阈值是" << HU_ON[i] << endl;*/
}
}
}
double BpNet::GetSumErr()
{
//求总误差,即所有样本的总误差
int m;
double sumErr = 0.0;
for (m=0; m<N; m++)
{
sumErr += E[m];
}
return sumErr;
}
void BpNet::Train( char *sampleFileName, char *weight, char *threshold)
{
//对给定文件名sampleFileName提取样本数据进行训练
//训练达到一定的精度后将权值存于weight
//阈值存于threshold内
int limitStudyTimes = 400000; //限定内的学习次数
long int studyFileTimes = 0; //学习文件次数,即对sample整个文件学习一次,算一次
long int studySampleTimes = 0; //样本学习次数,一组样本学习一次算一次
double minErr = 0.000001; //最小的学习误差,达到这个精度后训练完毕
double sumErr; //由实际训练得到的样本总误差
int m; //表示sample文件第m组样本
StartShow(); //程序开始界面
cout << "现在是训练模式···" << endl;
GetTrainingData(sampleFileName); //读取样本数据到内存
do
{
studyFileTimes++;
for (m=0; m<N; m++) //N组样本进行学习训练,循环完毕即一个文件学习完毕
{
NetInputOutput(m); //算网络内部输入输出
ErrorSignal(m); //算误差信号
UpdateWeight(m); //调整权值
}
sumErr = GetSumErr(); //所有样本的总误差
if (studyFileTimes > limitStudyTimes) //超出学习的限定次数,估计收敛不了了,在下去就是无限循环,强制停止
{
cout << "超出限定的学习次数,超时了,强制停止程序" << endl;
break ;
}
cout << "正在进行的训练次数: " << studyFileTimes << "\r" ;
} while (sumErr > minErr);
SaveBpNet(weight, threshold); //保存权值、阈值
cout << "学习次数是:" << studyFileTimes << endl;
cout << "最后误差是" << sumErr << endl;
cout << "权值文件" << weight << "已成功保存" << endl;
cout << "阈值文件" << threshold << "已成功保存" << endl;
}
void BpNet::Work( char *weight, char *threshold)
{
//将训练完毕后得到的权值用于工作
//从键盘输入数据,由神经网络输出结果
ReadWeight(weight, threshold); //读取文件中训练完毕的权值阈值到神经网络中
char flag; //控制是否继续输入
int i;
cout << "现在是工作模式···" << endl;
while (1)
{
GetOutput(); //从键盘输入数据,神经网络的输出存于OUT_ON[]中
for (i=0; i<ON; i++)
{
cout << "输出为:" << OUT_ON[i] << " " ;
}
cout << endl;
cout << "你是否想要继续输入Y/N" << endl;
cin >> flag;
if ( ( 'n' ==flag) || ( 'N' ==flag) )
{
break ;
}
}
}
void BpNet::GetOutput()
{
//从键盘输入数据,神经网络的输出存于ON[]中
int i, j;
float sum = 0.0;
float input[IN];
cout << "请输入神经网络的" << IN << "个输入数据:(以空格隔开)" << endl;
for (i=0; i<IN; i++) //输入数据
{
cin >> input[i];
}
for (i=0; i<HN; i++) //求隐层的净输入输出
{
for (j=0; j<IN; j++)
{
sum += W[i][j] * input[j]; //算隐层第i个神经元不包含阈值的输入
}
IN_HN[i] = sum + HU_HN[i]; //算隐层第i个神经元的净输入
OUT_HN[i] = 1.0 / (1.0 + exp(-IN_HN[i])); //算隐层第i个神经元的输出
}
sum = 0.0;
for (i=0; i<ON; i++) //求输出层的输入输出
{
for (j=0; j<HN; j++)
{
sum += V[i][j] * OUT_HN[j]; //算输出层第i个神经元的输入(不含阈值)
}
IN_ON[i] = sum + HU_ON[i]; //算输出层第i个神经元的净输入
OUT_ON[i] = 1.0 / (1.0 + exp(-IN_ON[i])); //算输出层第i个神经元的输出
}
}
#include "BpNet.h"
//
// Construction/Destruction
//
/*BpNet构造函数,对变量进行初始化*/
BpNet::BpNet()
{
srand(time(NULL)); //随机数种子
int i, j;
for (i=0; i<HN; i++) //输入层到隐层权值初始化为0附近的小数
{
for (j=0; j<IN; j++)
{
W[i][j] = ( float )(((rand()/32767.0)*2-1)/2);
}
}
for (i=0; i<ON; i++) //隐层到输出层权值初始化为0附近的小数
{
for (j=0; j<HN; j++)
{
V[i][j] = ( float )(((rand()/32767.0)*2-1)/2);
}
}
for (i=0; i<HN; i++) //阈值初始化
{
HU_HN[i] = 1.0;
}
for (i=0; i<ON; i++) //阈值初始化
{
HU_ON[i] = 1.0;
}
stuRate1 = 0.5; //输出层到隐层的学习率初始化
stuRate2 = 0.5; //隐层到输入层的学习率初始化
//测试权值的输出
/*
for (i=0; i<HN; i++)
{
for (j=0; j<IN; j++)
{
cout << W[i][j] << " ";
}
cout << endl;
}
cout << endl;
for (i=0; i<ON; i++)
{
for (j=0; j<HN; j++)
{
cout << V[i][j] << " ";
}
cout << endl;
}*/
}
/*BpNet析构函数,对动态申请内存进行释放*/
BpNet::~BpNet()
{
}
/*程序开始*/
void BpNet::StartShow()
{
cout << endl;
cout << "*************BP算法的使用程序(C++版)********" << endl << endl;
}
/**从文件中读取样本, 并显示到屏幕上以便于进行核对**/
void BpNet::GetTrainingData( char *sampleFileName)
{
ifstream infile(sampleFileName, ios::in); //打开样本文件
if (!infile) //如果打开失败
{
cerr << "打开样本文件出错" << endl;
exit(1);
}
int i, j; //循环下标
float temp;
for (i=0; i<N; i++) //i表示每一趟读取一个样本
{
for (j=0; j<IN+ON; j++)
{
if (!(infile >> temp)) //从文件读入一个数
{
cerr << "读入文件元素过程中出错,或以达到文件结尾" << endl;
}
if (j > (IN-1)) //读入教师信号
{
studyData[i].teach[j-IN] = temp;
}
else //读入样本输入
{
studyData[i].input[j] = temp;
}
}
}
infile.close(); //关闭文件
/*文件中的样本信息输出到屏幕*/
cout << "从指定文件中成功载入"
<< N * (IN + ON)
<< "个数据,显示如下:" << endl << endl;
for (i=0; i<IN; i++) //进行排版
{
cout << "输入值" << " " ;
}
for (i=0; i<ON; i++)
{
cout << "期望值" << " " ;
}
cout << endl;
for (i=0; i<N; i++)
{
for (j=0; j<IN+ON; j++)
{
if (j > (IN-1)) //输出教师信号
{
cout <<studyData[i].teach[j-IN] << " " ;
}
else //输出样本输入
{
cout <<studyData[i].input[j] << " " ;
}
}
cout << endl;
}
}
StudyData::StudyData()
{
}
StudyData::~StudyData()
{
}
void BpNet::NetInputOutput( int m)
{
//求BP网络的神经网络各层进输入输出
//断言参数m合法 参数m表示第m组测试样本
int i, j;
float sum = 0.0;
for (i=0; i<HN; i++) //求隐层的净输入输出
{
for (j=0; j<IN; j++)
{
sum += W[i][j] * studyData[m].input[j]; //算隐层第i个神经元不包含阈值的输入
}
IN_HN[i] = sum + HU_HN[i]; //算隐层第i个神经元的净输入
OUT_HN[i] = 1.0 / (1.0 + exp(-IN_HN[i])); //算隐层第i个神经元的输出
}
sum = 0.0;
for (i=0; i<ON; i++) //求输出层的输入输出
{
for (j=0; j<HN; j++)
{
sum += V[i][j] * OUT_HN[j]; //算输出层第i个神经元的输入(不含阈值)
}
IN_ON[i] = sum + HU_ON[i]; //算输出层第i个神经元的净输入
OUT_ON[i] = 1.0 / (1.0 + exp(-IN_ON[i])); //算输出层第i个神经元的输出
}
//测试输入输出 打印到屏幕上
/*
for (i=0; i<ON; i++)
{
cout << "输出层输出" << OUT_ON[i] << endl;
}
for (i=0; i<HN; i++)
{
cout << "隐层输入" << IN_HN[i] << endl;
}*/
}
void BpNet::ErrorSignal( int m)
{
//算误差信号δk,δj
//断言m合法,m表示第m组样本
int k;
float absErr[ON]; //期望-输出即绝对误差
float sqrErr = 0.0; //误差平方和
for (k=0; k<ON; k++)
{
absErr[k] = studyData[m].teach[k] - OUT_ON[k]; //算绝对误差
//算输出层的误差信号
errSignalON[k] = absErr[k] * OUT_ON[k] * (1.0-OUT_ON[k]);
sqrErr += absErr[k] * absErr[k]; //算第m组样本的误差平方和
}
E[m] = sqrErr / 2; //算第m组样本的总误差
int j; //下面算隐层的误差信号δj
float sum;
for (j=0; j<HN; j++)
{
sum = 0.0;
for (k=0; k<ON; k++)
{
sum += errSignalON[k] * V[k][j];
}
errSignalHN[j] = sum * OUT_HN[j] * (1-OUT_HN[j]); //得到隐层的误差信号
}
}
void BpNet::UpdateWeight( int m)
{
//更新两层权值,阈值
//断言m合法,m表示第m组测试样本
int i, j;
float deltaWeight; //权值的改变量
for (i=0; i<ON; i++) //更新隐层到输出层权值
{
for (j=0; j<HN; j++)
{
deltaWeight = stuRate1 * errSignalON[i] * OUT_HN[j]; //计算权值的改变量
V[i][j] += deltaWeight; //更新权值
}
HU_ON[i] += stuRate1 * errSignalON[i]; //更新输出层阈值
}
for (i=0; i<HN; i++) //调整隐层到输出层的权值
{
for (j=0; j<IN; j++)
{
//权值改变量
deltaWeight = stuRate2 * errSignalHN[i] * studyData[m].input[j];
W[i][j] += deltaWeight; //更新权值
}
HU_HN[i] += stuRate2 * errSignalHN[i]; //更新隐层阈值
}
}
void BpNet::SaveBpNet( char *weight, char *threshold)
{
//保存权值到指定的文件名weight内
//保存阈值到指定的文件名threshold内
ofstream outfile(weight, ios::out); //打开保存权值的文件
if (!outfile)
{
cerr << "打开权值文件失败" << endl;
exit(1);
}
int i, j;
for (i=0; i<HN; i++) //输入层到隐层的权值写入
{
for (j=0; j<IN; j++)
{
outfile << W[i][j] << " " ;
}
}
for (i=0; i<ON; i++) //将输出层到隐层的权值写入
{
for (j=0; j<HN; j++)
{
outfile << V[i][j] << " " ;
}
}
outfile.close(); //关闭文件
ofstream outfile2(threshold, ios::out); //打开阈值文件
if (!outfile2)
{
cerr << "阈值文件打开失败" << endl;
exit(1);
}
for (i=0; i<HN; i++) //写入隐层的阈值
{
outfile2 << HU_HN[i] << " " ;
}
for (i=0; i<ON; i++) //写入输出层的阈值
{
outfile2 << HU_ON[i] << " " ;
}
outfile2.close(); //关闭文件
}
void BpNet::ReadWeight( char *weight, char *threshold)
{
//从训练好网络中读取权值、阈值等
ifstream infile1(weight, ios::in); //打开文件
if (!infile1)
{
cerr << "打开权值文件失败" << endl;
exit(1);
}
int i, j;
float temp;
for (i=0; i<HN; i++) //读取输入层权值到隐层权值
{
for (j=0; j<IN; j++)
{
if (infile1 >> temp)
{
W[i][j] = temp;
}
}
}
for (i=0; i<ON; i++) //读取输出层到隐层的权值
{
for (j=0; j<HN; j++)
{
if (infile1 >> temp)
{
V[i][j] = temp;
}
}
}
infile1.close(); //关闭权值文件
ifstream infile2(threshold, ios::in); //打开阈值文件
if (!infile2)
{
cerr << "阈值文件打开失败" << endl;
exit(1);
}
for (i=0; i<HN; i++) //读取隐层阈值
{
if (infile2 >> temp)
{
HU_HN[i] = temp;
/* cout << "隐层的阈值是" << HU_HN[i] << endl;*/
}
}
for (i=0; i<ON; i++) //读取输出层阈值
{
if (infile2 >> temp)
{
HU_ON[i] = temp;
/* cout << "输出层的阈值是" << HU_ON[i] << endl;*/
}
}
}
double BpNet::GetSumErr()
{
//求总误差,即所有样本的总误差
int m;
double sumErr = 0.0;
for (m=0; m<N; m++)
{
sumErr += E[m];
}
return sumErr;
}
void BpNet::Train( char *sampleFileName, char *weight, char *threshold)
{
//对给定文件名sampleFileName提取样本数据进行训练
//训练达到一定的精度后将权值存于weight
//阈值存于threshold内
int limitStudyTimes = 400000; //限定内的学习次数
long int studyFileTimes = 0; //学习文件次数,即对sample整个文件学习一次,算一次
long int studySampleTimes = 0; //样本学习次数,一组样本学习一次算一次
double minErr = 0.000001; //最小的学习误差,达到这个精度后训练完毕
double sumErr; //由实际训练得到的样本总误差
int m; //表示sample文件第m组样本
StartShow(); //程序开始界面
cout << "现在是训练模式···" << endl;
GetTrainingData(sampleFileName); //读取样本数据到内存
do
{
studyFileTimes++;
for (m=0; m<N; m++) //N组样本进行学习训练,循环完毕即一个文件学习完毕
{
NetInputOutput(m); //算网络内部输入输出
ErrorSignal(m); //算误差信号
UpdateWeight(m); //调整权值
}
sumErr = GetSumErr(); //所有样本的总误差
if (studyFileTimes > limitStudyTimes) //超出学习的限定次数,估计收敛不了了,在下去就是无限循环,强制停止
{
cout << "超出限定的学习次数,超时了,强制停止程序" << endl;
break ;
}
cout << "正在进行的训练次数: " << studyFileTimes << "\r" ;
} while (sumErr > minErr);
SaveBpNet(weight, threshold); //保存权值、阈值
cout << "学习次数是:" << studyFileTimes << endl;
cout << "最后误差是" << sumErr << endl;
cout << "权值文件" << weight << "已成功保存" << endl;
cout << "阈值文件" << threshold << "已成功保存" << endl;
}
void BpNet::Work( char *weight, char *threshold)
{
//将训练完毕后得到的权值用于工作
//从键盘输入数据,由神经网络输出结果
ReadWeight(weight, threshold); //读取文件中训练完毕的权值阈值到神经网络中
char flag; //控制是否继续输入
int i;
cout << "现在是工作模式···" << endl;
while (1)
{
GetOutput(); //从键盘输入数据,神经网络的输出存于OUT_ON[]中
for (i=0; i<ON; i++)
{
cout << "输出为:" << OUT_ON[i] << " " ;
}
cout << endl;
cout << "你是否想要继续输入Y/N" << endl;
cin >> flag;
if ( ( 'n' ==flag) || ( 'N' ==flag) )
{
break ;
}
}
}
void BpNet::GetOutput()
{
//从键盘输入数据,神经网络的输出存于ON[]中
int i, j;
float sum = 0.0;
float input[IN];
cout << "请输入神经网络的" << IN << "个输入数据:(以空格隔开)" << endl;
for (i=0; i<IN; i++) //输入数据
{
cin >> input[i];
}
for (i=0; i<HN; i++) //求隐层的净输入输出
{
for (j=0; j<IN; j++)
{
sum += W[i][j] * input[j]; //算隐层第i个神经元不包含阈值的输入
}
IN_HN[i] = sum + HU_HN[i]; //算隐层第i个神经元的净输入
OUT_HN[i] = 1.0 / (1.0 + exp(-IN_HN[i])); //算隐层第i个神经元的输出
}
sum = 0.0;
for (i=0; i<ON; i++) //求输出层的输入输出
{
for (j=0; j<HN; j++)
{
sum += V[i][j] * OUT_HN[j]; //算输出层第i个神经元的输入(不含阈值)
}
IN_ON[i] = sum + HU_ON[i]; //算输出层第i个神经元的净输入
OUT_ON[i] = 1.0 / (1.0 + exp(-IN_ON[i])); //算输出层第i个神经元的输出
}
}
最后是main函数的内容,用来做一个异或运算的测试,内容如下:
sample1.txt内容如下:
0 0 0
0 1 1
1 0 1
1 1 0
0 1 1
1 0 1
1 1 0
表示异或运算的规则
#include "stdafx.h"
#include "BpNet.h"
int main( int argc, char * argv[])
{
/*BP分为两大阶段的函数:用于训练的函数Train(),用于工作的函数Work()*/
BpNet bp;
/*对亦或样本进行训练*/
// bp.Train("sample1.txt", "weight1.txt", "yuzhi1.txt");
/*对亦或样本训练结果进行工作测试*/
bp.Work( "weight1.txt" , "yuzhi1.txt" );
return 0;
}
#include "BpNet.h"
int main( int argc, char * argv[])
{
/*BP分为两大阶段的函数:用于训练的函数Train(),用于工作的函数Work()*/
BpNet bp;
/*对亦或样本进行训练*/
// bp.Train("sample1.txt", "weight1.txt", "yuzhi1.txt");
/*对亦或样本训练结果进行工作测试*/
bp.Work( "weight1.txt" , "yuzhi1.txt" );
return 0;
}