卷积神经网络实践指南(二)

原文:annas-archive.org/md5/b8bd6c69e42dba1814f4d08d925abff3

译者:飞龙

协议:CC BY-NC-SA 4.0

第四章:流行的 CNN 模型架构

在本章中,我们将介绍 ImageNet 图像数据库,并讨论以下流行 CNN 模型的架构:

  • LeNet

  • AlexNet

  • VGG

  • GoogLeNet

  • ResNet

ImageNet 介绍

ImageNet 是一个包含超过 1500 万张手工标注的高分辨率图像的数据库,涵盖大约 22,000 个类别。该数据库的组织结构类似于 WordNet 层次结构,其中每个概念也被称为 同义集(即 synset)。每个同义集都是 ImageNet 层次结构中的一个节点。每个节点包含超过 500 张图像。

ImageNet 大规模视觉识别挑战赛 (ILSVRC) 于 2010 年成立,旨在大规模提升物体检测和图像分类的最先进技术:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/20756743-ac07-44cb-a804-05763571d81d.png

在概述了 ImageNet 之后,我们将进一步介绍各种 CNN 模型架构。

LeNet

2010 年,ImageNet 发起了一项挑战(称为 ILSVRC 2010),该挑战使用了 Yann Lecun 构建的 CNN 架构 LeNet 5。该网络以 32 x 32 的图像作为输入,图像经过卷积层(C1),然后进入子采样层(S2)。今天,子采样层已被池化层替代。接着,又有一系列卷积层(C3),然后是一个池化(即子采样)层(S4)。最后,网络有三个全连接层,其中包括最后的 OUTPUT 层。该网络曾用于邮局的邮政编码识别。从那时起,每年都有不同的 CNN 架构通过这一竞赛得以推出:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/cb2b54bf-cb43-4e1f-897c-862f85f06423.png

LeNet 5 – 来自 Yann Lecun 1998 年文章的 CNN 架构

因此,我们可以得出以下几点结论:

  • 该网络的输入为一张灰度 32 x 32 的图像

  • 实现的架构是一个 CONV 层,接着是 POOL 层和一个全连接层

  • CONV 滤波器为 5 x 5,以步幅为 1 应用

AlexNet 架构

CNN 架构的第一个突破发生在 2012 年。这一获奖的 CNN 架构被称为 AlexNet,由多伦多大学的 Alex Krizhevsky 和他的教授 Jeffry Hinton 开发。

在第一次运行时,该网络使用了 ReLU 激活函数和 0.5 的 dropout 来抵抗过拟合。如以下图所示,架构中使用了一个归一化层,但由于使用了大量的数据增强技术,这在实际应用中已不再使用。尽管如今已有更为精确的网络,但由于其相对简单的结构和较小的深度,AlexNet 仍然在今天被广泛使用,尤其是在计算机视觉领域:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/dd1ba84c-d1ae-4f6f-87b6-0e641600eab4.png

AlexNet 使用两个独立的 GPU 在 ImageNet 数据库上进行训练,可能是由于当时 GPU 间连接的处理限制,正如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/1100c791-9be1-4e7b-8039-3172ad50606d.png

使用 AlexNet 的交通标志分类器

在这个例子中,我们将使用迁移学习进行特征提取,并使用一个德国交通标志数据集来开发分类器。这里使用的是 Michael Guerzhoy 和 Davi Frossard 实现的 AlexNet,AlexNet 的权重来自伯克利视觉与学习中心。完整的代码和数据集可以从 这里下载。

AlexNet 期望输入的是 227 x 227 x 3 像素的图像,而交通标志图像的尺寸是 32 x 32 x 3 像素。为了将交通标志图像输入到 AlexNet 中,我们需要将图像调整为 AlexNet 所期望的尺寸,即 227 x 227 x 3:

original_image = tf.placeholder(tf.float32, (None, 32, 32, 3))
resized_image = tf.image.resize_images(original_imag, (227, 227))

我们可以借助 TensorFlow 的 tf.image.resize_images 方法来实现。另一个问题是,AlexNet 是在 ImageNet 数据集上训练的,该数据集有 1,000 个类别的图像。因此,我们将用一个 43 神经元的分类层替换这一层。为此,首先计算最后一个全连接层输出的大小;由于这是一个全连接层,因此它的输出是一个 2D 形状,最后一个元素就是输出的大小。fc7.get_shape().as_list()[-1] 完成了这个任务;然后将其与交通标志数据集的类别数结合,得到最终全连接层的形状:shape = (fc7.get_shape().as_list()[-1], 43)。其余代码只是 TensorFlow 中定义全连接层的标准方式。最后,通过 softmax 计算概率:

#Refer AlexNet implementation code, returns last fully connected layer
fc7 = AlexNet(resized, feature_extract=True)
shape = (fc7.get_shape().as_list()[-1], 43)
fc8_weight = tf.Variable(tf.truncated_normal(shape, stddev=1e-2))
fc8_b = tf.Variable(tf.zeros(43))
logits = tf.nn.xw_plus_b(fc7, fc8_weight, fc8_b)
probs = tf.nn.softmax(logits)

VGGNet 架构

2014 年 ImageNet 挑战赛的亚军是来自牛津大学视觉几何小组的 VGGNet。这个卷积神经网络架构简单而优雅,错误率为 7.3%。它有两个版本:VGG16 和 VGG19。

VGG16 是一个 16 层神经网络,不包括最大池化层和 softmax 层。因此,它被称为 VGG16。VGG19 由 19 层组成。Keras 中有一个预训练模型,适用于 Theano 和 TensorFlow 后端。

这里的关键设计考虑因素是网络的深度。通过增加更多的卷积层来增加网络的深度,这样做是因为所有层中的卷积滤波器大小都为 3 x 3。该模型的默认输入图像大小为 224 x 224 x 3。图像通过一系列卷积层进行处理,步幅为 1 个像素,填充为 1。整个网络使用 3 x 3 卷积。最大池化在一个 2 x 2 像素窗口中进行,步幅为 2,然后是另一组卷积层,接着是三个全连接层。前两个全连接层每个有 4,096 个神经元,第三个全连接层负责分类,包含 1,000 个神经元。最后一层是 softmax 层。与 AlexNet 的 11 x 11 卷积窗口相比,VGG16 使用了更小的 3 x 3 卷积窗口。所有隐藏层都使用 ReLU 激活函数。网络架构如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/0a1c03f6-5c37-4e49-9b3d-ef56e005b84d.png

VGG16 网络架构

由于小型的 3 x 3 卷积滤波器,VGGNet 的深度得以增加。该网络的参数数量大约为 1.4 亿,主要来自于第一个全连接层。在后来的架构中,VGGNet 的全连接层被全局平均池化GAP)层替代,以减少参数数量。

另一个观察是,随着图像尺寸的减小,滤波器的数量会增加。

VGG16 图像分类代码示例

Keras 应用模块包含了预训练的神经网络模型,以及基于 ImageNet 训练的预训练权重。这些模型可以直接用于预测、特征提取和微调:

#import VGG16 network model and other necessary libraries 

from keras.applications.vgg16 import VGG16
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input
import numpy as np

#Instantiate VGG16 and returns a vgg16 model instance 
vgg16_model = VGG16(weights='imagenet', include_top=False) 
#include_top: whether to include the 3 fully-connected layers at the top of the network.
#This has to be True for classification and False for feature extraction. Returns a model instance
#weights:'imagenet' means model is pre-training on ImageNet data.
model = VGG16(weights='imagenet', include_top=True)
model.summary()

#image file name to classify
image_path = 'jumping_dolphin.jpg'
#load the input image with keras helper utilities and resize the image. 
#Default input size for this model is 224x224 pixels.
img = image.load_img(image_path, target_size=(224, 224))
#convert PIL (Python Image Library??) image to numpy array
x = image.img_to_array(img)
print (x.shape)

#image is now represented by a NumPy array of shape (224, 224, 3),
# but we need to expand the dimensions to be (1, 224, 224, 3) so we can
# pass it through the network -- we'll also preprocess the image by
# subtracting the mean RGB pixel intensity from the ImageNet dataset
#Finally, we can load our Keras network and classify the image:

x = np.expand_dims(x, axis=0)
print (x.shape)

preprocessed_image = preprocess_input(x)

preds = model.predict(preprocessed_image)
print('Prediction:', decode_predictions(preds, top=2)[0])

当第一次执行上述脚本时,Keras 会自动下载并将架构权重缓存到磁盘中的~/.keras/models目录。随后的运行会更快。

GoogLeNet 架构

2014 年,在 ILSVRC 比赛中,Google 发布了自己的网络,名为GoogLeNet。它的表现比 VGGNet 略好;GoogLeNet 的表现为 6.7%,而 VGGNet 的表现为 7.3%。GoogLeNet 的主要吸引力在于它运行非常快速,因为引入了一种叫做inception 模块的新概念,从而将参数数量减少到仅为 500 万;这是 AlexNet 的 12 倍还少。它的内存使用和功耗也更低。

它有 22 层,因此是一个非常深的网络。添加更多层会增加参数数量,并且可能导致网络过拟合。计算量也会增加,因为滤波器数量的线性增长会导致计算量的平方增长。因此,设计者使用了 inception 模块和 GAP。网络末端的全连接层被 GAP 层替代,因为全连接层通常容易导致过拟合。GAP 没有需要学习或优化的参数。

架构洞察

与以前的架构选择特定滤波器大小不同,GoogLeNet 设计者将 1 x 1、3 x 3 和 5 x 5 三种不同大小的滤波器应用到同一图像块上,再通过 3 x 3 最大池化和连接操作将它们合并为一个输出向量。

使用 1 x 1 卷积可以减少在计算量增加的地方,替代了昂贵的 3 x 3 和 5 x 5 卷积。在昂贵的 3 x 3 和 5 x 5 卷积之前,使用了带有 ReLU 激活函数的 1 x 1 卷积。

在 GoogLeNet 中,inception 模块一个接一个地堆叠。这种堆叠方式使得我们可以修改每个模块,而不会影响后面的层。例如,你可以增加或减少任何层的宽度:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/2636db13-c7fb-49c9-b20d-544287eacf55.png

GoogLeNet 架构

深度网络也面临着所谓的梯度消失问题,尤其是在反向传播时。通过向中间层添加辅助分类器,可以避免这一问题。此外,在训练过程中,将中间损失与折扣因子 0.3 相加,纳入总损失。

由于全连接层容易发生过拟合,因此被替换为 GAP 层。平均池化并不排除使用 dropout,这是一种用于克服深度神经网络中过拟合的正则化方法。GoogLeNet 在 60 层后添加了一个线性层,使用 GAP 层帮助其他人通过迁移学习技术为自己的分类器进行优化。

Inception 模块

以下图像展示了一个 Inception 模块的例子:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/ac511227-54cd-4a2a-a9ef-077ec291ec0f.png

ResNet 架构

在达到一定深度后,向前馈卷积网络添加额外的层会导致训练误差和验证误差增加。增加层数时,性能只会在一定深度内提升,然后会迅速下降。在ResNet残差网络)论文中,作者认为这种欠拟合不太可能是由于梯度消失问题引起的,因为即使使用批量归一化技术,这种情况仍然会发生。因此,他们提出了一个新的概念——残差块。ResNet 团队添加了可以跳过层的连接:

ResNet 使用标准卷积神经网络,并添加了可以一次跳过几个卷积层的连接。每个跳跃都形成一个残差块。

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/c7c96246-7f86-4e6c-89d0-22a25e655493.png

残差块

在 2015 年 ImageNet ILSVRC 竞赛中,微软的 ResNet 以 3.57%的错误率赢得了冠军。ResNet 在某种意义上类似于 VGG,因为它的结构会不断重复,从而使网络变得更深。与 VGGNet 不同,ResNet 有不同的深度变种,例如 34 层、50 层、101 层和 152 层。相比于 AlexNet 的 8 层、VGGNet 的 19 层和 GoogLeNet 的 22 层,它有着惊人的 152 层。ResNet 架构是由残差块堆叠而成。其主要思想是通过向神经网络添加连接来跳过某些层。每个残差块包含 3x3 的卷积层。最后的卷积层后面添加了一个 GAP 层。只有一个全连接层用于分类 1000 个类别。它有不同的深度变种,例如 34 层、50 层、101 层或 152 层,用于 ImageNet 数据集。对于更深的网络,比如超过 50 层的网络,使用了瓶颈特征概念来提高效率。这个网络没有使用 dropout。

其他需要注意的网络架构包括:

  • 网络中的网络

  • 超越 ResNet

  • FractalNet,一个没有残差的超深神经网络

摘要

本章中,我们学习了不同的 CNN 架构。这些模型是预训练的现有模型,在网络架构上有所不同。每个网络都是为了针对其架构特定的问题而设计的。因此,我们在此描述了它们的架构差异。

我们还理解了我们自己定义的 CNN 架构(在上一章中提到的)与这些先进架构之间的区别。

在下一章,我们将学习如何将这些预训练模型用于迁移学习。

第五章:迁移学习

在上一章中,我们学习了卷积神经网络(CNN)由多个层组成。我们还研究了不同的 CNN 架构,调整了不同的超参数,并确定了步幅、窗口大小和填充的值。然后我们选择了一个合适的损失函数并进行了优化。我们用大量图像训练了这个架构。那么,问题来了,我们如何利用这些知识处理不同的数据集呢?与其从头构建 CNN 架构并进行训练,不如使用一个现有的预训练网络,通过一种叫做迁移学习的技术将其适配到新的不同数据集上。我们可以通过特征提取和微调来实现这一点。

迁移学习是将已经训练好的网络的知识复制到新网络中,以解决类似问题的过程。

在本章中,我们将讨论以下主题:

  • 特征提取方法

  • 迁移学习示例

  • 多任务学习

特征提取方法

在特征提取方法中,我们只训练网络的顶层;其余部分保持不变。当新的数据集相对较小且与原始数据集相似时,可以考虑采用特征提取方法。在这种情况下,从原始数据集中学到的高层次特征应能很好地迁移到新数据集。

当新的数据集较大且与原始数据集相似时,可以考虑微调方法。修改原始权重是安全的,因为网络不太可能会对新的、大型数据集发生过拟合。

让我们考虑一个预训练的卷积神经网络,如下图所示。通过这个示例,我们可以研究如何在不同情况下使用知识迁移:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/688a6ad3-849b-42f7-b371-4523211bfcbc.png

我们什么时候使用迁移学习?迁移学习可以根据以下情况进行应用:

  • 新的(目标)数据集的大小

  • 原始数据集和目标数据集之间的相似性

主要有四种使用场景:

  • 案例 1:新的(目标)数据集较小,并且与原始训练数据集相似

  • 案例 2:新的(目标)数据集较小,但与原始训练数据集不同

  • 案例 3:新的(目标)数据集较大,并且与原始训练数据集相似

  • 案例 4:新的(目标)数据集较大,并且与原始训练数据集不同

现在让我们在接下来的章节中详细讲解每个案例。

目标数据集较小,并且与原始训练数据集相似

如果目标数据集较小且与原始数据集相似:

  • 在这种情况下,用一个新的全连接层替换最后一个全连接层,使其与目标数据集的类别数量匹配

  • 用随机权重初始化旧的权重

  • 训练网络以更新新的全连接层的权重:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/daa1d1de-8117-4897-9afd-94588783b1d9.png

转移学习可以作为一种避免过拟合的策略,特别是在数据集较小的情况下。

目标数据集较小,但与原始训练数据集不同

如果目标数据集较小,但与原始数据集类型不同——例如,原始数据集是狗的图像,而新的(目标)数据集是花卉的图像——那么应执行以下操作:

  • 切割网络的大部分初始层

  • 在其余的预训练层中添加一个新的全连接层,该层的节点数与目标数据集的类别数相匹配

  • 随机化新全连接层的权重,并冻结预训练网络的所有权重

  • 训练网络以更新新全连接层的权重

由于数据集较小,过拟合在这里仍然是一个问题。为了解决这个问题,我们将保持原始预训练网络的权重不变,只更新新全连接层的权重:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/f7a1081a-47f5-4e46-937c-c84bf035dc03.png

只需微调网络的高层部分。这是因为开始的层是用来提取更通用的特征的。通常,卷积神经网络的第一层并不特定于某个数据集。

目标数据集较大并且与原始训练数据集相似

由于数据集很大,我们不需要担心过拟合。因此,在这种情况下,我们可以重新训练整个网络:

  • 移除最后一个全连接层,并用一个与目标数据集类别数匹配的全连接层替换它

  • 随机初始化新添加的全连接层的权重

  • 使用预训练的权重初始化其余的权重

  • 训练整个网络:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/2df8e25a-0f59-43c8-85f6-4a9486cf6aa7.png

目标数据集较大,并且与原始训练数据集不同

如果目标数据集较大并且与原始数据集不同:

  • 移除最后一个全连接层,并用一个与目标数据集类别数匹配的全连接层替换它

  • 从头开始训练整个网络,并随机初始化权重:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/76604751-43f7-4ae8-875d-66e44fa19053.png

Caffe 库有 ModelZoo,可以在其中共享网络权重。

当数据集很大并且与原始数据集完全不同时,考虑从头开始训练。在这种情况下,我们有足够的数据来从头开始训练,而不必担心过拟合。然而,即便如此,使用预训练权重初始化整个网络并在新数据集上进行微调可能还是有益的。

转移学习示例

在这个例子中,我们将采用预训练的 VGGNet,并使用迁移学习训练一个 CNN 分类器,该分类器根据狗的图像预测狗的品种。Keras 包含许多预训练模型,并提供加载和可视化这些模型的代码。另一个是可以在这里下载的花卉数据集。狗品种数据集有 133 个狗品种类别和 8,351 张狗的图像。请在这里下载狗品种数据集并将其复制到你的文件夹中。VGGNet 从头到尾包含 16 层卷积池化层,以及三个全连接层,后接一个softmax函数。它的主要目标是展示网络深度如何带来最佳性能。它来自牛津的视觉几何组(VGG)。他们表现最佳的网络是 VGG16。狗品种数据集相对较小,并与imageNet数据集有些重叠。所以我们可以去除卷积层之后的最后一个全连接层,并用我们自己的层替换它。卷积层的权重保持不变。输入图像通过卷积层并停留在第 16 层:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/c6d97faf-9d6c-49ee-b829-2de1dd1da654.png

VGGNet 架构

我们将使用预训练的 VGG16 网络的瓶颈特征 —— 该网络已经从imageNet数据集中学习了特征。由于imageNet数据集已经包含了一些狗的图像,VGG16 网络模型已学到了用于分类的关键特征。类似地,其他预训练的 CNN 架构也可以作为解决其他图像分类任务的练习。

在此下载 VGG16 的bottleneck_features,将其复制到你自己的文件夹中,然后加载:

bottleneck_features = np.load('bottleneck_features/DogVGG16Data.npz')
train_vgg16 = bottleneck_features['train']
valid_vgg16 = bottleneck_features['valid']
test_vgg16 = bottleneck_features['test']

现在定义模型架构:

from keras.layers import GlobalAveragePooling2D

model = Sequential()
model.add(GlobalAveragePooling2D(input_shape=(7, 7, 512)))
model.add(Dense(133, activation='softmax'))
model.summary()
Layer (type)                     Output Shape          Param #     Connected to                     
=================================================================================================
globalaveragepooling2d_1 (Global (None, 512)           0           globalaveragepooling2d_input_1[0]
_________________________________________________________________________________________________
dense_2 (Dense)                  (None, 133)           68229       globalaveragepooling2d_1[0][0]   
=================================================================================================
Total params: 68,229
Trainable params: 68,229
Non-trainable params: 0
_________________________________________________________________________________________________

编译模型并训练:

model.compile(loss='categorical_crossentropy', optimizer='rmsprop', 
                  metrics=['accuracy'])
from keras.callbacks import ModelCheckpoint 

# train the model
checkpointer = ModelCheckpoint(filepath='dogvgg16.weights.best.hdf5', verbose=1, 
                               save_best_only=True)
model.fit(train_vgg16, train_targets, nb_epoch=20, validation_data=(valid_vgg16, valid_targets), 
          callbacks=[checkpointer], verbose=1, shuffle=True)

加载模型并计算测试集上的分类准确度:

# load the weights that yielded the best validation accuracy
model.load_weights('dogvgg16.weights.best.hdf5')
# get index of predicted dog breed for each image in test set
vgg16_predictions = [np.argmax(model.predict(np.expand_dims(feature, axis=0))) 
                     for feature in test_vgg16]

# report test accuracy
test_accuracy = 100*np.sum(np.array(vgg16_predictions)==
                           np.argmax(test_targets, axis=1))/len(vgg16_predictions)
print('\nTest accuracy: %.4f%%' % test_accuracy)

多任务学习

在多任务学习中,迁移学习是从一个预训练模型到多个任务的同时迁移。例如,在自动驾驶汽车中,深度神经网络同时检测交通标志、行人和前方的其他车辆。语音识别同样受益于多任务学习。

总结

在某些特定情况下,训练于图像上的卷积神经网络架构允许我们在新网络中重用已学到的特征。当基础任务和目标任务差异较大时,特征迁移的性能提升会减小。令人惊讶的是,几乎任何层数的卷积神经网络初始化,若采用转移过来的特征,在微调到新数据集后都能提升泛化性能。

第六章:自编码器在卷积神经网络(CNN)中的应用

在本章中,我们将覆盖以下主题:

  • 自编码器介绍

  • 卷积自编码器

  • 自编码器的应用

  • 一个压缩的例子

自编码器介绍

自编码器是一个普通的神经网络,是一种无监督学习模型,它接受输入并在输出层产生相同的输入。因此,训练数据中没有相关标签。一般来说,自编码器由两个部分组成:

  • 编码器网络

  • 解码器网络

它从无标签的训练数据中学习所有必需的特征,这被称为低维特征表示。在下图中,输入数据(x)通过编码器传递,编码器生成输入数据的压缩表示。从数学角度来看,在方程式中,z = h(x)z是特征向量,通常比x的维度更小。

然后,我们将从输入数据中生成的特征传递通过解码器网络,以重建原始数据。

编码器可以是一个全连接神经网络,也可以是一个卷积神经网络CNN)。解码器也使用与编码器相同类型的网络。在这里,我们通过卷积神经网络(ConvNet)解释并实现了编码器和解码器功能:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/7c90369e-7ca4-48a3-b473-30c24faaecd6.png

损失函数:||x - x||²

在这个网络中,输入层和输出层的尺寸相同。

卷积自编码器

卷积自编码器是一个神经网络(无监督学习模型的特殊情况),经过训练后能够在输出层重建输入图像。图像通过编码器传递,编码器是一个卷积神经网络(ConvNet),它生成图像的低维表示。解码器是另一个样本卷积神经网络,它接收这个压缩后的图像并重建原始图像。

编码器用于压缩数据,解码器用于重建原始图像。因此,自编码器可以用于数据压缩。压缩逻辑是特定于数据的,这意味着它是从数据中学习的,而不是预定义的压缩算法,如 JPEG、MP3 等。自编码器的其他应用包括图像去噪(从损坏的图像中生成更清晰的图像)、降维和图像搜索:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/9a8a64d7-7d61-446e-96ac-808858df19af.png

这与普通的卷积神经网络(ConvNets)或神经网络的不同之处在于,输入尺寸和目标尺寸必须相同。

应用

自编码器用于降维或数据压缩,以及图像去噪。降维反过来有助于提高运行时性能并减少内存消耗。图像搜索在低维空间中可以变得非常高效。

一个压缩的例子

网络架构包括一个编码器网络,这是一个典型的卷积金字塔。每个卷积层后面跟着一个最大池化层;这减少了层的维度。

解码器将输入从稀疏表示转换为宽度较大的重建图像。网络的示意图如下所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/6c58f5c9-4f78-423e-a375-4e94c7b5a9e3.png

编码器层的输出图像大小为 4 x 4 x 8 = 128。原始图像大小为 28 x 28 x 1 = 784,因此压缩后的图像向量大约是原始图像大小的 16%。

通常,你会看到使用反卷积(transposed convolution)层来增加层的宽度和高度。它们的工作方式与卷积层几乎完全相同,只不过是反向的。输入层的步幅(stride)在反卷积层中会变得更大。例如,如果你有一个 3 x 3 的卷积核,输入层的 3 x 3 区域在卷积层中将被缩小为一个单位。相比之下,输入层的一个单位在反卷积层中将被扩展成一个 3 x 3 的区域。TensorFlow API 为我们提供了一个简单的方式来创建这些层:tf.nn.conv2d_transpose,点击这里,www.tensorflow.org/api_docs/python/tf/nn/conv2d_transpose

总结

我们以简短的自编码器介绍开始了本章,并在卷积神经网络(ConvNets)的帮助下实现了编码器和解码器功能。

然后,我们转向卷积自编码器,并学习它们与常规卷积神经网络和神经网络的不同之处。

我们通过一个例子详细讲解了自编码器的不同应用,并展示了自编码器如何提高低维空间中图像搜索的效率。

在下一章中,我们将研究使用卷积神经网络(CNNs)进行物体检测,并了解物体检测与物体分类的区别。

第七章:基于 CNN 的物体检测和实例分割

到目前为止,在本书中,我们主要使用卷积神经网络CNNs)进行分类。分类将整张图像分类为具有最高检测概率的实体的类别。但如果图像中不仅有一个实体,而是多个感兴趣的实体,我们希望能够关联所有这些实体的图像该怎么办?一种方法是使用标签而不是类别,这些标签是倒数第二个 Softmax 分类层中具有高于给定阈值的概率的所有类别。然而,这里检测概率会根据实体的大小和位置有很大的差异,从下图中我们实际上可以问,*模型有多自信,认为识别出的实体确实是声明的那个?*假设我们非常自信,图像中确实有一个实体,比如狗,但它在图像中的尺度和位置不如它的主人,人类实体那样显眼。那么,多类别标签是一个有效的方式,但并不是最好的选择:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/6abbc29c-4951-427c-bc3e-f85cbefac003.jpeg

在本章中,我们将涵盖以下主题:

  • 物体检测和图像分类的区别

  • 传统的非 CNN 物体检测方法

  • 基于区域的 CNN 及其特点

  • Fast R-CNN

  • Faster R-CNN

  • Mask R-CNN

物体检测和图像分类的区别

让我们举另一个例子。假设你正在观看电影《101 忠犬》,你想知道在电影的某个场景中,实际能数到多少只达尔马提亚犬。图像分类在最佳情况下可以告诉你至少有一只狗或者一只达尔马提亚犬(具体取决于你为分类器训练的级别),但无法准确告诉你它们有多少只。

基于分类的模型的另一个问题是,它们无法告诉你图像中识别出的实体在哪里。很多时候,这一点非常重要。举个例子,假设你看到邻居的狗正在和他(人类)以及他的猫玩耍。你拍了一张他们的照片,想从中提取出狗的图像,以便在网上搜索它的品种或类似的狗。然而,问题是,搜索整张图像可能无法成功,而且如果没有从图像中识别出单独的对象,你就不得不手动进行裁剪-提取-搜索的工作,如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/8d289e70-4022-4ba7-b696-a5b2e1017574.jpeg

因此,你实际上需要一种技术,它不仅能识别图像中的实体,还能告诉你它们在图像中的位置。这就是所谓的物体检测。物体检测为图像中所有识别出的实体提供边界框和类别标签(以及检测的概率)。该系统的输出可以用于支持多个高级用例,这些用例依赖于特定类别的检测对象。

举个例子,像 Facebook、Google Photos 以及许多其他类似应用中的面部识别功能。在这些功能中,在你识别图像中是谁参加了派对之前,你需要先检测图像中的所有面部;然后,你可以将这些面部通过你的面部识别/分类模块来获取/分类出他们的名字。所以,物体检测中的“物体”命名不仅限于语言实体,还包括任何具有明确边界并且有足够数据来训练系统的事物,如下图所示:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/ea2c10b7-68bc-4eee-8901-c33a84738f3a.jpeg

现在,如果你想知道参加你派对的客人中有多少人实际上在享受派对,你甚至可以进行一个微笑脸的物体检测,或者使用一个微笑检测器。目前有非常强大和高效的物体检测模型,适用于大多数可检测的人体部位(眼睛、面部、上半身等)、常见的人的表情(例如微笑)以及许多其他常见物体。所以,下次你使用智能手机上的微笑快门功能时(该功能会在场景中大多数面孔被检测为微笑时自动拍照),你就知道是什么驱动了这个功能。

为什么物体检测比图像分类更加具有挑战性?

根据我们目前对 CNN 和图像分类的理解,让我们尝试理解如何解决物体检测问题,这应该会逻辑地引导我们发现其潜在的复杂性和挑战。假设我们为了简化问题,处理的是单色图像。

任何高级的物体检测都可以被视为两个任务的组合(我们稍后会反驳这一点):

  • 获取正确的边界框(或者获取足够多的边界框以便后续过滤)

  • 在该边界框内对物体进行分类(同时返回分类效果用于过滤)

因此,物体检测不仅要解决图像分类(第二个目标)的所有挑战,还面临着寻找正确或尽可能多的边界框这一新的挑战。我们已经知道如何使用 CNN 进行图像分类,以及相关的挑战,现在我们可以集中精力处理第一个任务,并探讨我们的方案在效果(分类精度)和效率(计算复杂度)方面的有效性——或者更确切地说,探讨这一任务将会有多么具有挑战性。

所以,我们从随机生成图像中的边界框开始。即使我们不担心生成这么多候选框的计算负载,技术上称为区域提议(我们发送作为分类物体的提议的区域),我们仍然需要有某种机制来寻找以下参数的最佳值:

  • 用于提取/绘制候选边界框的起始(或中心)坐标

  • 候选边界框的长度

  • 候选边界框的宽度

  • 跨越每个轴(从一个起始位置到另一个位置的* x *-水平轴和 * y *-垂直轴的距离)

假设我们可以生成一个算法,给出这些参数的最优值。那么,这些参数的一个值在大多数情况下适用吗?实际上,在某些一般情况下适用吗?根据我们的经验,我们知道每个物体的尺度不同,因此我们知道,对于这些框,LW 的一个固定值是行不通的。此外,我们还可以理解,相同的物体,比如狗,在不同的图像中可能以不同的比例/尺度和位置出现,正如我们之前的一些例子所示。所以这证实了我们的信念:我们需要的是不同尺度而且不同大小的框。

假设从前面的类比中修正过来,我们希望从图像中的每个起始坐标提取 N 个候选框,其中 N 包含大多数可能适合我们分类问题的尺寸/尺度。尽管这似乎是一个相当具有挑战性的任务,但假设我们已经有了那个魔法数字,而它远不是 L[1,l-image] x W[1,w-image](所有 LW 的组合,其中长度是实际图像的所有整数集合,宽度是从 1 到图像宽度);这将导致每个坐标有 lw* 个框:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/c76e332d-472c-46bd-8e0d-4f51af7b7126.jpeg

接下来,另一个问题是我们需要在图像中访问多少个起始坐标,从这些位置提取每个 N 个框,或者说步长。使用一个非常大的步长会导致我们提取到的子图像本身,而不是可以有效分类并用于实现我们之前示例中某些目标的单一同质物体。相反,步长过短(比如每个方向上 1 个像素)可能意味着会有大量候选框。

从前面的示例中,我们可以理解,即使假设放宽大部分约束条件,我们仍然无法制造出可以装进智能手机、实时检测微笑自拍或甚至明亮面孔的系统(实际上即便是一个小时也不行)。我们的机器人和自动驾驶汽车也无法在移动时识别物体(并通过避开它们来导航)。这种直觉应该帮助我们理解物体检测领域的进展,以及为什么这是一个如此具有影响力的工作领域。

传统的非 CNN 物体检测方法

像 OpenCV 这样的库以及其他一些库在智能手机、机器人项目和许多其他软件包中迅速被纳入,以提供特定物体(如面部、微笑等)的检测能力,以及计算机视觉等相关功能,尽管即便是在 CNN 广泛采用之前,也存在一些约束。

基于 CNN 的目标检测和实例分割领域的研究为该领域提供了许多进展和性能提升,不仅使这些系统的大规模部署成为可能,还为许多新解决方案开辟了道路。但在我们计划深入探讨基于 CNN 的进展之前,了解在前一部分中提到的挑战是如何得到解决的,以使得目标检测在各种限制条件下仍能得以实现,将是一个不错的主意。接着,我们将按逻辑开始讨论不同的研究人员以及如何将 CNN 应用于解决传统方法仍然存在的其他问题。

Haar 特征、级联分类器和 Viola-Jones 算法

与 CNN 或深度学习不同,后者以能够自动生成更高层次概念特征而著称,这些特征反过来可以大大提升分类器的性能,在传统的机器学习应用中,这些特征需要由领域专家手工设计。

正如我们从使用基于 CPU 的机器学习分类器的经验中也可以理解的那样,它们的性能受数据高维度和可应用于模型的特征数量过多的影响,尤其是对于一些非常流行且复杂的分类器,如支持向量机SVM),它曾被认为是最先进的技术,直到不久前。

在本节中,我们将了解一些创新的想法,这些想法来自不同科学和数学领域的启发,最终解决了上面提到的一些挑战,从而使得非 CNN 系统中的实时目标检测得以实现。

Haar 特征

Haar 或类似 Haar 特征是具有不同像素密度的矩形形式。Haar 特征通过在检测区域内特定位置的相邻矩形区域中求和像素强度,根据各区域像素强度总和之间的差异,分类图像的不同子区域。

类 Haar 特征的名称源自数学术语 Haar 小波,它是一系列重新缩放的方形函数,这些函数共同形成了一个小波族或基底。

由于 Haar-like 特征是基于区域间像素强度差异工作的,因此它们在单色图像上效果最佳。这也是为什么前面使用的图像以及本节中的图像是单色的,以便更好地直观理解。

这些类别可以分为三个主要组别,如下所示:

  • 两个矩形特征

  • 三个矩形特征

  • 四个矩形特征

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/b4eecaef-6d1f-4db9-b0f1-59ca9198ba09.png

类 Haar 特征

通过一些简单的技巧,图像中不同区域的像素强度计算变得非常高效,并可以实时以非常高的速度处理。

级联分类器

即使我们能够非常快速地从特定区域提取 Haar 特征,这也不能解决从图像中许多不同位置提取这些特征的问题;这时,级联特征的概念就能发挥作用。观察到在分类中,只有 1/10,000 的子区域会被判定为面部,但我们必须提取所有特征并在所有区域运行整个分类器。进一步观察到,通过仅使用少数几个特征(级联第一层中的两个特征),分类器可以消除大量区域(级联第一层中 50%的区域)。此外,如果样本仅包含这些减少的区域样本,那么只需稍多的特征(级联第二层中的 10 个特征)就能让分类器排除更多的情况,以此类推。因此,我们按层次进行分类,从需要非常低计算能力的分类器开始,逐步增加剩余子集所需的计算负载,依此类推。

Viola-Jones 算法

在 2001 年,Paul Viola 和 Michael Jones 提出了一个解决方案,可以很好地应对一些前述挑战,但也有一些约束。尽管这是一个近二十年前的算法,但到目前为止,甚至直到最近,许多流行的计算机视觉软件仍然以某种形式将其嵌入其中。这一事实使得在我们转向基于 CNN 的区域提议方法之前,理解这个非常简单而强大的算法变得非常重要。

OpenCV 是最流行的计算机视觉软件库之一,使用级联分类器作为物体检测的主要模式,Haar 特征类似的级联分类器在 OpenCV 中非常流行。为此,已有许多预训练的 Haar 分类器可供使用,涵盖多种类型的常见物体。

该算法不仅能够提供高TPR真正率)和低FPR假正率)的检测结果,还能在实时条件下工作(每秒至少处理两帧)。

高 TPR 结合低 FPR 是确定算法鲁棒性的一个非常重要的标准。

他们提出的算法的约束条件如下:

  • 该算法仅适用于检测面部,而非识别面部(尽管他们提出的算法是针对面部的,但同样可以用于许多其他物体)。

  • 面部必须出现在图像中并且是正面视角,其他视角无法被检测到。

该算法的核心是 Haar(类似)特征和级联分类器。Haar 特征将在后面的一个小节中介绍。Viola-Jones 算法使用 Haar 特征的一个子集来确定面部的一般特征,例如:

  • 眼睛(通过一个由两个矩形特征(水平)确定,其中一个较暗的水平矩形在眼睛上方形成眉毛,接着是一个较亮的矩形位于下方)

  • 鼻子(三个矩形特征(垂直),鼻子中心为浅矩形,两侧各有一个较暗的矩形,形成太阳穴),等等。

这些快速提取的特征可以用于创建一个分类器,以检测(区分)人脸(与非人脸)。

Haar 特征通过一些技巧,计算速度非常快。

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/8614f6b9-ba3d-4666-9c6a-d7a7540fcdd6.jpg

Viola-Jones 算法和 Haar-like 特征用于检测人脸

这些 Haar-like 特征随后被用于级联分类器,以加快检测问题的处理速度,同时不失检测的鲁棒性。

Haar 特征和级联分类器促成了前一代一些非常鲁棒、高效且快速的单目标检测器。然而,训练这些级联以适应新的目标仍然非常耗时,并且存在许多限制,正如前面提到的。这就是新一代基于 CNN 的物体检测器发挥作用的地方。

在本章中,我们仅介绍了 Haar 级联或 Haar 特征的基础(属于非 CNN 范畴),因为它们长期占据主导地位,并且是许多新类型的基础。我们鼓励读者也探索一些后来更有效的基于 SIFT 和 HOG 的特征/级联(相关论文见参考文献部分)。

R-CNN - 带 CNN 特征的区域

在“为什么物体检测比图像分类更具挑战性?”这一节中,我们使用了非 CNN 方法来生成区域提议,并用 CNN 进行分类,我们意识到这样做效果不佳,因为生成的区域并没有经过优化,且输入到 CNN 中的区域并不理想。R-CNN 或带 CNN 特征的区域,顾名思义,完全颠覆了这个示例,使用 CNN 来生成特征,然后采用一种叫做**支持向量机(SVM)**的非 CNN 技术进行分类。

R-CNN 使用滑动窗口方法(就像我们之前讨论的那样,选择一些L x W的窗口和步幅)生成约 2000 个感兴趣区域,然后将它们转换为 CNN 特征进行分类。记得我们在迁移学习章节中讨论的内容——最后一层平展层(分类或 Softmax 层之前)可以提取出来,进行迁移学习,从在通用数据上训练的模型中进行学习,并进一步训练它们(与使用领域特定数据从头开始训练的相似性能模型相比,通常需要的数据量要少得多),从而构建领域特定的模型。R-CNN 也使用类似的机制来提高其在特定目标检测上的效果:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/6173be31-0eb7-4bce-9345-27e63b442d91.png

R-CNN - 工作原理

R-CNN 原始论文声称,在 PASCAL VOC 2012 数据集上,它相较于以前的最佳结果提高了**平均精度(mAP)**超过 30%,并且达到了 53.3%的 mAP。

我们在对 ImageNet 数据集进行图像分类练习(使用 CNN)时,得到了非常高精度的结果。不要将该结果与此处给出的对比统计数据一起使用,因为使用的数据集不仅不同(因此无法进行比较),而且任务本身(分类与目标检测)也完全不同,目标检测比图像分类要更具挑战性。

PASCAL VOC视觉目标挑战):每个研究领域都需要某种标准化的数据集和标准的 KPI,以便在不同的研究和算法之间进行结果比较。我们用于图像分类的数据集 ImageNet 不能作为目标检测的标准化数据集,因为目标检测不仅需要对对象类别进行标注,还需要标注对象的位置。ImageNet 并未提供这种数据。因此,在大多数目标检测研究中,我们可能会看到使用标准化的目标检测数据集,如 PASCAL VOC。PASCAL VOC 数据集目前有 4 个版本:VOC2007、VOC2009、VOC2010 和 VOC2012。VOC2012 是其中最新(也是最丰富)的版本。

另一个我们遇到的难点是兴趣区域的不同尺度(和位置),使用区域进行识别。这就是所谓的定位挑战;在 R-CNN 中,它通过使用不同范围的感受野来解决这个问题,从高达 195 x 195 像素和 32 x 32 步长的区域开始,到较小的区域逐渐减小。

这种方法被称为使用区域进行识别

等一下!这是不是让你想起了什么?我们曾说过将使用 CNN 从这个区域生成特征,但 CNN 使用的是固定大小的输入来生成固定大小的平坦层。我们确实需要固定大小的特征(扁平化的向量大小)作为 SVM 的输入,但这里输入区域的大小是变化的。那么这如何实现呢?R-CNN 使用了一种流行的技术,叫做仿射图像变换,通过这种技术,可以从每个区域提议中计算出固定大小的 CNN 输入,无论区域的形状如何。

在几何学中,仿射变换是指在仿射空间之间的变换函数,这种变换保持点、直线和平面的不变。仿射空间是一种结构,它在保留与平行性和相应尺度相关的性质的同时,推广了欧几里得空间的性质。

除了我们已经讨论过的挑战,还有一个值得提到的挑战。我们在第一步中生成的候选区域(在第二步中进行分类)并不非常准确,或者它们缺乏围绕识别对象的紧密边界。因此,我们在这种方法中加入了第三阶段,通过运行回归函数(称为边界框回归器)来提高边界框的准确性,以识别分隔的边界。

与早期的端到端非 CNN 方法相比,R-CNN 证明非常成功。但它仅使用 CNN 将区域转换为特征。如我们所知,CNN 在图像分类中也非常强大,但由于我们的 CNN 仅对输入的区域图像工作,而不是对展平后的区域特征进行操作,因此无法直接使用它。在下一节中,我们将看到如何克服这一障碍。

从理解 CNN 在目标检测中的背景使用角度来看,R-CNN 非常重要,因为它是从所有非 CNN 方法中迈出的巨大一步。但由于 CNN 在目标检测中的进一步改进,正如我们接下来会讨论的那样,R-CNN 现在不再被积极开发,代码也不再维护。

Fast R-CNN – 快速区域卷积神经网络

Fast R-CNN,或称快速区域卷积神经网络方法,是对先前的 R-CNN 的改进。具体来说,与 R-CNN 相比,它的改进统计数据如下:

  • 训练速度提升 9 倍

  • 在评分/服务/测试时快 213 倍(每张图片处理 0.3 秒),不包括区域提议所花费的时间

  • 在 PASCAL VOC 2012 数据集上具有更高的 mAP,达 66%

在 R-CNN 使用较小的(五层)CNN 时,Fast R-CNN 使用更深的 VGG16 网络,这也提高了其准确性。此外,R-CNN 之所以慢,是因为它对每个对象提议执行一次卷积神经网络的前向传播,而没有共享计算:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/a12c5041-0ca5-492b-8a7f-12be6edd2eb9.png

Fast R-CNN:工作原理

在 Fast R-CNN 中,深度 VGG16 CNN 为所有阶段提供了必要的计算,即:

  • 感兴趣区域RoI)计算

  • 对区域内容进行分类(对象或背景)

  • 回归以增强边界框

在这种情况下,CNN 的输入不是来自图像的原始(候选)区域,而是完整的实际图像;输出不是最后的展平层,而是之前的卷积(映射)层。从生成的卷积映射中,使用 RoI 池化层(最大池化的变体)来生成对应每个目标提议的展平固定长度 RoI,这些 RoI 随后会通过一些全连接FC)层。

RoI 池化是最大池化的一种变体(我们在本书的初始章节中使用过),其中输出大小是固定的,输入矩形是一个参数。

RoI 池化层使用最大池化,将任何有效感兴趣区域中的特征转换为一个具有固定空间范围的小特征图。

来自倒数第二个全连接层的输出将用于以下两项:

  • 分类(SoftMax 层),类别数与目标提议的数量相同,额外+1 个类别用于背景(区域中未找到的任何类别)

  • 一组回归器,产生四个数字(两个数字表示该物体框的左上角的 x、y 坐标,接下来的两个数字对应于该区域内物体的高度和宽度),这些数字对于每个物体提议都是必需的,以便为该物体提供精确的边界框。

使用 Fast R-CNN 所取得的结果非常出色。更为出色的是,利用强大的 CNN 网络为我们需要克服的所有三个挑战提供了非常有效的特征。但仍然存在一些缺点,正如我们在下一节关于 Faster R-CNN 的内容中将了解的那样,仍然有进一步改进的空间。

Faster R-CNN – 基于更快区域提议网络的 CNN

我们在前一节中看到,Fast R-CNN 大幅减少了评分(测试)图像所需的时间,但这种减少忽略了生成区域提议所需的时间,这一过程使用了一个独立的机制(尽管是从 CNN 的卷积图中提取的),并继续形成瓶颈。此外,我们观察到,尽管所有三个挑战在 Fast R-CNN 中都使用了来自卷积图的共同特征来解决,但它们使用了不同的机制/模型。

Faster R-CNN 改进了这些缺点,并提出了区域提议网络RPNs)的概念,将评分(测试)时间减少到每张图像 0.2 秒,即使包括了区域提议的时间。

Fast R-CNN 在每张图像的评分(测试)上用了 0.3 秒,这还不包括区域提议过程所需的时间。

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/f947c085-62de-43b0-bde7-f7a27bb31127.png

Faster R-CNN: 工作原理 - 区域提议网络作为注意力机制

如前图所示,VGG16(或其他)CNN 直接作用于图像,生成一个卷积图(类似于在 Fast R-CNN 中所做的)。从这里开始有所不同,现在有两个分支,一个进入 RPN,另一个进入检测网络。这再次是相同 CNN 的扩展,用于预测,形成了全卷积网络FCN)。RPN 作为注意力机制并且与检测网络共享完整图像的卷积特征。此外,现在由于网络中的所有部分都可以使用高效的基于 GPU 的计算,因此减少了总体所需的时间:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/1600a405-9d97-4b14-aeaa-b7b322fb5d71.jpg

Faster R-CNN: 工作原理 - 区域提议网络作为注意力机制

要更好地理解注意力机制,请参考本书中关于 CNN 的注意力机制章节。

RPN 通过滑动窗口机制工作,其中一个窗口(类似 CNN 滤波器)在共享卷积层的最后一个卷积图上滑动。每次滑动时,滑动窗口会产生k (k=N[Scale] × N[Size])个锚框(类似候选框),其中N[Scale]是每个size的*N[Size]*大小(长宽比)框的尺度数,这些框从滑动窗口的中心提取,就像下图所示。

RPN 输出进入一个展平的全连接(FC)层。然后,输出进入两个网络,一个用于预测每个k框的四个数字(确定框的坐标、长宽,如同 Fast R-CNN 中一样),另一个进入一个二项分类模型,确定该框内是否包含目标物体的可能性。来自 RPN 的输出进入检测网络,检测每个 k 框中所包含的具体物体类别,给定框的位置及其物体性。

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/ce65a2bc-2168-4e83-be93-176926aef7e0.png

Faster R-CNN:工作原理 - 提取不同尺度和大小

这个架构中的一个问题是两个网络的训练,分别是区域提议网络(Region Proposal)和检测网络。我们了解到,CNN 是通过反向传播训练的,反向传播遍历所有层,并在每次迭代时减少每层的损失。但由于架构分成了两个不同的网络,我们每次只能对一个网络进行反向传播。为了解决这个问题,训练是通过在每个网络中迭代进行的,同时保持另一个网络的权重不变。这有助于两个网络快速收敛。

RPN 架构的一个重要特性是,它对两个函数具有平移不变性,一个生成锚点,另一个为锚点生成属性(其坐标和物体性)。由于平移不变性,反向操作或根据锚点图的向量图生成图像的部分是可行的。

由于平移不变性,我们可以在 CNN 中任意方向移动,即从图像到(区域)提议,从提议到图像的相应部分。

Mask R-CNN - 使用 CNN 进行实例分割

Faster R-CNN 是目前目标检测领域的最先进技术。但在目标检测的相关领域,Faster R-CNN 无法有效解决一些问题,这就是 Mask R-CNN——Faster R-CNN 的进化版本——能够提供帮助的地方。

本节介绍了实例分割的概念,它结合了本章描述的标准目标检测问题与语义分割的挑战。

在语义分割中,应用于图像时,目标是将每个像素分类到一个固定的类别集中,而不区分物体实例。

还记得我们在直觉部分中提到的计算图像中狗的数量的例子吗?我们能够轻松地数出狗的数量,因为它们彼此相隔很远,没有重叠,因此基本上只需数对象的数量即可完成任务。现在,以以下这张图片为例,使用目标检测数番茄的数量。这将是一项艰巨的任务,因为边界框(Bounding Boxes)重叠严重,很难区分番茄实例与框的关系。

所以,本质上,我们需要进一步深入,超越边界框,进入像素层面,以便获得这种级别的分离和识别。就像我们在目标检测中用物体名称来分类边界框一样,在实例分割中,我们对每个像素进行分割/分类,不仅标出具体的物体名称,还要标出物体实例。

目标检测和实例分割可以被视为两个不同的任务,一个逻辑上引导另一个,正如我们在目标检测中发现的那样,任务是查找区域提议和分类。但是,正如在目标检测中,尤其是使用像 Fast/Faster R-CNN 这样的技术时,我们发现如果能够同时进行这些任务,同时还能够利用大量计算和网络资源来完成任务,这将更加高效,从而使这些任务无缝衔接。

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/d580f08c-98db-4d19-9f25-fcffec41326e.jpeg

实例分割 – 直觉

Mask R-CNN 是 Faster R-CNN 的一种扩展,前者在之前的网络中已经覆盖,并且使用了 Faster R-CNN 中的所有技术,唯一的新增部分是——在网络中增加了一个额外的路径,用于并行生成每个检测到的对象实例的分割掩码(或对象掩码)。此外,由于这种方法主要利用现有网络,因此它对整个处理过程仅增加了最小的开销,并且其评分(测试)时间几乎等同于 Faster R-CNN。它在所有单模型解决方案中,尤其是在应用于 COCO2016 挑战(使用 COCO2015 数据集)时,具有最好的准确度之一。

类似于 PASCAL VOC,COCO 是另一个大规模的标准数据集(由微软提供)。除了目标检测,COCO 还用于分割和图像描述。COCO 比许多其他数据集更为广泛,最近在目标检测方面的很多比较都是基于 COCO 数据集进行的。COCO 数据集有三个版本,分别是 COCO 2014、COCO 2015 和 COCO 2017。

在 Mask R-CNN 中,除了有两个分支分别生成每个锚框或 RoI 的目标性(objectness)和定位信息外,还有一个第三个全卷积网络(FCN),它接受 RoI 并以逐像素的方式为给定的锚框预测一个分割掩码。

但是仍然存在一些挑战。尽管 Faster R-CNN 确实展示了变换不变性(也就是说,我们可以从 RPN 的卷积图追踪到实际图像的像素图),但卷积图的结构与实际图像像素的结构不同。因此,网络输入和输出之间没有像素级的对齐,这对于我们通过该网络提供像素到像素的遮罩非常重要。为了解决这个问题,Mask R-CNN 使用了一个无量化层(在原文中称为 RoIAlign),它有助于对齐精确的空间位置。这个层不仅提供了精确的对齐,还大大提高了精度,因此 Mask R-CNN 能够超越许多其他网络:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/50bfd31a-acb9-4003-8b02-bcdf236793f3.jpg

Mask R-CNN – 实例分割遮罩(示例输出)

实例分割的概念非常强大,它能够实现很多使用物体检测单独无法完成的有影响力的应用场景。

我们甚至可以使用实例分割来估计同一框架中的人体姿势并将其消除。

代码中的实例分割

现在是时候将我们学到的内容付诸实践了。我们将使用 COCO 数据集及其 API 来获取数据,并使用 Facebook Research 的 Detectron 项目(链接见参考文献),该项目提供了许多前面讨论的技术的 Python 实现,遵循 Apache 2.0 许可协议。该代码适用于 Python2 和 Caffe2,因此我们需要一个带有指定配置的虚拟环境。

创建环境

可以按照 References 部分中 Caffe2 仓库链接中的 caffe2 安装说明来创建带有 Caffe2 安装的虚拟环境。接下来,我们将安装依赖。

安装 Python 依赖(Python2 环境)

我们可以按照以下代码块中的方式安装 Python 依赖:

Python 2X 和 Python 3X 是 Python 的两种不同版本(或者更准确地说是 CPython),并不是一个传统意义上的版本升级,因此一个版本的库可能与另一个版本不兼容。在这一部分中,请使用 Python 2X。

当我们提到(解释型的)编程语言 Python 时,我们需要使用特定的解释器(因为它是解释型语言,而不是像 Java 那样的编译型语言)。我们通常所提到的 Python 解释器(例如从 Python.org 下载的版本或与 Anaconda 捆绑的版本)技术上叫做 CPython,它是 Python 的默认字节码解释器,使用 C 语言编写。但是,也有其他 Python 解释器,例如 Jython(基于 Java 构建)、PyPy(用 Python 本身编写——有点不直观吧?)、IronPython(.NET 实现的 Python)。

pip install numpy>=1.13 pyyaml>=3.12 matplotlib opencv-python>=3.2 setuptools Cython mock scipy

下载并安装 COCO API 和 detectron 库(操作系统命令行命令)

然后我们将下载并安装 Python 依赖项,如以下代码块所示:

# COCO API download and install
# COCOAPI=/path/to/clone/cocoapi
git clone https://github.com/cocodataset/cocoapi.git $COCOAPI
cd $COCOAPI/PythonAPI
make install

# Detectron library download and install
# DETECTRON=/path/to/clone/detectron
git clone https://github.com/facebookresearch/detectron $DETECTRON
cd $DETECTRON/lib && make

或者,我们可以下载并使用该环境的 Docker 镜像(需要 Nvidia GPU 支持):

# DOCKER image build
cd $DETECTRON/docker docker build -t detectron:c2-cuda9-cudnn7.
nvidia-docker run --rm -it detectron:c2-cuda9-cudnn7 python2 tests/test_batch_permutation_op.py

准备 COCO 数据集文件夹结构

现在我们将看到准备 COCO 数据集文件夹结构的代码,如下所示:

# We need the following Folder structure: coco [coco_train2014, coco_val2014, annotations]
mkdir -p $DETECTRON/lib/datasets/data/coco
ln -s /path/to/coco_train2014 $DETECTRON/lib/datasets/data/coco/
ln -s /path/to/coco_val2014 $DETECTRON/lib/datasets/data/coco/
ln -s /path/to/json/annotations $DETECTRON/lib/datasets/data/coco/annotations

在 COCO 数据集上运行预训练模型

我们现在可以在 COCO 数据集上实现预训练模型,如以下代码片段所示:

python2 tools/test_net.py \
    --cfg configs/12_2017_baselines/e2e_mask_rcnn_R-101-FPN_2x.yaml \
    TEST.WEIGHTS https://s3-us-west-2.amazonaws.com/detectron/35861858/12_2017_baselines/e2e_mask_rcnn_R-101-             FPN_2x.yaml.02_32_51.SgT4y1cO/output/train/coco_2014_train:coco_2014_valminusminival/generalized_rcnn/model_final.pkl \
    NUM_GPUS 1

参考文献

  1. Paul Viola 和 Michael Jones,《使用增强级联简单特征进行快速目标检测》,计算机视觉与模式识别会议,2001 年。

  2. Paul Viola 和 Michael Jones,《鲁棒的实时目标检测》,国际计算机视觉杂志,2001 年。

  3. Itseez2015opencv,OpenCV,《开源计算机视觉库》,Itseez,2015 年。

  4. Ross B. Girshick,Jeff Donahue,Trevor Darrell,Jitendra Malik,《准确目标检测和语义分割的丰富特征层次》,CoRR,arXiv:1311.2524,2013 年。

  5. Ross Girshick,Jeff Donahue,Trevor Darrell,Jitendra Malik,《准确目标检测和语义分割的丰富特征层次》,计算机视觉与模式识别,2014 年。

  6. M. Everingham, L. VanGool, C. K. I. Williams, J. Winn, A. Zisserman,《PASCAL 视觉目标类别挑战赛 2012》,VOC2012,结果。

  7. D. Lowe,《基于尺度不变关键点的独特图像特征》,IJCV,2004 年。

  8. N. Dalal 和 B. Triggs,《用于人类检测的方向梯度直方图》,CVPR,2005 年。

  9. Ross B. Girshick,Fast R-CNN,CoRR,arXiv:1504.08083,2015 年。

  10. Rbgirshick,fast-rcnn,GitHub,github.com/rbgirshick/fast-rcnn,2018 年 2 月。

  11. Shaoqing Ren, Kaiming He, Ross B. Girshick, Jian Sun, Faster R-CNN: 《基于区域提议网络的实时目标检测》,CoRR,arXiv:1506.01497,2015 年。

  12. Shaoqing Ren 和 Kaiming He 和 Ross Girshick 和 Jian Sun,Faster R-CNN:《基于区域提议网络的实时目标检测》神经信息处理系统 (NIPS),2015 年。

  13. Rbgirshick,py-faster-rcnn,GitHub,github.com/rbgirshick/py-faster-rcnn,2018 年 2 月。

  14. Ross Girshick,Ilija Radosavovic,Georgia Gkioxari,Piotr Dollar,Kaiming He,

    Detectron,GitHub,github.com/facebookresearch/Detectron,2018 年 2 月。

  15. Tsung-Yi Lin, Michael Maire, Serge J. Belongie, Lubomir D. Bourdev, Ross B. Girshick, James Hays, Pietro Perona, Deva Ramanan, Piotr Dollar, C. Lawrence Zitnick,《Microsoft COCO:上下文中的常见物体》,CoRR,arXiv:1405.0312,2014 年。

  16. Kaiming He, Georgia Gkioxari, Piotr Dollar, Ross B. Girshick,Mask R-CNN,CoRR,arXiv:1703.06870,2017 年。

  17. Liang-Chieh Chen, Alexander Hermans, George Papandreou, Florian Schroff, Peng Wang, Hartwig Adam, MaskLab: 通过语义和方向特征优化目标检测的实例分割,CoRR,arXiv:1712.04837,2017。

  18. Anurag Arnab, Philip H. S. Torr,使用动态实例化网络的像素级实例分割,CoRR,arXiv:1704.02386,2017。

  19. Matterport,Mask_RCNN,GitHub,github.com/matterport/Mask_RCNN,2018 年 2 月。

  20. CharlesShang, FastMaskRCNN,GitHub,github.com/CharlesShang/FastMaskRCNN,2018 年 2 月。

  21. Caffe2,Caffe2,GitHub,github.com/caffe2/caffe2,2018 年 2 月。

总结

在本章中,我们从目标检测任务背后的简单直觉入手,然后逐步介绍了更为先进的概念,例如实例分割,这是当今的研究热点。目标检测在零售、媒体、社交媒体、移动性和安全领域的创新中占据核心地位;这些技术具有巨大的潜力,可以为企业和社会消费创造具有深远影响和盈利潜力的功能。

从算法的角度来看,本章从传奇的 Viola-Jones 算法及其底层机制开始,诸如 Haar 特征和级联分类器。基于这些直觉,我们开始探索卷积神经网络(CNN)在目标检测中的应用,涉及的算法包括 R-CNN、Fast R-CNN,一直到最先进的 Faster R-CNN。

在本章中,我们还奠定了基础,并介绍了一个非常新颖且具有深远影响的研究领域——实例分割。我们还讨论了基于 Mask R-CNN 等方法的先进深度 CNN,用于实现实例分割的简便且高效的实现。

第八章:GAN:使用 CNN 生成新图像

通常,神经网络需要带标签的示例才能有效学习。无监督学习方法从未标注的数据中学习的效果并不好。生成对抗网络,简称GAN,是一种无监督学习方法,但基于可微分的生成器网络。GAN 最初由 Ian Goodfellow 等人于 2014 年发明。从那时起,它们变得非常流行。这是基于博弈论的,有两个参与者或网络:生成器网络和判别器网络,它们相互竞争。这种基于双网络的博弈论方法大大改善了从未标注数据中学习的过程。生成器网络生成伪造数据并传递给判别器。判别器网络也看到真实数据并预测它收到的数据是假的还是真的。因此,生成器被训练成可以轻松生成非常接近真实数据的数据,从而欺骗判别器网络。判别器网络被训练成分类哪些数据是真实的,哪些数据是假的。所以,最终,生成器网络学会生成非常非常接近真实数据的数据。GAN 将在音乐和艺术领域广泛流行。

根据 Goodfellow 的说法,“你可以把生成模型看作是赋予人工智能一种想象力的形式。”

以下是一些 GAN 的示例:

  • Pix2pix

  • CycleGAN

Pix2pix - 图像到图像翻译 GAN

该网络使用条件生成对抗网络cGAN)来学习图像的输入和输出之间的映射。以下是原始论文中可以完成的一些示例:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/5bd742bd-4360-4fb0-acaf-595d92808a92.jpeg

Pix2pix 的 cGAN 示例

在手袋示例中,网络学习如何为黑白图像上色。在这里,训练数据集中的输入图像是黑白的,目标图像是彩色版。

CycleGAN

CycleGAN 也是一种图像到图像翻译器,但没有输入/输出对。例如,从画作中生成照片,将马的图像转换成斑马图像:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/60b9b3b9-a03d-4a53-acbf-49563705690e.jpeg

在判别器网络中,使用 dropout 非常重要。否则,它可能会产生较差的结果。

生成器网络以随机噪声作为输入,并产生一个真实感的图像作为输出。对不同类型的随机噪声运行生成器网络会产生不同种类的真实图像。第二个网络,称为判别器网络,与常规的神经网络分类器非常相似。该网络在真实图像上进行训练,尽管训练 GAN 与监督训练方法有很大不同。在监督训练中,每个图像在显示给模型之前都会先被标注。例如,如果输入是一张狗的图像,我们会告诉模型这是狗。而在生成模型中,我们会向模型展示大量图像,并要求它从相同的概率分布中生成更多类似的图像。实际上,第二个判别器网络帮助生成器网络实现这一目标。

判别器输出图像是真实的还是生成器生成的假的概率。换句话说,它试图给真实图像分配一个接近 1 的概率,而给假的图像分配一个接近 0 的概率。与此同时,生成器则做相反的事情。它被训练成输出能被判别器判定为接近 1 的图像。随着时间的推移,生成器会生成更真实的图像,从而欺骗判别器:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/31c3ec0c-b8e9-407f-9e05-9764f2b33178.png

训练 GAN 模型

在前几章中解释的大多数机器学习模型都是基于优化的,也就是说,我们在其参数空间中最小化代价函数。生成对抗网络(GAN)则不同,因为它包含了两个网络:生成器 G 和判别器 D。每个网络都有自己的代价函数。一个简单的方式来理解 GAN 是,判别器的代价函数是生成器代价函数的负值。在 GAN 中,我们可以定义一个值函数,生成器需要最小化,而判别器需要最大化。生成模型的训练过程与监督训练方法大不相同。GAN 对初始权重非常敏感,因此我们需要使用批量归一化(batch normalization)。批量归一化不仅能提高性能,还能使模型更加稳定。在这里,我们同时训练两个模型:生成模型和判别模型。生成模型 G 捕捉数据分布,而判别模型 D 估计一个样本来自训练数据的概率,而不是来自 G。

GAN – 代码示例

在以下示例中,我们使用 MNIST 数据集并利用 TensorFlow 构建和训练一个 GAN 模型。这里,我们将使用一种特殊版本的 ReLU 激活函数,称为Leaky ReLU。输出是一个新的手写数字类型:

Leaky ReLU 是 ReLU 激活函数的一种变体,其公式为f(x) = max(α∗x, x**)。因此,x为负值时,输出为alpha * x,而x为正值时,输出为x

#import all necessary libraries and load data set
%matplotlib inline

import pickle as pkl
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data')

为了构建这个网络,我们需要两个输入,一个是生成器的输入,一个是判别器的输入。在下面的代码中,我们为判别器创建real_input的占位符,为生成器创建z_input的占位符,输入尺寸分别为dim_realdim_z

#place holder for model inputs 
def model_inputs(dim_real, dim_z):
    real_input = tf.placeholder(tf.float32, name='dim_real')
    z_input = tf.placeholder(tf.float32, name='dim_z')

    return real_input, z_input

这里,输入z是一个随机向量传入生成器,生成器将这个向量转化为图像。然后我们添加一个隐藏层,这是一个带有泄漏的 ReLU 层,以允许梯度向后传播。泄漏 ReLU 就像普通的 ReLU(对负值输出零)一样,除了对于负输入值,输出有一个小的非零值。生成器使用tanhsigmoid函数表现更好。生成器的输出是tanh输出。因此,我们必须将 MNIST 图像重新缩放到-1 到 1 之间,而不是 0 到 1 之间。通过这些知识,我们可以构建生成器网络:

#Following code builds Generator Network
def generator(z, out_dim, n_units=128, reuse=False, alpha=0.01):
    ''' Build the generator network.

        Arguments
        ---------
        z : Input tensor for the generator
        out_dim : Shape of the generator output
        n_units : Number of units in hidden layer
        reuse : Reuse the variables with tf.variable_scope
        alpha : leak parameter for leaky ReLU

        Returns
        -------
        out: 
    '''
    with tf.variable_scope('generator', reuse=reuse) as generator_scope: # finish this
        # Hidden layer
        h1 = tf.layers.dense(z, n_units, activation=None )
        # Leaky ReLU
        h1 = tf.nn.leaky_relu(h1, alpha=alpha,name='leaky_generator')

        # Logits and tanh output
        logits = tf.layers.dense(h1, out_dim, activation=None)
        out = tf.tanh(logits)

        return out

判别器网络与生成器相同,只是输出层使用的是sigmoid函数:


def discriminator(x, n_units=128, reuse=False, alpha=0.01):
    ''' Build the discriminator network.

        Arguments
        ---------
        x : Input tensor for the discriminator
        n_units: Number of units in hidden layer
        reuse : Reuse the variables with tf.variable_scope
        alpha : leak parameter for leaky ReLU

        Returns
        -------
        out, logits: 
    '''
    with tf.variable_scope('discriminator', reuse=reuse) as discriminator_scope:# finish this
        # Hidden layer
        h1 = tf.layers.dense(x, n_units, activation=None )
        # Leaky ReLU
        h1 = tf.nn.leaky_relu(h1, alpha=alpha,name='leaky_discriminator')

        logits = tf.layers.dense(h1, 1, activation=None)
        out = tf.sigmoid(logits)

        return out, logits

要构建网络,使用以下代码:

#Hyperparameters
# Size of input image to discriminator
input_size = 784 # 28x28 MNIST images flattened
# Size of latent vector to generator
z_size = 100
# Sizes of hidden layers in generator and discriminator
g_hidden_size = 128
d_hidden_size = 128
# Leak factor for leaky ReLU
alpha = 0.01
# Label smoothing 
smooth = 0.1

我们希望在真实数据和假数据之间共享权重,因此需要重用变量:

#Build the network
tf.reset_default_graph()
# Create our input placeholders
input_real, input_z = model_inputs(input_size, z_size)

# Build the model
g_model = generator(input_z, input_size, n_units=g_hidden_size, alpha=alpha)
# g_model is the generator output

d_model_real, d_logits_real = discriminator(input_real, n_units=d_hidden_size, alpha=alpha)
d_model_fake, d_logits_fake = discriminator(g_model, reuse=True, n_units=d_hidden_size, alpha=alpha)

计算损失

对于判别器,总损失是对真实图像和假图像损失的总和。损失将是 sigmoid 交叉熵损失,我们可以使用 TensorFlow 的tf.nn.sigmoid_cross_entropy_with_logits得到。然后我们计算批次中所有图像的均值。因此,损失将如下所示:

tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=logits, labels=labels))

为了帮助判别器更好地泛化,可以通过例如使用平滑参数,将labels从 1.0 稍微减少到 0.9。 这被称为标签平滑,通常与分类器一起使用以提高性能。假的数据的判别器损失类似。logitsd_logits_fake,它是通过将生成器输出传递给判别器得到的。这些假的logits与全为零的labels一起使用。记住,我们希望判别器对真实图像输出 1,对假图像输出 0,因此我们需要设置损失函数来反映这一点。

最后,生成器的损失使用的是d_logits_fake*,*即假的图像logits。但现在labels全为 1。生成器试图欺骗判别器,因此它希望判别器对假的图像输出 1:

# Calculate losses
d_loss_real = tf.reduce_mean(
                  tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_real, 
                                                          labels=tf.ones_like(d_logits_real) * (1 - smooth)))
d_loss_fake = tf.reduce_mean(
                  tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake, 
                                                          labels=tf.zeros_like(d_logits_real)))
d_loss = d_loss_real + d_loss_fake

g_loss = tf.reduce_mean(
             tf.nn.sigmoid_cross_entropy_with_logits(logits=d_logits_fake,
                                                     labels=tf.ones_like(d_logits_fake)))

添加优化器

我们需要分别更新生成器和判别器的变量。因此,首先获取图中的所有变量,然后如前所述,我们可以仅从生成器作用域获取生成器变量,类似地从判别器作用域获取判别器变量:

# Optimizers
learning_rate = 0.002

# Get the trainable_variables, split into G and D parts
t_vars = tf.trainable_variables()
g_vars = [var for var in t_vars if var.name.startswith('generator')]
d_vars = [var for var in t_vars if var.name.startswith('discriminator')]

d_train_opt = tf.train.AdamOptimizer(learning_rate).minimize(d_loss, var_list=d_vars)
g_train_opt = tf.train.AdamOptimizer(learning_rate).minimize(g_loss, var_list=g_vars)

要训练网络,使用:

batch_size = 100
epochs = 100
samples = []
losses = []
# Only save generator variables
saver = tf.train.Saver(var_list=g_vars)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    for e in range(epochs):
        for ii in range(mnist.train.num_examples//batch_size):
            batch = mnist.train.next_batch(batch_size)

            # Get images, reshape and rescale to pass to D
            batch_images = batch[0].reshape((batch_size, 784))
            batch_images = batch_images*2 - 1

            # Sample random noise for G
            batch_z = np.random.uniform(-1, 1, size=(batch_size, z_size))

            # Run optimizers
            _ = sess.run(d_train_opt, feed_dict={input_real: batch_images, input_z: batch_z})
            _ = sess.run(g_train_opt, feed_dict={input_z: batch_z})

        # At the end of each epoch, get the losses and print them out
        train_loss_d = sess.run(d_loss, {input_z: batch_z, input_real: batch_images})
        train_loss_g = g_loss.eval({input_z: batch_z})

        print("Epoch {}/{}...".format(e+1, epochs),
              "Discriminator Loss: {:.4f}...".format(train_loss_d),
              "Generator Loss: {:.4f}".format(train_loss_g)) 
        # Save losses to view after training
        losses.append((train_loss_d, train_loss_g))

        # Sample from generator as we're training for viewing afterwards
        sample_z = np.random.uniform(-1, 1, size=(16, z_size))
        gen_samples = sess.run(
                       generator(input_z, input_size, n_units=g_hidden_size, reuse=True, alpha=alpha),
                       feed_dict={input_z: sample_z})
        samples.append(gen_samples)
        saver.save(sess, './checkpoints/generator.ckpt')

# Save training generator samples
with open('train_samples.pkl', 'wb') as f:
    pkl.dump(samples, f)

一旦模型训练并保存后,你可以可视化生成的数字(代码不在此处,但可以下载)。

半监督学习与 GAN

到目前为止,我们已经看到 GAN 如何用于生成逼真的图像。在本节中,我们将看到 GAN 如何用于分类任务,尤其是在标签数据较少的情况下,但仍希望提高分类器的准确性。这里我们仍然使用相同的 街景房屋号码SVHN)数据集来对图像进行分类。如前所述,我们这里也有两个网络,生成器 G 和判别器 D。在这种情况下,判别器被训练成一个分类器。另一个变化是,判别器的输出将传递给 softmax 函数,而不是早期看到的 sigmoid 函数。softmax 函数返回标签的概率分布:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/8f617d78-0c02-4e22-9e9c-1e99923915dc.png

现在我们将网络建模为:

总成本 = 有标签数据的成本 + 无标签数据的成本

为了获取有标签数据的成本,我们可以使用 cross_entropy 函数:

cost of labeled data  = cross_entropy ( logits, labels)
cost of unlabeled data =   cross_entropy ( logits, real)

然后我们可以计算所有类别的总和:

real prob = sum (softmax(real_classes))

正常的分类器作用于有标签数据。然而,基于 GAN 的半监督分类器作用于有标签数据、真实未标注数据和假图像。这种方法非常有效,即使我们在训练过程中有较少的标注数据,分类错误也较少。

特征匹配

特征匹配的思想是,在生成器的成本函数中添加一个额外的变量,以惩罚测试数据和训练数据中的绝对误差之间的差异。

使用 GAN 示例进行半监督分类

在本节中,我们将解释如何使用 GAN 来构建一个采用半监督学习方法的分类器。

在监督学习中,我们有一个包含输入 X 和类别标签 y 的训练集。我们训练一个模型,该模型以 X 作为输入并输出 y

在半监督学习中,我们的目标仍然是训练一个模型,该模型以 X 作为输入并生成 y 作为输出。然而,并非所有的训练示例都有标签 y

我们使用 SVHN 数据集。我们将 GAN 判别器转变为一个 11 类判别器(0 到 9 以及一个假图像标签)。它将识别真实 SVHN 数字的 10 个不同类别,以及来自生成器的第 11 类假图像。判别器将能在真实标注图像、真实未标注图像和假图像上进行训练。通过利用三种数据来源,而不仅仅是单一来源,它将在测试集上表现得比传统的仅在单一数据源上训练的分类器更好:

def model_inputs(real_dim, z_dim):
    inputs_real = tf.placeholder(tf.float32, (None, *real_dim), name='input_real')
    inputs_z = tf.placeholder(tf.float32, (None, z_dim), name='input_z')
    y = tf.placeholder(tf.int32, (None), name='y')
    label_mask = tf.placeholder(tf.int32, (None), name='label_mask')

    return inputs_real, inputs_z, y, label_mask

添加生成器:

def generator(z, output_dim, reuse=False, alpha=0.2, training=True, size_mult=128):
    with tf.variable_scope('generator', reuse=reuse):
        # First fully connected layer
        x1 = tf.layers.dense(z, 4 * 4 * size_mult * 4)
        # Reshape it to start the convolutional stack
        x1 = tf.reshape(x1, (-1, 4, 4, size_mult * 4))
        x1 = tf.layers.batch_normalization(x1, training=training)
        x1 = tf.maximum(alpha * x1, x1)

        x2 = tf.layers.conv2d_transpose(x1, size_mult * 2, 5, strides=2, padding='same')
        x2 = tf.layers.batch_normalization(x2, training=training)
        x2 = tf.maximum(alpha * x2, x2)

        x3 = tf.layers.conv2d_transpose(x2, size_mult, 5, strides=2, padding='same')
        x3 = tf.layers.batch_normalization(x3, training=training)
        x3 = tf.maximum(alpha * x3, x3)

        # Output layer
        logits = tf.layers.conv2d_transpose(x3, output_dim, 5, strides=2, padding='same')

        out = tf.tanh(logits)

        return out

添加判别器:

def discriminator(x, reuse=False, alpha=0.2, drop_rate=0., num_classes=10, size_mult=64):
    with tf.variable_scope('discriminator', reuse=reuse):
        x = tf.layers.dropout(x, rate=drop_rate/2.5)

        # Input layer is 32x32x3
        x1 = tf.layers.conv2d(x, size_mult, 3, strides=2, padding='same')
        relu1 = tf.maximum(alpha * x1, x1)
        relu1 = tf.layers.dropout(relu1, rate=drop_rate)

        x2 = tf.layers.conv2d(relu1, size_mult, 3, strides=2, padding='same')
        bn2 = tf.layers.batch_normalization(x2, training=True)
        relu2 = tf.maximum(alpha * x2, x2)

        x3 = tf.layers.conv2d(relu2, size_mult, 3, strides=2, padding='same')
        bn3 = tf.layers.batch_normalization(x3, training=True)
        relu3 = tf.maximum(alpha * bn3, bn3)
        relu3 = tf.layers.dropout(relu3, rate=drop_rate)

        x4 = tf.layers.conv2d(relu3, 2 * size_mult, 3, strides=1, padding='same')
        bn4 = tf.layers.batch_normalization(x4, training=True)
        relu4 = tf.maximum(alpha * bn4, bn4)

        x5 = tf.layers.conv2d(relu4, 2 * size_mult, 3, strides=1, padding='same')
        bn5 = tf.layers.batch_normalization(x5, training=True)
        relu5 = tf.maximum(alpha * bn5, bn5)

        x6 = tf.layers.conv2d(relu5, 2 * size_mult, 3, strides=2, padding='same')
        bn6 = tf.layers.batch_normalization(x6, training=True)
        relu6 = tf.maximum(alpha * bn6, bn6)
        relu6 = tf.layers.dropout(relu6, rate=drop_rate)

        x7 = tf.layers.conv2d(relu5, 2 * size_mult, 3, strides=1, padding='valid')
        # Don't use bn on this layer, because bn would set the mean of each feature
        # to the bn mu parameter.
        # This layer is used for the feature matching loss, which only works if
        # the means can be different when the discriminator is run on the data than
        # when the discriminator is run on the generator samples.
        relu7 = tf.maximum(alpha * x7, x7)

        # Flatten it by global average pooling
        features = raise NotImplementedError()

        # Set class_logits to be the inputs to a softmax distribution over the different classes
        raise NotImplementedError()

        # Set gan_logits such that P(input is real | input) = sigmoid(gan_logits).
        # Keep in mind that class_logits gives you the probability distribution over all the real
        # classes and the fake class. You need to work out how to transform this multiclass softmax
        # distribution into a binary real-vs-fake decision that can be described with a sigmoid.
        # Numerical stability is very important.
        # You'll probably need to use this numerical stability trick:
        # log sum_i exp a_i = m + log sum_i exp(a_i - m).
        # This is numerically stable when m = max_i a_i.
        # (It helps to think about what goes wrong when...
        # 1\. One value of a_i is very large
        # 2\. All the values of a_i are very negative
        # This trick and this value of m fix both those cases, but the naive implementation and
        # other values of m encounter various problems)
        raise NotImplementedError()

        return out, class_logits, gan_logits, features

计算损失:

def model_loss(input_real, input_z, output_dim, y, num_classes, label_mask, alpha=0.2, drop_rate=0.):
    """
    Get the loss for the discriminator and generator
    :param input_real: Images from the real dataset
    :param input_z: Z input
    :param output_dim: The number of channels in the output image
    :param y: Integer class labels
    :param num_classes: The number of classes
    :param alpha: The slope of the left half of leaky ReLU activation
    :param drop_rate: The probability of dropping a hidden unit
    :return: A tuple of (discriminator loss, generator loss)
    """

    # These numbers multiply the size of each layer of the generator and the discriminator,
    # respectively. You can reduce them to run your code faster for debugging purposes.
    g_size_mult = 32
    d_size_mult = 64

    # Here we run the generator and the discriminator
    g_model = generator(input_z, output_dim, alpha=alpha, size_mult=g_size_mult)
    d_on_data = discriminator(input_real, alpha=alpha, drop_rate=drop_rate, size_mult=d_size_mult)
    d_model_real, class_logits_on_data, gan_logits_on_data, data_features = d_on_data
    d_on_samples = discriminator(g_model, reuse=True, alpha=alpha, drop_rate=drop_rate, size_mult=d_size_mult)
    d_model_fake, class_logits_on_samples, gan_logits_on_samples, sample_features = d_on_samples

    # Here we compute `d_loss`, the loss for the discriminator.
    # This should combine two different losses:
    # 1\. The loss for the GAN problem, where we minimize the cross-entropy for the binary
    # real-vs-fake classification problem.
    # 2\. The loss for the SVHN digit classification problem, where we minimize the cross-entropy
    # for the multi-class softmax. For this one we use the labels. Don't forget to ignore
    # use `label_mask` to ignore the examples that we are pretending are unlabeled for the
    # semi-supervised learning problem.
    raise NotImplementedError()

    # Here we set `g_loss` to the "feature matching" loss invented by Tim Salimans at OpenAI.
    # This loss consists of minimizing the absolute difference between the expected features
    # on the data and the expected features on the generated samples.
    # This loss works better for semi-supervised learning than the tradition GAN losses.
    raise NotImplementedError()

    pred_class = tf.cast(tf.argmax(class_logits_on_data, 1), tf.int32)
    eq = tf.equal(tf.squeeze(y), pred_class)
    correct = tf.reduce_sum(tf.to_float(eq))
    masked_correct = tf.reduce_sum(label_mask * tf.to_float(eq))

    return d_loss, g_loss, correct, masked_correct, g_model

添加优化器:

def model_opt(d_loss, g_loss, learning_rate, beta1):
    """
    Get optimization operations
    :param d_loss: Discriminator loss Tensor
    :param g_loss: Generator loss Tensor
    :param learning_rate: Learning Rate Placeholder
    :param beta1: The exponential decay rate for the 1st moment in the optimizer
    :return: A tuple of (discriminator training operation, generator training operation)
    """
    # Get weights and biases to update. Get them separately for the discriminator and the generator
    raise NotImplementedError()

    # Minimize both players' costs simultaneously
    raise NotImplementedError()
    shrink_lr = tf.assign(learning_rate, learning_rate * 0.9)

    return d_train_opt, g_train_opt, shrink_lr

构建网络模型:

class GAN:
    """
    A GAN model.
    :param real_size: The shape of the real data.
    :param z_size: The number of entries in the z code vector.
    :param learnin_rate: The learning rate to use for Adam.
    :param num_classes: The number of classes to recognize.
    :param alpha: The slope of the left half of the leaky ReLU activation
    :param beta1: The beta1 parameter for Adam.
    """
    def __init__(self, real_size, z_size, learning_rate, num_classes=10, alpha=0.2, beta1=0.5):
        tf.reset_default_graph()

        self.learning_rate = tf.Variable(learning_rate, trainable=False)
        inputs = model_inputs(real_size, z_size)
        self.input_real, self.input_z, self.y, self.label_mask = inputs
        self.drop_rate = tf.placeholder_with_default(.5, (), "drop_rate")

        loss_results = model_loss(self.input_real, self.input_z,
                                  real_size[2], self.y, num_classes,
                                  label_mask=self.label_mask,
                                  alpha=0.2,
                                  drop_rate=self.drop_rate)
        self.d_loss, self.g_loss, self.correct, self.masked_correct, self.samples = loss_results

        self.d_opt, self.g_opt, self.shrink_lr = model_opt(self.d_loss, self.g_loss, self.learning_rate, beta1)

训练并保存模型:

def train(net, dataset, epochs, batch_size, figsize=(5,5)):

    saver = tf.train.Saver()
    sample_z = np.random.normal(0, 1, size=(50, z_size))

    samples, train_accuracies, test_accuracies = [], [], []
    steps = 0

    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        for e in range(epochs):
            print("Epoch",e)

            t1e = time.time()
            num_examples = 0
            num_correct = 0
            for x, y, label_mask in dataset.batches(batch_size):
                assert 'int' in str(y.dtype)
                steps += 1
                num_examples += label_mask.sum()

                # Sample random noise for G
                batch_z = np.random.normal(0, 1, size=(batch_size, z_size))

                # Run optimizers
                t1 = time.time()
                _, _, correct = sess.run([net.d_opt, net.g_opt, net.masked_correct],
                                         feed_dict={net.input_real: x, net.input_z: batch_z,
                                                    net.y : y, net.label_mask : label_mask})
                t2 = time.time()
                num_correct += correct

            sess.run([net.shrink_lr])

            train_accuracy = num_correct / float(num_examples)

            print("\t\tClassifier train accuracy: ", train_accuracy)

            num_examples = 0
            num_correct = 0
            for x, y in dataset.batches(batch_size, which_set="test"):
                assert 'int' in str(y.dtype)
                num_examples += x.shape[0]

                correct, = sess.run([net.correct], feed_dict={net.input_real: x,
                                                   net.y : y,
                                                   net.drop_rate: 0.})
                num_correct += correct

            test_accuracy = num_correct / float(num_examples)
            print("\t\tClassifier test accuracy", test_accuracy)
            print("\t\tStep time: ", t2 - t1)
            t2e = time.time()
            print("\t\tEpoch time: ", t2e - t1e)

            gen_samples = sess.run(
                                   net.samples,
                                   feed_dict={net.input_z: sample_z})
            samples.append(gen_samples)
            _ = view_samples(-1, samples, 5, 10, figsize=figsize)
            plt.show()

            # Save history of accuracies to view after training
            train_accuracies.append(train_accuracy)
            test_accuracies.append(test_accuracy)

        saver.save(sess, './checkpoints/generator.ckpt')

    with open('samples.pkl', 'wb') as f:
        pkl.dump(samples, f)

    return train_accuracies, test_accuracies, samples

深度卷积 GAN

深度卷积生成对抗网络,也称为 DCGAN,用于生成彩色图像。在这里,我们在生成器和判别器中使用了卷积层。我们还需要使用批量归一化来确保 GAN 能够正常训练。我们将在《深度神经网络性能提升》章节中详细讨论批量归一化。我们将在 SVHN 数据集上训练 GAN;以下是一个小示例。训练后,生成器将能够创建几乎与这些图像相同的图像。你可以下载这个示例的代码:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/ca8ff0f4-19fc-465c-9b3e-aab5e4b9f1d7.png

Google 街景房屋号码视图

批量归一化

批量归一化是一种提高神经网络性能和稳定性的技术。其思想是对层输入进行归一化,使其均值为零,方差为 1。批量归一化最早由 Sergey Ioffe 和 Christian Szegedy 于 2015 年在论文 Batch Normalization is Necessary to Make DCGANs Work 中提出。其思路是,与其仅对网络输入进行归一化,不如对网络中各个层的输入进行归一化。之所以称之为 批量 归一化,是因为在训练过程中,我们使用当前小批量中的均值和方差来对每一层的输入进行归一化。

概述

在这一章中,我们看到了 GAN 模型如何真正展示 CNN 的强大功能。我们学习了如何训练自己的生成模型,并看到了一个实际的 GAN 示例,它能够将画作转化为照片,将马变成斑马。

我们理解了 GAN 与其他判别模型的区别,并学会了为什么生成模型更受青睐。

在下一章中,我们将从头开始学习深度学习软件的比较。

第九章:CNN 和视觉模型中的注意力机制

并非图像或文本中的每一部分——或者一般来说,任何数据——从我们需要获得的洞察角度来看都是同等相关的。例如,考虑一个任务,我们需要预测一个冗长陈述序列中的下一个单词,如 Alice and Alya are friends. Alice lives in France and works in Paris. Alya is British and works in London. Alice prefers to buy books written in French, whereas Alya prefers books in _____.

当这个例子给人类时,即使是语言能力相对较好的孩子也能很好地预测下一个词很可能是 English。从数学角度出发,在深度学习的背景下,我们同样可以通过创建这些单词的向量嵌入,并使用向量数学计算结果,来得出类似的结论,具体如下:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/7834171b-7edd-4d0f-84b3-cc116bcd796a.png

在这里,V(Word) 是所需单词的向量嵌入;类似地,V(French)V(Paris)V(London) 分别是单词 FrenchParisLondon 的向量嵌入。

嵌入通常是低维且密集(数值型)的向量表示,用于表示输入或输入的索引(对于非数值数据);在此情况下为文本。

诸如 Word2Vecglove 等算法可以用来获得单词嵌入。这些模型的预训练变种可在流行的基于 Python 的 NLP 库中找到,如 SpaCy、Gensim 等,也可以使用大多数深度学习库(如 Keras、TensorFlow 等)进行训练。

嵌入(embeddings)这一概念与视觉和图像领域的相关性与文本领域同样重要。

可能没有现成的向量与我们刚刚得到的向量完全匹配,如 https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/c3f164b3-d260-4473-9ec8-892bf9703ee6.png;但是,如果我们尝试找到最接近的现有向量,如 https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/a8e0ee9f-386b-48a2-a6df-61e883dde986.png,并使用反向索引查找代表词,那么这个词很可能与我们人类之前想到的词相同,即 English

诸如余弦相似度等算法可以用来找到与计算出的向量最接近的向量。

对于实现来说,找到最接近向量的计算效率更高的方式是 近似最近邻 (ANN),可以通过 Python 的 annoy 库来实现。

尽管我们通过认知和深度学习方法帮助得出了相同的结果,但两者的输入并不相同。对于人类,我们给出的句子与计算机相同,但对于深度学习应用,我们仔细选择了正确的单词(FrenchParisLondon)及其在方程中的正确位置以获得结果。想象一下,我们如何能轻松地意识到需要关注的正确单词,以理解正确的上下文,从而得出结果;但在当前的形式下,我们的深度学习方法无法做到这一点。

现在,使用不同变体和架构的 RNN(如 LSTM 和 Seq2Seq)的语言建模算法已经相当复杂。这些算法本来可以解决这个问题并得到正确的答案,但它们在较短且直接的句子中最为有效,例如巴黎对于法语来说,就像伦敦对于 _____ 一样。为了正确理解一个长句并生成正确的结果,重要的是要有一种机制来教导架构,使得在一长串单词中需要特别关注某些特定的词。这就是深度学习中的注意力机制,它适用于许多类型的深度学习应用,但方式略有不同。

RNN代表循环神经网络,用于表示深度学习中的时间序列数据。由于梯度消失问题,RNN 很少被直接使用;相反,其变体,如LSTM长短期记忆网络)和GRU门控循环单元)在实际应用中更为流行。Seq2Seq代表序列到序列模型,由两个 RNN(或其变体)网络组成(因此被称为Seq2Seq,其中每个 RNN 网络表示一个序列);一个作为编码器,另一个作为解码器。这两个 RNN 网络可以是多层或堆叠的 RNN 网络,并通过一个思维或上下文向量连接。此外,Seq2Seq 模型可以使用注意力机制来提高性能,特别是对于更长的序列。

事实上,更准确地说,即使我们需要分层处理前面的信息,首先要理解最后一句话是关于 Alya 的。然后我们可以识别并提取 Alya 所在的城市,接着是 Alice 的城市,依此类推。人类这种分层思维方式类似于深度学习中的堆叠,因此在类似的应用中,堆叠架构非常常见。

若想更了解堆叠在深度学习中的运作方式,特别是在基于序列的架构中,可以探讨一些话题,如堆叠 RNN 和堆叠注意力网络。

本章将涵盖以下主题:

  • 图像字幕生成的注意力机制

  • 注意力机制的类型(硬注意力和软注意力)

  • 使用注意力机制改善视觉模型

    • 视觉注意力的递归模型

图像字幕生成的注意力机制

从介绍中,到目前为止,您一定已经清楚注意力机制是在一系列对象上工作的,为每个序列中的元素分配一个特定迭代所需输出的权重。随着每一步的推进,不仅序列会改变,注意力机制中的权重也会发生变化。因此,基于注意力的架构本质上是序列网络,最适合使用 RNN(或其变体)在深度学习中实现。

现在的问题是:我们如何在静态图像上实现基于序列的注意力,尤其是在卷积神经网络CNN)中表示的图像上?好吧,让我们通过一个介于文本和图像之间的例子来理解这个问题。假设我们需要根据图像内容为其生成标题。

我们有一些带有人工提供的图像和标题作为训练数据,基于这些数据,我们需要创建一个系统,能够为任何新图像生成合适的标题,且这些图像此前未曾被模型见过。如前所述,我们可以举个例子,看看作为人类我们如何理解这个任务,以及需要在深度学习和卷积神经网络(CNN)中实现的类似过程。让我们考虑以下这张图像,并为其构思一些合理的标题。我们还将通过人工判断对这些标题进行启发式排序:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/4769fd23-01c6-49b2-a5bb-003b7d071e8b.jpeg

一些可能的标题(从最可能到最不可能的顺序)是:

  • 女人在雪林中看着狗

  • 棕色狗在雪中

  • 一位戴帽子的人在森林和白雪覆盖的土地上

  • 狗、树、雪、人、阳光

这里需要注意的一点是,尽管女人在图像中是中心对象,而狗并不是图像中最大的物体,但我们寻求的标题显然集中在它们及其周围环境。这是因为我们认为它们在此处是重要的实体(假设没有其他上下文)。作为人类,我们如何得出这个结论的过程如下:我们首先快速浏览了整张图像,然后将焦点集中在女人身上,并以高分辨率呈现她,同时将背景的其他部分(假设是散景效果)置于低分辨率。我们为此部分确定了标题,然后将狗呈现为高分辨率,背景其他部分则保持低分辨率;接着我们补充了与狗相关的标题部分。最后,我们对周围环境做了相同处理并为之添加了标题部分。

所以,本质上,我们通过以下顺序得出第一个标题:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/2f25f7ff-82b8-48db-b230-bbb01e70cc2e.jpg

图像 1:先快速浏览图像

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/c27a5da0-d904-4809-87fd-45f43765afcd.jpg

图像 2:聚焦于女人

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/75e4944d-5be6-42ca-b241-941152165171.jpg

图像 3:聚焦于狗

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/1c6594a7-aeca-4723-b445-37b172ec0a1c.jpg

图像 4:聚焦于雪

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/cdf6808c-9cef-4710-bcf3-a959a2ab1810.jpg

图像 5:聚焦于森林

从注意力或焦点的权重来看,在快速浏览图像之后,我们会将注意力集中在图像中最重要的第一个物体上:这里是女人。这个过程类似于在创建一个心理框架,在这个框架中,我们将包含女人的图像部分置于高分辨率,而图像的其余部分则保持低分辨率。

在深度学习的参考中,注意力序列将会为表示女人概念的向量(嵌入)分配最高的权重。在输出/序列的下一步中,权重将更多地向代表狗的向量偏移,依此类推。

为了直观理解这一点,我们将以 CNN 形式表示的图像转换为一个扁平化的向量或其他类似的结构;然后,我们创建图像的不同切片或具有不同部分和不同分辨率的序列。同时,正如我们在第七章中讨论的那样,使用 CNN 进行目标检测与实例分割,我们必须拥有需要检测的相关部分,并且这些部分也必须在不同的尺度下进行有效的检测。这个概念在这里同样适用,除了分辨率,我们还会改变尺度;但为了直观理解,我们暂时忽略尺度部分,保持简单。

这些图像切片或序列现在充当一系列单词,就像我们之前的例子一样,因此它们可以在 RNN/LSTM 或类似的基于序列的架构中进行处理,用于注意力机制。这样做的目的是在每次迭代中获得最合适的单词作为输出。因此,序列的第一次迭代会得到 woman(来自表示Woman的序列在Image 2中的权重)→ 然后下一次迭代得到→ seeing(来自表示Woman背面的序列,如Image 2)→ Dog(来自Image 3中的序列)→ in(来自所有模糊像素生成的序列,生成填充词,从实体过渡到环境)→ Snow(来自Image 4中的序列)→ Forest(来自Image 5中的序列)。

填充词如in和动作词如seeing也可以通过在人类生成的字幕与多张图像之间进行最佳图像切片/序列映射时自动学习。但在更简单的版本中,像WomanDogSnowForest这样的字幕也能很好地描述图像中的实体和周围环境。

注意力的类型

注意力机制有两种类型。它们如下:

  • 硬性注意力

  • 软性注意力

接下来,我们将详细了解每一种类型。

硬性注意力

实际上,在我们最近的图像描述示例中,会选择更多的图片,但由于我们用手写字幕进行训练,这些图片的权重永远不会更高。然而,重要的是要理解系统如何理解所有像素(或者更精确地说,是它们的 CNN 表示),系统聚焦于这些像素,以绘制不同方面的高分辨率图像,并且如何选择下一个像素以重复这一过程。

在前面的例子中,点是从分布中随机选择的,并且该过程会重复进行。而且,哪些像素会获得更高的分辨率是由注意力网络内部决定的。这种类型的注意力被称为硬性注意力

硬注意力存在所谓的可微性问题。我们花些时间来理解这个问题。我们知道,在深度学习中,网络需要训练,而训练它们的方式是通过遍历训练批次来最小化损失函数。我们可以通过沿着最小值的梯度方向改变权重,从而最小化损失函数,这样就可以得到最小值,而这个过程是通过对损失函数进行微分得到的。*

这一过程,即从最后一层到第一层在深度网络中最小化损失,是反向传播

在深度学习和机器学习中使用的一些可微损失函数示例包括对数似然损失函数、平方误差损失函数、二项式和多项式交叉熵等。

然而,由于在每次迭代中硬注意力是随机选择点的——而且这种随机像素选择机制不是一个可微函数——我们本质上无法训练这种注意力机制,正如前文所解释的。这个问题可以通过使用强化学习RL)或切换到软注意力来解决。

强化学习涉及解决两个问题的机制,可能是分别解决,也可能是结合解决。第一个问题叫做控制问题,它决定了在给定状态下,代理在每一步应该采取的最优动作;第二个问题叫做预测问题,它决定了状态的最优

软注意力

正如在前面的硬注意力小节中介绍的那样,软注意力使用强化学习逐步训练并确定下一步寻找的地方(控制问题)。

使用硬注意力和强化学习(RL)结合来实现所需目标存在两个主要问题:

  • 将强化学习和训练基于强化学习的代理及递归神经网络(RNN)/深度网络分开处理会稍显复杂。

  • 策略函数的梯度方差不仅很高(如A3C模型),而且其计算复杂度为O(N),其中N是网络中单元的数量。这大大增加了此类方法的计算负担。此外,由于注意力机制在过长的序列(无论是词语还是图像嵌入片段)中能提供更多价值——而且训练涉及更长序列的网络需要更大的内存,因此需要更深的网络——这种方法在计算上并不高效。

强化学习中的策略函数,表示为Q(a,s),是用来确定最优策略或在给定状态*(s)下应该采取的动作(a)*,以最大化奖励的函数。

那么,替代方案是什么呢?正如我们所讨论的,问题的出现是因为我们为注意力机制选择的方式导致了一个不可微的函数,因此我们不得不使用强化学习(RL)。所以让我们在这里采取不同的方式。通过类比我们在前面提到的语言建模问题示例(如在Attention 机制 - 直觉*部分),我们假设我们已经有了注意力网络中对象/词语的向量。此外,在同一向量空间中(比如嵌入超空间),我们将特定序列步骤中的查询所需的对象/词语的标记引入。采用这种方法,找到注意力网络中标记的正确注意力权重与查询空间中的标记之间的关系,就像计算它们之间的向量相似度一样简单;例如,计算余弦距离。幸运的是,大多数向量距离和相似度函数都是可微的;因此,使用这种向量距离/相似度函数在该空间中推导出的损失函数也是可微的,我们的反向传播可以在这种情况下正常工作。

两个向量之间的余弦距离,例如 https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/a1f3eff3-207d-4afa-89ea-46df50ec41d7.pnghttps://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/4d0ee39a-0f58-4d78-9d0c-9ac88bd3089f.png,在一个多维(此例为三维)向量空间中的计算公式为:https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/4ae1832d-60bb-4a85-b664-748a097ef5c4.png

使用可微损失函数训练注意力网络的方法被称为软注意力

使用注意力来改善视觉模型

正如我们在之前的注意力机制 - 直觉部分的 NLP 示例中发现的,注意力确实帮助我们在实现新的用例方面取得了巨大进展,这些用例是传统 NLP 无法高效实现的,同时也大大提高了现有 NLP 机制的性能。在 CNN 和视觉模型中,注意力的使用也是类似的。

在前一章第七章,基于 CNN 的目标检测与实例分割中,我们发现了如何使用类似注意力机制的区域提议网络(如 Faster R-CNN 和 Mask R-CNN),大大增强和优化了提议区域,并生成分割掩码。这对应于讨论的第一部分。在本节中,我们将讨论第二部分,我们将使用“注意力”机制来提高我们 CNN 的性能,即使在极端条件下。

视觉 CNN 模型性能不佳的原因

通过采用适当的调优和设置机制,CNN 网络的性能可以在一定程度上得到改善,这些机制包括:数据预处理、批归一化、权重的最佳预初始化;选择正确的激活函数;使用正则化技术来避免过拟合;使用最佳的优化函数;以及使用大量(优质)数据进行训练。

除了这些训练和架构相关的决策外,还有与图像相关的细节,这些细节可能影响视觉模型的表现。即便在控制了上述训练和架构因素后,传统的基于 CNN 的图像分类器在以下一些与底层图像相关的条件下表现不佳:

  • 非常大的图像

  • 包含多个分类实体的高度杂乱的图像

  • 非常嘈杂的图像

让我们尝试理解在这些条件下性能不佳的原因,然后我们将从逻辑上理解可能修复问题的方案。

在传统的基于 CNN 的模型中,即便是经过层间下采样,计算复杂度仍然相当高。实际上,复杂度是以 https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/9576a375-e104-439d-8c29-614bc5121d3a.png 为量级,其中 LW 是图像的长度和宽度(以英寸为单位),PPI 是每英寸的像素数(像素密度)。这意味着计算复杂度与图像中总像素数(P)线性相关,即 。这直接回答了挑战中的第一个问题;对于更高的 LWPPI,我们需要更高的计算能力和时间来训练网络。

操作如最大池化、平均池化等有助于大幅减少计算负担,相比于在所有层中对实际图像进行的所有计算。

如果我们可视化 CNN 中每一层形成的模式,我们将理解 CNN 工作原理背后的直觉,并且明白为什么它需要是深层的。在每一层中,CNN 训练更高层次的概念特征,这些特征逐层帮助更好地理解图像中的物体。所以,在 MNIST 的情况下,第一层可能只识别边界,第二层识别基于边界的对角线和直线形状,以此类推:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/84b09c81-669a-48a9-a78a-a9dc717454a5.jpg

在 CNN 的不同(初始)层中形成的 MNIST 相关的概念特征

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/7917f980-20eb-4bb3-9d7b-8c5add0c914a.jpg

MNIST 是一个简单的数据集,而现实生活中的图像则相当复杂;这需要更高层次的概念特征来区分它们,因此需要更复杂且更深的网络。此外,在 MNIST 中,我们试图区分相似类型的物体(所有都是手写数字)。而在现实生活中,物体可能差异很大,因此需要的不同特征类型也非常多:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/902073a5-cb0b-4ff7-ac0a-7710d216b9de.jpeg

这引出了我们的第二个挑战。一个包含过多物体的杂乱图像需要一个非常复杂的网络来建模所有这些物体。此外,由于需要识别的物体太多,图像分辨率需要足够高才能正确提取和映射每个物体的特征,这也意味着图像大小和像素数量需要足够高,以便进行有效的分类。这反过来会通过结合前两个挑战,成倍增加复杂性。

在 ImageNet 挑战赛中使用的流行 CNN 架构的层数,以及因此而增加的复杂性,近年来不断增加。一些例子包括:VGG16(2014)有 16 层,GoogLeNet(2014)有 19 层,ResNet(2015)有 152 层。

并非所有图像都具有完美的单反相机(SLR)质量。通常,由于低光照、图像处理、低分辨率、缺乏稳定性等原因,图像中可能会引入大量噪声。这只是噪声的一种形式,是比较容易理解的一种。从卷积神经网络(CNN)的角度来看,噪声的另一种形式可能是图像过渡、旋转或变换:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/9a998d97-d806-4d65-ade6-9cb2aeab0b4e.jpeg

没有噪声的图像

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/843cf705-b4bc-4751-b10e-77f64284478f.jpg

添加噪声后的同一图像

在前面的图像中,试着在带噪声和不带噪声的图像中阅读报纸标题Business,或者在两张图像中识别手机。带噪声的图像中很难做到这一点,对吧?这就像在噪声图像的情况下,CNN 的检测/分类挑战一样。

即使经过大量训练,完美的超参数调整,以及诸如丢弃法等技术,这些现实中的挑战依然会降低 CNN 网络的图像识别准确性。现在我们已经理解了导致 CNN 准确性和性能不足的原因和直觉,让我们探讨一些使用视觉注意力来缓解这些挑战的方法和架构。

视觉注意力的递归模型

视觉注意力的递归模型可以用来解决我们在前面部分提到的一些挑战。这些模型使用硬注意力方法,正如在之前的(注意力类型)部分中所讲述的那样。在这里,我们使用的是一种流行的视觉注意力递归模型变体——递归注意力模型RAM)。

如前所述,硬注意力问题是不可微分的,因此必须使用强化学习(RL)来解决控制问题。因此,RAM 使用强化学习来进行此优化。

视觉注意力的递归模型不会一次性处理整个图像,甚至不会处理基于滑动窗口的边界框。它模仿人眼的工作方式,基于图像中不同位置的注视,并结合每次注视所获得的重要信息,逐步建立起图像场景的内部表示。它使用递归神经网络(RNN)以顺序方式进行处理。

模型根据 RL 智能体的控制策略选择下一个要固定的位置信息,以最大化基于当前状态的奖励。当前状态又是所有过去信息和任务需求的函数。因此,它找到下一个固定坐标,以便在已经收集的信息基础上(通过 RNN 的记忆快照和先前访问的坐标)最大化奖励(任务需求)。

大多数 RL 机制使用马尔可夫决策过程(MDP),其中下一个动作仅由当前状态决定,而与之前访问的状态无关。在这里使用 RNN,能够将来自先前固定视点的重要信息结合到当前状态中。

上述机制解决了 CNN 在前面部分中强调的最后两个问题。此外,在 RAM 中,参数的数量和计算量可以独立于输入图像的大小进行控制,从而也解决了第一个问题。

在噪声 MNIST 样本上应用 RAM

为了更详细地理解 RAM 的工作原理,让我们尝试创建一个包含一些早期部分所提到的问题的 MNIST 样本:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/a2c083ac-a490-449a-9c63-bf7c251244d2.jpg

更大的噪声和失真的 MNIST 图像

上图展示了一个较大的图像/拼贴,使用了一个实际且略微噪声化的 MNIST 图像(数字2),以及其他一些失真和部分样本的片段。此外,实际的数字2并未居中。此示例代表了之前所述的所有问题,但足够简单,便于理解 RAM 的工作原理。

RAM 使用瞥视传感器的概念。RL 智能体将目光固定在特定的坐标(l)和特定的时间(t-1)。在时间 t-1 时刻,坐标l[t-1]和图像x[t]的内容,通过瞥视传感器提取以l[t-1]为中心的类似视网膜的多分辨率图像补丁。这些在时间t-1提取的表示 collectively 被称为p(x[t], l[t-1])

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/619b6dcc-4967-458b-8628-3becc0bafd72.jpg

瞥视传感器的概念

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/a545ac17-e9dd-4116-b679-169f3fb2ec46.jpg  ;https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/c44ab12e-cd94-4a50-ae69-1d5876ba0978.jpg

这些图像展示了我们的图像在两个固定视点下使用瞥视传感器的表示。

瞥视传感器获得的表示经过’瞥视网络’处理,表示会在两个阶段被展平。在第一阶段,瞥视传感器瞥视网络的表示分别被展平(https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/7963a240-8377-44d3-ae47-0ddb6b5cd3e3.png),然后它们合并为一个单一的展平层(https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/f6d21a4f-d1e9-4fbf-a645-7f785778784e.png),以生成时间t的输出表示g[t]

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/c09406b3-37cb-4929-a9c1-42e08c8742e7.jpg

Glimpse 网络的概念

这些输出表示然后传递通过 RNN 模型架构。下一步的固定点由 RL 代理决定,以最大化来自此架构的奖励:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/41cb0399-2275-462d-b820-135b3243e91d.jpg

模型架构(RNN)

如直观理解所示,Glimpse 传感器捕捉了跨越注视点的重要信息,这有助于识别重要的概念。例如,我们第二个示例图像中表示的 Fixation 处的多个分辨率(此处为 3)具有标记的三种分辨率(按分辨率递减顺序为红色、绿色和蓝色)。如图所示,即使这些被直接使用,我们依然能够检测到由这一噪声拼贴表示的正确数字:

https://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/c44ab12e-cd94-4a50-ae69-1d5876ba0978.jpghttps://github.com/OpenDocCN/freelearn-dl-pt6-zh/raw/master/docs/prac-cnn/img/a15e4752-6c1d-4d40-b966-cacf871b6701.jpg

Glimpse 传感器代码

如前一节所讨论,Glimpse 传感器是一个强大的概念。结合 RNN 和 RL 等其他概念,如前所述,它是提高视觉模型性能的核心。

让我们在这里更详细地查看。代码的每一行都有注释,方便理解,并且自解释:


import tensorflow as tf
# the code is in tensorflow
import numpy as np

def glimpseSensor(image, fixationLocation):
    '''
    Glimpse Sensor for Recurrent Attention Model (RAM)
    :param image: the image xt
    :type image: numpy vector
    :param fixationLocation: cordinates l for fixation center
    :type fixationLocation: tuple
    :return: Multi Resolution Representations from Glimpse Sensor
    :rtype:
    '''

    img_size=np.asarray(image).shape[:2]
    # this can be set as default from the size of images in our dataset, leaving the third 'channel' dimension if any

    channels=1
    # settings channels as 1 by default
    if (np.asarray(img_size).shape[0]==3):
        channels=np.asarray(image).shape[-1]
    # re-setting the channel size if channels are present

    batch_size=32
    # setting batch size

    loc = tf.round(((fixationLocation + 1) / 2.0) * img_size)
    # fixationLocation coordinates are normalized between -1 and 1 wrt image center as 0,0

    loc = tf.cast(loc, tf.int32)
    # converting number format compatible with tf

    image = tf.reshape(image, (batch_size, img_size[0], img_size[1], channels))
    # changing img vector shape to fit tf

    representaions = []
    # representations of image
    glimpse_images = []
    # to show in window

    minRadius=img_size[0]/10
    # setting the side size of the smallest resolution image
    max_radius=minRadius*2
    offset = 2 * max_radius
    # setting the max side and offset for drawing representations
    depth = 3
    # number of representations per fixation
    sensorBandwidth = 8
    # sensor bandwidth for glimpse sensor

    # process each image individually
    for k in range(batch_size):
        imageRepresentations = []
        one_img = image[k,:,:,:]
        # selecting the required images to form a batch

        one_img = tf.image.pad_to_bounding_box(one_img, offset, offset, max_radius * 4 + img_size, max_radius * 4 + img_size)
        # pad image with zeros for use in tf as we require consistent size

        for i in range(depth):
            r = int(minRadius * (2 ** (i)))
            # radius of draw
            d_raw = 2 * r
            # diameter

            d = tf.constant(d_raw, shape=[1])
            # tf constant for dia

            d = tf.tile(d, [2])
            loc_k = loc[k,:]
            adjusted_loc = offset + loc_k - r
            # location wrt image adjusted wrt image transformation and pad

            one_img2 = tf.reshape(one_img, (one_img.get_shape()[0].value, one_img.get_shape()[1].value))
            # reshaping image for tf

            representations = tf.slice(one_img2, adjusted_loc, d)
            # crop image to (d x d) for representation

            representations = tf.image.resize_bilinear(tf.reshape(representations, (1, d_raw, d_raw, 1)), (sensorBandwidth, sensorBandwidth))
            # resize cropped image to (sensorBandwidth x sensorBandwidth)

            representations = tf.reshape(representations, (sensorBandwidth, sensorBandwidth))
            # reshape for tf

            imageRepresentations.append(representations)
            # appending the current representation to the set of representations for image

        representaions.append(tf.stack(imageRepresentations))

    representations = tf.stack(representations)

    glimpse_images.append(representations)
    # return glimpse sensor output
    return representations

参考文献

  1. Kelvin Xu, Jimmy Ba, Ryan Kiros, Kyunghyun Cho, Aaron C. Courville, Ruslan Salakhutdinov, Richard S. Zemel, Yoshua Bengio, Show, Attend and Tell: 基于视觉注意力的神经图像描述生成,CoRR,arXiv:1502.03044,2015 年。

  2. Karl Moritz Hermann, Tom’s Kocisk, Edward Grefenstette, Lasse Espeholt, Will Kay, Mustafa Suleyman, Phil Blunsom, 教机器阅读与理解,CoRR,arXiv:1506.03340,2015 年。

  3. Volodymyr Mnih, Nicolas Heess, Alex Graves, Koray Kavukcuoglu, 视觉注意力的递归模型,CoRR,arXiv:1406.6247,2014 年。

  4. Long Chen, Hanwang Zhang, Jun Xiao, Liqiang Nie, Jian Shao, Tat-Seng Chua, SCA-CNN: 卷积网络中的空间与通道注意力用于图像描述,CoRR,arXiv:1611.05594,2016 年。

  5. Kan Chen, Jiang Wang, Liang-Chieh Chen, Haoyuan Gao, Wei Xu, Ram Nevatia, ABC-CNN: 一种基于注意力的卷积神经网络用于视觉问答,CoRR,arXiv:1511.05960,2015 年。

  6. Wenpeng Yin, Sebastian Ebert, Hinrich Schutze, 基于注意力的卷积神经网络用于机器理解,CoRR,arXiv:1602.04341,2016 年。

  7. Wenpeng Yin, Hinrich Schutze, Bing Xiang, Bowen Zhou, ABCNN: 基于注意力的卷积神经网络用于建模句子对,CoRR,arXiv:1512.05193,2015 年。

  8. Zichao Yang, Xiaodong He, Jianfeng Gao, Li Deng, Alexander J. Smola, 用于图像问答的堆叠注意力网络,CoRR,arXiv:1511.02274,2015 年。

  9. Y. Chen, D. Zhao, L. Lv 和 C. Li,一种基于视觉注意力的卷积神经网络用于图像分类2016 年第 12 届世界智能控制与自动化大会(WCICA),桂林,2016 年,第 764-769 页。

  10. H. Zheng,J. Fu,T. Mei 和 J. Luo,学习多注意力卷积神经网络用于细粒度图像识别2017 年 IEEE 国际计算机视觉会议(ICCV),威尼斯,2017 年,5219-5227 页。

  11. 肖天俊、徐一冲、杨奎远、张家兴、彭宇欣、张正,两级注意力模型在深度卷积神经网络中的应用:用于细粒度图像分类,CoRR,arXiv:1411.6447,2014 年。

  12. Jlindsey15,循环注意力模型的 TensorFlow 实现,GitHub,github.com/jlindsey15/RAM,2018 年 2 月。

  13. QihongL,循环注意力模型的 TensorFlow 实现,GitHub,github.com/QihongL/RAM,2018 年 2 月。

  14. Amasky,循环注意力模型,GitHub,github.com/amasky/ram,2018 年 2 月。

摘要

注意力机制是当今深度学习中最热门的话题,被认为是当前研究中大多数前沿算法的核心,并且在未来的应用中也可能处于中心地位。像图像描述、视觉问答等问题,已经通过这种方法得到了很好的解决。事实上,注意力机制不仅限于视觉任务,早期也被应用于神经机器翻译等复杂的自然语言处理问题。因此,理解注意力机制对于掌握许多高级深度学习技术至关重要。

卷积神经网络(CNN)不仅用于视觉任务,还在许多应用中与注意力机制结合,用于解决复杂的自然语言处理问题,如建模句子对和机器翻译。本章介绍了注意力机制及其在一些自然语言处理问题中的应用,以及图像描述和循环视觉模型。在 RAM 中,我们没有使用 CNN,而是将 RNN 和注意力机制应用于从 Glimpse 传感器获得的图像缩小表示。然而,最近的研究也开始将注意力机制应用于基于 CNN 的视觉模型。

强烈建议读者参考文献中的原始论文,并探索使用注意力机制的高级概念,如多层次注意力、堆叠注意力模型以及使用 RL 模型(例如异步优势行为者-批评家A3C)模型解决硬注意力控制问题)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值