6.md 37.2 KB
Newer Older
W
wizardforcel 已提交
1
# 六、使用迁移学习的视觉搜索
W
wizardforcel 已提交
2 3 4

视觉搜索是一种显示与用户上传到零售网站的图像相似的图像的方法。 通过使用 CNN 将图像转换为特征向量,可以找到相似的图像。 视觉搜索在网上购物中具有许多应用,因为它补充了文本搜索,从而更好地表达了用户对产品的选择,并且更加精致。 购物者喜欢视觉发现,并发现它是传统购物体验所没有的独特东西。

W
wizardforcel 已提交
5
在本章中,我们将使用在“第 4 章”,“图像深度学习”和“第 5 章”,“神经网络架构和模型”中学习的深度神经网络的概念。我们将使用迁移学习为图像类别开发神经网络模型,并将其应用于视觉搜索。 本章中的练习将帮助您开发足够的实践知识,以编写自己的神经网络代码和迁移学习。
W
wizardforcel 已提交
6 7 8 9

本章涵盖的主题如下:

*   使用 TensorFlow 编码深度学习模型
W
wizardforcel 已提交
10
*   使用 TensorFlow 开发迁移学习模型
W
wizardforcel 已提交
11
*   了解视觉搜索的架构和应用
W
wizardforcel 已提交
12 13 14 15
*   使用`tf.data`处理视觉搜索输入数据管道

# 使用 TensorFlow 编码深度学习模型

W
wizardforcel 已提交
16
我们在“第 5 章”,“神经网络架构和模型”中了解了各种深度学习模型的架构。 在本节中,我们将学习如何使用 TensorFlow/Keras 加载图像,浏览和预处理数据,然后应用三个 CNN 模型(VGG16,ResNet 和 Inception)的预训练权重来预测对象类别。
W
wizardforcel 已提交
17

W
wizardforcel 已提交
18
请注意,由于我们不是在构建模型,而是将使用已构建的模型(即具有预先训练的权重的模型)来预测图像类别,因此本部分不需要训练。
W
wizardforcel 已提交
19

W
wizardforcel 已提交
20
[本部分的代码可以在以下位置找到](https://github.com/PacktPublishing/Mastering-Computer-Vision-with-TensorFlow-2.0/blob/master/Chapter06/Chapter6_CNN_PretrainedModel.ipynb)
W
wizardforcel 已提交
21 22 23

让我们深入研究代码并了解其每一行的目的。

W
wizardforcel 已提交
24
# 下载权重
W
wizardforcel 已提交
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90

下载权重的代码如下:

```py
from tensorflow.keras.applications import VGG16
from keras.applications.vgg16 import preprocess_input
from tensorflow.keras.applications.resnet50 import ResNet50, preprocess_input
from tensorflow.keras.applications import InceptionV3
from keras.applications.inception_v3 import preprocess_input
```

前面的代码执行以下两个任务:

1.  权重将作为以下代码的输出的一部分按以下方式下载:

*   `Download VGG16 weight, the *.h5 file`
*   `Download Resnet50 weight, the *.h5 file`
*   `Download InceptionV3 weight, the *.h5 file`

2.  它对图像进行预处理,以将当前图像标准化为`ImageNet RGB`数据集。 由于该模型是在`ImageNet`数据集上开发的,因此,如果没有此步骤,该模型可能会导致错误的类预测。

# 解码预测

ImageNet 数据具有 1,000 个不同的类别。 诸如在 ImageNet 上训练的 Inception 之类的神经网络将以整数形式输出该类。 我们需要使用解码将整数转换为相应的类名称。 例如,如果输出的整数值为`311`,则需要解码`311`的含义。 通过解码,我们将知道`311`对应于折叠椅。

用于解码预测的代码如下:

```py
from tensorflow.keras.applications.vgg16 import decode_predictions
from tensorflow.keras.applications.resnet50 import decode_predictions
from tensorflow.keras.applications.inception_v3 import decode_predictions
```

前面的代码使用`decode_predictions`命令将类整数映射到类名称。 没有此步骤,您将无法预测类名。

# 导入其他常用功能

本节是关于导入 Keras 和 Python 的通用包的。 Keras `preprocessing`是 Keras 的图像处理模块。 其他常见导入功能的代码如下所示:

```py
from keras.preprocessing import image
import numpy as np
import matplotlib.pyplot as plt
import os
from os import listdir
```

您可以在前面的代码中观察以下内容:

*   我们加载了 Keras 图像预处理功能。
*   `numpy`是 Python 数组处理功能。
*   `matplotlib`是 Python 绘图功能。
*   需要`os`模块才能访问目录以输入文件。

# 建立模型

在本节中,我们将导入一个模型。 用于模型构建的代码如下所示(每个代码段的说明都在代码下方):

```py
model = Modelx(weights='imagenet', include_top=True,input_shape=(img_height, img_width, 3))
Modelx = VGG16 or ResNet50 or InceptionV3
```

模型构建具有三个重要参数:

*   `weights` 是我们在以前下载的 ImageNet 图像上使用的预训练模型。
W
wizardforcel 已提交
91
*   `include_top`功能指示是否应包含最终的密集层。 对于预训练模型的类别预测,始终为`True`; 但是,在本章的后面部分(“使用 TensorFlow 开发迁移学习模型”)中,我们将学习到,在迁移学习期间,此功能设置为`False`仅包含卷积层。
W
wizardforcel 已提交
92 93 94 95 96 97 98 99 100 101 102 103
*   `input_shape`是通道的高度,宽度和数量。 由于我们正在处理彩色图像,因此通道数设置为`3`

# 从目录输入图像

从目录输入图像的代码如下所示:

```py
folder_path = '/home/…/visual_search/imagecnn/'
images = os.listdir(folder_path)
fig = plt.figure(figsize=(8,8))
```

W
wizardforcel 已提交
104
前面的代码指定图像文件夹路径,并定义了图像属性,以便能够在以后的部分中下载图像。 它还将图形尺寸指定为`8 x 8`
W
wizardforcel 已提交
105

W
wizardforcel 已提交
106
# 使用 TensorFlow Keras 导入并处理多个图像的循环
W
wizardforcel 已提交
107

W
wizardforcel 已提交
108
本节介绍如何批量导入多个图像以一起处理所有图像,而不是一个一个地导入它们。 这是学习的一项关键技能,因为在大多数生产应用中,您不会一步一步地导入图像。 使用 TensorFlow Keras 导入和处理多个图像的循环函数的代码如下:
W
wizardforcel 已提交
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131

```py
for image1 in images:
 i+=1
 im = image.load_img(folder_path+image1, target_size=(224, 224))
 img_data = image.img_to_array(im) 
img_data = np.expand_dims(img_data, axis=0)
img_data = preprocess_input(img_data)
resnet_feature = model_resnet.predict(img_data,verbose=0)
 label = decode_predictions(resnet_feature)
 label = label[0][0]
 fig.add_subplot(rows,columns,i)
 fig.subplots_adjust(hspace=.5)
 plt.imshow(im)
 stringprint ="%.1f" % round(label[2]*100,1)
 plt.title(label[1] + " " + str(stringprint) + "%")
plt.show()
```

前面的代码执行以下步骤:

1.`image1`作为循环的中间值,循环遍历`images`属性。
2.  `image.load`功能将每个新图像添加到文件夹路径。 请注意,对于 VGG16 和 ResNet,目标大小是`224`,对于启动,目标大小是`299`
W
wizardforcel 已提交
132
3.  使用 NumPy 数组将图像转换为数组函数并扩展其尺寸,然后按照“下载权重”部分中的说明应用`preprocessing`函数。
W
wizardforcel 已提交
133 134 135 136 137 138
4.  接下来,它使用`model.predict()`函数计算特征向量。
5.  然后,预测解码类别标签名称。
6.  `label`函数存储为数组,并且具有两个元素:类名和置信度`%`

7.  接下来的几节涉及使用`matplotlib`库进行绘图:

W
wizardforcel 已提交
139
*   `fig.add_subplot`具有三个元素:`rows``columns``i` –例如,总共 9 幅图像分为三列三行,`i`术语将从`1`变为`9``1`是第一张图片,`9`是最后一张图片。
W
wizardforcel 已提交
140 141 142
*   `fig.subplots_adjust`在图像之间添加垂直空间。
*   `plt.title`将标题添加到每个图像。

W
wizardforcel 已提交
143
请注意,[完整的代码可以在本书的 GitHub 链接中找到](https://github.com/PacktPublishing/Mastering-Computer-Vision-with-TensorFlow-2.0/blob/master/Chapter06/Chapter6_CNN_PretrainedModel.ipynb)
W
wizardforcel 已提交
144 145 146

为了验证模型,将九张不同的图像存储在目录中,并逐个通过每个模型以生成预测。 下表显示了使用三种不同的神经网络模型对每个目标图像的最终预测输出:

W
wizardforcel 已提交
147
| **目标图片** | **VGG16** | **ResNet** | **Inception** |
W
wizardforcel 已提交
148
| --- | --- | --- | --- |
W
wizardforcel 已提交
149 150
| 餐桌 | 餐桌 58% | 台球桌 30.1% | 桌子 51% |
| 炒锅 | 钢包 55% | 钢包 87% | 钢包 30% |
W
wizardforcel 已提交
151 152
| 沙发 | 单人沙发 42% | 单人沙发 77% | 单人沙发 58% |
| 床 | 单人沙发 35% | 四海报 53% | 单人沙发 44% |
W
wizardforcel 已提交
153 154 155
| 水壶 | 水壶 93% | 水壶 77% | 水壶 98% |
| 行李 | 折叠椅 39% | 背包 66% | 邮袋 35% |
| 背包 | 背包 99.9% | 背包 99.9% | 背包 66% |
W
wizardforcel 已提交
156
| 长椅 | 单人沙发 79% | 单人沙发 20% | 单人沙发 48% |
W
wizardforcel 已提交
157
| 越野车 | 小型货车 74% | 小型货车 98% | 小型货车 52% |
W
wizardforcel 已提交
158 159 160 161 162 163 164

下图显示了九个类的 VGG16 输出:

![](img/50eb1879-7f28-46db-9100-6772af53432b.png)

在上图中,您可以观察到以下内容:

W
wizardforcel 已提交
165
*   每个图形尺寸为`224 x 224`,标签的顶部打印有置信度百分比。
W
wizardforcel 已提交
166 167 168 169 170 171 172 173
*   三种错误的预测:炒锅(预测为钢包),床(预测为工作室沙发)和行李(预测为折叠椅)。

下图显示了这九类的 ResNet 预测输出:

![](img/fbc53a45-2cac-4c98-b150-2296b121f8b1.png)

在上图中,您可以观察到以下内容:

W
wizardforcel 已提交
174
*   每个图形尺寸为`224 x 224`,标签的顶部打印有置信度百分比。
W
wizardforcel 已提交
175 176 177 178 179 180 181 182
*   有两个错误的预测:炒锅(预测为钢包)和行李(预测为背包)。

下图显示了这九类的初始预测输出:

![](img/9ffee1bb-eb5f-4368-a95b-42b4bb236979.png)

由此,您可以观察到以下内容:

W
wizardforcel 已提交
183
*   每个图形尺寸为`224 x 224`,标签的顶部打印有置信度百分比。
W
wizardforcel 已提交
184 185 186 187
*   有两个错误的预测:炒锅(预测为钢包)和床(预测为工作室沙发)。

通过本练习,我们现在了解了如何在不训练单个图像的情况下使用预先训练的模型来预测知名的类对象。 我们之所以这样做,是因为每个图像模型都使用 ImageNet 数据库中的 1,000 个类别进行了训练,并且计算机视觉社区可以使用该模型产生的权重,以供其他模型使用。

W
wizardforcel 已提交
188
在下一节中,我们将学习如何使用迁移学习为自定义图像训练模型以进行预测,而不是从直接从 ImageNet 数据集开发的模型中进行推断。
W
wizardforcel 已提交
189

W
wizardforcel 已提交
190
# 使用 TensorFlow 开发迁移学习模型
W
wizardforcel 已提交
191

W
wizardforcel 已提交
192
我们在“第 5 章”,“神经网络架构和模型”中引入了迁移学习的概念,并在“使用 TensorFlow 编写深度学习模型”部分中,演示了如何基于预训练模型预测图像类别。 我们已经观察到,预训练模型在大型数据集上可以获得合理的准确率,但是我们可以通过在自己的数据集上训练模型来对此进行改进。 一种方法是构建整个模型(例如,ResNet)并在我们的数据集上对其进行训练-但是此过程可能需要大量时间才能运行模型,然后为我们自己的数据集优化模型参数。
W
wizardforcel 已提交
193

W
wizardforcel 已提交
194
另一种更有效的方法(称为**迁移学习**)是从基本模型中提取特征向量,而无需在 ImageNet 数据集上训练顶层,然后添加我们的自定义全连接层,包括激活,退出和 softmax,构成我们的最终模型。 我们冻结了基础模型,但是新添加的组件的顶层仍未冻结。 我们在自己的数据集上训练新创建的模型以生成预测。 从大型模型迁移学习的特征图,然后通过微调高阶模型参数在我们自己的数据集上对其进行自定义的整个过程称为迁移学习。 在接下来的几节中,将从“分析和存储数据”开始,说明迁移学习工作流程以及相关的 TensorFlow/Keras 代码。
W
wizardforcel 已提交
195 196 197

# 分析和存储数据

W
wizardforcel 已提交
198
首先,我们将从分析和存储数据开始。 在这里,我们正在构建具有三个不同类别的家具模型:`bed``chair``sofa`。 我们的目录结构如下。 每个图像都是大小为`224 x 224`的彩色图像。
W
wizardforcel 已提交
199 200 201 202 203 204 205 206 207 208 209 210 211

`Furniture_images`

*   `train`(2,700 张图像)
    *   `bed`(900 张图像)
    *   `chair`(900 张图像)
    *   `sofa`(900 张图像)

*   `val`(300 张图像)
    *   `bed`(100 张图像)
    *   `chair`(100 张图像)
    *   `sofa`(100 张图像)

W
wizardforcel 已提交
212
注意,图像数量仅是示例; 在每种情况下都是独一无二的。 要注意的重要一点是,为了进行良好的检测,我们每班需要约 1,000 张图像,并且训练和验证的比例为 90%:10%。
W
wizardforcel 已提交
213 214 215

# 导入 TensorFlow 库

W
wizardforcel 已提交
216
我们的下一步是导入 TensorFlow 库。 以下代码导入 ResNet 模型权重和预处理的输入,与上一节中的操作类似。 我们在“第 5 章”,“神经网络架构和模型”中了解了每个概念:
W
wizardforcel 已提交
217 218 219 220 221 222 223 224 225

```py
from tensorflow.keras.applications.resnet50 import ResNet50, preprocess_input
from tensorflow.keras.layers import Dense, Activation, Flatten, Dropout
from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.optimizers import SGD, Adam
img_width, img_height = 224, 224
```

W
wizardforcel 已提交
226
该代码还导入了​​几个深度学习参数,例如`Dense`(全连接层),`Activation``Flatten``Dropout`。 然后,我们导入顺序模型 API 以创建逐层模型,并使用**随机梯度下降****SGD**)和 Adam 优化器。 对于 ResNet 和 VGG,图像的高度和宽度为`224`,对于 Inception 模型,图像的高度和宽度为`299`
W
wizardforcel 已提交
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263

# 设置模型参数

为了进行分析,我们设置模型参数,如以下代码块所示:

```py
NUM_EPOCHS = 5
batchsize = 10
num_train_images = 900
num_val_images = 100
```

然后,基础模型的构建类似于上一节中的示例,不同之处在于,我们不通过设置`include_top=False`来包括顶级模型:

```py
base_model = ResNet50(weights='imagenet',include_top=False,input_shape=(img_height, img_width, 3))
```

在此代码中,我们使用基本模型通过仅使用卷积层来生成特征向量。

# 建立输入数据管道

我们将导入一个图像数据生成器,该图像数据生成器使用诸如旋转,水平翻转,垂直翻转和数据预处理之类的数据增强来生成张量图像。 数据生成器将重复训练和验证数据。

# 训练数据生成器

让我们看一下用于训练数据生成器的以下代码:

```py
from keras.preprocessing.image import ImageDataGenerator
train_dir = '/home/…/visual_search/furniture_images/train'
train_datagen =  ImageDataGenerator(preprocessing_function=preprocess_input,rotation_range=90,horizontal_flip=True,vertical_flip=True)
```

目录 API 的流程-用于从目录导入数据。 它具有以下参数:

*   `Directory`:这是文件夹路径,应设置为存在所有三个类的图像的路径。 在这种情况下,示例为`train`目录的路径。 要获取路径,您可以将文件夹拖到终端,它会向您显示路径,然后可以将其复制并粘贴。
W
wizardforcel 已提交
264
*   `Target_size`:将其设置为等于模型拍摄的图像大小,例如,对于 Inception 为 299 x 299,对于 ResNet 和 VGG16 为`224 x 224`
W
wizardforcel 已提交
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
*   `Color_mode`:将黑白图像设置为`grayscale`,将彩色图像设置为`RGB`
*   `Batch_size`:每批图像的数量。
*   `class_mode`:如果只有两个类别可以预测,则设置为二进制;否则,请设置为二进制。 如果不是,则设置为`categorical`
*   `shuffle`:如果要重新排序图像,请设置为`True`; 否则,设置为`False`
*   `seed`:随机种子,用于应用随机图像增强和混排图像顺序。

以下代码显示了如何编写最终的训练数据生成器,该数据将导入模型中:

```py
train_generator = train_datagen.flow_from_directory(train_dir,target_size=(img_height, img_width), batch_size=batchsize)
```

# 验证数据生成器

接下来,我们将在以下代码中重复验证过程。 该过程与训练生成器的过程相同,除了我们将验证图像目录而不是训练图像目录:

```py
from keras.preprocessing.image import ImageDataGenerator
val_dir = '/home/…/visual_search/furniture_images/val'
val_datagen =  ImageDataGenerator(preprocessing_function=preprocess_input,rotation_range=90,horizontal_flip=True,vertical_flip=True)
val_generator = val_datagen.flow_from_directory(val_dir,target_size=(img_height, img_width),batch_size=batchsize)
```

前面的代码显示了最终的验证数据生成器。

W
wizardforcel 已提交
290
# 使用迁移学习构建最终模型
W
wizardforcel 已提交
291

W
wizardforcel 已提交
292
我们首先定义一个名为`build_final_model()`的函数,该函数接收基本模型和模型参数,例如丢弃,全连接层以及类的数量。 我们首先使用`layer.trainable = False`冻结基本模型。 然后,我们将基础模型输出特征向量展平,以进行后续处理。 接下来,我们添加一个全连接层并将其放置到展平的特征向量上,以使用 softmax 层预测新类:
W
wizardforcel 已提交
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320

```py
def build_final_model(base_model, dropout, fc_layers, num_classes):
 for layer in base_model.layers:
    layer.trainable = False
    x = base_model.output
    x = Flatten()(x)
    for fc in fc_layers:
    # New FC layer, random init
    x = Dense(fc, activation='relu')(x)
    x = Dropout(dropout)(x)
    # New softmax layer
    predictions = Dense(num_classes, activation='softmax')(x)
    final_model = Model(inputs=base_model.input, outputs=predictions)
        return final_model
    class_list = ["bed", "chair", "sofa"]
    FC_LAYERS = [1024, 1024]
    dropout = 0.3
    final_model = build_final_model(base_model,dropout=dropout,fc_layers=FC_LAYERS,num_classes=len(class_list))
```

使用带有分类交叉熵损失的`adam`优化器编译模型:

```py
adam = Adam(lr=0.00001)
final_model.compile(adam, loss='categorical_crossentropy', metrics=['accuracy'])
```

W
wizardforcel 已提交
321
使用`model.fit_generator`命令开发并运行最终模型。 历史记录存储历元,每步时间,损失,准确率,验证损失和验证准确率:
W
wizardforcel 已提交
322 323 324 325 326 327 328 329 330

```py
history = final_model.fit(train_dir,epochs=NUM_EPOCHS,steps_per_epoch=num_train_images // batchsize,callbacks=[checkpoint_callback],validation_data=val_dir, validation_steps=num_val_images // batchsize)
```

此处说明`model.fit()`的各种参数。 注意`mode.fit_generator`将来会被弃用,并由`model.fit()`功能代替,如上所示:

*   `train_dir`:输入训练数据; 其操作的详细信息已在上一节中进行了说明。
*   `epochs`:一个整数,指示训练模型的时期数。 时期从`1`递增到`epochs`的值。
W
wizardforcel 已提交
331
*   `steps_per_epoch`:这是整数。 它显示了训练完成和开始下一个纪元训练之前的步骤总数(样本批量)。 其最大值等于(训练图像数/ `batch_size`)。 因此,如果有 900 张训练图像且批量大小为 10,则每个时期的步数为 90。
W
wizardforcel 已提交
332
*   `workers`:较高的值可确保 CPU 创建足够的批量供 GPU 处理,并且 GPU 永远不会保持空闲状态。
W
wizardforcel 已提交
333
*   `shuffle`:这是布尔类型。 它指示每个时期开始时批量的重新排序。 仅与`Sequence``keras.utils.Sequence`)一起使用。 当`steps_per_epoch`不是`None`时,它无效。
W
wizardforcel 已提交
334
*   `Validation_data`:这是一个验证生成器。
W
wizardforcel 已提交
335
*   `validation_steps`:这是`validation_data`生成器中使用的步骤总数(样本批量),等于验证数据集中的样本数量除以批量大小。
W
wizardforcel 已提交
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358

# 使用检查点保存模型

一个 TensorFlow 模型可以运行很长时间,因为每个时期都需要几分钟才能完成。 TensorFlow 有一个名为`Checkpoint`的命令,使我们能够在每个时期完成时保存中间模型。 因此,如果由于损失已经饱和而不得不在中间中断模型或将 PC 用于其他用途,则不必从头开始—您可以使用到目前为止开发的模型进行分析。 下面的代码块显示了对先前代码块的添加,以执行检查点:

```py
from tensorflow.keras.callbacks import ModelCheckpoint
filecheckpath="modelfurn_weight.hdf5"
checkpointer = ModelCheckpoint(filecheckpath, verbose=1, save_best_only=True)

history = final_model.fit_generator(train_generator, epochs=NUM_EPOCHS, workers=0,steps_per_epoch=num_train_images // batchsize, shuffle=True, validation_data=val_generator,validation_steps=num_val_images // batchsize, callbacks = [checkpointer])
```

前面代码的输出如下:

```py
89/90 [============================>.] - ETA: 2s - loss: 1.0830 - accuracy: 0.4011 Epoch 00001: val_loss improved from inf to 1.01586, saving model to modelfurn_weight.hdf5 90/90 [==============================] - 257s 3s/step - loss: 1.0834 - accuracy: 0.4022 - val_loss: 1.0159 - val_accuracy: 0.4800
 Epoch 2/5 89/90 [============================>.] - ETA: 2s - loss: 1.0229 - accuracy: 0.5067 Epoch 00002: val_loss improved from 1.01586 to 0.87938, saving model to modelfurn_weight.hdf5 90/90 [==============================] - 253s 3s/step - loss: 1.0220 - accuracy: 0.5067 - val_loss: 0.8794 - val_accuracy: 0.7300
 Epoch 3/5 89/90 [============================>.] - ETA: 2s - loss: 0.9404 - accuracy: 0.5719 Epoch 00003: val_loss improved from 0.87938 to 0.79207, saving model to modelfurn_weight.hdf5 90/90 [==============================] - 256s 3s/step - loss: 0.9403 - accuracy: 0.5700 - val_loss: 0.7921 - val_accuracy: 0.7900
 Epoch 4/5 89/90 [============================>.] - ETA: 2s - loss: 0.8826 - accuracy: 0.6326 Epoch 00004: val_loss improved from 0.79207 to 0.69984, saving model to modelfurn_weight.hdf5 90/90 [==============================] - 254s 3s/step - loss: 0.8824 - accuracy: 0.6322 - val_loss: 0.6998 - val_accuracy: 0.8300
 Epoch 5/5 89/90 [============================>.] - ETA: 2s - loss: 0.7865 - accuracy: 0.7090 Epoch 00005: val_loss improved from 0.69984 to 0.66693, saving model to modelfurn_weight.hdf5 90/90 [==============================] - 250s 3s/step - loss: 0.7865 - accuracy: 0.7089 - val_loss: 0.6669 - val_accuracy: 0.7700
```

W
wizardforcel 已提交
359
输出显示每个时期的损失和准确率,并且相应的文件另存为`hdf5`文件。
W
wizardforcel 已提交
360 361 362

# 绘制训练历史

W
wizardforcel 已提交
363
使用 Python `matplotlib`函数显示了显示训练精度和每个时期的训练损失的折线图。 我们将首先导入`matplotlib`,然后为训练和验证损失以及准确率定义参数:
W
wizardforcel 已提交
364 365 366 367 368 369 370 371 372

```py
import matplotlib.pyplot as plt
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
```

W
wizardforcel 已提交
373
以下代码是使用 Keras 和 TensorFlow 绘制模型输出的标准代码。 我们首先定义图形大小(`8 x 8`),并使用子图函数显示`(2,1,1)``(2,1,2)`。 然后,我们定义标签,限制和标题:
W
wizardforcel 已提交
374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398

```py
plt.figure(figsize=(8, 8))
plt.subplot(2, 1, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.ylabel('Accuracy')
plt.ylim([min(plt.ylim()),1])
plt.title('Training and Validation Accuracy')
plt.subplot(2, 1, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.ylabel('Cross Entropy')
plt.ylim([0,5.0])
plt.title('Training and Validation Loss')
plt.xlabel('epoch')
plt.show()
```

让我们看一下前面代码的输出。 以下屏幕截图显示了不同型号之间的精度比较。 它显示了 Inception 的训练参数:

![](img/394d81c7-a636-4a18-9502-6d6b37eee833.png)

W
wizardforcel 已提交
399
在前面的屏幕截图中,您可以观察到 Inception 的准确率在五个时期内达到了约 90%。 接下来,我们绘制 VGG16 的训练参数:
W
wizardforcel 已提交
400 401 402

![](img/b2b36265-00ac-461f-871b-730f38818235.png)

W
wizardforcel 已提交
403
上图显示 VGG16 的精度在五个时期内达到了约 80%。 接下来,我们绘制 ResNet 的训练参数:
W
wizardforcel 已提交
404 405 406

![](img/f0585a68-826f-4fd7-a5f7-f5fd9ef838d8.png)

W
wizardforcel 已提交
407
前面的屏幕截图显示 ResNet 的准确率在四个时期内达到了约 80%。
W
wizardforcel 已提交
408

W
wizardforcel 已提交
409
对于所有三个型号,在四个时期内,精度始终达到至少 80%。 Inception 模型的结果具有最高的准确率。
W
wizardforcel 已提交
410

W
wizardforcel 已提交
411
# 了解视觉搜索的架构和应用
W
wizardforcel 已提交
412 413 414 415 416 417 418 419 420 421 422 423

视觉搜索使用深度神经网络技术来检测和分类图像及其内容,并使用它在图像数据库内搜索以返回匹配结果列表。 视觉搜索与零售行业特别相关,因为它允许零售商显示大量类似于客户上传图像的图像以增加销售收入。视觉搜索可以与语音搜索结合使用,从而进一步增强搜索效果。 视觉信息比文本信息更相关,这导致视觉搜索更加流行。 许多不同的公司,包括 Google,Amazon,Pinterest,Wayfair,Walmart,Bing,ASOS,Neiman Marcus,IKEA,Argos 等,都建立了强大的视觉搜索引擎来改善客户体验。

# 视觉搜索的架构

诸如 ResNet,VGG16 和 Inception 之类的深度神经网络模型实质上可以分为两个部分:

*   第一个组件标识图像的低级内容,例如特征向量(边缘)。
*   第二部分表示图像的高级内容,例如最终的图像特征,它们是各种低级内容的集合。 下图说明了将七个类别分类的卷积神经网络:

![](img/34641430-6c28-4ced-8e2e-eda0bcefb882.png)

W
wizardforcel 已提交
424
上图显示了整个图像分类神经网络模型可以分为两个部分:卷积层和顶层。 在全连接层之前的最后一个卷积层是形状的特征向量(`图像数, X, Y, 通道数`),并且被展平(`图像数 * X * Y * 通道数`)以生成`n`维向量。
W
wizardforcel 已提交
425 426 427

特征向量形状具有四个分量:

W
wizardforcel 已提交
428 429 430 431
+   `图像数`表示训练图像的数量。 因此,如果您有 1,000 个训练图像,则该值为 1,000。
+   `X`表示层的宽度。 典型值为 14。
+   `Y`表示层的高度,可以是 14。
+   `通道数`表示过滤器的数量或深度 Conv2D。 典型值为 512。
W
wizardforcel 已提交
432 433 434 435 436 437 438

在视觉搜索中,我们通过使用欧几里得距离或余弦相似度等工具比较两个特征向量的相似度来计算两个图像的相似度。 视觉搜索的架构如下所示:

![](img/4be26b18-8336-4880-b921-764ca4f4b2d7.png)

此处列出了各个步骤:

W
wizardforcel 已提交
439
1.  通过将顶层与知名模型(例如 ResNet,VGG16 或 Inception)分离,然后添加自定义顶层(包括全连接层,退出,激活和 softmax 层),使用迁移学习来开发新模型。
W
wizardforcel 已提交
440 441 442 443
2.  使用新的数据集训练新模型。
3.  通过刚刚开发的新模型运行图像,上传图像并找到其特征向量和图像类。
4.  为了节省时间,请仅在与上载图像相对应的目录类中进行搜索。
5.  使用诸如欧几里得距离或余弦相似度的算法进行搜索。
W
wizardforcel 已提交
444
6.  如果余弦相似度`> 0.999`,则显示搜索结果;否则,显示搜索结果。 如果不是,请使用上传的图像重新训练模型,或者调整模型参数并重新运行该过程。
W
wizardforcel 已提交
445 446 447 448
7.  为了进一步加快搜索速度,请使用生成的边界框检测上载图像内以及搜索图像数据库和目录内对象的位置。

上述流程图包含几个关键组件:

W
wizardforcel 已提交
449 450
*   **模型开发**:这包括选择一个合适的预训练模型,除去其顶部附近的层,冻结之前的所有层,并添加一个新的顶层以匹配我们的班级。 这意味着,如果我们使用在 1,000 个类的 ImageNet 数据集上训练的预训练模型,我们将删除其顶层,并仅用 3 类`bed``chair``sofa`替换为新的顶层。 。
*   **模型训练**:这涉及首先编译模型,然后使用`model.fit()`功能开始训练。
W
wizardforcel 已提交
451 452 453 454 455
*   **模型输出**:将上传的图像和测试图像数据库中的每个图像传递给模型,以生成特征向量。 上载的图像也用于确定模型类别。
*   **搜索算法**:搜索算法在给定类别指定的测试图像文件夹中执行,而不是在整个测试图像集中执行,从而节省了时间。 搜索算法依赖于 CNN 模型选择的正确类别。 如果类匹配不正确,则最终的视觉搜索将导致错误的结果。 要解决此问题,可以采取几个步骤:

1.  使用新的图像集重新运行模型,增加训练图像的大小,或改善模型参数。

W
wizardforcel 已提交
456
2.  但是,无论使用多少数据,CNN 模型都永远不会具有 100% 的准确率。 因此,为解决此问题,通常在视觉搜索结果中添加文本关键字搜索。 例如,客户可能会写,“您能找到一张与上图中显示的床相似的床吗?” 在这种情况下,我们知道上载的课程是床。 这是使用**自然语言处理****NLP**)完成的。
W
wizardforcel 已提交
457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553
3.  解决该问题的另一种方法是针对多个预先训练的模型运行相同的上载图像,如果类别预测彼此不同,则采用模式值。

接下来,我们将详细说明用于视觉搜索的代码。

# 视觉搜索代码和说明

在本部分中,我们将解释用于视觉搜索的 TensorFlow 代码及其功能:

1.  首先,我们将为上传的图像指定一个文件夹(共有三个文件夹,我们将针对每种图像类型切换文件夹)。 请注意,此处显示的图像仅是示例; 您的图片可能有所不同:

```py
#img_path = '/home/…/visual_search/ test/bed/bed1.jpg'
#img_path ='/home/…/visual_search/test/chair/chair1.jpg'
#img_path ='/home/…/visual_search/test/sofa/sofa1.jpg'
```

2.  然后,我们将上传图像,将图像转换为数组,并像之前一样对图像进行预处理:

```py
img = image.load_img(img_path, target_size=(224, 224))
img_data = image.img_to_array(img)
img_data = np.expand_dims(img_data, axis=0)
img_data = preprocess_input(img_data)
```

前面的代码是在进一步处理之前将图像转换为数组的标准代码。

# 预测上传图像的类别

一旦上传了新图像,我们的任务就是找出它属于哪个类。 为此,我们计算图像可能属于的每个类别的概率,然后选择概率最高的类别。 此处的示例说明了使用 VGG 预训练模型进行的计算,但相同的概念在其他地方也适用:

```py
vgg_feature = final_model.predict(img_data,verbose=0)
vgg_feature_np = np.array(vgg_feature)
vgg_feature1D = vgg_feature_np.flatten()
print (vgg_feature1D)
y_prob = final_model.predict(img_data)
y_classes = y_prob.argmax(axis=-1)
print (y_classes)
```

在前面的代码中,我们使用`model.predict()`函数计算了图像属于特定类别的概率,并使用`probability.argmax`计算了类别名称以指示具有最高概率的类别。

# 预测所有图像的类别

以下函数导入必要的软件包,以从目录和相似度计算中获取文件。 然后,根据上传图像的输入类别指定要定位的文件夹:

```py
import os
from scipy.spatial import distance as dist
from sklearn.metrics.pairwise import cosine_similarity
if y_classes == [0]:
    path = 'furniture_images/val/bed'
elif y_classes == [1]:
    path = 'furniture_images/val/chair'
else:
    path = 'furniture_images/val/sofa'
```

以下函数循环遍历测试目录中的每个图像,并将该图像转换为数组,然后使用训练后的模型将其用于预测特征向量:

```py
mindist=10000
maxcosine =0
i=0
for filename in os.listdir(path):
    image_train = os.path.join(path, filename)
    i +=1
    imgtrain = image.load_img(image_train, target_size=(224, 224))
    img_data_train = image.img_to_array(imgtrain)
    img_data_train = np.expand_dims(img_data_train, axis=0)
    img_data_train = preprocess_input(img_data_train)
    vgg_feature_train = final_model.predict(img_data_train)
    vgg_feature_np_train = np.array(vgg_feature_train)
    vgg_feature_train1D = vgg_feature_np_train.flatten()
    eucldist = dist.euclidean(vgg_feature1D,vgg_feature_train1D)
    if mindist > eucldist:
        mindist=eucldist
        minfilename = filename
        #print (vgg16_feature_np)
    dot_product = np.dot(vgg_feature1D,vgg_feature_train1D)#normalize the results, to achieve     similarity measures independent #of the scale of the vectors
    norm_Y = np.linalg.norm(vgg_feature1D)
    norm_X = np.linalg.norm(vgg_feature_train1D)
    cosine_similarity = dot_product / (norm_X * norm_Y)
    if maxcosine < cosine_similarity:
        maxcosine=cosine_similarity
        cosfilename = filename
    print ("%s filename %f euclediandist %f cosine_similarity" %(filename,eucldist,cosine_similarity))
    print ("%s minfilename %f mineuclediandist %s cosfilename %f maxcosinesimilarity" %(minfilename,mindist, cosfilename, maxcosine))
```

您可以在前面的代码中观察以下内容:

*   将每个特征向量与上传的图像特征向量进行比较,以计算出欧几里得距离和余弦相似度。
*   通过确定欧几里得距离的最小值和余弦相似度的最大值来计算图像相似度。
*   确定并显示与最小距离相对应的图像文件。

W
wizardforcel 已提交
554
[可以在本书的 GitHub 存储库中找到包括迁移学习和可视搜索的完整代码](https://github.com/PacktPublishing/Mastering-Computer-Vision-with-TensorFlow-2.0/blob/master/Chapter06/Chapter6_Transferlearning_VisualSearch.ipynb)
W
wizardforcel 已提交
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576

下图显示了使用三种不同的模型和两种不同的搜索算法(欧几里得距离和余弦相似度)对一张床的上传图像的视觉搜索预测:

![](img/c4cfc420-d39b-40c4-9560-f8f7aa066106.png)

在上图中,可以观察到以下内容:

*   Inception 模型预测的类别不准确,这导致可视搜索模型预测了错误的类别。
*   请注意,视觉搜索模型无法捕获预测错误类的神经网络。 我们还可以使用其他模型检查同一张上载图片的类别预测,以查看模式(多数)值-在这种情况下,ResNet 和 VGG 预测为`bed`时为`bed`,而 Inception 预测为`sofa`
*   综上所述,由于我们不知道给定的模型是否可以正确预测类别,因此建议的方法是同时使用三个或更多不同的模型预测上传图像的类别,然后选择具有多数值的预测类别。 使用这种方法,我们将增加我们预测的信心。

下图显示了使用其他上载图像的预测,这似乎是正确的:

![](img/d4590308-4b65-4a35-b46b-4e340a566402.png)

下图显示了使用三种不同的模型和两种不同的搜索算法(欧几里得距离和余弦相似度)对椅子上载图像的视觉搜索预测:

![](img/df7b30ff-e9f6-419c-823d-70fd80764f0e.png)

您可以在上图中观察到以下内容:

*   尽管余弦相似度函数显示的图像与欧几里得距离函数的图像不同,但在所有情况下预测都是正确的。
W
wizardforcel 已提交
577
*   两者似乎都非常接近,这两种显示方法为我们提供了一种显示多个图像的方法。 这意味着,如果用户上传一张椅子的图像并希望在在线目录中找到类似的椅子,我们的系统将显示两幅图像供用户选择,而不仅仅是一张图像,这将增加我们的椅子的销售机会。 如果两个图像相同,则算法将仅显示一个图像,否则将显示两个图像。 另一个选择是使用相同算法显示前两个匹配项。
W
wizardforcel 已提交
578 579 580 581 582 583 584 585 586 587

下图显示了使用三种不同的模型和两种不同的搜索算法(欧几里得距离和余弦相似度)对沙发上载图像的视觉搜索预测:

![](img/f0b0dc06-e57a-4142-86fa-e4cbe795edd4.png)

您可以在上图中观察到以下内容:

*   尽管余弦相似度函数显示的图像与欧几里得距离函数的图像不同,但在所有情况下预测都是正确的。
*   两者似乎都非常接近,显示的两种方法为我们提供了一种显示多个图像的方法。 如前所述,对于与上载图像相似的两个图像,用户可以选择更多选项。

W
wizardforcel 已提交
588
# 使用`tf.data`的视觉搜索输入管道
W
wizardforcel 已提交
589

W
wizardforcel 已提交
590
TensorFlow `tf.data` API 是一种高效的数据管道,其处理数据的速度比 Keras 数据输入过程快一个数量级。 它在分布式文件系统中聚合数据并对其进行批量。 有关更多详细信息,请参阅[这里](https://www.tensorflow.org/guide/data)
W
wizardforcel 已提交
591 592 593 594 595 596 597 598 599 600 601 602 603

以下屏幕截图显示了`tf.data`与 Keras 图像输入过程的图像上传时间比较:

![](img/4035d412-3430-4278-8fec-2635438742d0.png)

请注意,一千张图像大约需要 1.58 秒,比 Keras 图像输入过程快 90 倍。

这是`tf.data`的一些常见功能:

*   为了使该 API 起作用,您需要导入`pathlib`库。
*   `tf.data.Dataset.list_files`用于创建与模式匹配的所有文件的数据集。
*   `tf.strings.splot`基于定界符分割文件路径。
*   `tf.image.decode_jpeg`将 JPEG 图像解码为张量(请注意,转换必须没有文件路径)。
W
wizardforcel 已提交
604
*   `tf.image.convert_image_dtype`将图像转换为`dtype float32`
W
wizardforcel 已提交
605

W
wizardforcel 已提交
606
[以下链接提供了更新的视觉搜索代码](https://github.com/PacktPublishing/Mastering-Computer-Vision-with-TensorFlow-2.0/blob/master/Chapter06/Chapter6_Transferlearning_VisualSearch_tfdata_tensor.ipynb)
W
wizardforcel 已提交
607 608 609 610 611 612 613 614 615 616

如前所述,该代码包含`tf.data`。 除了`tf.data`以外,它还通过在`build_final_model`中进行以下更改来解冻模型的顶部:

```py
layer.trainable = True
layer_adjust = 100
for layer in base_model.layers[:layer_adjust]:
    layer.trainable = False
```

W
wizardforcel 已提交
617
前面的更改使模型可以在第一百层之后开始训练,而不是仅训练最后几层。 如下图所示,此更改提高了准确率:
W
wizardforcel 已提交
618 619 620

![](img/fa44cd9b-f3e2-4b7b-839b-b0d367434d42.png)

W
wizardforcel 已提交
621
训练花费更多时间,但是模型的准确率接近 100%,而不是 90%。
W
wizardforcel 已提交
622

W
wizardforcel 已提交
623
在总结本章之前,让我们回顾一下训练 CNN 的两个重要概念:准确率和损失性。 Keras 将`y-pred``y-true`定义为模型预测值和地面真实值。 准确率定义为将`y-pred``y-true`进行比较,然后平均差异。 损失项(也称为熵)定义为乘积类别概率与分类指数的负和。 可能还有其他损耗项,例如 RMSProp。 通常,如果`accuracy > 0.9``loss < 1`,训练会给出正确的输出。
W
wizardforcel 已提交
624

W
wizardforcel 已提交
625
# 总结
W
wizardforcel 已提交
626

W
wizardforcel 已提交
627
在本章中,我们学习了如何使用 TensorFlow/Keras 为上一章研究的深度学习模型开发迁移学习代码。 我们学习了如何从包含多个类的目录中导入经过训练的图像,并使用它们来训练模型并进行预测。 然后,我们学习了如何使模型的基础层保持冻结状态,移除顶层并用我们自己的顶层替换它,并使用它来训练结果模型。
W
wizardforcel 已提交
628

W
wizardforcel 已提交
629
我们研究了视觉搜索的重要性以及如何使用迁移学习来增强视觉搜索方法。 我们的示例包括三种不同类别的家具-我们了解了模型的准确率以及如何改善由此造成的损失。 在本章中,我们还学习了如何使用 TensorFlow `tf.data`输入管道在训练期间更快地处理图像。
W
wizardforcel 已提交
630 631

在下一章中,我们将研究 YOLO,以便在图像和视频上绘制边界框对象检测,并将其用于进一步改善视觉搜索。