C语言实现全连接神经网络识别手写字符-MNIST字符集

可能存在一些语法错误,误差计算似乎有问题

setting.h

#ifndef SETTING_H
#define SETTING_H

#include "time.h"
#include <stdio.h>

#define IPNNUM 784
#define HDNNUM 100//取自hdnnum=(ipnnum*opmmum)^0.5,约数。
//若取log 2 ipnnum,hdnnum=10(约数)隐含层太少,准确率低
#define OPNNUM 10
#define TrainImgNum 60000
#define TestImgNum 10000

typedef struct
{
	unsigned char tag;
	double data[IPNNUM];
	double label[OPNNUM];
} ImgData;//单个图像信息存储结构体

typedef struct
{
	ImgData mImgData[TrainImgNum];
}GetTrainImgs;//存储所有训练图像数据

typedef struct
{
	ImgData mImgData[TestImgNum];
}GetTestImgs;//存储所有测试图像数据

typedef struct innode
{
	double value;
	double W[HDNNUM]; //权重的值
}innode;

typedef struct hidenode
{
	double value;
	double W[OPNNUM]; //权重的值
}hidenode;

typedef struct outnode
{
	double value;
}outnode;

typedef struct layer
{
	innode inlayer[IPNNUM];
	hidenode hidlayer[HDNNUM];
	outnode outlayer[OPNNUM];

	double learnrate;
	double k1;//隐含层偏置项
	double k2;//输出层偏置项
	double target[OPNNUM];//应该叫tend,半途发现写错了,懒得改了
	double realout[OPNNUM];
}layer;//三层神经网络结构体

#endif //

net.h

#ifndef NET_H
#define NET_H

#include "setting.h"
#include <math.h>

void InitInNode(innode* node, int num);
void InitHideNode(hidenode* node, int num);
void InitNet(layer* layer);
double sigmoid(double z);
double Getloss(layer* layer);
void ForwardPropagation(layer* layer, ImgData* imgdata);
void BackPropagation(layer* layer, ImgData* imgdata);
void PrintResult(int traintimes,  layer* layer);

#endif

getImg.h

#ifndef GETIMG_H
#define GETIMG_H

#include "setting.h"

void GetTrainImgData(GetTrainImgs* img, const char* datapath, const char* labelpath);
void GetTestImgData(GetTestImgs* img, const char* datapath, const char* labelpath);

#endif

net.c

#include "setting.h"
#include <stdlib.h> 
#include <math.h>

double learnrate = 0.10;

void InitInNode(innode *node,int num)
{
	srand((unsigned)time(NULL));
	for (int i = 0; i < num; i++)
	{
		node->W[i] = rand() % 100 / (double)100 * 0.1;
		if(rand()%2==0)
	 	{
			node->W[i] = -node->W[i];
		}
	}
}

void InitHideNode(hidenode* node, int num)
{
	srand((unsigned)time(NULL));
	for (int i = 0; i < num; i++)
	{
		node->W[i] = rand() % 100 / (double)100 * 0.1;
		if (rand() % 2 == 0)
		{
			node->W[i] = -node->W[i];
		}
	}
}

void InitNet(layer* layer)
{
	srand((unsigned)time(NULL));
	layer->k1 = rand() % 100 / (double)100;
	layer->k2 = rand() % 100 / (double)100;
	//初始化偏置项
	for (int i = 0; i < IPNNUM; i++)
	{
		InitInNode(&layer->inlayer[i], HDNNUM);
	}
	for (int i = 0; i < HDNNUM; i++)
	{
		InitHideNode(&layer->hidlayer[i], OPNNUM);
	}
}

double sigmoid(double z)
{
	return 1 / (1 + exp(-z));
}//激活函数,可以选择sigmoid或者tanh

double Getloss(layer* layer)
{
	double alloss = 0;//积累值
	for (int i = 0; i < OPNNUM; i++)
	{
		alloss += (layer->target[i] - layer->realout[i]) * (layer->target[i] - layer->realout[i]);//报错
	}
	return alloss/OPNNUM;
}
//均方误差,计算正确率时可用

void ForwardPropagation(layer* layer, ImgData* imgdata)
{
	//printf("the tag is %d\n", imgdata->tag);
	for (int i = 0; i < IPNNUM; i++)//将输入赋给输入层
	{
		layer->inlayer[i].value = imgdata->data[i];//将图像数据赋给输入层
	}
	for (int i = 0; i < HDNNUM; i++)//计算隐含层的值
	{
		double z = 0;
		for (int j = 0; j < IPNNUM; j++)
		{
			z += layer->inlayer[j].value * layer->inlayer[j].W[i];
			//第j个输入层的值乘以第j个输入层到第i个隐含层的权重
		}
		z+=layer->k1;//加上偏置项
		layer->hidlayer[i].value = sigmoid(z);//激活
	}
	for (int i = 0; i < OPNNUM; i++)
	{
		double z = 0;
		for (int j = 0; j < HDNNUM; j++)
		{
			z += layer->hidlayer[j].value * layer->hidlayer[j].W[i];
		}
		z += layer->k2;
		layer->outlayer[i].value = sigmoid(z);
		layer->realout[i] = layer->outlayer[i].value;
	}
}

//参数:标签,网络,输入
void BackPropagation(layer* layer,ImgData* imgdata)//传进来的T参数是标签
{
	for (int i = 0; i < OPNNUM; i++)
	{
		layer->target[i] = imgdata->label[i];//将标签赋给target(target实际应该是tend期待输出值)
	}
	for (int innum = 0; innum < IPNNUM; innum++)//更新输入层权重,先改1-->all,而后2-->all
		{
			for (int hidnum = 0; hidnum < HDNNUM; hidnum++)
			{//怀疑与输入层权重的赋值有关,learnrate???
				double y = layer->hidlayer[hidnum].value;//隐含层的输出
				double LOSS = 0;//E对Y的偏导数
				for (int outnum = 0; outnum < OPNNUM; outnum++)
				{
					LOSS += (layer->realout[outnum] - layer->target[outnum])//O-T
						* layer->realout[outnum]//O
							* (1 - layer->realout[outnum])//1-O
								* layer->hidlayer[hidnum].W[outnum];//W
				}
				layer->inlayer[innum].W[hidnum]-= learnrate * LOSS * y * (1 - y) * layer->inlayer[innum].value;
			}
		}
		for(int hidnum=0;hidnum< HDNNUM;hidnum++)//更新隐含层的权重
		{
			for(int outnum = 0; outnum < OPNNUM; outnum++)
			{
				layer->hidlayer[hidnum].W[outnum] -= (layer->realout[outnum] - layer->target[outnum])
					* layer->realout[outnum]
						* (1 - layer->realout[outnum])
							* layer->hidlayer[hidnum].value*learnrate;
			}
		}
}

void PrintResult(int traintimes, layer *layer)//如能修改成把输入的标签也打印出来就好了
{
	double loss = Getloss(layer);//target-->tend
	printf("第%d次训练,误差为%f\n", traintimes, loss);
	for (int i = 0; i < OPNNUM; i++)
	{
		printf("输出%d为:%f\n", i + 1, layer->realout[i]);
	}
}

getImg.c

#include "setting.h"

/*
	读取训练数据集
	参数:图像信息存储结构体,数据集路径,标签集路径	(const表示这个变量的值不能被修改
	返回值:无
	功能:读取训练数据集,将数据集的图像数据和标签数据存入ImgData结构体数组中
*/

void GetTrainImgData(GetTrainImgs* img, const char* datapath, const char* labelpath)
{
	unsigned char cache[4];//缓存区
	FILE* f;
	fopen_s(&f, datapath, "rb");//二进制方式打开文件,并将信息放到f这个结构体指针里
	if (f == NULL)
	{
		printf("open file error!\n");
		return;
	}//检查文件是否打开成功

	fread_s(cache, 4, 1, 4, f);//读取校验位
	//fread_s(读取到哪里,读取数据总字节数,一个数据几字节,读几个数据,在哪里读);

	fread_s(cache, 4, 1, 4, f);//读取数据集图像个数
	int imgnum = (cache[0] << 24) + (cache[1] << 16) + (cache[2] << 8) + cache[3];
	//换算,原本unsigned char类型的字节是无符号8位二进制整数,范围是0~255,这里换算成十进制整型数
	if (imgnum != TrainImgNum)
	{
		printf("error!");
		return;
	}

	fread_s(cache, 4, 1, 4, f);//读行数
	int imglength = (cache[0] << 24) + (cache[1] << 16) + (cache[2] << 8) + cache[3];

	fread_s(cache, 4, 1, 4, f);//读列数
	int imgwidth = (cache[0] << 24) + (cache[1] << 16) + (cache[2] << 8) + cache[3];

	for (int i = 0; i < imgnum; i++)
	{
		for (int j = 0; j < IPNNUM; j++)
		{
			unsigned char pixel;
			fread_s(&pixel, 1, 1, 1, f);
			img->mImgData[i].data[j] = (double)pixel / (double)255.0 * 0.99 + 0.01;
		}
	}
	fclose(f);//关闭文件



	fopen_s(&f, labelpath, "rb");
	if (f == NULL)
	{
		printf("open file error!\n");
		return;
	}
	fread_s(cache, 4, 1, 4, f);
	fread_s(cache, 4, 1, 4, f);
	imgnum = (cache[0] << 24) + (cache[1] << 16) + (cache[2] << 8) + cache[3];
	if (imgnum != TrainImgNum )
	{
		printf("error!");
		return;
	}
    for (int i = 0; i < imgnum; i++)
	{
		fread(&img->mImgData[i].tag, 1, 1, f);
		for (int j = 0; j < OPNNUM; j++)
		{
			img->mImgData[i].label[j] = 0.01;
		}
		img->mImgData[i].label[img->mImgData[i].tag] = 0.99;
	}
	fclose(f);
}
//malloc函数分配多图像存储空间必报错,无法写入参数,猜测是因为imgnum过大无法正常分配动态内存。

void GetTestImgData(GetTestImgs* img, const char* datapath, const char* labelpath)
{
	unsigned char cache[4];
	FILE* f;
	fopen_s(&f, datapath, "rb");
	if (f == NULL)
	{
		printf("open file error!\n");
		return;
	}//检查文件是否打开成功

	fread_s(cache, 4, 1, 4, f);//读取校验位

	fread_s(cache, 4, 1, 4, f);//读取数据集图像个数
	int imgnum = (cache[0] << 24) + (cache[1] << 16) + (cache[2] << 8) + cache[3];

	if (imgnum != TestImgNum)
	{
		printf("error!");
		return;
	}

	fread_s(cache, 4, 1, 4, f);//读行数
	int imglength = (cache[0] << 24) + (cache[1] << 16) + (cache[2] << 8) + cache[3];

	fread_s(cache, 4, 1, 4, f);//读列数
	int imgwidth = (cache[0] << 24) + (cache[1] << 16) + (cache[2] << 8) + cache[3];
	
	for (int i = 0; i < imgnum; i++)
	{
		for (int j = 0; j < IPNNUM; j++)
		{
			unsigned char pixel;
			fread_s(&pixel, 1, 1, 1, f);
			img->mImgData[i].data[j] = (double)pixel / (double)255.0 * 0.99 + 0.01;
		}
	}
	fclose(f);//关闭文件



	fopen_s(&f, labelpath, "rb");
	if (f == NULL)
	{
		printf("open file error!\n");
		return;
	}
	fread_s(cache, 4, 1, 4, f);
	fread_s(cache, 4, 1, 4, f);
	imgnum = (cache[0] << 24) + (cache[1] << 16) + (cache[2] << 8) + cache[3];
	if (imgnum != TestImgNum)
	{
		printf("error!");
		return;
	}
	for (int i = 0; i < imgnum; i++)
	{
		fread(&img->mImgData[i].tag, 1, 1, f);
		for (int j = 0; j < OPNNUM; j++)
		{
			img->mImgData[i].label[j] = 0.01;
		}
		img->mImgData[i].label[img->mImgData[i].tag] = 0.99;//跟tag一样的label置0.99
	}
	fclose(f);
}

main.c

#include "setting.h"
#include "net.h"
#include "getImg.h"
#include <stdlib.h> 

GetTrainImgs TrainImgs;
GetTestImgs TestImgs;
layer TheLayer;

void RightRate(int time,layer* layer, GetTestImgs* getImg)
{
	int rightnum = 0;
	double loss = 0;
	for (int i = 0; i < 10000; i++)
	{
		ForwardPropagation(layer, &getImg->mImgData[i]);
		double value = -100;
		int gettag = -100;

		for (int j = 0; j < OPNNUM; j++)
		{
			if (layer->outlayer[j].value > value)//找到输出的最大值
			{
				value = layer->outlayer[j].value;
				gettag = j;
			}
		}
		if (gettag == getImg->mImgData[i].tag)//如果最大值的下标和标签相同,说明识别正确
		{
			rightnum++;
		}
	}
	PrintResult(time+1, layer);
	//printf("第%d轮:  ", time + 1);
	if (rightnum != 0)
		printf("正确率为:%f\n", rightnum / 10000.0);
	else
		printf("正确率为:未定义(rightnum 为 0)\n");
}

void main()
{
	srand((unsigned)time(NULL));
	InitNet(&TheLayer);

	/*printf("wi是%lf,wo是%lf\n", TheLayer.inlayer[1].W[1], TheLayer.hidlayer[1].W[2]);
      测试权重是否被初始化好*/	

	GetTrainImgData(&TrainImgs, "train-images.idx3-ubyte", "train-labels.idx1-ubyte");

	/*for (int i = 0; i < 10; i++)
	{
		printf("%d\n", TrainImgs.mImgData[i].tag);
		for(int j=0;j<10;j++)
		printf("%lf\n", TrainImgs.mImgData[i].label[j]);
	}*/
		
	GetTestImgData( &TestImgs, "t10k-images.idx3-ubyte", "t10k-labels.idx1-ubyte");
	
	/*for (int i = 0; i < 10; i++)
		printf("%d\n", TestImgs.mImgData[i].tag);
	测试数据是否被读取好,结果是数据被读取好了*/


	for (int i = 0; i < 10; i++)
	{
		for (int j = 0; j < 60000; j++)//每次处理一张图像
		{
			//printf("the tag is -- %d\n", TrainImgs.mImgData[j].tag);
			//数据有成功传进ForwardPropagation函数
			ForwardPropagation(&TheLayer, &TrainImgs.mImgData[j]);//传入第j张图像的数据
			BackPropagation( &TheLayer, &TrainImgs.mImgData[j]);

			//测试用,可查看权重变化特征
			/*if (j % 1000 == 0)
			{
				printf("第%d张图像,wi是%lf,wo是%lf\n", j + 1, TheLayer.inlayer[1].W[2], TheLayer.hidlayer[1].W[2]);
			}*/
		}
		RightRate(i, &TheLayer, &TestImgs);
	}
	printf("已经结束嘞!/n");
}

参考:深度学习3—用三层全连接神经网络训练MNIST手写数字字符集

 

 

### 回答1: 基于神经网络手写字符识别MNIST是一个经典的机器学习问题,旨在通过训练一个神经网络模型来识别手写数字图像。MNIST数据集包含了60000张28x28像素的训练图像和10000张测试图像,每个图像都标有对应的数字(0到9)。 神经网络模型通常由多个神经元构成的多层结构组成。每个神经元将输入与其自身的权重相乘,并通过激活函数输出结果。在MNIST任务中,输入层接收一个28x28的图像矩阵,并将其展平为一个784个像素的向量。随后的隐藏层通常由多个层级组成,最后输出层包含10个神经元,代表数字0到9的类别。 模型的训练过程通过反向传播算法来自动调整权重以最小化损失函数。常用的优化算法如梯度下降法可以帮助模型在训练中不断调整权重,提高准确性。训练时,模型将训练图像输入网络,输出一个概率向量,表示每个数字的可能性。最后,我们选择概率最高的数字作为预测结果。 对于测试图像,我们将其输入已经训练好的模型中,通过前向传播得到预测结果。通过将预测结果与实际标签进行比较,可以评估模型的性能。常用的评估指标包括准确率和混淆矩阵等。 在实际应用中,基于神经网络手写字符识别MNIST已经取得了很高的准确率。并且该问题也可以作为入门级别的机器学习任务,帮助初学者理解神经网络的基本原理和训练过程。 ### 回答2: 基于神经网络手写字符识别MNIST(Modified National Institute of Standards and Technology) 是一个经典的问题,旨在通过训练模型来正确识别手写数字图像。 MNIST数据集包含60,000个用于训练的手写数字图像和10,000个用于测试的手写数字图像,每个图像的大小为28x28像素。神经网络是一种模拟人脑的计算模型,通过模拟大量的神经元之间的连接以及它们之间的传递和处理信息的方式,来实现学习和推理能力。 在手写字符识别MNIST问题中,神经网络的输入层对应于图像的像素值,通常使用784个输入神经元(28x28)来表示一个图像。然后,神经网络通过多个隐藏层来对图像进行特征提取和抽象化。每个隐藏层都由多个神经元组成,每个神经元将前一层的输出与一个权重向量进行线性组合,并通过使用激活函数对结果进行非线性变换。 常见的激活函数有ReLU、Sigmoid和Tanh函数等。通过反向传播算法,神经网络可以通过调整权重和偏置来最小化训练数据与实际值之间的差异,从而实现手写字符的准确识别。 一旦神经网络完成了训练,我们就可以使用测试集进行性能评估。我们将测试集中的每个图像输入神经网络,并根据输出层中神经元的激活程度来判断图像代表的数字。我们可以根据预测结果与实际标签之间的差异来评估模型的准确性。 基于神经网络手写字符识别MNIST是一个经典的机器学习问题,它不仅可以帮助我们理解神经网络的工作原理,还可以作为一个起点来研究更复杂的问题,如自然语言处理、图像识别等。 ### 回答3: 基于神经网络手写字符识别是一种广泛应用的机器学习技术,尤其在MNIST数据集上。MNIST是一个包含手写数字图片的数据集,其中每个图片都是28x28像素的灰度图片,并对应一个0到9的标签。 神经网络是由多个神经元组成的网络结构,由于其能够自动学习特征,并且适用于处理高维数据,因此被广泛应用于手写字符识别。基于神经网络手写字符识别MNIST通常包含以下步骤: 1. 数据准备:首先,需要从MNIST数据集中获取训练集和测试集。我们将训练集作为神经网络的输入数据,并使用测试集评估网络的性能。 2. 网络架构设计:选择合适的网络架构是非常重要的。常见的神经网络架构包括卷积神经网络(CNN)和全连接神经网络(FCN)。对于MNIST手写字符识别,常用的网络结构包括LeNet-5和AlexNet等。 3. 网络训练:通过将训练集的图片输入网络,网络会自动学习输入图片的特征和对应的标签。在训练过程中,使用反向传播算法来更新网络中的权重和偏差,以最小化预测值和真实标签之间的差异。 4. 网络评估:训练完成后,使用测试集评估网络的性能。通常使用准确率(Accuracy)来衡量网络的性能,即正确预测的样本数占总样本数的比例。 基于神经网络手写字符识别MNIST具有较高的准确率和鲁棒性,已经在现实生活中得到广泛应用。随着深度学习的快速发展,也出现了更加复杂和高效的网络结构,进一步提升了手写字符识别的准确率和性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值