购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.1 CIFAR-10数据集

2.1.1 CIFAR-10数据集简介

CIFAR-10是由Hinton的学生Alex Krizhevsky和Ilya Sutskever整理的一个用于识别普适物体的小型数据集。它一共包含10个类别的RGB彩色图片:飞机(airplane)、汽车(automobile)、鸟类(bird)、猫(cat)、鹿(deer)、狗(dog)、蛙类(frog)、马(horse)、船(ship)和卡车(truck)。图片的尺寸为 32×32,数据集中一共有 50000 张训练图片和 10000 张测试图片。CIFAR-10的图片样例如图2-1所示。

图2-1 CIFAR-10数据集的图片样例

与MNIST数据集相比,CIFAR-10具有以下不同点:

●CIFAR-10是3通道的彩色RGB图像,而MNIST是灰度图像。

●CIFAR-10 的图片尺寸为 32×32,而 MNIST 的图片尺寸为 28×28,比MNIST稍大。

●相比于手写字符,CIFAR-10 含有的是现实世界中真实的物体,不仅噪声很大,而且物体的比例、特征都不尽相同,这为识别带来很大困难。直接的线性模型如Softmax在CIFAR-10上表现得很差。

本章将以CIFAR-10为例,介绍深度图像识别的基本方法。本章代码中的一部分来自于TensorFlow的官方示例,如表2-1所示。

表2-1 TensorFlow官方示例的CIFAR-10代码文件

除了以上官方提供的文件外,本书还额外编写了三个文件,如表2-2所示。

表2-2 本书中额外提供的三个文件

2.1.2 下载CIFAR-10数据

运行cifar10_download.py程序就可以下载CIFAR-10数据集的全部数据:

这段程序会把 CIFAR-10 数据集下载到目录 cifar10_data/中。在 cifar10模块中,预先使用语句 tf.app.flags.DEFINE_string(′data_dir′,′/tmp/cifar10_data′,″″″Path to the CIFAR-10 data directory.″″″)定义了默认的data_dir 是/tmp/cifar10_data,也就是把数据集下载到目录/tmp/cifar10_data中。本书做了简单修改,即使用FLAGS=tf.app.flags.FLAGS、FLAGS.data_dir=′cifar10_data/′两条语句把待下载目录修改为cifar10_data/。

cifar10.maybe_download_and_extract()函数会自动检测数据集有没有下载,如果下载过则直接跳过,不做操作,如果之前没有下载就会自动下载数据集并解压缩。

等待一段时间后,系统会输出:“Successfully downloaded cifar-10-binary.tar.gz 170052171 bytes.”。这表明下载成功了。此时打开文件夹cifar10_data/,会看到一个cifar-10-binary.tar.gz文件,是数据集原始的压缩包;还有一个cifar-10-batches-bin文件夹,是压缩包解压后的结果。

打开 cifar10_data/cifar-10-batches-bin/文件夹,一共有 8 个文件,是CIFAR-10的全部数据,文件名及用途如表2-3所示。

表2-3 CIFAR-10数据集的数据文件名及用途

2.1.3 TensorFlow的数据读取机制

在讲解如何用TensorFlow读取CIFAR-10数据之前,作为基础,先来简单介绍TensorFlow中数据读取的基本机制。

首先需要思考的一个问题是,什么是数据读取?以图像数据为例,读取数据的过程可以用图2-2来表示。

图2-2 图像的数据读取过程

假设硬盘中有一个图片数据集0001.jpg、0002.jpg、0003.jpg……只需要把它们读取到内存中,然后提供给GPU或是CPU进行计算就可以了。这听起来很容易,但事实远没有那么简单。 事实上,必须先读入数据后才能进行计算,假设读入用时 0.1s ,计算用时 0.9s ,那么就意味着每过 1s GPU 都会有 0.1s 无事可做,这大大降低了运算的效率。

如何解决这个问题?方法就是将读入数据和计算分别放在两个线程中,将数据读入内存的一个队列,如图2-3所示。

图2-3 改进的读取方式:先将图片读取到内存队列中

读取线程源源不断地将文件系统中的图片读入一个内存的队列中,而负责计算的是另一个线程,计算需要数据时,直接从内存队列中取就可以了。这样可以解决GPU因为I/O而空闲的问题!

而在 TensorFlow 中,为了方便管理,在内存队列前又添加了一层所谓的“文件名队列”。

为什么要添加这一层文件名队列呢?首先需要了解机器学习中的一个概念:epoch。对于一个数据集来讲,运行一个 epoch 就是将这个数据集中的图片全部计算一遍。如果一个数据集中有三张图片 A.jpg、B.jpg、C.jpg,那么运行一个 epoch 就是指对 A、B、C 三张图片都计算一遍。两个 epoch就是指先对 A、B、C 各计算一遍,然后再全部计算一遍,也就是说每张图片都计算了两遍。

TensorFlow 使用“文件名队列+内存队列”双队列的形式读入文件,可以很好地管理 epoch。下面用图片的形式来说明这个机制的运行方式。如图2-4所示,还是以数据集A.jpg、B.jpg、C.jpg为例,假定要运行一个epoch,那么就在文件名队列中把A、B、C各放入一次,并在之后标注队列结束。

图2-4 TensorFlow中的文件名队列和内存队列

程序运行后,内存队列首先读入A(此时A从文件名队列中出队),如图2-5所示。

图2-5 读入A

再依次读入B和C,如图2-6所示。

图2-6 读入B和C

此时,如果再尝试读入,由于系统检测到了“结束”,就会自动抛出一个异常(OutOfRange)。外部捕捉到这个异常后就可以结束程序了。这就是TensorFlow中读取数据的基本机制。如果要运行2个epoch而不是1个epoch,则只要在文件名队列中将A、B、C依次放入两次再标记结束就可以了。

如何在TensorFlow中创建上述的两个队列呢?

对于文件名队列,使用 tf.train.string_input_producer 函数。这个函数需要传入一个文件名list,系统会自动将它转为一个文件名队列。

此外,tf.train.string_input_producer 还有两个重要的参数:一个是num_epochs,它就是上文中提到的epoch数;另外一个是shuffle,shuffle是指在一个 epoch 内文件的顺序是否被打乱。若设置 shuffle=False,如图 2-7所示,每个 epoch 内,数据仍然按照 A、B、C的顺序进入文件名队列,这个顺序不会改变。

图2-7 shuffle=False时的数据读取顺序

如果设置 shuffle=True,那么在一个 epoch 内,数据的前后顺序就会被打乱,如图2-8所示。

图2-8 shuffle=True时的数据读取顺序

在 TensorFlow 中,内存队列不需要自己建立,只需要使用 reader 对象从文件名队列中读取数据就可以了,具体实现可以参考下面的实战代码。

除了 tf.train.string_input_producer 外,还要额外介绍一个函数:tf.train.start_queue_runners。初学者会经常在代码中看到这个函数,但往往很难理解它的用处。有了上面的铺垫后,就可以解释这个函数的作用了。

在使用 tf.train.string_input_producer 创建文件名队列后,整个系统其实还处于“停滞状态”,也就是说,文件名并没有真正被加入队列中,如图2-9所示。如果此时开始计算,因为内存队列中什么也没有,计算单元就会一直等待,导致整个系统被阻塞。

图2-9 未调用

而使用tf.train.start_queue_runners之后,才会启动填充队列的线程,这时系统就不再“停滞”。此后,计算单元就可以拿到数据并进行计算,整个程序也就运行起来了,这就是函数tf.train.start_queue_runners的用处。

下面用一个具体的例子体会 TensorFlow 中的数据读取(对应的程序为test.py)。如图 2-10 所示,假设在当前文件夹中已经有 A.jpg、B.jpg、C.jpg三张图片,希望读取这三张图片的 5 个 epoch 并且把读取的结果重新存到read文件夹中。

图2-10 当前文件夹中包含的文件

代码如下(对应的文件为test.py):

这里使用filename_queue=tf.train.string_input_producer(filename,shuffle=False,num_epochs=5)建立了一个会运行5个epoch的文件名队列。并使用reader读取,reader每次读取一张图片并保存。

运行代码后(程序最后会抛出一个 OutOfRangeError 异常,不必担心,这就是epoch跑完,队列关闭的标志),得到read文件夹中的图片,正好是按顺序的5个epoch,如图2-11所示。

如果设置 filename_queue=tf.train.string_input_producer(filename,shuffle=False,num_epochs=5)中的shuffle=True,那么在每个epoch内图像会被打乱,如图2-12所示。

图2-11 shuffle=False时读取出的5个

图2-12 shuffle=True时读取出的5个epoch

这里只是用三张图片举例,实际应用中一个数据集肯定不止 3 张图片,不过涉及的原理都是共通的。

2.1.4 实验:将CIFAR-10数据集保存为图片形式

介绍了TensorFlow的数据读取的基本原理,再来看如何读取CIFAR-10数据。在CIFAR-10数据集中,文件data_batch_1.bin、data_batch_2.bin、……、data_batch_5.bin和test_batch.bin中各有10000个样本。一个样本由3073个字节组成,第一个字节为标签(label),剩下3072个字节为图像数据 。如下所示:

样本和样本之间没有多余的字节分割,因此这几个二进制文件的大小都是30730000字节。

如何用TensorFlow读取CIFAR-10数据呢?步骤与第2.1.3节类似:

●第一步,用tf.train.string_input_producer建立队列。

●第二步,通过reader.read读数据。在第2.1.3节中,一个文件就是一张图片,因此用的reader是tf.WholeFileReader()。CIFAR-10数据是以固定字节存在文件中的,一个文件中含有多个样本,因此不能使用tf.WholeFileReader(),而是用tf.FixedLengthRecordReader()。

●第三步,调用tf.train.start_queue_runners。

●最后,通过sess.run()取出图片结果。

遵循上面的步骤,本节会做一个实验:将CIFAR-10数据集中的图片读取出来,并保存为.jpg 格式。对应的程序为 cifar10_extract.py。读者现在就可以在这个代码里查找,看步骤中的 tf.train.string_input_producer、tf.FixedLengthRecordReader()、tf.train.start_queue_runners、sess.run()都在什么地方。

按照程序的执行顺序来看:

inputs_origin是一个函数。这个函数中包含了前两个步骤,tf.train.string_input_producer和使用reader。函数的返回值reshaped_image是一个Tensor,对应一张训练图像。下面要做的并不是直接运行sess.run(reshaped_image),而是使用 threads=tf.train.start_queue_runners(sess=sess)。只有调用过tf.train.start_queue_runners后,才会让系统中的所有队列真正地“运行”,开始从文件中读数据。如果不调用这条语句,系统将会一直等待。

最后用sess.run(reshaped_image)取出训练图片并保存。此程序一共在文件夹cifar10_data/raw/中保存了30张图片。读者可以打开该文件夹,看到原始的CIFAR-10训练图片,如图2-13所示。

图2-13 cifar10_data/raw/目录下保

再回过头来看inputs_origin函数:

tf.train.string_input_producer(filenames)创建了一个文件名队列,其中filenames是一个列表,包含从data_batch_1.bin到data_batch_5.bin一共5个文件名。这正好对应了 CIFAR-10 的训练集。cifar10_input.read_cifar10 (filename_queue)对应“使用reader”的步骤。为此需要查看cifar10_input.py中的read_cifar10函数,其中关键的代码如下:

语句 tf.FixedLengthRecordReader(record_bytes=record_bytes)创建了一个reader,它每次在文件中读取 record_bytes 字节的数据,直到文件结束。结合代码,record_bytes就等于1+32*32*3,即3073,正好对应CIFAR-10中一个样本的字节长度。使用 reader.read(filename_queue)后,reader 从之前建立好的文件名队列中读取数据(以Tensor的形式)。简单处理结果后由函数返回。至此,读者应当对CIFAR-10数据的读取流程及TensorFlow的读取机制相当熟悉了。 GiUpUEVRtg3a7zZoi+jVzygRDYmMBSyohwhcbQwBVbhIN0rJtfhvh+N9fPCXT3VW

点击中间区域
呼出菜单
上一章
目录
下一章
×