1. MNIST数据集
1.1 MNIST数据集获取
MNIST数据集是入门机器学习/模式识别的最经典数据集之一。最早于1998年Yan Lecun在论文:
中提出。经典的LeNet-5 CNN网络也是在该论文中提出的。
数据集包含了0-9共10类手写数字图片,每张图片都做了尺寸归一化,都是28x28大小的灰度图。每张图片中像素值大小在0-255之间,其中0是黑色背景,255是白色前景。如下图所示:
![](http://upload-images.jianshu.io/upload_images/2795802-3348f6ef291f598b.png?imageMogr2/auto-orient/strip|imageView2/2/w/489/format/webp)
MNIST共包含70000张手写数字图片,其中有60000张用作训练集,10000张用作测试集。原始数据集可在MNIST官网下载。
下载之后得到4个压缩文件:
train-images-idx3-ubyte.gz #60000张训练集图片
train-labels-idx1-ubyte.gz #60000张训练集图片对应的标签
t10k-images-idx3-ubyte.gz #10000张测试集图片
t10k-labels-idx1-ubyte.gz #10000张测试集图片对应的标签
将其解压,得到
train-images-idx3-ubyte
train-labels-idx1-ubyte
t10k-images-idx3-ubyte
t10k-labels-idx1-ubyte
1.2 MNIST二进制文件的存储格式
解压得到的四个文件都是二进制格式,我们如何获取其中的信息呢?这得首先了解MNIST二进制文件的存储格式(官网底部有介绍),以训练集图像文件train-images-idx3-ubyte
为例:
![](http://upload-images.jianshu.io/upload_images/2795802-c0c679831719840e.png?imageMogr2/auto-orient/strip|imageView2/2/w/773/format/webp)
图像文件的
- 第1-4个byte(字节,1byte=8bit),即前32bit存的是文件的magic number,对应的十进制大小是2051;
- 第5-8个byte存的是number of images,即图像数量60000;
- 第9-12个byte存的是每张图片行数/高度,即28;
- 第13-16个byte存的是每张图片的列数/宽度,即28。
- 从第17个byte开始,每个byte存储一张图片中的一个像素点的值。
因为train-images-idx3-ubyte
文件总共包含了60000张图片数据,按照以上的存储方式,我们算一下该文件的大小:
- 一张图片包含28x28=784个像素点,需要784bytes的存储空间;
- 60000张图片则需要784x60000=47040000 bytes的存储空间;
- 此外,文件开始处使用了16个bytes用于存储magic number、图像数量、图像高度和图像宽度,因此,训练集图像文件的大小应该是47040000+16=47040016 bytes。
我们查看解压后的train-images-idx3-ubyte
文件的属性:
![](http://upload-images.jianshu.io/upload_images/2795802-d6c7ee436fb87d75.png?imageMogr2/auto-orient/strip|imageView2/2/w/472/format/webp)
文件实际大小和我们计算的结果一致。
类似地,我们查看训练集标签文件train-labels-idx1-ubyte
的存储格式:
![](http://upload-images.jianshu.io/upload_images/2795802-ee2ed520378f764b.png?imageMogr2/auto-orient/strip|imageView2/2/w/526/format/webp)
和图像文件类似:
- 第1-4个byte存的是文件的magic number,对应的十进制大小是2049;
- 第5-8个byte存的是number of items,即label数量60000;
- 从第9个byte开始,每个byte存一个图片的label信息,即数字0-9中的一个。
计算一下训练集标签文件train-labels-idx1-ubyte
的文件大小:
- 1x60000+8=60008 bytes。
与该文件实际的大小一致:
![](http://upload-images.jianshu.io/upload_images/2795802-1dcff1a1b877a28c.png?imageMogr2/auto-orient/strip|imageView2/2/w/479/format/webp)
另外两个文件,即测试集图像文件、测试集标签文件的存储方式和训练图像文件、训练标签文件相似,只是图像数量由60000变为10000。
1.3 使用python访问MNIST数据集文件内容
知道了MNIST二进制文件的存储方式,下面介绍如何使用python访问文件内容。同样以训练集图像文件train-images-idx3-ubyte
为例:
首先,使用open()函数打开文件,并使用read()方法将所有的文件数据读入到一个字符串中:
yan@yanubuntu:~/codes/Deep-Learning-21-Examples/chapter_1/MNIST_data$ python
Python 2.7.12 (default, Nov 12 2018, 14:36:49)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> with open('train-images.idx3-ubyte', 'rb') as f:
... file = f.read()
...
>>>
file是str类型,其中的每个元素就存储的1个字节的内容。我们现在查看前4个字节,即magic number的内容,看下是否是前面说的2051:
>>> magic_number=file[:4]
>>> magic_number
'\x00\x00\x08\x03'
>>> magic_number.encode('hex')
'00000803'
>>> int(magic_number.encode('hex'),16)
2051
可以看出前4个byte的值确实是2051,但是不能直接输出magic number的内容,需要将其编码,然后才能转成十进制的int类型(有关字节编码的知识暂时没懂,先用着)。
同样的方式,查看图像数量、图像高度和图像宽度信息:
>>> num_images = int(file[4:8].encode('hex'),16)
>>> num_images
60000
>>> h_image = int(file[8:12].encode('hex'),16)
>>> h_image
28
>>> w_image = int(file[12:16].encode('hex'),16)
>>> w_image
28
现在获取第1张图片的像素信息,然后利用numpy和cv2模块转换其格式,并保存成.jpg
格式的图片:
>>> image1 = [int(item.encode('hex'), 16) for item in file[16:16+784]]
>>> len(image1)
784
>>> import numpy as np
>>> import cv2
>>> image1_np = np.array(image1, dtype=np.uint8).reshape(28,28,1)
>>> image1_np.shape
(28, 28, 1)
>>> cv2.imwrite('image1.jpg', image1_np)
True
>>>
保存下来的图片image1.jpg如下图所示:
该图片的标签是5,我们可以验证一下训练集标签文件train-labels-idx1-ubyte
文件的第一个标签是否和图像内容一一对应:
>>> with open('train-labels.idx1-ubyte', 'rb') as f:
... label_file = f.read()
...
>>> label1 = int(label_file[8].encode('hex'), 16)
>>> label1
5
>>>
训练标签文件的第一张图片标签是第9个byte(索引从0开始,所以第9个byte是label_file[8]),结果没问题。
1.4 将MNIST数据集保存成.jpg图片格式
因为使用上面得到的file和label_file文件是str类型,因此可以使用迭代的方式,将所有训练和测试集的二进制文件格式转成.jpg
图片格式。转换脚本mnist2jpg.py如下:
# coding=utf-8
'''将二进制格式的MNIST数据集转成.jpg图片格式并保存,图片标签包含在图片名中'''
import numpy as np
import cv2
import os
def save_mnist_to_jpg(mnist_image_file, mnist_label_file, save_dir):
if ‘train’ in os.path.basename(mnist_image_file):
num_file = 60000
prefix = ‘train’
else:
num_file = 10000
prefix = ‘test’
with open(mnist_image_file, ‘rb’) as f1:
image_file = f1.read()
with open(mnist_label_file, ‘rb’) as f2:
label_file = f2.read()
image_file = image_file[16:]
label_file = label_file[8:]
for i in range(num_file):
label = int(label_file[i].encode(‘hex’), 16)
image_list = [int(item.encode(‘hex’), 16) for item in image_file[i784:i784+784]]
image_np = np.array