基于BP神经网络的手写体识别

实验描述

  使用BP神经网络,编程实现手写体的识别,输出识别率。

浅谈BP

  BP神经网络也称后向传播学习的前馈型神经网络( Back Propagation Feed-forward Neural
Network,BPFNN/BPNN),是一种应用最为广泛的神经网络。
  BP神经网络是有监督学习网络,是一种按误差逆传播算法训练的多层前馈网络,BP网络能学习和存贮大量的输入-输出模式映射关系,而无需事前揭示描述这种映射关系的数学方程。BP神经网络模型结构包括输入层、隐含层和输出层。
  BP神经网络主要分为信号的正向传播和误差反向传播两个过程,正向传播是指在样本数据输入后,计算输入层到隐含层,再到输出层的数据;误差反向传播就是将输出误差以某种形式通过隐含层向输入层逐层反传,将误差分摊给各层的单元,再使用梯度下降法修正各单元的权值,使得最终输出结果和预期结果的误差最小。一般当网络输出的误差减少到可接受的程度或者进行到预先设定的学习次数时,训练结束。

基于BP神经网络的手写体识别_第1张图片
  BP神经网络最主要的优点是具有极强的非线性映射能力。理论上,对于一个三层和三层以上的BP网络,只要隐层神经元数目足够多,该网络就能以任意精度逼近一个非线性函数。 BP神经网络具有对外界刺激和输入信息进行联想记忆的能力。这是因为它采用了分布并行的信息处理方式,对信息的提取必须采用联想的方式,才能将相关神经元全部调动起来

BP神经网络算法流程

  1. 初始化网络权值。网络权值和神经元偏执随机赋值在(-1,1)之间。
  2. 向前传播输入。
  3. 反向传播误差。
  4. 网络权值与神经元偏置调整。
基于BP神经网络的手写体识别_第2张图片

激活函数

  sigmoid函数的作用是将输入映射到一个(0,1)的输出范围。

基于BP神经网络的手写体识别_第3张图片

运行环境

  系统:Windows系统
  语言:C++
  软件:Dev-C++ 5.11

MNIST数据集

  MNIST是一个非常有名的手写体数字识别数据集,在很多资料中,这个数据集都会被用作深度学习的入门样例。
  数据集下载网址:http://yann.lecun.com/exdb/mnist/

数据集简介

  MINIST实验包含了四个文件,其中:
  train-images-idx3-ubyte是60000个图片样本;跳过开头16个字节,每784个字节代表一张图片,其中每一个字节代表28 * 28 中的一个像素点,取值范围为0~256。(本次实验中,像素点值大于128, 输入层的0-1矩阵对应维取1;小于128,0-1矩阵对应维取0)。

基于BP神经网络的手写体识别_第4张图片
  train-labels-idx1-ubyte是这60000个图片对应的数字标签,跳过开头8个字节;
基于BP神经网络的手写体识别_第5张图片
  t10k-images-idx3-ubyte是用于测试的样本,数量为10000张,跳过开头16个字节;   t10k-labels-idx1-ubyte是测试样本对应的数字标签,片数量为10000张,跳过开头8个字节。 MNIST的图像大小是28*28,先读取训练集中的第一张图像。   注意:在 train-images-idx3-ubyte 文件头部有4个integer类型,需要跳过去。
基于BP神经网络的手写体识别_第6张图片

  数据集和整个实验项目在https://gitee.com/haaaaaaaaaaaa/SourceCode.git下的IntelligenceSystem/BPNNHandwritingRecognition中。

实验中数据处理

  对每一张手写图片,先把它处理成一个28 * 28 的0-1矩阵,其中1代表数字的笔画着色部分,0则代表空白。然后我们把该矩阵,扁平成一个784维的输入向量,输入到输入层。经过隐含层(此次实验隐藏层结点数取100),到达输出层时,是一个10维的输出向量,每一位分别对应数字的0~9 。

实验步骤

  1. 提取数据(使用MNIST数据集)
  2. 特征提取
  3. 训练BP神经网络
    1)权值和神经元偏置随机赋值
    2)前向求出隐含层和输出层的输出
    3)求出输出层与预期输出的误差
    4)反向传播误差,求出隐含层误差
    5)调整权值和神经元偏置
    6)结束(文件读取完毕)
  4. 数据测试,输出识别率

实验代码

/*
Description:智能系统理论与应用实验
			题目:基于BP神经网络的手写体识别
			1、提取数据(使用MNIST数据集)
			2、特征提取
			3、训练BP神经网络
				1)权值和神经元偏置随机赋值
				2)前向求出隐含层和输出层的输出
				3)求出输出层与预期输出的误差
				4)反向传播误差,求出隐含层误差
				5)调整权值和神经元偏置
				6)结束(文件读取完毕) 
			4、数据测试,输出识别率 
Date:		2020/05/25
Version:	Dev-C++ 5.11
*/

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

/*全局变量*/ 
const int first = 784;			//输入层到隐藏层,输入层是784维输入向量(数据集中照片是28*28) 
const int second = 100;			//隐含层节点数取100 
const int third = 10;			//输出层是一个10维的向量,每一位分别对应数字0~9 
const double alpha = 0.35;		//神经网络的学习率 
int input[first];				//784维输入向量					
int target[third];				//10维输出向量					
double weight1[first][second];	//输入层到隐含层权重		
double weight2[second][third];	//隐含层到输出层权重		
double output1[second];			//隐含层输出	
double output2[third];			//输出层输出,即目标输出		
double delta1[second];			//隐含层误差 
double delta2[third];			//输出层与预期输出的误差 
double b1[second];				//隐含层神经元偏置,偏置的存在是为了更好的拟合数据	
double b2[third];				//输出层神经元偏置,偏置的存在是为了更好的拟合数据		
double test_num = 0.0;			//测试次数 
double test_success_count = 0.0;//测试成功次数 

/*激活函数*/ 
//sigmoid函数的作用是将输入映射到一个(0,1)的输出范围 
double Sigmoid(double x){
	return 1.0 / (1.0 + exp(-x));
}

/*初始化权重矩阵和神经元偏置*/ 
void Initialize(){
	srand((int)time(0) + rand());//算法的随机种子数,有这个数以后才可以产生随机数 ,只调用rand,每次出来的东西是一样的 
	for (int i = 0; i < first; i++){
		for (int j = 0; j < second; j++){
			weight1[i][j] = rand()%1000 * 0.001 - 0.5;//输入层到隐含层权值 
		}
	}	
	for (int j = 0; j < second; j++){
		for (int k = 0; k < third; k++){
			weight2[j][k] = rand()%1000 * 0.001 - 0.5;//隐含层到输出层权值 
		}
	}
	for (int j = 0; j < second; j++){
		b1[j] = rand()%1000 * 0.001 - 0.5;//隐含层神经元偏置 
	}
	for (int k = 0; k < third; k++){
		b2[k] = rand()%1000 * 0.001 - 0.5;//输出层神经元偏置 
	}
}
/*隐含层输出*/ 
void HiddenLayerOutput(){
	for (int j = 0; j < second; j++){
		double sigma = 0;
		for (int i = 0; i < first; i++){
			sigma += input[i] * weight1[i][j]; //输入和权值乘积的累加和 
		}
		double x = sigma + b1[j];//加上神经元偏置 
		output1[j] = Sigmoid(x);//激活函数激活,得到隐含层输出 
	}
}

/*输出层输出*/ 
void OutputLayerOutput(){
	for (int k = 0; k < third; k++){
		double sigma = 0;
		for (int j = 0; j < second; j++){
			sigma += output1[j] * weight2[j][k];//隐含层输出和权值乘积的累加和 
		}
		double x = sigma + b2[k];//加上神经元偏置 
		output2[k] = Sigmoid(x);//激活函数激活,得到输出层输出 
	}
}

/*计算输出层与预期输出的误差*/ 
void OutputTargetError(){
	for (int k = 0; k < third; k++){
		delta2[k] = (output2[k]) * (1.0 - output2[k]) * (output2[k] - target[k]);
	}
}

/*反向传播误差,求出隐含层误差*/ 
void HiddenLayerError(){
	for (int j = 0; j < second; j++){
		double sigma = 0;
		for (int k = 0; k < third; k++){
			sigma += weight2[j][k] * delta2[k];//需要用到输出层和预期输出之间的误差 
		}
		delta1[j] = (output1[j]) * (1.0 - output1[j]) * sigma; 
	}
}

/*调整隐含层到输出层的权值和神经元偏置*/
void FeedbackThirdLayer(){
	for (int k = 0; k < third; k++){
		b2[k] = b2[k] - alpha * delta2[k];//调整神经元偏置 
		for (int j = 0; j < second; j++){
			weight2[j][k] = weight2[j][k] - alpha * output1[j] * delta2[k];//调整权值 
		}
	}
}

/*调整输入层到隐含层的权值和神经元偏置*/ 
void FeedbackSecondLayer(){
	for (int j = 0; j < second; j++){
		b1[j] = b1[j] - alpha * delta1[j];//调整神经元偏置 
		for (int i = 0; i < first; i++){
			weight1[i][j] = weight1[i][j] - alpha * input[i] * delta1[j];//调整权值 
		}
	}
}

/*训练*/ 
void Training(){
	FILE *image_train;
	FILE *image_label;
	image_train = fopen("../MNIST/train-images.idx3-ubyte", "rb");
	image_label = fopen("../MNIST/train-labels.idx1-ubyte", "rb");
	if (image_train == NULL || image_label == NULL){
		cout << "Can't open the file!" << endl;
		exit(0);
	}

	unsigned char image_buf[784];
	unsigned char label_buf[10];
	
	int useless[1000];//存放测试文件中开头无用的数据 
	fread(useless, 1, 16, image_train);//跳过开头16字节 
	fread(useless, 1, 8, image_label);//跳过开头8字节 

	int count = 0;//计算图片数量 
	cout << "Start training..." << endl;
	//循环60000次 
	while (!feof(image_train) && !feof(image_label)){
		memset(image_buf, 0, 784);
		memset(label_buf, 0, 10);
		fread(image_buf, 1, 784, image_train);//每784个字节为一张图片 
		fread(label_buf, 1, 1, image_label);//读取对应数字标签 

		//处理成一个28 * 28 的0-1矩阵,其中1代表数字的笔画着色部分,0则代表空白
		//MNIST数据集中图片背景为黑色(0纯黑),手写体为白色(255纯白) 
		for (int i = 0; i < 784; i++){
			if ((unsigned int)image_buf[i] < 128){
				input[i] = 0;
			}
			else{
				input[i] = 1;
			}
		}

		//初始化目标输出 
		int target_value = (unsigned int)label_buf[0];
		for (int k = 0; k < third; k++){
			target[k] = 0;
		}
		target[target_value] = 1;

		//训练
		HiddenLayerOutput();
		OutputLayerOutput();
		OutputTargetError();
		HiddenLayerError();
		FeedbackSecondLayer();
		FeedbackThirdLayer();

		count ++;
		//每1000张显示一次 
		if (count % 1000 == 0){
			cout << "Training number: " << count << endl;
		}
	}
	cout << endl;
}

/*测试*/
void Testing(){
	FILE *image_test;
	FILE *image_test_label;
	image_test = fopen("../MNIST/t10k-images.idx3-ubyte", "rb");
	image_test_label = fopen("../MNIST/t10k-labels.idx1-ubyte", "rb");
	if (image_test == NULL || image_test_label == NULL){
		cout << "Can't open the file!" << endl;
		exit(0);
	}

	unsigned char image_buf[784];
	unsigned char label_buf[10];
	
	int useless[1000];//存放测试文件中开头无用的数据 
	fread(useless, 1, 16, image_test);//跳过开头16字节 
	fread(useless, 1, 8, image_test_label);//跳过开头8字节 

	while (!feof(image_test) && !feof(image_test_label)){
		memset(image_buf, 0, 784);
		memset(label_buf, 0, 10);
		fread(image_buf, 1, 784, image_test);//每784个字节为一张图片 
		fread(label_buf, 1, 1, image_test_label);//读取对应数字标签 

		//处理成一个28 * 28 的0-1矩阵,其中1代表数字的笔画着色部分,0则代表空白
		for (int i = 0; i < 784; i++){
			if ((unsigned int)image_buf[i] < 128){
				input[i] = 0;
			}
			else{
				input[i] = 1;
			}
		}

		//初始化目标输出 
		for (int k = 0; k < third; k++){
			target[k] = 0;
		}
		int target_value = (unsigned int)label_buf[0];
		target[target_value] = 1;
		
		//得到输出 
		HiddenLayerOutput();
		OutputLayerOutput();

		double max_value = -99999;
		int max_index = 0;
		for (int k = 0; k < third; k++){
			if (output2[k] > max_value){
				max_value = output2[k];
				max_index = k;
			}
		}

		//系统输出和预期输出对比 
		if (target[max_index] == 1){
			test_success_count ++;
		}
		
		test_num ++;

		if ((int)test_num % 1000 == 0){
			cout << "Testing number: " << test_num << "  Success number: " << test_success_count << endl;
		}
	}
	cout << endl;
	cout << "The success rate: " << test_success_count / test_num << endl;
}
/*主函数*/
int main(){
	cout<<"******************************************************************"<<endl; 
	cout<<"*                     智能系统理论与应用实验                     *"<<endl; 
	cout<<"*                   基于BP神经网络的手写体识别                   *"<<endl; 
	cout<<"******************************************************************"<<endl; 
	Initialize();
	Training();
	Testing();
	system("pause");
}

总结

  本人对BP的理解还不够深入,有不对的地方希望大家指出,一起探讨。


声明:
如因本人发布的作品内容涉及版权或存在其他问题,请联系我进行删除。
谢谢浏览!

你可能感兴趣的:(笔记,神经网络)