参考博客:
https://blog.csdn.net/tostq/article/details/51786265
参考代码:
https://github.com/tostq/DeepLearningC/tree/master/CNN
MINST数据库是由Yann提供的手写数字数据库文件,其官方下载地址http://yann.lecun.com/exdb/mnist/
这个里面还包含了对这个数据库进行识别的各类算法的结果比较及相关算法的论文
数据库的里的图像都是28*28大小的灰度图像,每个像素的是一个八位字节(0~255)
这个数据库主要包含了60000张的训练图像和10000张的测试图像,主要是下面的四个文件
/*
Training set images: train-images-idx3-ubyte.gz (9.9 MB, 解压后 47 MB, 包含 60,000 个样本)
Training set labels: train-labels-idx1-ubyte.gz (29 KB, 解压后 60 KB, 包含 60,000 个标签)
Test set images: t10k-images-idx3-ubyte.gz (1.6 MB, 解压后 7.8 MB, 包含 10,000 个样本)
Test set labels: t10k-labels-idx1-ubyte.gz (5KB, 解压后 10 KB, 包含 10,000 个标签)
*/
上述四个文件直接解压就可以使用了,虽然数据未压缩,但是我们还是需要将图像提取出来,方便我们进行操作
(1)存储图像数据的相关数据结构:
单张图像结构及保存图像的链表
typedef struct MinstImg{
int c; // 图像宽
int r; // 图像高
float** ImgData; // 图像数据二维动态数组
}MinstImg;typedef struct MinstImgArr{
int ImgNum; // 存储图像的数目
MinstImg* ImgPtr; // 存储图像数组指针
}*ImgArr; // 存储图像数据的数组
图像实际数字标号(0~9),数据库里是用一个八位字节来表示,不过为了方便学习,需要用10位来表示。这里的10位表示网络输出层的10个神经元,某位为1表示数字标号即为该位。
这里我们可以理解为
float* LabelData; 相等 float LabelData[10]={0};
一共数组大小为10,数组中,第i个元素为1,即该数字标号为i
上文中说,数据库里是用一个8位字节,下面读入Label函数的时候我们再解析
接着:
typedef struct MinstLabel{
int l; // 输出标记的长 0 1 2 4 5 6 7 8 9
float* LabelData; // 输出标记数据
}MinstLabel;typedef struct MinstLabelArr{
int LabelNum;
MinstLabel* LabelPtr;
}*LabelArr; // 存储图像标记的数组
(2)读入图像数据的相关函数
ImgArr read_Img(const char* filename) // 读入图像
{
FILE *fp = NULL;
fp = fopen(filename, "rb");
if (fp == NULL)
printf("open file failed\n");
assert(fp);
int magic_number = 0;
int number_of_images = 0;
int n_rows = 0;
int n_cols = 0;
//从文件中读取sizeof(magic_number) 个字符到 &magic_number
fread((char*)&magic_number, sizeof(magic_number), 1, fp);
magic_number = ReverseInt(magic_number);
//获取训练或测试image的个数number_of_images
fread((char*)&number_of_images, sizeof(number_of_images), 1, fp);
number_of_images = ReverseInt(number_of_images);
//获取训练或测试图像的高度Heigh
fread((char*)&n_rows, sizeof(n_rows), 1, fp);
n_rows = ReverseInt(n_rows);
//获取训练或测试图像的宽度Width
fread((char*)&n_cols, sizeof(n_cols), 1, fp);
n_cols = ReverseInt(n_cols);
//获取第i幅图像,保存到vec中
int i, r, c;
// 图像数组的初始化
ImgArr imgarr = (ImgArr)malloc(sizeof(MinstImgArr));
imgarr->ImgNum = number_of_images;
imgarr->ImgPtr = (MinstImg*)malloc(number_of_images*sizeof(MinstImg));
for (i = 0; i < number_of_images; ++i)
{
imgarr->ImgPtr[i].r = n_rows;
imgarr->ImgPtr[i].c = n_cols;
imgarr->ImgPtr[i].ImgData = (float**)malloc(n_rows*sizeof(float*));
for (r = 0; r < n_rows; ++r)
{
imgarr->ImgPtr[i].ImgData[r] = (float*)malloc(n_cols*sizeof(float));
for (c = 0; c < n_cols; ++c)
{
unsigned char temp = 0;
fread((char*)&temp, sizeof(temp), 1, fp);
imgarr->ImgPtr[i].ImgData[r][c] = (float)temp / 255.0;
}
}
}
fclose(fp);
return imgarr;
}
为什么这么读入,我们看一下官网的文件,32bit = 4字节 = int(C++/C)
读出第1个4字节放入number_of_images
//从文件中读取sizeof(magic_number) 个字符到 &magic_number
fread((char*)&magic_number, sizeof(magic_number), 1, fp);
magic_number = ReverseInt(magic_number);
//获取训练或测试image的个数number_of_images
fread((char*)&number_of_labels, sizeof(number_of_labels), 1, fp);
number_of_labels = ReverseInt(number_of_labels);
通过函数ReverseInt,去解析出真正的个数
int ReverseInt(int i)
{
unsigned char ch1, ch2, ch3, ch4;
ch1 = i & 255;
ch2 = (i >> 8) & 255;
ch3 = (i >> 16) & 255;
ch4 = (i >> 24) & 255;
return((int)ch1 << 24) + ((int)ch2 << 16) + ((int)ch3 << 8) + ch4;
}
他是用4字节的大小空间,去存放了number_of_images这个数,我们第一次取到的是4字节,并不是一个int数,解析它
截完图我发现截错了,应该截读入图片的,现在是读入Label的,反正大致意思一样,我觉得大家可以清晰理解的
就以截图中的数据说话,原来是1625948160 解析后 变成了60000
96=0110 0000, 234=1110 1010
0000 0000 ,0000 0000,1110 1010,0110 0000
下面都是同理的,不再叙述
读出第2个4字节放入number_of_images
读出第3个4字节放入n_rows
读出第4个4字节放入n_cols
接着:
放入之前还进行了归一化
imgarr->ImgPtr[i].ImgData[r][c] = (float)temp / 255.0;
一个像素点,用一个无符号字节表示
一个字节=8bit
程序中使用,unsigned char,在C/C++中,unsigned char=1字节
即每次从文件中取出一个字节,作为1个像素点
最后一句话,像素按行排列,至此,解析完毕
(3)读入图像数据标号
LabelArr read_Lable(const char* filename)// 读入图像
{
FILE *fp = NULL;
fp = fopen(filename, "rb");
if (fp == NULL)
printf("open file failed\n");
assert(fp);
int magic_number = 0;
int number_of_labels = 0;
int label_long = 10;
//从文件中读取sizeof(magic_number) 个字符到 &magic_number
fread((char*)&magic_number, sizeof(magic_number), 1, fp);
magic_number = ReverseInt(magic_number);
//获取训练或测试image的个数number_of_images
fread((char*)&number_of_labels, sizeof(number_of_labels), 1, fp);
number_of_labels = ReverseInt(number_of_labels);
int i, l;
// 图像标记数组的初始化
LabelArr labarr = (LabelArr)malloc(sizeof(MinstLabelArr));
labarr->LabelNum = number_of_labels;
labarr->LabelPtr = (MinstLabel*)malloc(number_of_labels*sizeof(MinstLabel));
for (i = 0; i < number_of_labels; ++i)
{
labarr->LabelPtr[i].l = 10;
labarr->LabelPtr[i].LabelData = (float*)calloc(label_long, sizeof(float));
unsigned char temp = 0;
fread((char*)&temp, sizeof(temp), 1, fp);
labarr->LabelPtr[i].LabelData[(int)temp] = 1.0;
}
fclose(fp);
return labarr;
}
上述代码中,
读出第1个4字节放入magic_number
读出第2个4字节放入number_of_labels
接着:
一个label,用一个无符号字节表示
一个字节=8bit
程序中使用,unsigned char,在C/C++中,unsigned char=1字节
即每次从文件中取出一个字节,作为1个Label
labarr->LabelPtr[i].LabelData[(int)temp] = 1.0;
label为i,就将数组第i个元素,置为1
上图是最终的读入信息
接着:
用代码将每一个数据显示,查看它是
0 0 0 0.....1 1 1 1 1 ...... 2 2 2 2 ........ 3 3 3 3 3....... 9 9 9.....这样显示
还是
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3......这样显示
还是
无规律..........
很明显,是第三种情况,读入的分别是,2 7 8 9 5 8 5 0 4 ..............
至此,全部解析完毕