)

Posted wyy_persist

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了)相关的知识,希望对你有一定的参考价值。

1.介绍

欢迎使用Python学习计算机视觉的深度学习实践包!这本书是在完成Starter Bundle之后,您的计算机视觉教育深度学习的下一个合乎逻辑的步骤。

实践者包的目的是建立在您从Starter包中获得的知识的基础上,并介绍更高级的算法、概念和行业技巧——这些技术将在本书的三个不同部分中介绍。

第一部分将侧重于以某种方式提高分类准确性的方法。提高分类准确性的一个方法是应用迁移学习方法,如微调或将你的网络作为一个特征提取器。

我们还将探讨集成方法(例如,训练多个网络并结合结果),以及这些方法如何通过很少的额外努力来提高你的分类。正则化方法(如数据增强)用于生成额外的训练数据——在几乎所有情况下,数据增强可以提高模型的泛化能力。更高级的优化算法,如Adam[1]、RMSprop[2]和其他算法也可以在一些数据集上使用,以帮助您获得更低的损失。在我们回顾这些技术之后,我们将研究应用这些方法的最佳途径,以确保您以最少的努力获得最大的效益。

然后,我们将转向实践者包的第二部分,该部分主要关注更大的数据集和更奇特的网络架构。到目前为止,我们只处理了能够装入系统主存的数据集——但是如果我们的数据集太大而不能装入RAM呢?然后我们做什么?当我们使用HDF5时,我们将在第9章中讨论这个问题。

考虑到我们将使用更大的数据集,我们也将能够使用AlexNet、GoogLeNet、ResNet和VGGNet的更深层变体来讨论更高级的网络架构。这些网络架构将应用于更具挑战性的数据集和竞争,包括Kaggle: dog vs. Cats recognition challenge[3]以及cs231n Tiny ImageNet challenge[4],斯坦福CNN学生参加的任务完全相同。正如我们所发现的,我们将能够在Kaggle狗对猫排行榜上获得前25名并在cs231n挑战中获得我们技术类型的冠军。

本书的最后一部分涵盖了图像分类之外的计算机视觉深度学习的应用,包括基本的对象检测,深度做梦和神经风格,生成对抗网络(GANs),和图像超分辨率。同样,本卷中涉及的技术比Starter Bundle要先进得多——这是您将从深度学习新手中脱颖而出并转变为真正的深度学习实践者的地方。要开始向深度学习专家转变,只需翻开这一页。

2.数据增强

根据Goodfellow等人的说法,正则化是“我们对学习算法所做的任何修改,其目的是减少其泛化误差,而不是减少其训练误差”[5]。简而言之,正则化寻求减少我们的测试误差,可能以略微增加训练误差为代价。

我们已经在Starter Bundle的第9章中看到了不同形式的正则化;然而,这些都是正则化的参数化形式,要求我们更新损失/更新函数。事实上,还有其他类型的正则化:

  1. 修改网络架构本身;
  2. 增加传入网络进行训练的数据;

Dropout是通过实现更大的通用性来修改网络架构的一个很好的例子。在这里,我们插入一个层,该层随机地将节点从上一层断开到下一层,从而确保没有单个节点负责学习如何表示给定的类。

在本章的其余部分,我们将讨论另一种称为数据扩充的正则化。该方法在将训练样本送入网络进行训练之前,故意对其进行扰动,略微改变其外观。最终的结果是,网络始终可以看到由原始训练数据生成的“新的”训练数据点,这在一定程度上减轻了我们收集更多训练数据的需要(尽管一般来说,收集更多的训练数据很少会损害算法)。

2.1 数据增强

数据增强包括广泛的技术,通过应用随机抖动和扰动,从原始训练样本生成新的训练样本,这样类标签就不会改变。在应用数据扩充时,我们的目标是增加模型的通用性。鉴于我们的网络不断地看到新的、稍作修改的输入数据点,它能够学习到更健壮的特性。在测试时,我们不会应用数据增强和评估我们训练过的网络——在大多数情况下,您会看到测试准确性的提高,但可能会以训练准确性略微下降为代价。

左图:250个数据点的样本,完全符合正态分布。右:在分布中添加少量的随机“抖动”。这种类型的数据增强可以增加我们的网络的泛化性。

让我们考虑均值和单位方差为零的正态分布的图2.1(左)。在这些数据上训练一个机器学习模型可能会导致我们精确地建模分布——然而,在现实世界的应用程序中,数据很少遵循这样整洁的分布。

相反,为了增加我们的分类器的可泛化性,我们可以首先通过添加一些从随机分布(右)中提取的值来随机地使点抖动。图中仍然近似服从正态分布,但不像左边那样是完美分布。在此数据上训练的模型更有可能泛化到训练集中不包括的示例数据点。

在计算机视觉的背景下,数据增强是很自然的。例如,我们可以通过应用简单的几何变换从原始图像中获得额外的训练数据,如random:

  1. 翻译2。旋转3。4级的变化。剪切5。水平(在某些情况下,是垂直)翻转

对输入图像应用(少量)这些转换会稍微改变图像的外观,但不会改变类标签——因此使数据增强成为一种非常自然、简单的方法,可以应用于计算机视觉任务的深度学习。应用于计算机视觉的更先进的数据增强技术包括给定颜色空间中颜色的随机扰动[6]和非线性几何畸变[7]。

2.2 可视化数据增强

我们的脚本需要三个命令行参数,每个参数详细如下:

•-image:这是到输入图像的路径,我们想要应用数据增强并可视化结果。

•——output:在对给定的图像应用数据扩充后,我们希望将结果存储在磁盘上,以便我们可以检查它——这个开关控制输出目录。

•——prefix:一个字符串,将被添加到输出的imagefilename前。

现在,我们的命令行参数已经被解析,让我们加载我们的输入图像,将其转换为一个keras兼容的数组,并为图像添加一个额外的维度,就像我们在为图像分类做准备时所做的那样:

ImageDataGenerator类有很多参数,太多了,在本书中无法列举。有关参数的完整介绍,请参阅Keras的官方文档(http://pyimg.co/j8ad8)。

相反,我们将重点关注您最可能在自己的应用程序中使用的增强参数。参数rotation_range控制随机旋转的程度范围。在这里,我们将允许我们的输入图像随机旋转±30度。width_shift_range和height_shift_range分别用于水平和垂直移动。参数值是给定维度的一部分,在本例中为10%。

shear_range以弧度的形式控制逆时针方向的角度,我们的图像将允许被剪切。然后我们有zoom_range,这是一个浮点值,它允许图像根据以下值的均匀分布被“放大”或“缩小”:[1 - zoom_range, 1 + zoom_range]。

最后,horizontal_flip布尔值控制是否允许在训练过程中水平翻转给定的输入。对于大多数计算机视觉应用程序来说,图像的水平翻转不会改变生成的类标签——但在有些应用程序中,水平(或垂直)翻转会改变图像的语义。在应用这种类型的数据增强时要注意,因为我们的目标是略微修改输入图像,从而生成一个新的训练样本,而不改变类标签本身。关于图像转换的更详细的回顾,请参考PyImageSearch Gurus ([8], PyImageSearch Gurus)中的模块#1以及Szeliski[9]。

一旦ImageDataGenerator被初始化,我们就可以生成新的训练示例:

第34行和35行初始化了一个用于构造增强图像的Python生成器。我们将传入输入图像,batch_size为1(因为我们只增加一张图像),以及一些额外的参数来指定输出的imagefile路径、每个文件路径的前缀和imagefile格式。第38行然后开始在imageGen生成器中的每个图像上循环。在内部,imageGen会在每次通过循环请求一个样本时自动生成一个新的训练样本。然后增加写入磁盘的数据增强示例的总数,并在达到10个示例时停止执行脚本。

为了将数据增强可视化,我们将使用图2.2(左),这是我家的小猎犬Jemma的图像。要生成新的Jemma训练示例图像,只需执行以下命令:

我已经为这些图像构建了一个蒙版,所以你可以在图2.2(右)中看到它们。注意每个图像是如何被随机旋转、剪切、缩放和水平翻转的。在每一种情况下,图像保留原始的类别标签:狗;然而,每一幅图像都进行了轻微的修改,从而使我们的神经网络在训练时可以学习新的模式。由于输入图像会不断地变化(而类标签保持不变),与没有数据增强的训练相比,我们的训练准确度会下降,这是很常见的。

然而,正如我们在本章后面会发现的那样,数据增强可以大大减少过拟合,同时确保我们的模型更好地适用于新的输入样本。此外,当我们在处理数据集时,如果样本太少,无法应用深度学习,我们可以利用数据增强来生成额外的训练数据,从而减少训练深度学习网络所需的手动标记数据的数量。

2.3 使用数据增强与否对训练的影响

在本节的第一部分,我们将讨论Flowers-17数据集,这是一个非常小的数据集(就计算机视觉任务的深度学习而言),以及数据增强如何通过生成额外的训练样本来帮助我们人为地增加该数据集的大小。在此,我们将执行两个实验:

  1. 训练MiniVGGNet on Flowers-17使用数据增强;
  2. 训练MiniVGGNet on Flowers-17不使用数据增强。

我们会发现,应用数据增强可以显著减少过拟合,并允许MiniVGGNet获得更高的分类精度。

​​​​​​​2.3.1 Flowers-17 数据集

Flowers-17数据集[10]是一个细粒度的分类挑战,我们的任务是识别17种不同的花卉。图像数据集非常小,每个类只有80幅图像,而总共有1,360幅图像。将深度学习应用于计算机视觉任务时,一般的经验法则是每节课要有1000 - 5000个示例,所以我们在这方面肯定存在巨大的不足。

我们称Flowers-17为细粒度分类任务,因为所有类别都非常相似(例如,种花)。事实上,我们可以把每一个类别看作是子类别。这些类别当然是不同的,但有很多共同的结构(例如,花瓣,雄蕊、雌蕊等)。细粒度的分类任务对于深度学习从业者来说是最具挑战性的,因为这意味着我们的机器学习模型需要学习极具鉴别性的特征,以区分非常相似的类。考虑到我们有限的训练数据,这个细粒度的分类任务变得更加困难。

 ​​​​​​​

2.3.2 创建预处理

到目前为止,我们只对图像进行了预处理,将其调整为固定大小,忽略了宽高比。在某些情况下,特别是对于基本的基准数据集,这样做是可以接受的。

然而,对于更有挑战性的数据集,我们仍然应该设法调整到固定大小,但保持高宽比。要将此操作可视化,请考虑图2.4。

在左边,我们有一个输入图像,我们需要将其大小调整为固定的宽度和高度。忽略长宽比,我们将图像的大小调整为256 × 256像素(中间),有效地挤压和扭曲图像,使其满足我们所需的尺寸。一个更好的方法是考虑图像的长宽比(右),我们首先沿着较短的尺寸调整大小,使其宽度为256像素,然后沿着高度裁剪图像,使其高度为256像素。

在裁剪过程中,我们有效地丢弃了部分图像,同时也保持了图像的原始长宽比。保持一致的纵横比可以让我们的卷积神经网络学习更有区别的、一致的特征。这是一种常见的技术,我们将在整个练习者Bundle和ImageNet Bundle的其余部分中使用它处理更高级的数据集。

就像在SimplePreprocessor中一样,我们的构造函数需要两个参数(目标输出图像的期望宽度和高度)以及在调整图像大小时使用的插值方法。然后我们可以定义预处理函数如下:

preprocess函数只接受一个参数,即我们希望进行预处理的图像。第16行抓取输入图像的宽度和高度,而第17行和第18行决定了我们将在沿着较大的尺寸裁剪时使用的delta偏移量。同样,我们的方面感知预处理器是一个两步算法:

  1. 步骤1:确定最短的尺寸并沿着它调整大小。
  2. 步骤2:沿着最大的尺寸裁剪图像,获得目标的宽度和高度。

下面的代码块处理检查宽度是否小于高度,如果是,则沿着宽度调整大小:

//截止到P22页

 # if the width is smaller than the height, then resize

21 # along the width (i.e., the smaller dimension) and then

22 # update the deltas to crop the height to the desired

23 # dimension

24 if w < h:

25 image = imutils.resize(image, width=self.width,

26 inter=self.inter)

27 dH = int((image.shape[0] - self.height) / 2.0)

# otherwise, the height is smaller than the width so

30 # resize along the height and then update the deltas

31 # to crop along the width

32 else:

33 image = imutils.resize(image, height=self.height,

34 inter=self.inter)

35 dW = int((image.shape[1] - self.width) / 2.0)

# now that our images have been resized, we need to

38 # re-grab the width and height, followed by performing

39 # the crop

40 (h, w) = image.shape[:2]

41 image = image[dH:h - dH, dW:w - dW]

42

43 # finally, resize the image to the provided spatial

44 # dimensions to ensure our output image is always a fixed

45 # size

46 return cv2.resize(image, (self.width, self.height),

47 interpolation=self.inter)

​​​​​​​2.3.3 Flowers-17:没哟使用数据增强的情况下的实验

第2-14行导入所需的Python包。这些进口产品你以前都见过,但我想提请你注意:1。第6行:这里我们导入新定义的AspectAwarePreprocessor。2. 第7行:尽管使用了单独的图像预处理器,我们仍然能够使用SimpleDatasetLoader从磁盘加载我们的数据集。3.第8行:我们将在我们的数据集上训练MiniVGGNet架构。

让我们继续从我们的输入图像中提取类标签:

数据集的形式:

因此,为了提取类标签,我们可以简单地在路径分隔符(第26行)上拆分后提取第二个到最后一个索引,从而产生文本bluebell。如果您很难理解这个路径和标签提取是如何工作的,我建议您打开一个Python shell,使用文件路径和路径分隔符。特别是,请注意如何根据操作系统的路径分隔符分割字符串,然后使用Python索引来提取数组的各个部分。然后,第27行从图像路径确定唯一的类标签集(在本例中,共有17个类)。

给定我们的imagepath,我们可以从磁盘加载Flowers-17数据集:

第30行初始化了AspectAwarePreprocessor,这样它处理的每个图像都将是64 × 64像素。然后在第31行初始化ImageToArrayPreprocessor,允许我们将图像转换为keras兼容的数组。然后,我们分别使用这两个预处理器实例化SimpleDatasetLoader(第35行)。

数据和相应的标签在第36行从磁盘加载。然后,通过将原始像素强度除以255,将数据数组中的所有图像归一化到范围[0,1]。

现在,我们的数据已经加载,我们可以执行一个训练和测试分割(75%的训练,25%的测试),以及一个热编码我们的标签:

为了训练我们的花分类器,我们将使用MiniVGGNet架构和SGD优化器:

# initialize the optimizer and model
print("[INFO] compiling model...")
opt = SGD(lr=0.05)
model = MiniVGGNet.MiniVGGNet.build(width=64, height=64, depth=3,
classes=len(classNames))
model.compile(loss="categorical_crossentropy", optimizer=opt,
metrics=["accuracy"])
# train the network
print("[INFO] training network...")
H = model.fit(trainX, trainY, validation_data=(testX, testY),
batch_size=32, epochs=100, verbose=1)

MiniVGGNet架构将接受空间尺寸为64 × 64 × 3(64像素宽,64像素高,3通道)的图像。类的总数是len(classNames),在本例中,它等于17,每个类对应Flowers-17数据集中的每个类别。

我们将使用SGD训练MiniVGGNet,初始学习率为α = 0.05。我们将故意忽略学习率衰减,以便在下一节中演示数据增强的影响。58、59号线列车MiniVGGNet共100个时代。

然后,我们评估我们的网络,并画出随着时间的推移我们的损失和准确性:

运行以下命令:

python minivggnet_flowers17.py --dataset ../flowers17/images

从输出中我们可以看到,我们能够获得64%的分类准确率,在我们有限的训练数据量下,这是相当合理的。然而,值得关注的是我们的loss and accuracy plot (Figure(2.5))。正如图中所示,我们的网络很快开始过拟合过去的epoch 20。这种行为的原因是,我们只有1,020个训练示例,每个类只有60张图片(其他图片用于测试)。请记住,在训练卷积神经网络时,理想情况下每个类应该有1000 - 5000个示例。

此外,训练精度在前几个阶段飙升至95%以上,最终在后几个阶段获得100%的精度——这一输出是一个明显的过拟合案例。由于缺乏大量的训练数据,MiniVGGNet正在对训练数据中的底层模式进行建模太接近,无法概括到测试数据。为了对抗过拟合,我们可以应用正则化技术-在本章的上下文中,我们的正则化方法将是数据扩充。在实践中,您还将包括其他形式的正则化(权重衰减、退出等),以进一步减少过拟合的影响。

​​​​​​​2.3.4 Flowers-17:数据增强

在本例中,我们将应用与前一节相同的训练过程,只是增加了一个内容:我们将应用数据增强。为了了解数据增强如何在防止过拟合的同时提高我们的分类精度,打开一个新文件,命名为minivggnet_flowers17_data_augy,让我们开始工作:

在这里,我们将允许图像为:随机旋转±30°2。水平和垂直移动0.2倍。剪切0.2 4。在范围内均匀采样放大[0.8,1.2]5。随机horizontallyflipped。

根据您的确切数据集,您将希望调整这些数据增强值。根据应用程序的不同,通常可以看到旋转范围在[10,30]之间。水平和垂直移动通常在范围[0.1,0.2](缩放值也是如此)。除非水平翻转你的图像会改变类标签,否则你也应该包括水平翻转。

就像在之前的实验中,我们将使用SGD优化器训练MiniVGGNet:

然而,用来训练我们的网络的代码需要稍微改变一下,因为我们现在使用的是一个图像生成器:

# train the network

63 print("[INFO] training network...")

64 H = model.fit_generator(aug.flow(trainX, trainY, batch_size=32),

65 validation_data=(testX, testY), steps_per_epoch=len(trainX) // 32,

66 epochs=100, verbose=1)

我们现在需要调用.fit_generator,而不是调用模型的.fit方法。.fit_generator的第一个参数是aug.flow,这是我们的数据增强函数,用于从训练数据生成新的训练样本。aug.flow要求我们传递训练数据和相应的标签。我们还需要提供批处理大小,以便生成器在训练网络时可以构造适当的批处理。

然后,我们将validation_data作为(testX, testY)的二元组提供——该数据在每个epoch的末尾用于验证。steps_per_epoch参数控制每个epoch的批处理数量——我们可以通过编程方式确定适当的steps_per_epoch值,方法是将训练样本的总数除以批处理大小,并将其转换为整数。最后,epoch控制我们的网络应该训练的epoch的总数,在本例中是100个epoch。

在网络完成训练后,你会发现准确率从64%提高到71%,比我们之前的测试提高了10.9%。然而,准确性并不是一切——真正的问题是数据增强是否有助于防止过拟合。为了回答这个问题,我们需要检查图2.6中的损失和准确性图。

在仍然存在过拟合现象的情况下,利用数据增强技术显著抑制了过拟合效应。同样,请记住,这两个实验是相同的——我们所做的唯一更改是是否应用了数据增强。作为正则化器,你还可以看到数据增强有影响。我们能够提高我们的验证准确性,从而提高我们的模型的通用性,尽管有降低的训练精度。

进一步的准确性可以通过随时间衰减学习速率来获得。本章没有特别提到学习速率,所以我们可以只关注正则化器在训练卷积神经网络时对数据增强的影响。

​​​​​​​2.4 总结

数据增强是一种对训练数据进行操作的正则化技术。顾名思义,数据增强通过应用一系列随机的翻译、旋转、剪切和翻转,随机地抖动我们的训练数据。应用这些简单的转换不会改变输入图像的类标签;然而,每个增强图像都可以被认为是训练算法以前没有见过的“新”图像。因此,我们的训练算法不断地被提出新的训练样本,使其学习到更鲁棒和更有区别的模式。

正如我们的结果所显示的,应用数据增强提高了我们的分类准确性,同时帮助减轻过拟合的影响。此外,数据增强还允许我们在每个类只使用60个样本来训练卷积神经网络,远低于建议的每类1000 - 5000个样本。

虽然收集“自然的”训练样本总是更好的,但在必要时,可以使用数据增强来克服小数据集的限制。当涉及到您自己的实验时,您应该将数据增强应用到您运行的几乎每个实验中。由于CPU现在负责随机转换你的输入,所以你必须承受轻微的性能冲击;但是,通过在将数据传递给负责培训网络的线程之前,在后台使用线程和增强数据,可以减轻这种性能冲击。

同样,在实践者Bundle和ImageNet Bundle的几乎所有剩余章节中,我们都将使用数据增强。现在就花点时间熟悉一下这项技术,因为它将帮助您更快地获得性能更好的深度学习模型(使用更少的数据)。

3. 网络作为特征提取器

在接下来的几章中,我们将讨论迁移学习的概念,即使用一个预先训练的模型作为从数据中学习模式的“捷径”的能力。

考虑一个传统的机器学习场景,我们面临两个分类挑战。在第一个挑战中,我们的目标是训练一个卷积神经网络来识别图像中的狗和猫(正如我们将在第10章中做的那样)。

然后,在第二个项目中,我们的任务是识别三种不同的熊:灰熊、北极熊和大熊猫。使用机器学习、神经网络和深度学习的标准实践,我们将这些挑战视为两个独立的问题。首先,我们将收集足够的狗和猫的标记数据集,然后在数据集上训练一个模型。然后,我们将重复这个过程第二次,只是这一次,收集我们的熊品种的图像,然后在标记的熊数据集上训练一个模型。

迁移学习提出了一种不同的训练范式——如果我们可以使用现有的预先训练的分类器,并将其作为一个新的分类任务的起点,会怎么样?在上述提出的挑战的背景下,我们将首先训练一个卷积神经网络来识别狗和猫。然后,我们将使用在狗和猫数据上训练过的相同CNN来区分熊的类别,即使没有熊的数据与狗和猫的数据混合。

这听起来好得令人难以置信吗?实际上并不是。在大规模数据集(如ImageNet)上训练的深度神经网络在迁移学习任务中表现出色。这些网络学习一组丰富的、具有鉴别性的特征,以识别1000种不同的对象类别。这些过滤器可以用于分类任务,而不是CNN最初训练的内容,这是有道理的。

一般来说,在应用于计算机视觉深度学习时,迁移学习有两种类型:

  1. 将网络视为任意特征提取器;
  2. 移除现有网络的全连接层,将新的FC层设置在CNN之上,并微调这些权重(以及之前的层)来识别对象类;

在这一章中,我们将主要关注迁移学习的第一种方法,将网络作为特征提取器。然后,我们将在第5章中讨论如何调整网络的权重以适应特定的分类任务。

​​​​​​​3.1 利用预先训练的CNN提取特征

到目前为止,我们一直将卷积神经网络视为端到端图像分类器:

  1. 我们像网络中输入一个图片;
  2. 图片前向转播网络;
  3. 得到一个最终的分类可能性从网络的最后输出层;

然而,没有“规则”说我们必须允许图像通过整个网络转发传播。相反,我们可以在任意一层停止传播,比如激活层或池化层,从这个时候的网络中提取值,然后使用它们作为特征向量。例如,让我们考虑Simonyan和Zisserman[11]的VGG16网络架构(图3.1,左)。

左图:原始的VGG16网络架构,输出1,000个ImageNet类标签的每个标签的概率。右:从VGG16中移除FC层,而不是返回最后一个POOL层的输出。这个输出将作为我们提取的特征。

除了网络中的层,我们还包括了每一层的输入和输出形状。当将网络作为一个特征提取器时,我们本质上是在任意点“切断”网络(通常是在完全连接的层之前,但这实际上取决于你的特定数据集)。

现在我们网络中的最后一层是最大池化层(图3.1,右),它的输出形状为7 × 7 × 512,这意味着有512个大小为7 × 7的滤波器。如果我们通过这个去掉FC头的网络转发一幅图像,我们将会得到5127 × 7次激活,这些激活或未激活都是基于图像内容的。因此,我们实际上可以取这7 × 7 × 512 = 25,088的值,将其作为一个特征向量来量化图像的内容。

如果我们对整个图像数据集重复这个过程(包括VGG16没有训练的数据集),我们将得到N个图像的设计矩阵,每个图像有25088列用于量化其内容(即特征向量)。给定我们的特征向量,我们可以在这些特征之上训练现成的机器学习模型,如线性支持向量机、逻辑回归分类器或随机森林,以获得识别新的图像类别的分类器。

请记住,CNN本身并不能够识别这些新类——相反,我们使用CNN作为一个中介特征提取器。下游的机器学习分类器将负责学习从CNN中提取的特征的基本模式。

在本章的后面,我将演示如何使用预先训练的cnn(特别是VGG16)和Keras库,以获得> 95%的分类精度的图像数据集,如Animals, cal技术-101和Flowers-17。这两个数据集都不包含VGG16训练过的图像,但通过应用迁移学习,我们能够轻松地构建超精确的图像分类器。诀窍是提取这些特征,并以有效的方式存储它们。为了完成这个任务,我们需要HDF5。

​​​​​​​3.1.1 HDF5是什么?

HDF5是由HDF5组[12]创建的二进制数据格式,用于在磁盘上存储巨大的数字数据集(太大了,无法存储在内存中),同时方便对数据集的行进行访问和计算。HDF5中的数据是分层存储的,类似于文件系统存储数据的方式。数据首先在组中定义,组是一种容器状的结构,可以保存数据集和其他组。一旦定义了组,就可以在组内创建数据集。数据集可以被看作是同构数据类型(整数、浮点数、unicode等)的多维数组(即NumPy数组)。图3.2显示了一个包含多个数据集的hdf5文件的例子。

HDF5是用C语言编写的;然而,通过使用h5py模块(h5py.org),我们可以使用Python编程语言访问底层的C API。h5py之所以如此令人惊叹,是因为它与数据的交互非常简单。我们可以在HDF5数据集中存储大量的数据,并以类似numpy的方式操作数据。例如,我们可以使用标准的Python语法来访问和切片存储在磁盘上的tb级数据集,就像它们是加载到内存中的简单NumPy数组一样。由于有了专门的数据结构,这些片和行访问非常快。当使用HDF5和h5py时,你可以把你的数据想象成一个巨大的NumPy数组,它太大了,不能放进主存,但仍然可以被访问和操作。

也许最重要的是,HDF5格式是标准化的,这意味着以HDF5格式存储的数据集天生就具有可移植性,并且可以被其他使用不同编程语言(如C、MATLAB和Java)的开发人员访问。

在本章的其余部分,我们将编写一个定制的Python类,它允许我们有效地接受输入数据并将其写入HDF5数据集。这个类有两个目的:

  1. 将我们从VGG16中提取的特征,以高效的方式写入HDF5数据集,为我们应用迁移学习提供了一种方法;
  2. 允许我们从原始图像生成HDF5数据集,以促进更快的训练(第9章);

一个包含三个数据集的hdf5文件的例子。第一个数据集包含CALTECH-101的label_names。然后我们有标签,将每个图像映射到相应的类标签。最后,特征数据集包含CNN提取的图像量化。

如果您的系统上还没有安装HDF5和h5py,请参阅Starter Bundle第6章的补充材料,了解如何配置您的系统。

​​​​​​​3.1.2 向HDF5数据集写入特性

我们从简单的进口开始。我们只需要两个Python包来构建这个类中的功能——内置os模块和h5py,这样我们就可以访问HDF5绑定了。

定义构造函数:

def __init__(self, dims, outputPath, dataKey="images",bufSize=1000):
    ## check to see if the output path exists, and if so, raise# an exception
    if os.path.exists(outputPath):
        raise ValueError("The supplied‘outputPath‘ already "
    "exists and cannot be overwritten. Manually delete "
    "the file before continuing.", outputPath)

    # open the HDF5 database for writing and create two datasets:
    # one to store the images/features and another to store the
    # class labels
    self.db = h5py.File(outputPath, "w")
    self.data = self.db.create_dataset(dataKey, dims,
    dtype="float")
    self.labels = self.db.create_dataset("labels", (dims[0],),
    dtype="int")

    # store the buffer size, then initialize the buffer itself
    # along with the index into the datasets
    self.bufSize = bufSize
    self.buffer = "data": [], "labels": []
    self.idx = 0

HDF5DatasetWriter的构造函数接受四个参数,其中两个是可选的。dim参数控制我们将存储在数据集中的数据的尺寸或形状。把dim看作NumPy数组的.shape。如果我们存储28 × 28 = 784 MNIST数据集的(平坦的)原始像素强度,那么dimms =(70000, 784),因为MNIST中有70000个示例,每个示例的维数为784。如果我们想存储原始的cifar10图像,那么我们将设置dims=(60000, 32, 32, 3),因为在cifar10数据集中总共有60000张图像,每一张都由一个32 × 32 × 3的RGB图像表示。

在迁移学习和特征提取的背景下,我们将使用VGG16架构,并在最终的POOL层之后获取输出。最后的POOL层的输出是512 × 7 × 7,将其平铺后,得到长度为25088的特征向量。因此,在使用VGG16进行特征提取时,我们设置dims=(N, 25088),其中N为我们数据集中图像的总数。

HDF5DatasetWriter构造函数的下一个参数是outputPath——这是我们的输出hdf5文件存储在磁盘上的路径。可选的dataKey是将存储我们的算法将从中学习的数据的数据集的名称。我们默认这个值为"images",因为在大多数情况下,我们将以HDF5格式存储原始图像。然而,在这个例子中,当我们实例化HDF5DatasetWriter时,我们将设置dataKey="features"来表示我们正在存储从CNN中提取的特征。

最后,bufSize控制内存缓冲区的大小,我们默认为1000个特征向量/图像。一旦达到bufSize,我们将把缓冲区刷新到HDF5数据集。

然后,第10-13行检查outputPath是否已经存在。如果是,则向最终用户抛出一个错误(因为我们不想覆盖现有的数据库)。

第18行打开hdf5文件,使用提供的outputPath进行写入。第19和20行创建了一个具有dataKey名称和提供的亮度的数据集——这是我们存储原始图像/提取的特征的地方。第21和22行创建了第二个数据集,这个数据集用于存储数据集中每条记录的(整数)类标签。第25-28行然后初始化我们的缓冲区。

接下来,让我们回顾一下添加数据到缓冲区的方法:

def add(self, rows, labels):

31 # add the rows and labels to the buffer

32 self.buffer["data"].extend(rows)

33 self.buffer["labels"].extend(labels)

34

35 # check to see if the buffer needs to be flushed to disk

36 if len(self.buffer["data"]) >= self.bufSize:

37 self.flush()

add方法需要两个参数:我们将添加到数据集的行,以及它们相应的类标签。行和标签都被添加到第32行和第33行各自的缓冲区中。如果缓冲区被填满,我们调用flush方法将缓冲区写入文件并重置它们。

定义实现flush()方法:

def flush(self):

40 # write the buffers to disk then reset the buffer

41 i = self.idx + len(self.buffer["data"])

42 self.data[self.idx:i] = self.buffer["data"]

43 self.labels[self.idx:i] = self.buffer["labels"]

44 self.idx = i

45 self.buffer = "data": [], "labels": []

如果我们认为我们的HDF5数据集是一个大的NumPy数组,那么我们需要跟踪当前的索引到下一个可用的行,我们可以存储数据(不覆盖现有的数据)-第41行决定了矩阵中的下一个可用行。然后,第42行和第43行应用NumPy数组切片将数据和标签存储在缓冲区中。第45行然后重置缓冲区。

我们还将定义一个名为storeClassLabels的实用函数,如果调用它,将在一个单独的数据集中存储类标签的原始字符串名称:

def storeClassLabels(self, classLabels):

48 # create a dataset to store the actual class label names,

49 # then store the class labels

50 dt = h5py.special_dtype(vlen=unicode)

51 labelSet = self.db.create_dataset("label_names",

52 (len(classLabels),), dtype=dt)

53 labelSet[:] = classLabels

最后,我们的最后一个函数close将被用来将缓冲区中的任何数据写入HDF5,并关闭数据集:

正如你所看到的,HDF5DatasetWriter与机器学习或深度学习没有太多关系——它只是一个用来帮助我们以HDF5格式存储数据的类。当您继续深入学习的时候,您会注意到,当您设置一个新问题时,大部分的初始劳动都是将数据转换成您可以处理的格式。一旦你有了易于操作的数据格式,将机器学习和深度学习技术应用到你的数据上就变得非常容易了。

综上所述,由于HDF5DatasetWriter类是一个非特定于深度学习和计算机视觉的实用程序类,因此我对该类的解释要比本书中的其他代码示例更简短。如果你发现自己很难理解这门课,我建议你:

  1. 读完本章剩下的部分,你就能理解我们如何在特征提取的上下文中使用它。
  2. 花点时间自学一些基本的Python编程范例——我在这里推荐一个Python编程源码列表:http://pyimg.co/ida57。
  3. 将这个类拆开,逐个手工实现,直到您理解在底层发生了什么。

现在我们的HDF5DatasetWriter已经实现,我们可以继续使用预先训练的卷积神经网络来实际提取特征。

​​​​​​​3.2 特征提取过程

第2-13行导入所需的Python包。请注意,在第2行,我们是如何导入经过训练的VGG16网络的Keras实现的——这是我们将用作特征提取器的体系结构。第6行上的LabelEncoder类将用于将类标签从字符串转换为整数。我们还在第7行导入HDF5DatasetWriter,这样我们就可以将从CNN提取的特征写入HDF5数据集。

您还没有看到的一个导入是第10行中的progressbar。这个包与深度学习无关,但我喜欢在长时间运行的任务中使用它,因为它会在你的终端上显示一个格式化良好的进度条,以及提供大致的时间,当你的脚本将完成执行:

然后我们可以提供—batch-size—这是批处理中每次通过VGG16传递的图像的数量。这里值为32是合理的,但是如果您的机器有足够的内存,您可以增加它。在为HDF5数据集编写缓冲区之前,——buffer-size开关控制我们将存储在内存中的提取特征的数量。同样,如果您的机器有足够的内存,您可以增加缓冲区大小。

下一步是从磁盘中抓取我们的图像路径,打乱它们,并对标签进行编码:

# 存储batch_size到一个方便的变量中
bs = args["batch_size"]

# 抓取我们将描述的图像列表,然后随机洗牌他们,以方便训练和测试分割通过32 #数组切

以上是关于)的主要内容,如果未能解决你的问题,请参考以下文章

为啥在 Keras 中 fit_generator 的准确度、evaluate_generator 的准确度和自定义准确度不同?

相比于汇编语言的准确性c语言延时精确度如何提升

为啥在第一个 epoch 验证准确度高于训练准确度?

评估模型给出的准确度不等于 sklearn 分类报告准确度

准确率,精确度(AP)与召回率(AR)

Keras 2.3.0 指标准确度、精确度和召回率的值相同