5.5 CNN 的实现
利用TensorFlow实现目标识别与分类要求对卷积(对CNN)、常见层(非线性、池化、全连接等)、图像加载、图像操作和颜色空间相关的基础知识有所了解。当掌握了这些内容后,便有可能利用TensorFlow构建一个用于图像识别与分类的CNN模型。在这种情况下,训练数据来自于Stanford的一个包含了许多狗及其品种标签的数据集。我们需要依据这些图像训练一个网络,然后再评估它对狗的品种的预测准确性。
我们的网络架构采取了Alex Krizhevsky的AlexNet的简化版本,但并未使用AlexNet的所有层。AlexNet架构在本章最开始已做过介绍,它是ILSVRC2012挑战赛的冠军。这个网络使用了本章介绍过的层和技术,与TensorFlow提供的CNN入门教程也非常类似。
本节所介绍的网络包含每层之后的输出TensorShape。这些层按照自左向右、自上向下的顺序被依次读取,且存在关联的层被分为一组。当输入经过网络时,其高度和宽度都会减小,而其深度会增加。深度值的增加减少了使用该网络所需的计算量。
5.5.1 Stanford Dogs数据集
用于训练该模型的数据集可从Stanford的计算机视觉站点http://vision.stanford.edu/aditya86/ImageNetDogs/下载。训练模型时需要事先下载相关的数据。下载完包含所有图像的压缩文件后,需要将其解压至一个新的名为imagenet-dogs的目录下,该目录应当与用于构建模型的代码位于同一路径下。
由Stanford提供的压缩文件包含被组织为120个不同品种的狗的图像。该模型的目标是将这个数据集中80%的图像用于训练,而用其余20%做测试。如果这是一个产品模型,则还应预留一些原始数据做交叉验证。为验证模型的准确性,交叉验证是一个有用的步骤,但该模型的设计初衷只是为说明这个过程,而非出于完整性的考虑。
这个数据压缩包遵循了ImageNet的实践原则。每个狗品种都对应一个类似于n02085620-Chihuahua的文件夹,其中目录名称的后一半对应于狗品种的英语表述(Chihuahua)。在每个目录中,都有大量属于该品种的狗的图像,每幅图像都为JPEG格式(RGB)且尺寸各异。各图像尺寸不一是一种挑战,因为TensorFlow希望各张量都具有相同的维数。
5.5.2 将图像转为TFRecord文件
被组织在某个目录中的原始图像无法直接用于训练,因为这些图像尺寸不一,且相应的品种标签也不在图像文件中。将图像提前转换为TFRecord文件将有助于加速训练,并简化与图像标签的匹配。另一个好处是与训练和测试有关的图像可以事先分离,这样当训练开始时,就可利用检查点文件对模型进行不间断的测试。
转换图像数据格式时需要将它们的颜色空间变为灰度空间,将图像尺寸修改为统一尺寸,并将标签依附于每幅图像。在训练开始前,这种转换应当仅进行一次,通常它会花费较长的时间。
这个例子展示了该文档的结构。利用glob模块可枚举指定路径下的目录,从而显示出数据集中的文件结构。文件名中的8个数字对应于ImageNet中每个类别的WordNet ID。ImageNet网站拥有一个可依据WordNet ID查询图像细节的浏览器。例如,要查看Chihuahua(吉娃娃)品种的样本,可通过下列网址访问http://www.image-net.org/synset?wnid=n02085620。
这段示例代码将目录和图像('./imagenet-dogs/n02085620-Chihuahua/n02085620_10131.jpg')组织到了两个与每个品种相关的字典中,这些字典中包含了属于各品种的所有图像。现在,每个字典就按照下列格式包含了所有的Chihuahua(吉娃娃)图像:
将各品种的狗的图像组织到这些字典中能够简化选择每种类型的图像并对其归类的过程。在预处理阶段,所有品种的狗的图像都会被依次遍历,并依据列表中的文件名被打开。
这段示例代码完成的任务包括:打开每幅图像,将其转换为灰度图,调整其尺寸,然后将其添加到一个TFRecord文件中。这个逻辑与之前的例子基本一致,唯一的区别是这里使用了tf.image.resize_images函数。这个尺寸调整方法会将所有图像变为相同的尺寸,即便会有扭曲发生。例如,假设有一幅纵向的图像和一幅横向的图像,若用这段代码调整两者的尺寸,则横向图像的输出将会产生扭曲。这种扭曲之所以发生,是因为tf.image.resize_images并不考虑图像的长宽比(宽度与高度的比值)。为了对一组图像进行恰当的尺寸调整,裁剪或边界填充是一种推荐的方法,因为这些方式能够保持图像的纵横比,不至于使图像产生扭曲。
5.5.3 加载图像
一旦测试集和训练集被转换为TFRecord格式,便可按照TFRecord文件而非JPEG文件进行读取。我们的目标是每次加载少量图像及相应的标签。
这段示例代码通过匹配所有在训练集所在目录下找到的TFRecord文件而加载训练图像。每个TFRecord文件中都包含了多幅图像,但tf.parse_single_example将只从该文件中提取单个样本。之前讨论过的批运算可用于同时训练多幅图像。对多幅图像进行批处理非常有用,因为这些运算既可对多幅图像进行处理,也可对单幅图像进行处理。批处理时,必须要满足的条件是系统拥有足够的内存。
当可用的图像都加载到内存中后,接下来的步骤便是创建用于训练和测试的模型。
5.5.4 模型
这里所使用的模型与前面的MNIST卷积网络的例子非常类似,也常出现在介绍卷积神经网络的TensorFlow入门教程中。该模型的架构虽然简单,但对于解释图像分类与识别中所使用的不同技术却非常有价值。更复杂的模型可参考AlexNet的设计,它引入了更多的卷积层。
该模型的第1层是利用tf.contrib.layers.convolution2d创建的。值得注意的是weight_init被设置为正态随机值,这意味着第一组滤波器填充了服从正态分布的随机数(自TensorFlow 0.9起,该参数被重命名为weights_initializer)。这些滤波器被设置为trainable,以便当将信息输入给网络时,这些权值能够调整,以提高模型的准确率。
当将卷积运用于图像之后,利用一个max_pool运算将输出降采样。该运算之后,由于在池化运算中使用的ksize和strides,卷积的输出形状减半。这里输出形状的减小,并不改变滤波器的数量(输出通道)或图像批数据的尺寸。减少的分量与图像(滤波器)的高度和宽度有关。
与第1层相比,第2层改动很小,唯一的区别在于滤波器的深度。现在滤波器的数量变为第一层的2倍,同时减小了图像的高度和宽度。多个卷积和池化层连续地减少了输入的高度和宽度,同时进一步增加了深度。
此时,可进一步增加卷积和池化步骤。在许多架构中,卷积层和池化层都超过5层。最复杂的架构需要的训练和调试时间也更长,但它们能够匹配更多更复杂的模式。在本例中,为解释卷积网络的基本原理,使用两个卷积层和池化已经足够了。
被处理的张量仍然相当复杂,接下来的步骤是将图像中的每个点都与输出神经元建立全连接。由于在本例中,后面要使用softmax,因此全连接层需要修改为二阶张量。张量的第1维将用于区分每幅图像,而第2维对应于每个输入张量的秩1张量。
执行上述代码后,可得到输出:
tf.reshape拥有一个特殊值,其可用于指示和使用其余所有维。在这段示例代码中,-1用于将最后一个池化层调整为一个巨大的秩1张量。
池化层展开后,便可与将网络当前状态与所预测的狗的品种关联的两个全连接层进行整合。
这段示例代码创建了网络的最后一个全连接层,其中的每个像素都与每个狗的品种关联着。该网络的每一步都会通过将输入图像转化为滤波器来减小它们的尺寸,这些滤波器之后又会与一个品种的狗(标签)进行匹配。这项技术减少了训练和测试一个网络所需的计算量,同时使输出更具一般性。
5.5.5 训练
一旦模型做好了训练的准备,最后的步骤便与本书前面的章节所讨论的过程完全一致。依据模型对输入到训练优化器(作用是优化每层的权值)的训练数据的真实标签和模型的预测结果计算模型的损失。这个优化过程会经历数次迭代,每次迭代时都试图提升模型的准确率。
对于该模型,有一点需要注意,那就是在训练过程中,大部分分类函数(tf.nn.softmax)都要求标签为数值类型。在介绍从TFRecord文件加载图像的那一节中已经强调过这一点。在本例中,每个标签都是一个类似于n02085620-Chihuahua的字符串。由于tf.nn.softmax无法直接使用这些字符串,所以需要将每个标签转换为一个独一无二的数字。将这些标签转换为整数表示应当在预处理阶段进行。
对于本数据集,每个标签都被转换为一个代表包含所有狗的品种的列表中名称索引的整数。完成该任务有多种方法。在本例中,将使用一个新的TensorFlow工具运算tf.map_fn。
这段示例代码使用了两种不同形式的map运算。第一种形式的map用于依据一个目录列表创建一个仅包含狗的品种名的列表。第二种形式的map是tf.map_fn,它是一个TensorFlow运算,可用指定的函数对数据流图中的张量进行映射。tf.map_fn用于生成一个仅包含每个标签在所有类标签构成的列表中的索引的秩1张量。这样,tf.nn.softmax便可利用这些独一无二的整数对狗的品种进行预测。
5.5.6 用TensorBoard调试滤波器
CNN拥有多个可调整的部分,它们在训练阶段可能会引发一些问题,从而导致模型的准确率较差。在调试CNN中的问题时,通常可从观察滤波器(卷积核)在每轮迭代后的变化入手。当网络试图依据训练方法学习最精确的一组权重时,滤波器中的每个权值都会持续不断地发生改变。
在一个设计良好的CNN中,当第一个卷积层开始工作时,输入权值被随机初始化(在本例中使用了weight_init=tf.random_normal)。这些权值通过一幅图像激活,且激活函数的输出(特征图)也是随机的。可将特征图作为图像可视化,输出的外观与原始图像类似,并被施加了静力(static)。静力是由所有权值的随机激发所导致的。经过多轮迭代之后,权值不断地被调整以拟合训练反馈,每个滤波器都趋于一致。当网络收敛时,各个滤波器都与从图像中能够找到的不同的细小模式非常类似。下图展示的是一幅作为训练数据的尚未经过第一个卷积层的原始灰度图像。
一幅作为训练数据的、尚未经过第一个卷积层的原始灰度图像
下面再给出一个由第1个卷积层输出的特征图,它突出了输出的随机性。
一个突出了输出的随机性的由第1个卷积层输出的特征图
调试CNN时需要能够熟练使用这些滤波器。截至本书撰写之时,TensorBoard尚未提供任何显示滤波器或特征图的内置支持。可利用tf.image_summary运算得到训练后的滤波器和所生成的特征图的简单视图。为数据流图添加一个图像概要输出(image summary output)能够对所使用的滤波器和通过将它们运用于输入图像而得到的特征图获得整体性的了解。
一个值得一提的Jupyter Notebook扩展是TensorDebugger,它目前尚处在开发初期。该扩展拥有一种能够在迭代中以GIF动画形式查看滤波器变化的功能。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论