Neural Network based on Eorr Back Propagation典型BP网络c++实现

参考资料:人工神经网络-韩力群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();
};

然后BpNet.h的实现在BpNet.cpp,内容如下:

// BpNet.cpp: implementation of the BpNet class.
//
//////////////////////////////////////////////////////////////////////

#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个神经元的输出
	}
}

最后是main函数的内容,用来做一个异或运算的测试,内容如下:

sample1.txt内容如下:

0 0 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;
}

最后的测试结果图如下:

时间: 2024-10-20 16:31:37

Neural Network based on Eorr Back Propagation典型BP网络c++实现的相关文章

Some Improvements on Deep Convolutional Neural Network Based Image Classif ication

本文的大概思想就是: (1)增加训练样本:(2)增加测试样本预测数量:(3)多个CNN模型的融合: 一.增加训练样本的数量 常用的增加样本的方法有:crop.flip及add randomly generated ligthing: 1.传统的crop方法是把图像resize到256*256,然后在进行crop,但是这样会损失掉一部分有用的信息例如下图: 因此本文采用的方法:先将图像的最小的一边放大到256,这样就形成了256*N或者N*256,然后在进行crop: 2.除了随机加入光照噪声以外

pytorch --Rnn语言模型 -- 《Recurrent neural network based language model》

论文通过实现RNN来完成了文本分类. 论文地址:88888888 模型结构图: 原理自行参考论文,code and comment: 1 # -*- coding: utf-8 -*- 2 # @time : 2019/11/9 15:12 3 4 import numpy as np 5 import torch 6 import torch.nn as nn 7 import torch.optim as optim 8 from torch.autograd import Variable

Recurrent neural network language modeling toolkit 源码剖析(三)

系列前言 参考文献: RNNLM - Recurrent Neural Network  Language Modeling Toolkit(点此阅读) Recurrent neural network based language model(点此阅读) EXTENSIONS OF RECURRENT NEURAL NETWORK LANGUAGE MODEL(点此阅读) Strategies for Training Large Scale Neural Network  Language

Recurrent neural network language modeling toolkit 源码走读(五)

系列前言 参考文献: RNNLM - Recurrent Neural Network  Language Modeling Toolkit(点此阅读) Recurrent neural network based language model(点此阅读) EXTENSIONS OF RECURRENT NEURAL NETWORK LANGUAGE MODEL(点此阅读) Strategies for Training Large Scale Neural Network  Language

Recurrent neural network language modeling toolkit 源码深入剖析系列(一)

系列前言 参考文献: RNNLM - Recurrent Neural Network  Language Modeling Toolkit(点此阅读) Recurrent neural network based language model(点此阅读) EXTENSIONS OF RECURRENT NEURAL NETWORK LANGUAGE MODEL(点此阅读) Strategies for Training Large Scale Neural Network  Language

Recurrent neural network language modeling toolkit 源码深入剖析系列(二)

系列前言 参考文献: RNNLM - Recurrent Neural Network  Language Modeling Toolkit(点此阅读) Recurrent neural network based language model(点此阅读) EXTENSIONS OF RECURRENT NEURAL NETWORK LANGUAGE MODEL(点此阅读) Strategies for Training Large Scale Neural Network  Language

Recurrent neural network language modeling toolkit 源码走读(六)

系列前言 参考文献: RNNLM - Recurrent Neural Network  Language Modeling Toolkit(点此阅读) Recurrent neural network based language model(点此阅读) EXTENSIONS OF RECURRENT NEURAL NETWORK LANGUAGE MODEL(点此阅读) Strategies for Training Large Scale Neural Network  Language

Recurrent neural network language modeling toolkit 源码走读(八)

系列前言 参考文献: RNNLM - Recurrent Neural Network  Language Modeling Toolkit(点此阅读) Recurrent neural network based language model(点此阅读) EXTENSIONS OF RECURRENT NEURAL NETWORK LANGUAGE MODEL(点此阅读) Strategies for Training Large Scale Neural Network  Language

Recurrent neural network language modeling toolkit 源码走读(七)

系列前言 参考文献: RNNLM - Recurrent Neural Network  Language Modeling Toolkit(点此阅读) Recurrent neural network based language model(点此阅读) EXTENSIONS OF RECURRENT NEURAL NETWORK LANGUAGE MODEL(点此阅读) Strategies for Training Large Scale Neural Network  Language