TensorFlow2 使用迁移学习的视觉搜索详解

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

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

本章涵盖的主题如下:

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

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

本部分的代码可以在以下位置找到

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

下载权重的代码如下:

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

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

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

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 的图像处理模块。 导入其他常见库的代码如下所示:

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模块才能访问输入文件的目录。

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

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

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

  • weights 是我们在以前下载的 ImageNet 图像上使用的预训练模型。
  • include_top参数指示是否应包含最终的密集层。 对于预训练模型的类别预测,始终为True; 但是,在本章的后面部分(“使用 TensorFlow 开发迁移学习模型”)中,我们将学习到,在迁移学习期间,此参数设置为False仅包含卷积层。
  • input_shape是通道的高度,宽度和数量。 由于我们正在处理彩色图像,因此通道数设置为3

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

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

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

本节介绍如何批量导入多个图像以一起处理所有图像,而不是一个一个地导入它们。 这是学习的一项关键技能,因为在大多数生产应用中,您不会一步一步地导入图像。 使用 TensorFlow Keras 导入和处理多个图像的循环函数的代码如下:

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

  3. 使用 NumPy 数组将图像转换为数组函数并扩展其尺寸,然后按照“下载权重”部分中的说明应用preprocessing函数。

  4. 接下来,它使用model.predict()函数计算特征向量。

  5. 然后,预测解码类别标签名称。

  6. label函数存储为数组,并且具有两个元素:类名和置信度%

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

  • fig.add_subplot具有三个元素:rowscolumnsi –例如,总共 9 幅图像分为三列三行,i项将从1变为91是第一张图片,9是最后一张图片。
  • fig.subplots_adjust在图像之间添加垂直空间。
  • plt.title将标题添加到每个图像。

请注意,完整的代码可以在本书的 GitHub 链接中找到

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

目标图片 VGG16 ResNet Inception
餐桌 餐桌 58% 台球桌 30.1% 桌子 51%
炒锅 钢包 55% 钢包 87% 钢包 30%
沙发 单人沙发 42% 单人沙发 77% 单人沙发 58%
单人沙发 35% 四海报 53% 单人沙发 44%
水壶 水壶 93% 水壶 77% 水壶 98%
行李 折叠椅 39% 背包 66% 邮袋 35%
背包 背包 99.9% 背包 99.9% 背包 66%
长椅 单人沙发 79% 单人沙发 20% 单人沙发 48%
越野车 小型货车 74% 小型货车 98% 小型货车 52%

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

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

  • 每个图像尺寸为224 x 224,标签的顶部打印有置信度百分比。
  • 三种错误的预测:炒锅(预测为钢包),床(预测为工作室沙发)和行李(预测为折叠椅)。

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

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

  • 每个图像尺寸为224 x 224,标签的顶部打印有置信度百分比。
  • 有两个错误的预测:炒锅(预测为钢包)和行李(预测为背包)。

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

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

  • 每个图像尺寸为224 x 224,标签的顶部打印有置信度百分比。
  • 有两个错误的预测:炒锅(预测为钢包)和床(预测为工作室沙发)。

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

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

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

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

首先,我们将从分析和存储数据开始。 在这里,我们正在构建具有三个不同类别的家具模型:bedchairsofa。 我们的目录结构如下。 每个图像都是大小为224 x 224的彩色图像。

Furniture_images

  • train(2,700 张图像)

    • bed(900 张图像)
    • chair(900 张图像)
    • sofa(900 张图像)
  • val(300 张图像)

    • bed(100 张图像)
    • chair(100 张图像)
    • sofa(100 张图像)

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

我们的下一步是导入 TensorFlow 库。 以下代码导入 ResNet 模型权重和预处理的输入,与上一节中的操作类似。 我们在“第 5 章”,“神经网络架构和模型”中了解了每个概念:

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

该代码还导入了​​几个深度学习参数,例如Dense(全连接层),ActivationFlattenDropout。 然后,我们导入顺序模型 API 以创建逐层模型,并使用随机梯度下降SGD)和 Adam 优化器。 对于 ResNet 和 VGG,图像的高度和宽度为224,对于 Inception 模型,图像的高度和宽度为299

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

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

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

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

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

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

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

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目录的路径。 要获取路径,您可以将文件夹拖到终端,它会向您显示路径,然后可以将其复制并粘贴。
  • Target_size:将其设置为等于模型拍摄的图像大小,例如,对于 Inception 为 299 x 299,对于 ResNet 和 VGG16 为224 x 224
  • Color_mode:将黑白图像设置为grayscale,将彩色图像设置为RGB
  • Batch_size:每批图像的数量。
  • class_mode:如果只有两个类别可以预测,则设置为二进制;否则,请设置为二进制。 如果不是,则设置为categorical
  • shuffle:如果要重新排序图像,请设置为True; 否则,设置为False
  • seed:随机种子,用于应用随机图像增强和混排图像顺序。

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

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

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

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)

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

我们首先定义一个名为build_final_model()的函数,该函数接收基本模型和模型参数,例如丢弃,全连接层以及类的数量。 我们首先使用layer.trainable = False冻结基本模型。 然后,我们将基础模型输出特征向量展平,以进行后续处理。 接下来,我们添加一个全连接层并将其放置到展平的特征向量上,以使用 softmax 层预测新类:

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优化器编译模型:

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

使用model.fit_generator命令开发并运行最终模型。 历史记录存储周期,每步时间,损失,准确率,验证损失和验证准确率:

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的值。
  • steps_per_epoch:这是整数。 它显示了训练完成和开始下一个周期训练之前的步骤总数(样本批量)。 其最大值等于(训练图像数/ batch_size)。 因此,如果有 900 张训练图像且批量大小为 10,则每个周期的步数为 90。
  • workers:较高的值可确保 CPU 创建足够的批量供 GPU 处理,并且 GPU 永远不会保持空闲状态。
  • shuffle:这是布尔类型。 它指示每个周期开始时批量的重新排序。 仅与Sequencekeras.utils.Sequence)一起使用。 当steps_per_epoch不是None时,它无效。
  • Validation_data:这是一个验证生成器。
  • validation_steps:这是validation_data生成器中使用的步骤总数(样本批量),等于验证数据集中的样本数量除以批量大小。

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

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])

前面代码的输出如下:

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

输出显示每个周期的损失和准确率,并且相应的文件另存为hdf5文件。

使用 Python matplotlib函数显示了显示训练精度和每个周期的训练损失的折线图。 我们将首先导入matplotlib,然后为训练和验证损失以及准确率定义参数:

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']

以下代码是使用 Keras 和 TensorFlow 绘制模型输出的标准代码。 我们首先定义图像大小(8 x 8),并使用子图函数显示(2,1,1)(2,1,2)。 然后,我们定义标签,限制和标题:

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 的训练参数:

在前面的屏幕截图中,您可以观察到 Inception 的准确率在五个周期内达到了约 90%。 接下来,我们绘制 VGG16 的训练参数:

上图显示 VGG16 的精度在五个周期内达到了约 80%。 接下来,我们绘制 ResNet 的训练参数:

前面的屏幕截图显示 ResNet 的准确率在四个周期内达到了约 80%。

对于所有三个模型,在四个周期内,精度始终达到至少 80%。 Inception 模型的结果具有最高的准确率。

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

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

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

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

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

  • 图像数表示训练图像的数量。 因此,如果您有 1,000 个训练图像,则该值为 1,000。
  • X表示层的宽度。 典型值为 14。
  • Y表示层的高度,可以是 14。
  • 通道数表示过滤器的数量或深度 Conv2D。 典型值为 512。

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

此处列出了各个步骤:

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

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

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

  2. 但是,无论使用多少数据,CNN 模型都永远不会具有 100% 的准确率。 因此,为解决此问题,通常在视觉搜索结果中添加文本关键字搜索。 例如,客户可能会写,“您能找到一张与上图中显示的床相似的床吗?” 在这种情况下,我们知道上载的类是床。 这是使用自然语言处理NLP)完成的。

  3. 解决该问题的另一种方法是针对多个预先训练的模型运行相同的上载图像,如果类别预测彼此不同,则采用模式值。

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

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

  1. 首先,我们将为上传的图像指定一个文件夹(共有三个文件夹,我们将针对每种图像类型切换文件夹)。 请注意,此处显示的图像仅是示例; 您的图片可能有所不同:
#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'
  1. 然后,我们将上传图像,将图像转换为数组,并像之前一样对图像进行预处理:
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 预训练模型进行的计算,但相同的概念在其他地方也适用:

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计算了类别名称以指示具有最高概率的类别。

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

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'

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

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))

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

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

可以在本书的 GitHub 存储库中找到包括迁移学习和可视搜索的完整代码

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

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

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

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

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

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

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

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

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

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

TensorFlow tf.data API 是一种高效的数据管道,其处理数据的速度比 Keras 数据输入过程快一个数量级。 它在分布式文件系统中聚合数据并对其进行批量。 有关更多详细信息,请参阅这里

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

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

这是tf.data的一些常见函数:

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

以下链接提供了更新的视觉搜索代码

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

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

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

训练花费更多时间,但是模型的准确率接近 100%,而不是 90%。

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

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

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

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

教程来源于Github,感谢apachecn大佬的无私奉献,致敬!

技术教程推荐

从0开始学架构 -〔李运华〕

玩转Git三剑客 -〔苏玲〕

面试现场 -〔白海飞〕

Web协议详解与抓包实战 -〔陶辉〕

性能测试实战30讲 -〔高楼〕

Service Mesh实战 -〔马若飞〕

零基础学Python(2023版) -〔尹会生〕

现代C++20实战高手课 -〔卢誉声〕

AI绘画核心技术与实战 -〔南柯〕