【DL】第 14 章使用卷积神经网络的深度计算机视觉

  🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎

📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃

🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝​

📣系列专栏 - 机器学习【ML】 自然语言处理【NLP】  深度学习【DL】

 🖍foreword

✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。

如果你对这个系列感兴趣的话,可以关注订阅哟👋

虽然IBM 的深蓝超级计算机早在 1996 年就击败了国际象棋世界冠军加里·卡斯帕罗夫,直到最近,计算机才能够可靠地执行看似微不足道的任务,例如检测图片中的小狗或识别口语。为什么这些任务对我们人类来说如此轻松?答案在于,感知主要发生在我们的意识范围之外,在我们大脑中专门的视觉、听觉和其他感觉模块内。当感觉信息到达我们的意识时,它已经被高级特征装饰了;例如,当你看一张可爱小狗的照片时,你不能选择去看小狗,不去注意它的可爱。也无法解释如何你认出了一只可爱的小狗;这对你来说很明显。因此,我们不能相信我们的主观体验:感知根本不是微不足道的,要理解它,我们必须看看感觉模块是如何工作的。

卷积神经网络 (CNN) 源于对大脑视觉皮层的研究,自 1980 年代以来一直用于图像识别。在过去的几年里,由于计算能力的提高、可用训练数据的数量以及第 11 章中介绍的用于训练深度网络的技巧,CNN 已经成功地在一些复杂的视觉任务上实现了超人的性能。它们为图像搜索服务、自动驾驶汽车、自动视频分类系统等提供支持。此外,CNN不仅限于视觉感知:它们在许多其他任务上也很成功,例如语音识别和自然语言处理。但是,我们现在将专注于视觉应用程序。

在本章中,我们将探讨 CNN 的来源,它们的构建块是什么样的,以及如何使用 TensorFlow 和 Keras 来实现它们。然后我们将讨论一些最好的 CNN 架构,以及其他视觉任务,包括对象检测(对图像中的多个对象进行分类并在它们周围放置边界框)和语义分割(根据对象的类别对每个像素进行分类)属于)。

视觉皮层的架构

大卫·H·休贝尔和 Torsten Wiesel 在1958 年11959 年2(以及几年后的猴子3 )对猫进行了一系列实验,对视觉皮层的结构提供了重要的见解(作者于 1981 年获得了诺贝尔生理学或医学奖为他们的工作)。特别是,他们表明,视觉皮层中的许多神经元具有小的局部感受野,这意味着它们仅对位于视野有限区域的视觉刺激作出反应(见图 14-1,其中 5 个局部感受野神经元由虚线圆圈表示)。不同神经元的感受野可能会重叠,它们一起平铺整个视野。

此外,作者表明,一些神经元只对水平线的图像有反应,而另一些神经元只对不同方向的线有反应(两个神经元可能具有相同的感受野,但对不同的线方向有反应)。他们还注意到,一些神经元具有更大的感受野,它们会对由较低层次模式组合而成的更复杂模式做出反应。这些观察导致了高层神经元基于相邻低层神经元的输出的想法(在图 14-1中,请注意每个神经元仅连接到前一层的几个神经元)。这种强大的架构能够检测视野任何区域中的各种复杂模式。

图 14-1。视觉皮层中的生物神经元对称为感受野的小视野区域中的特定模式作出反应;当视觉信号通过连续的大脑模块时,神经元会在更大的感受野中对更复杂的模式做出反应。

这些对视觉皮层的研究启发了1980 年引入的neocognitron 4,它逐渐演变成我们现在所说的卷积神经网络。一个重要的里程碑是Yann LeCun 等人在1998 年发表的一篇论文5 。它引入了著名的LeNet-5架构,被银行广泛用于识别手写支票号码。这个架构有一些你已经知道的构建块,例如全连接层和 sigmoid 激活函数,但它还引入了两个新的构建块:卷积层池化层。现在让我们看看它们。

笔记

为什么不简单地使用具有完全连接层的深度神经网络来完成图像识别任务呢?不幸的是,尽管这对于小图像(例如 MNIST)来说效果很好,但由于它需要大量的参数,它对于更大的图像就失效了。例如,一张 100 × 100 像素的图像有 10,000 个像素,如果第一层只有 1,000 个神经元(这已经严重限制了传输到下一层的信息量),这意味着总共有 1000 万个连接。这只是第一层。CNN 使用部分连接的层和权重共享来解决这个问题。

卷积层

CNN 最重要的构建块是卷积层:第一个卷积层中的6 个神经元并没有连接到输入图像中的每个像素(就像它们在前几章讨论的层中一样),而只连接到它们的接受像素字段(见图 14-2)。反过来,第二个卷积层中的每个神经元只连接到位于第一层的一个小矩形内的神经元。这种架构允许网络专注于第一个隐藏层中小的低级特征,然后将它们组装成下一个隐藏层中更大的高级特征,依此类推。这种层次结构在现实世界的图像中很常见,这也是 CNN 在图像识别方面表现出色的原因之一。

图 14-2。具有矩形局部感受野的 CNN 层

笔记

到目前为止,我们看到的所有多层神经网络都有由一长串神经元组成的层,我们必须将输入图像展平为一维,然后再将它们输入神经网络。在 CNN 中,每一层都以 2D 表示,这使得将神经元与其相应的输入进行匹配变得更加容易。

位于给定层的第i行、第j列的神经元连接到位于第ii + h – 1 行、第jj + w – 1 列的前一层神经元的输出,其中h f w是感受野的高度和宽度(见图14-3)。为了使层具有与前一层相同的高度和宽度,通常在输入周围添加零,如图所示。这个称为 填充

图 14-3。层和零填充之间的连接

如图 14-4所示,也可以通过分隔感受野来将一个大的输入层连接到一个小得多的层。这大大降低了模型的计算复杂度。从一个感受野到一个感受野的转变接下来称为步幅。在图中,一个 5 × 7 的输入层(加上零填充)连接到一个 3 × 4 的层,使用 3 × 3 的感受野和 2 的步幅(在这个例子中,两个方向的步幅相同,但它不必如此)。位于上层第 i 行第 j 列的神经元连接到位于i × s hi × s h f h – 1行、第j × wj列的前一层神经元的输出× w + fw – 1,其中hw是垂直和水平步幅。

图 14-4。使用 2 的步幅降低维度

过滤器

一个神经元的权重可以表示为感受野大小的小图像。例如,图 14-5显示了两组可能的权重,称为过滤器(或卷积核))。第一个表示为一个黑色的正方形,中间有一条垂直的白线(它是一个7×7的矩阵,除了中间的一列全是1,全是0);使用这些权重的神经元将忽略其感受野中除了中央垂直线之外的所有内容(因为所有输入都将乘以 0,除了位于中央垂直线上的输入)。第二个过滤器是一个黑色正方形,中间有一条水平白线。再一次,使用这些权重的神经元将忽略其感受野中的所有内容,除了中央水平线。

现在,如果一个层中的所有神经元都使用相同的垂直线滤波器(和相同的偏置项),并且您向网络提供图 14-5所示的输入图像(底部图像),该层将输出左上角的图像. 请注意,垂直的白线得到增强,而其余的则变得模糊。同样,如果所有神经元都使用相同的水平线滤波器,则右上方的图像是你得到的;请注意,水平白线得到增强,而其余的则变得模糊。于是,一层充满使用相同过滤器的神经元输出一个特征图,它突出显示图像中最激活过滤器的区域。当然,您不必手动定义过滤器:相反,在训练期间,卷积层会自动学习对其任务最有用的过滤器,并且上面的层将学习将它们组合成更复杂的模式。

图 14-5。应用两个不同的过滤器得到两个特征图

堆叠多个特征图

向上到目前为止,为简单起见,我将每个卷积层的输出表示为 2D 层,但实际上卷积层有多个过滤器(您决定多少个)并且每个过滤器输出一个特征图,因此它更准确地表示为3D(见图 14-6)。它在每个特征图中每个像素都有一个神经元,并且给定特征图中的所有神经元共享相同的参数(即相同的权重和偏置项)。不同特征图中的神经元使用不同的参数。神经元的感受野与前面描述的相同,但它延伸到所有先前层的特征图。简而言之,卷积层同时将多个可训练滤波器应用于其输入,使其能够检测其输入中任意位置的多个特征。

笔记

特征图中的所有神经元共享相同的参数这一事实大大减少了模型中的参数数量。一旦 CNN 学会识别一个位置的模式,它就可以在任何其他位置识别它。相比之下,一旦常规 DNN 学会识别一个位置的模式,它就只能在该特定位置识别它。

输入图像也由多个子层组成:一个每个颜色通道。通常有三种:红色、绿色和蓝色 (RGB)。灰度图像只有一个通道,但有些图像可能有更多通道——例如,捕获额外光频率(如红外线)的卫星图像。

图 14-6。具有多个特征图的卷积层和具有三个颜色通道的图像

具体来说,位于给定卷积层l中特征图k的第i行、第j列的神经元连接到前一层l -1 中神经元的输出,位于第i × hi × h行+ h – 1 和列j × wj × w + w – 1,跨所有特征图(在第l – 1层)。请注意,位于同一行i和列j但在不同特征图中的所有神经元都连接到前一层中完全相同的神经元的输出。

公式 14-1用一个大的数学公式总结了前面的解释:它显示了如何计算卷积层中给定神经元的输出。由于所有不同的指标,它有点难看,但它所做的只是计算所有输入的加权和,加上偏差项。

公式 14-1。计算卷积层中神经元的输出

在这个等式中:

  • i, j, k是卷积层(第l层)的特征图k中位于第 i 行第 j 列的神经输出。

  • 如前所述,hw是垂直和水平步长, hw是感受野的高度和宽度,n '是前一层(第l - 1层)的特征图的数量)。

  • i ', j ', k '是位于第l -1 层、第i ' 行、第j ' 列、特征图k '(或通道k ',如果前一层是输入层)中的神经元的输出。

  • k是特征图k(在第l层)的偏置项。您可以将其视为一个旋钮,用于调整特征图k的整体亮度。

  • u , v , k ' , k是第l层的特征图k中的任何神经元与其位于第u行、第 v列(相对于神经元的感受野)和特征图k '的输入之间的连接权重。

TensorFlow 实现

在 TensorFlow 中,每个输入图像通常表示为形状为 [高度、宽度、通道] 的 3D 张量。小批量表示为形状为 [小批量大小、高度、宽度、通道] 的 4D 张量。卷积层的权重表示为形状为 [ h , w , n ' , n ] 的 4D 张量。卷积层的偏置项简单地表示为形状为 [ n ] 的一维张量。

让我们看一个简单的例子。下面的代码使用 Scikit-Learn 加载两张示例图像load_sample_image()(加载两张彩色图像,一张是中国寺庙,另一张是一朵花),然后创建两个过滤器并将它们应用于两张图像,最后显示一个得到的特征图。请注意,您必须 pip 安装Pillow包才能使用load_sample_image()

from sklearn.datasets import load_sample_image

# Load sample images
china = load_sample_image("china.jpg") / 255
flower = load_sample_image("flower.jpg") / 255
images = np.array([china, flower])
batch_size, height, width, channels = images.shape

# Create 2 filters
filters = np.zeros(shape=(7, 7, channels, 2), dtype=np.float32)
filters[:, 3, :, 0] = 1  # vertical line
filters[3, :, :, 1] = 1  # horizontal line

outputs = tf.nn.conv2d(images, filters, strides=1, padding="SAME")

plt.imshow(outputs[0, :, :, 1], cmap="gray") # plot 1st image's 2nd feature map
plt.show()

让我们看一下这段代码:

  • 每个颜色通道的像素强度表示为从 0 到 255 的一个字节,因此我们只需通过除以 255 来缩放这些特征,以获得从 0 到 1 的浮点数。

  • 然后我们创建两个 7 × 7 的过滤器(一个中间有一条垂直白线,另一个中间有一条水平白线)。

  • 我们使用该tf.nn.conv2d()函数将它们应用于两个图像,该函数是 TensorFlow 低级深度学习 API 的一部分。在此示例中,我们使用零填充 ( padding="SAME") 和 1 的步幅。

  • 最后,我们绘制一张生成的特征图(类似于图 14-5中的右上角图像)。

tf.nn.conv2d()行值得更多解释:

  • images是输入小批量(一个 4D 张量,如前所述)。

  • filters是要应用的一组过滤器(也是一个 4D 张量,如前所述)。

  • strides等于1,但它也可以是具有四个元素的一维数组,其中两个中心元素是垂直和水平步幅(hw)。第一个和最后一个元素当前必须等于1。有朝一日,它们可能会用于指定批量步幅(跳过某些实例)和通道步幅(跳过前一层的某些特征图或通道)。

  • padding必须是"SAME""VALID"

    • 如果设置为"SAME",则卷积层在必要时使用零填充。输出大小设置为输入神经元的数量除以步幅,向上取整。例如,如果输入大小为 13,步幅为 5(见图 14-7),则输出大小为 3(即 13 / 5 = 2.6,四舍五入为 3)。然后根据需要在输入周围尽可能均匀地添加零。当 时strides=1,层的输出将具有与其输入相同的空间维度(宽度和高度),因此名称相同

    • 如果设置为"VALID",则卷积层不使用零填充,并且可能会忽略输入图像底部和右侧的一些行和列,具体取决于步幅,如图 14-7所示(为简单起见,仅水平维度为此处显示,但当然相同的逻辑适用于垂直维度)。这意味着每个神经元的感受野都严格位于输入内的有效位置内(它不会超出范围),因此命名为valid

图 14-7。“相同”或“有效”填充(输入宽度为 13,过滤器宽度为 6,步幅为 5)

在此示例中,我们手动定义了过滤器,但在真实的 CNN 中,您通常会将过滤器定义为可训练变量,以便神经网络可以了解哪些过滤器效果最好,如前所述。不要手动创建变量,而是使用keras.layers.Conv2D图层:

conv = keras.layers.Conv2D(filters=32, kernel_size=3, strides=1,
                           padding="same", activation="relu")

这段代码创建了一个Conv2D包含 32 个过滤器的层,每个过滤器为 3 × 3,使用步长 1(水平和垂直)和"same"填充,并将 ReLU 激活函数应用于其输出。如您所见,卷积层有很多超参数:您必须选择过滤器的数量、它们的高度和宽度、步幅和填充类型。与往常一样,您可以使用交叉验证来找到正确的超参数值,但这非常耗时。稍后我们将讨论常见的 CNN 架构,让您了解哪些超参数值在实践中最有效。

内存要求

其他CNN 的问题是卷积层需要大量 RAM。在训练期间尤其如此,因为反向传播的反向传播需要在前向传播期间计算的所有中间值。

例如,考虑一个具有 5 × 5 过滤器的卷积层,输出 200 个大小为 150 × 100 的特征图,步长为 1 和"same"填充。如果输入是150×100的RGB图像(三通道),那么参数个数是(5×5×3+1)×200=15200(+1对应偏置项)到一个全连接层。7然而,200 个特征映射中的每一个都包含 150 × 100 个神经元,每个神经元都需要计算其 5 × 5 × 3 = 75 个输入的加权和:总共有 2.25 亿次浮点乘法。没有全连接层那么糟糕,但仍然是计算密集型的。此外,如果使用 32 位浮点数表示特征图,则卷积层的输出将占用 200 × 150 × 100 × 32 = 9600 万位 (12 MB) 的 RAM。8这只是一个实例——如果一个训练批次包含 100 个实例,那么这一层将使用 1.2 GB 的 RAM!

在推理期间(即,在对新实例进行预测时),一旦计算了下一层,就可以释放一层占用的 RAM,因此您只需要两个连续层所需的 RAM。但是在训练过程中,前向传递期间计算的所有内容都需要为反向传递保留,因此所需的 RAM 量(至少)是所有层所需的 RAM 总量。

小费

如果由于内存不足错误导致训练崩溃,您可以尝试减小小批量大小。或者,您可以尝试使用步幅降低维度,或删除几层。或者您可以尝试使用 16 位浮点数而不是 32 位浮点数。或者,您可以将 CNN 分布在多个设备上。

现在让我们看一下 CNN 的第二个常见构建块:池化层

池化层

一次你了解卷积层是如何工作的,池化层很容易掌握。他们的目标是对输入图像进行二次采样(即缩小),以减少计算负载、内存使用和参数数量(从而限制过拟合的风险)。

就像在卷积层中一样,池化层中的每个神经元都连接到前一层中有限数量的神经元的输出,这些神经元位于一个小的矩形感受野内。您必须像以前一样定义它的大小、步幅和填充类型。然而,池化神经元没有权重。它所做的只是使用聚合函数(例如最大值或平均值)聚合输入。图 14-8显示最大池化层,这是最常见的池化层类型。在此示例中,我们使用 2 × 2池化内核9,步长为 2,无填充。只有每个感受野中的最大输入值才能进入下一层,而其他输入则被丢弃。例如,在图 14-8的左下感受野中,输入值为 1、5、3、2,因此只有最大值 5 会传播到下一层。由于步幅为 2,输出图像的高度和宽度是输入图像的一半(由于我们没有使用填充,因此向下舍入)。

图 14-8。最大池化层(2 × 2 池化内核,步幅 2,无填充)

笔记

池化层通常在每个输入通道上独立工作,因此输出深度与输入深度相同。

其他除了减少计算量、内存使用量和参数数量之外,最大池化层还为小型平移引入了某种程度的不变性,如图 14-9所示. 在这里,我们假设亮像素的值低于暗像素,并且我们考虑三个图像(A、B、C)通过具有 2 × 2 内核和步长 2 的最大池化层。图像 B 和 C 相同与图像 A 相同,但向右移动了 1 个和 2 个像素。如您所见,图像 A 和 B 的最大池化层的输出是相同的。这就是翻译不变性的意思。对于图像 C,输出不同:它向右移动了一个像素(但仍有 50% 的不变性)。通过在 CNN 中每隔几层插入一个最大池化层,就有可能在更大的范围内获得某种程度的平移不变性。此外,最大池化提供了少量的旋转不变性和轻微的尺度不变性。

图 14-9。小翻译的不变性

然而,最大池也有一些缺点。首先,它显然是非常具有破坏性的:即使使用很小的 2 × 2 内核和 2 步幅,输出在两个方向上都会小两倍(因此它的面积将小四倍),只需丢弃 75% 的输入价值观。并且在某些应用中,不变性是不可取的。拿语义分割(根据像素所属的对象对图像中的每个像素进行分类的任务,我们将在本章后面探讨):显然,如果输入图像向右平移一个像素,则输出应该也向右平移一个像素。在这种情况下,目标是等变,而不是不变:输入的微小变化应该导致输出的相应微小变化。

TensorFlow 实现

实施TensorFlow 中的最大池化层非常简单。以下代码使用 2 × 2 内核创建一个最大池化层。步幅默认为内核大小,因此该层将使用 2 的步幅(水平和垂直)。默认情况下,它使用"valid"填充(即,根本没有填充):

max_pool = keras.layers.MaxPool2D(pool_size=2)

创建一个平均池化层,只需使用AvgPool2D而不是MaxPool2D. 正如您所料,它的工作原理与最大池化层完全一样,只是它计算的是平均值而不是最大值。平均池化层曾经非常流行,但现在人们大多使用最大池化层,因为它们通常表现更好。这似乎令人惊讶,因为计算平均值通常比计算最大值丢失的信息少。但另一方面,最大池化只保留最强的特征,去掉所有无意义的特征,因此下一层得到更清晰的信号来处理。此外,最大池提供比平均池更强的平移不变性,并且它需要的计算量略少。

请注意,最大池化和平均池化可以沿深度维度而不是空间维度执行,尽管这并不常见。这可以让 CNN 学会对各种特征保持不变。例如,它可以学习多个过滤器,每个过滤器检测相同模式的不同旋转(例如手写数字;见图 14-10),并且深度最大池化层将确保输出相同,而不管回转。CNN 可以类似地学习对其他任何事物保持不变:厚度、亮度、偏斜、颜色等。

图 14-10。深度最大池化可以帮助 CNN 学习任何不变性

Keras 不包含深度最大池化层,但 TensorFlow 的低级深度学习 API 包含:只需使用该tf.nn.max_pool()函数,并将内核大小和步幅指定为 4 元组(即大小为 4 的元组)。每个的前三个值都应该是 1:这表示内核大小和沿着批次、高度和宽度维度的步幅应该是 1。最后一个值应该是你想要沿着深度维度的任何内核大小和步幅——例如, 3 (这必须是输入深度的除数;如果前一层输出 20 个特征图,它将不起作用,因为 20 不是 3 的倍数):

output = tf.nn.max_pool(images,
                        ksize=(1, 1, 1, 3),
                        strides=(1, 1, 1, 3),
                        padding="VALID")

如果您想将此作为层包含在您的 Keras 模型中,请将其包装在Lambda层中(或创建自定义 Keras 层):

depth_pool = keras.layers.Lambda(
    lambda X: tf.nn.max_pool(X, ksize=(1, 1, 1, 3), strides=(1, 1, 1, 3),
                             padding="VALID"))

您在现代架构中经常看到的最后一种池化层是全局平均池化层。它的工作方式非常不同:它所做的只是计算每个整个特征图的平均值(它就像使用与输入具有相同空间维度的池化内核的平均池化层)。这意味着它只为每个特征图和每个实例输出一个数字。虽然这当然具有极大的破坏性(特征图中的大部分信息都丢失了),但它可以作为输出层有用,我们将在本章后面看到。要创建这样一个层,只需使用keras.layers.GlobalAvgPool2D类:

global_avg_pool = keras.layers.GlobalAvgPool2D()

它相当于这个简单的Lambda层,它计算空间维度(高度和宽度)的平均值:

global_avg_pool = keras.layers.Lambda(lambda X: tf.reduce_mean(X, axis=[1, 2]))

现在您知道了创建卷积神经网络的所有构建块。让我们看看如何组装它们。

CNN 架构

典型的CNN架构堆叠了几个卷积层(每个卷积层通常后跟一个 ReLU 层),然后是一个池化层,然后是另外几个卷积层(+ReLU),然后是另一个池化层,依此类推。图像在通过网络时会变得越来越小,但它通常也会变得越来越深(即,具有更多的特征图),这要归功于卷积层(见图 14-11)。在堆栈的顶部,添加了一个常规的前馈神经网络,由几个全连接层(+ReLUs)组成,最后一层输出预测(例如,输出估计类概率的 softmax 层)。

图 14-11。典型的CNN架构

小费

一个常见的错误是使用太大的卷积核。例如,不要使用具有 5 × 5 内核的卷积层,而是使用 3 × 3 内核堆叠两个层:它将使用更少的参数并需要更少的计算,并且通常性能会更好。一个例外是第一个卷积层:它通常可以有一个大内核(例如,5 × 5),通常步长为 2 或更大:这将减少图像的空间维度而不会丢失太多信息,并且由于输入图像一般只有三个通道,成本不会太高。

以下是如何实现一个简单的 CNN 来处理 Fashion MNIST 数据集(在第 10 章中介绍):

model = keras.models.Sequential([
    keras.layers.Conv2D(64, 7, activation="relu", padding="same",
                        input_shape=[28, 28, 1]),
    keras.layers.MaxPooling2D(2),
    keras.layers.Conv2D(128, 3, activation="relu", padding="same"),
    keras.layers.Conv2D(128, 3, activation="relu", padding="same"),
    keras.layers.MaxPooling2D(2),
    keras.layers.Conv2D(256, 3, activation="relu", padding="same"),
    keras.layers.Conv2D(256, 3, activation="relu", padding="same"),
    keras.layers.MaxPooling2D(2),
    keras.layers.Flatten(),
    keras.layers.Dense(128, activation="relu"),
    keras.layers.Dropout(0.5),
    keras.layers.Dense(64, activation="relu"),
    keras.layers.Dropout(0.5),
    keras.layers.Dense(10, activation="softmax")
])

让我们来看看这个模型:

  • 第一层使用 64 个相当大的过滤器(7 × 7),但只有步幅 1,因为输入图像不是很大。它还设置input_shape=[28, 28, 1],因为图像是 28 × 28 像素,具有单一颜色通道(即灰度)。

  • 接下来我们有一个最大池化层,它使用大小为 2 的池,因此它将每个空间维度除以 2。

  • 然后我们重复相同的结构两次:两个卷积层,然后是一个最大池化层。对于较大的图像,我们可以将这种结构重复多次(重复次数是您可以调整的超参数)。

  • 请注意,随着我们从 CNN 向输出层爬升,过滤器的数量会增加(最初是 64,然后是 128,然后是 256):增加它是有意义的,因为低级特征的数量通常相当低(例如,小圆圈、水平线),但是有许多不同的方法可以将它们组合成更高级别的特征。在每个池化层之后将过滤器的数量加倍是一种常见的做法:由于池化层将每个空间维度除以 2,因此我们可以将下一层的特征图数量翻倍,而不必担心数量爆炸参数、内存使用或计算负载。

  • 接下来是全连接网络,由两个隐藏的密集层和一个密集的输出层组成。请注意,我们必须展平其输入,因为密集网络需要每个实例的一维特征数组。我们还添加了两个 dropout 层,每个层的 dropout 率为 50%,以减少过度拟合。

这个 CNN 在测试集上达到了 92% 以上的准确率。这不是最先进的,但它非常好,而且显然比我们在第 10 章中使用密集网络所取得的成果要好得多。

多年来,已经开发了这种基本架构的变体,从而在该领域取得了惊人的进步。衡量这一进步的一个很好的指标是 ILSVRC ImageNet 挑战等比赛中的错误率。在本次比赛中,图像分类前五名的错误率在短短六年内从超过 26% 下降到不到 2.3%。前五位错误率是系统的前五位预测不包括正确答案的测试图像的数量。图像很大(256 像素高),有 1000 个类别,其中一些非常微妙(尝试区分 120 个犬种)。查看获奖作品的演变是了解 CNN 工作原理的好方法。

我们将首先看看经典的 LeNet-5 架构(1998 年),然后是 ILSVRC 挑战赛的三个获胜者:AlexNet(2012 年)、GoogLeNet(2014 年)和 ResNet(2015 年)。

LeNet-5

 LeNet-5 架构10可能是最广为人知的 CNN 架构。如前所述,它由 Yann LeCun 于 1998 年创建,并已广泛用于手写数字识别 (MNIST)。它由表 14-1所示的层组成。

表 14-1。LeNet-5 架构
Layer类型Maps尺寸内核大小Stride激活
Out

完全连接

10

RBF

F6

完全连接

84

tanh

C5

卷积

120

1 × 1

5 × 5

1

tanh

S4

平均池化

16

5 × 5

2 × 2

2

tanh

C3

卷积

16

10 × 10

5 × 5

1

tanh

S2

平均池化

6

14 × 14

2 × 2

2

tanh

C1

卷积

6

28 × 28

5 × 5

1

tanh
In

输入

1

32 × 32

有一些额外的细节需要注意:

  • MNIST 图像是 28 × 28 像素,但它们在被输入到网络之前被零填充到 32 × 32 像素并进行归一化。网络的其余部分不使用任何填充,这就是为什么随着图像在网络中前进时尺寸不断缩小的原因。

  • 平均池化层比平常稍微复杂一些:每个神经元计算其输入的平均值,然后将结果乘以一个可学习的系数(每个地图一个)并添加一个可学习的偏置项(同样,每个地图一个),然后最后应用激活函数。

  • C3 映射中的大多数神经元仅连接到三个或四个 S2 映射中的神经元(而不是所有六个 S2 映射)。有关详细信息,请参见原始论文10中的表 1(第 8 页) 。

  • 输出层有点特殊:每个神经元不是计算输入和权重向量的矩阵相乘,而是输出其输入向量与其权重向量之间的欧几里得距离的平方。每个输出测量图像属于特定数字类别的程度。现在首选交叉熵成本函数,因为它对错误预测的惩罚更大,产生更大的梯度并更快地收敛。

Yann LeCun 的网站提供了 LeNet-5 数字分类的精彩演示。

AlexNet

 AlexNet CNN architecture 11以较大优势赢得了 2012 ImageNet ILSVRC 挑战赛:前五名的错误率达到了 17%,而第二名的错误率仅达到了 26%!它由 Alex Krizhevsky(因此得名)、Ilya Sutskever 和 Geoffrey Hinton 开发。它与 LeNet-5 相似,只是更大更深,它是第一个将卷积层直接堆叠在彼此之上,而不是在每个卷积层之上堆叠一个池化层。表 14-2展示了这种架构。

表 14-2。AlexNet 架构
类型地图尺寸内核大小跨步填充激活
Out

完全连接

1,000

Softmax

F10

完全连接

4,096

ReLU

F9

完全连接

4,096

ReLU

S8

最大池化

256

6 × 6

3 × 3

2

valid

C7

卷积

256

13 × 13

3 × 3

1

same

ReLU

C6

卷积

384

13 × 13

3 × 3

1

same

ReLU

C5

卷积

384

13 × 13

3 × 3

1

same

ReLU

S4

最大池化

256

13 × 13

3 × 3

2

valid

C3

卷积

256

27 × 27

5 × 5

1

same

ReLU

S2

最大池化

96

27 × 27

3 × 3

2

valid

C1

卷积

96

55 × 55

11 × 11

4

valid

ReLU
In

输入

3 (RGB)

227 × 227

为了减少过度拟合,作者使用了两种正则化技术。首先,他们在训练期间以 50% 的 dropout 率将 dropout(在第 11 章中介绍)应用于层 F9 和 F10 的输出。其次,他们通过通过各种偏移量随机移动训练图像,水平翻转它们,并改变光照条件。

数据增强

数据增强通过生成每个训练实例的许多现实变体,人为地增加了训练集的大小。这减少了过度拟合,使其成为一种正则化技术。生成的实例应该尽可能真实:理想情况下,给定来自增强训练集的图像,人类不应该能够判断它是否被增强。简单地添加白噪声无济于事。修改应该是可学习的(白噪声不是)。

例如,您可以将训练集中的每张图片稍微移动、旋转和调整大小,然后将结果图片添加到训练集中(见图 14-12)。这迫使模型更能容忍图片中对象的位置、方向和大小的变化。对于更能容忍不同光照条件的模型,您可以类似地生成许多具有不同对比度的图像。一般来说,您还可以水平翻转图片(文本和其他不对称对象除外)。通过结合这些转换,您可以大大增加训练集的大小。

图 14-12。从现有的训练实例生成新的训练实例

亚历克斯网还在 C1 和 C3 层的 ReLU 步骤之后立即使用竞争性归一化步骤,称为局部响应归一化(LRN):最强烈激活的神经元抑制位于相邻特征图中相同位置的其他神经元(这种竞争性激活已在生物神经元)。这鼓励不同的特征图专门化,将它们分开并迫使它们探索更广泛的特征,最终提高泛化能力。公式 14-2显示了如何应用 LRN。

公式 14-2。局部响应归一化 (LRN)

在这个等式中:

  • i是位于特征图i中的神经元的归一化输出,位于某行u和列v(请注意,在此等式中,我们仅考虑位于该行和列的神经元,因此未显示uv )。

  • i是在 ReLU 步骤之后但在归一化之前该神经元的激活。

  • kαβr是超参数。k称为偏差r称为深度半径

  • n是特征图的数量。

例如,如果r = 2 并且一个神经元具有强激活,它将抑制位于其自身上方和下方的特征图中的神经元的激活。

在 AlexNet 中,超参数设置如下:r = 5, α = 0.0001, β = 0.75, and k = 2。这一步可以使用函数来实现(如果你想在 Keras 模型中使用它tf.nn.local_response_normalization(),你可以将它包裹在一个层中)。Lambda

一个AlexNet 的变体称为ZF Net 12,由 Matthew Zeiler 和 Rob Fergus 开发,并赢得了 2013 年的 ILSVRC 挑战。它本质上是 AlexNet,带有一些经过调整的超参数(特征图的数量、内核大小、步幅等)。

GoogLeNet

 GoogLeNet 架构由 Christian Szegedy 等人开发。来自 Google Research,13,它通过将前五名的错误率降低到 7% 以下赢得了 ILSVRC 2014 挑战。这种出色的性能在很大程度上来自于网络比以前的 CNN 更深的事实(如图 14-14 所示)。这是通过称为inception 模块的子网络实现的,14它允许 GoogLeNet 比以前的架构更有效地使用参数:GoogLeNet 实际上的参数比 AlexNet 少 10 倍(大约 600 万而不是 6000 万)。

图 14-13显示了一个初始模块的架构。符号“3 × 3 + 1(S)”表示该层使用 3 × 3 内核、步幅 1 和"same"填充。输入信号首先被复制并馈送到四个不同的层。所有卷积层都使用 ReLU 激活函数。请注意,第二组卷积层使用不同的内核大小(1 × 1、3 × 3 和 5 × 5),允许它们捕获不同尺度的模式。另请注意,每一层都使用 1 的步幅和"same"填充(甚至是最大池化层),因此它们的输出都具有与输入相同的高度和宽度。这使得可以沿深度维度连接所有输出在最终的深度连接层中(即,堆叠来自所有四个顶部卷积层的特征图)。这个连接层可以在 TensorFlow 中使用tf.concat()操作来实现,with axis=3(轴是深度)。

图 14-13。初始模块

您可能想知道为什么 inception 模块具有 1 × 1 内核的卷积层。这些层肯定不能捕获任何特征,因为它们一次只看一个像素?事实上,这些层有三个目的:

  • 尽管它们不能捕获空间模式,但它们可以沿深度维度捕获模式。

  • 他们被配置为输出比输入更少的特征图,因此它们充当瓶颈层,这意味着它们会降低维度。这降低了计算成本和参数数量,加快了训练速度并提高了泛化能力。

  • 每对卷积层([1 × 1, 3 × 3] 和 [1 × 1, 5 × 5])就像一个强大的卷积层,能够捕获更复杂的模式。实际上,这对卷积层不是在图像上扫描一个简单的线性分类器(就像单个卷积层那样),而是在图像上扫描一个两层神经网络。

简而言之,您可以将整个 inception 模块视为类固醇上的卷积层,能够输出捕获各种尺度的复杂模式的特征图。

警告

每个卷积层的卷积核数量是一个超参数。不幸的是,这意味着您要为添加的每个初始层再调整六个超参数。

现在让我们看看 GoogLeNet CNN 的架构(见图 14-14)。每个卷积层和每个池化层输出的特征图数量显示在内核大小之前。该架构是如此之深,以至于它必须用三列来表示,但 GoogLeNet 实际上是一个高栈,包括九个初始模块(带有旋转陀螺的盒子)。inception 模块中的六个数字表示模块中每个卷积层输出的特征图的数量(与图 14-13中的顺序相同)。请注意,所有卷积层都使用 ReLU 激活函数。

图 14-14。GoogLeNet 架构

让我们通过这个网络:

  • 前两层将图像的高度和宽度除以 4(因此其面积除以 16),以减少计算量。第一层使用较大的内核大小,以便保留大部分信息。

  • 然后局部响应归一化层确保前面的层学习各种各样的特征(如前所述)。

  • 接下来是两个卷积层,其中第一个充当瓶颈层。如前所述,您可以将这一对视为一个更智能的卷积层。

  • 同样,局部响应归一化层确保前面的层捕获各种模式。

  • 接下来,最大池化层将图像高度和宽度减少 2,再次加快计算速度。

  • 然后是九个初始模块的高堆栈,与几个最大池层交错以降低维数并加速网络。

  • 接下来,全局平均池化层输出每个特征图的平均值:这会丢弃任何剩余的空间信息,这很好,因为那时没有太多空间信息。事实上,GoogLeNet 输入图像通常预计为 224 × 224 像素,因此在 5 个最大池化层之后,每个池化层将高度和宽度除以 2,特征图下降到 7 × 7。此外,这是一个分类任务,而不是本地化,因此对象在哪里并不重要。由于这一层带来的降维,不需要在 CNN 的顶部有几个完全连接的层(就像在 AlexNet 中一样),这大大减少了网络中的参数数量并限制了过拟合的风险。

  • 最后一层是不言自明的:正则化的 dropout,然后是具有 1,000 个单元的全连接层(因为有 1,000 个类)和一个 softmax 激活函数输出估计的类概率。

这张图稍微简化了一点:最初的 GoogLeNet 架构还包括两个辅助分类器,它们插在第三个和第六个 inception 模块之上。它们都由一个平均池化层、一个卷积层、两个全连接层和一个 softmax 激活层组成。在训练期间,他们的损失(按比例缩小了 70%)被添加到整体损失中。目标是解决梯度消失问题并规范网络。然而,后来证明它们的影响相对较小。

谷歌研究人员后来提出了几种 GoogLeNet 架构的变体,包括 Inception-v3 和 Inception-v4,它们使用略有不同的 inception 模块并达到了更好的性能。

VGGNet

ILSVRC 2014 挑战赛的亚军是VGGNet15由牛津大学视觉几何小组 (VGG) 研究实验室的 Karen Simonyan 和 Andrew Zisserman 开发。它有一个非常简单和经典的架构,有 2 或 3 个卷积层和一个池化层,然后是 2 或 3 个卷积层和一个池化层,依此类推(总共只有 16 或 19 个卷积层,具体取决于VGG 变体),加上一个具有 2 个隐藏层和输出层的最终密集网络。它只使用了 3 × 3 个过滤器,但使用了很多过滤器。

ResNet

Kaiming He et al. wonILSVRC 2015 挑战使用残差网络(或ResNet)16提供了惊人的前五名错误率低于 3.6%。获胜的变体使用由 152 层组成的极深 CNN(其他变体有 34、50 和 101 层)。它印证了总体趋势:模型越来越深,参数越来越少。能够训练这样一个深度网络的关键是使用跳过连接(也称为快捷连接):馈入层的信号也被添加到位于堆栈更高一点的层的输出中。让我们看看为什么这很有用。

在训练神经网络时,目标是使其对目标函数h ( x ) 进行建模。如果将输入x添加到网络的输出(即添加跳过连接),则网络将被迫建模f ( x ) = h ( x ) – x而不是h ( x )。这个称为残差学习见图 14-15)。

图 14-15。剩余学习

当你初始化一个常规的神经网络时,它的权重接近于零,所以网络只输出接近于零的值。如果添加跳过连接,则生成的网络只会输出其输入的副本;换句话说,它最初对恒等函数进行建模。如果目标函数非常接近恒等函数(通常是这种情况),这将大大加快训练速度。

此外,如果添加许多跳过连接,即使有几层还没有开始学习,网络也可以开始进步(见图 14-16)。由于跳过连接,信号可以轻松地在整个网络中传播。深度残差网络可以看作一堆残差单元(RU),其中每个残差单元是一个带有跳跃连接的小型神经网络。

图 14-16。常规深度神经网络(左)和深度残差网络(右)

现在让我们看看 ResNet 的架构(见图 14-17)。这非常简单。它的开始和结束与 GoogLeNet 完全一样(除了没有 dropout 层),中间只是一个非常深的简单残差单元堆栈。每个残差单元由两个卷积层(并且没有池化层!)组成,具有批量归一化(BN)和 ReLU 激活,使用 3 × 3 内核并保留空间维度(步幅 1,"same"填充)。

图 14-17。ResNet 架构

请注意,特征图的数量每隔几个残差单元就会增加一倍,同时它们的高度和宽度减半(使用步长为 2 的卷积层)。发生这种情况时,输入不能直接添加到残差单元的输出,因为它们的形状不同(例如,这个问题会影响图 14-17中虚线箭头表示的跳过连接)。为了解决这个问题,输入通过一个 1×1 的卷积层,步长为 2,输出特征图的数量正确(见图 14-18)。

图 14-18。更改特征图大小和深度时跳过连接

ResNet-34 是 34 层的 ResNet(仅计算卷积层和全连接层)17包含 3 个残差单元,输出 64 个特征图,4 个 RU 有 128 个图,6 个 RU 有 256 个图,3 个 RU 有 512 个图. 我们将在本章后面实现这个架构。

比这更深的 ResNet,例如 ResNet-152,使用略有不同的残差单元。他们使用三个卷积层而不是两个 3 × 3 卷积层,比如 256 个特征图:首先是一个 1 × 1 卷积层,只有 64 个特征图(少 4 倍),它充当瓶颈层(如前所述),然后是具有 64 个特征图的 3 × 3 层,最后是另一个具有 256 个特征图(4 倍 64)的 1 × 1 卷积层,用于恢复原始深度。ResNet-152 包含 3 个这样的 RU,输出 256 个地图,然后是 8 个 RU 和 512 个地图,多达 36 个 RU 和 1,024 个地图,最后是 3 个 RU 和 2,048 个地图。

笔记

Google 的Inception-v4 18架构融合了 GoogLeNet 和 ResNet 的思想,在 ImageNet 分类上取得了接近 3% 的 top-5 错误率。

Xception

其他GoogLeNet 架构的变体值得注意:Xception 19(代表Extreme Inception)由 François Chollet(Keras 的作者)于 2016 年提出,它在巨大的视觉任务(3.5 亿张图像和 17,000 张类)。就像 Inception-v4 一样,它融合了 GoogLeNet 和 ResNet 的思想,但是它取代了inception 模块具有一种特殊类型的层,称为深度可分离卷积层(或简称为可分离卷积层20)。这些层以前曾在一些 CNN 架构中使用过,但它们不像在 Xception 架构中那样中心化。虽然常规卷积层使用过滤器尝试同时捕获空间模式(例如,椭圆形)和跨通道模式(例如,嘴 + 鼻子 + 眼睛 = 脸),但可分离卷积层强烈假设空间模式和交叉- 通道模式可以单独建模(见图 14-19)。因此,它由两部分组成:第一部分对每个输入特征图应用一个空间滤波器,然后第二部分专门寻找跨通道模式——它只是一个具有 1×1 滤波器的常规卷积层。

图 14-19。深度可分离卷积层

由于可分离卷积层的每个输入通道只有一个空间滤波器,因此应避免在通道太少的层之后使用它们,例如输入层(当然,这就是图 14-19所表示的,但这只是为了说明目的) . 出于这个原因,Xception 架构从 2 个常规卷积层开始,但随后架构的其余部分仅使用可分离卷积(总共 34 个),加上几个最大池化层和通常的最终层(一个全局平均池化层和一个密集输出层)。

您可能想知道为什么 Xception 被认为是 GoogLeNet 的变体,因为它根本不包含任何初始模块。好吧,正如我们之前讨论的,一个初始模块包含带有 1 × 1 过滤器的卷积层:这些过滤器专门用于跨通道模式。然而,位于它们之上的卷积层是常规的卷积层,它们同时寻找空间和跨通道模式。因此,您可以将初始模块视为常规卷积层(联合考虑空间模式和跨通道模式)和可分离卷积层(分别考虑它们)之间的中间体。在实践中,似乎可分离卷积层通常表现更好。

小费

与常规卷积层相比,可分离卷积层使用更少的参数、更少的内存和更少的计算,并且通常它们甚至表现更好,因此您应该考虑默认使用它们(除了在具有少量通道的层之后)。

ILSVRC 2016 挑战赛由香港中文大学的 CUImage 团队赢得。他们使用了许多不同技术的集合,包括称为GBD-Net的复杂对象检测系统,21以实现低于 3% 的前五名错误率。尽管这个结果无疑令人印象深刻,但解决方案的复杂性与 ResNet 的简单性形成鲜明对比。此外,一年后,另一个相当简单的架构表现得更好,正如我们现在所看到的。

SENet

在 ILSVRC 2017 挑战赛中获胜的架构是Squeeze-and-Excitation Network (SENet)22这种架构扩展了现有架构,例如 inception 网络和 ResNet,并提高了它们的性能。这让 SENet 以惊人的 2.25% 的前五错误率赢得了比赛!初始网络的扩展版本和 ResNet 分别称为SE-InceptionSE-ResNet。提升来自这样一个事实,即 SENet在原始架构中的每个单元(即每个初始模块或每个残差单元)中添加了一个称为SE 块的小型神经网络,如图 14-20所示。

图 14-20。SE-Inception 模块(左)和 SE-ResNet 单元(右)

SE 块分析它所连接的单元的输出,只关注深度维度(它不寻找任何空间模式),并了解哪些特征通常一起最活跃。然后它使用这些信息重新校准特征图,如图 14-21所示。例如,一个 SE 块可能会知道嘴巴、鼻子和眼睛通常一起出现在图片中:如果你看到嘴巴和鼻子,那么你应该期望看到眼睛。因此,如果块在嘴和鼻子特征图中看到强烈的激活,但在眼睛特征图中只有轻微的激活,它将提升眼睛特征图(更准确地说,它将减少不相关的特征图)。如果眼睛与其他东西有些混淆,这种特征图重新校准将有助于解决歧义。

图 14-21。SE 块执行特征图重新校准

SE 块仅由三层组成:全局平均池化层、使用 ReLU 激活函数的隐藏密集层和使用 sigmoid 激活函数的密集输出层(见图 14-22)。

图 14-22。SE 块架构

如前所述,全局平均池化层计算每个特征图的平均激活值:例如,如果它的输入包含 256 个特征图,它将输出 256个数字,代表每个过滤器的整体响应水平。下一层是“挤压”发生的地方:这一层的神经元明显少于 256 个——通常比特征图的数量(例如,16 个神经元)少 16 倍——因此 256 个数字被压缩成一个小向量(例如, 16 个维度)。这是特征响应分布的低维向量表示(即嵌入)。这个瓶颈步骤迫使 SE 块学习特征组合的一般表示(当我们在第 17 章讨论自动编码器时,我们将再次看到这个原理的作用。)。最后,输出层接受嵌入并输出一个重新校准向量,每个特征图包含一个数字(例如,256),每个数字介于 0 和 1 之间。然后将特征图乘以这个重新校准向量,因此不相关的特征(重新校准低分数)被缩小,而相关特征(重新校准分数接近 1)被单独留下。

使用 Keras 实现 ResNet-34 CNN

最多到目前为止描述的 CNN 架构实现起来相当简单(尽管通常你会加载一个预训练的网络,正如我们将看到的那样)。为了说明这个过程,让我们使用 Keras 从头开始​​实现 ResNet-34。首先,让我们创建一个ResidualUnit图层:

class ResidualUnit(keras.layers.Layer):
    def __init__(self, filters, strides=1, activation="relu", **kwargs):
        super().__init__(**kwargs)
        self.activation = keras.activations.get(activation)
        self.main_layers = [
            keras.layers.Conv2D(filters, 3, strides=strides,
                                padding="same", use_bias=False),
            keras.layers.BatchNormalization(),
            self.activation,
            keras.layers.Conv2D(filters, 3, strides=1,
                                padding="same", use_bias=False),
            keras.layers.BatchNormalization()]
        self.skip_layers = []
        if strides > 1:
            self.skip_layers = [
                keras.layers.Conv2D(filters, 1, strides=strides,
                                    padding="same", use_bias=False),
                keras.layers.BatchNormalization()]

    def call(self, inputs):
        Z = inputs
        for layer in self.main_layers:
            Z = layer(Z)
        skip_Z = inputs
        for layer in self.skip_layers:
            skip_Z = layer(skip_Z)
        return self.activation(Z + skip_Z)

如你所见,这段代码与图 14-18非常吻合。在构造函数中,我们创建了所有需要的层:主要层是图表右侧的层,跳过层是左侧的层(仅当步幅大于 1 时才需要)。然后在该call()方法中,我们让输入通过主层和跳过层(如果有的话),然后我们添加两个输出并应用激活函数。

接下来,我们可以使用Sequential模型构建 ResNet-34,因为它实际上只是一个很长的层序列(现在我们有了类,我们可以将每个残差单元视为一个层ResidualUnit):

model = keras.models.Sequential()
model.add(keras.layers.Conv2D(64, 7, strides=2, input_shape=[224, 224, 3],
                              padding="same", use_bias=False))
model.add(keras.layers.BatchNormalization())
model.add(keras.layers.Activation("relu"))
model.add(keras.layers.MaxPool2D(pool_size=3, strides=2, padding="same"))
prev_filters = 64
for filters in [64] * 3 + [128] * 4 + [256] * 6 + [512] * 3:
    strides = 1 if filters == prev_filters else 2
    model.add(ResidualUnit(filters, strides=strides))
    prev_filters = filters
model.add(keras.layers.GlobalAvgPool2D())
model.add(keras.layers.Flatten())
model.add(keras.layers.Dense(10, activation="softmax"))

这段代码中唯一有点棘手的部分是ResidualUnit向模型添加层的循环:如前所述,前 3 个 RU 有 64 个过滤器,接下来的 4 个 RU 有 128 个过滤器,依此类推。然后,当过滤器的数量与之前的 RU 相同时,我们将 stride 设置为 1,否则我们将其设置为 2。然后我们添加ResidualUnit,最后我们更新prev_filters

令人惊讶的是,在不到 40 行代码中,我们就可以构建赢得 ILSVRC 2015 挑战的模型!这展示了 ResNet 模型的优雅和 Keras API 的表现力。实现其他 CNN 架构并不难。然而,Keras 内置了几个这样的架构,那么为什么不使用它们呢?

使用 Keras 的预训练模型

一般来说,您不必手动实现像 GoogLeNet 或 ResNet 这样的标准模型,因为预训练的网络很容易通过keras.applications包中的一行代码获得。例如,您可以使用以下代码行加载在 ImageNet 上预训练的 ResNet-50 模型:

model = keras.applications.resnet50.ResNet50(weights="imagenet")

就这样!这将创建一个 ResNet-50 模型并下载在 ImageNet 数据集上预训练的权重。要使用它,您首先需要确保图像具有正确的大小。ResNet-50 模型需要 224 × 224 像素的图像(其他模型可能需要其他尺寸,例如 299 × 299),所以让我们使用 TensorFlow 的tf.image.resize()函数来调整我们之前加载的图像的大小:

images_resized = tf.image.resize(images, [224, 224])

小费

tf.image.resize()不会保留纵横比。如果这是一个问题,请在调整大小之前尝试将图像裁剪为适当的纵横比。两种操作都可以用tf.image.crop_and_resize().

预训练模型假设图像以特定方式进行预处理。在某些情况下,他们可能期望输入从 0 缩放到 1,或从 –1 缩放到 1,依此类推。每个模型都提供了一个preprocess_input()函数,您可以使用它来预处理您的图像。这些函数假定像素值的范围是 0 到 255,因此我们必须将它们乘以 255(因为之前我们将它们缩放到 0-1 范围):

inputs = keras.applications.resnet50.preprocess_input(images_resized * 255)

现在我们可以使用预训练模型进行预测:

Y_proba = model.predict(inputs)

像往常一样,输出Y_proba是一个矩阵,每个图像一行,每个类一列(在本例中,有 1,000 个类)。如果要显示前K个预测,包括类名和每个预测类的估计概率,请使用该decode_predictions()函数。对于每个图像,它返回一个包含前K个预测的数组,其中每个预测表示为一个包含类标识符、23名称和相应置信度分数的数组:

top_K = keras.applications.resnet50.decode_predictions(Y_proba, top=3)
for image_index in range(len(images)):
    print("Image #{}".format(image_index))
    for class_id, name, y_proba in top_K[image_index]:
        print("  {} - {:12s} {:.2f}%".format(class_id, name, y_proba * 100))
    print()

输出如下所示:

图片#0
  n03877845 - 宫殿 42.87%
  n02825657 - bell_cote 40.57%
  n03781244 - 修道院 14.56%

图片#1
  n04522168 - 花瓶 46.83%
  n07930864 - 杯 7.78%
  n11939491 - 雏菊 4.87%

正确的类(修道院和雏菊)出现在两个图像的前三个结果中。考虑到模型必须从 1,000 个类中进行选择,这非常好。

如您所见,使用预训练模型创建一个非常好的图像分类器非常容易。其他视觉模型可用keras.applications,包括几个 ResNet 变体、GoogLeNet 变体(如 Inception-v3 和 Xception)、VGGNet 变体以及 MobileNet 和 MobileNetV2(用于移动应用程序的轻量级模型)。

但是,如果您想对不属于 ImageNet 的图像类别使用图像分类器怎么办?在这种情况下,您仍然可以从预训练模型中受益,以执行迁移学习。

用于迁移学习的预训练模型

如果如果你想构建一个图像分类器,但没有足够的训练数据,那么重用预训练模型的较低层通常是一个好主意,正如我们在第 11 章中讨论的那样。例如,让我们训练一个模型来对花卉图片进行分类,重用一个预训练的 Xception 模型。首先,让我们使用 TensorFlow Datasets 加载数据集(参见第 13 章):

import tensorflow_datasets as tfds

dataset, info = tfds.load("tf_flowers", as_supervised=True, with_info=True)
dataset_size = info.splits["train"].num_examples # 3670
class_names = info.features["label"].names # ["dandelion", "daisy", ...]
n_classes = info.features["label"].num_classes # 5

请注意,您可以通过设置获取有关数据集的信息with_info=True。在这里,我们得到数据集大小和类的名称。不幸的是,只有一个"train"数据集,没有测试集或验证集,所以我们需要拆分训练集。TF Datasets 项目为此提供了一个 API。例如,让我们将数据集的前 10% 用于测试,接下来的 15% 用于验证,剩下的 75% 用于训练:

test_set_raw, valid_set_raw, train_set_raw = tfds.load(
    "tf_flowers",
    split=["train[:10%]", "train[10%:25%]", "train[25%:]"],
    as_supervised=True)

接下来我们必须对图像进行预处理。CNN 需要 224 × 224 的图像,因此我们需要调整它们的大小。我们还需要通过 Xception 的preprocess_input()函数运行图像:

def preprocess(image, label):
    resized_image = tf.image.resize(image, [224, 224])
    final_image = keras.applications.xception.preprocess_input(resized_image)
    return final_image, label

让我们将此预处理功能应用于所有三个数据集,打乱训练集,并为所有数据集添加批处理和预取:

batch_size = 32
train_set = train_set.shuffle(1000)
train_set = train_set.map(preprocess).batch(batch_size).prefetch(1)
valid_set = valid_set.map(preprocess).batch(batch_size).prefetch(1)
test_set = test_set.map(preprocess).batch(batch_size).prefetch(1)

如果要执行一些数据增强,请更改训练集的预处理函数,向训练图像添加一些随机变换。例如,用于tf.image.random_crop()随机裁剪图像,用于tf.image.random_flip_left_right()随机水平翻转图像,等等(有关示例,请参见笔记本的“用于迁移学习的预训练模型”部分)。

小费

该类keras.preprocessing.image.ImageDataGenerator可以轻松地从磁盘加载图像并以各种方式增强它们:您可以移动每个图像,旋转它,重新缩放它,水平或垂直翻转它,剪切它,或者应用任何你想要的转换函数。这对于简单的项目非常方便。但是,构建 tf.data 管道有很多优点:它可以从任何源(例如,并行)高效地读取图像,而不仅仅是本地磁盘;您可以随心所欲地操作Dataset;如果你编写一个基于tf.image操作的预处理函数,这个函数既可以在 tf.data 管道中使用,也可以在你将部署到生产环境的模型中使用(参见第 19 章)。

接下来让我们加载一个在 ImageNet 上预训练的 Xception 模型。我们通过设置排除网络顶部include_top=False:这不包括全局平均池化层和密集输出层。然后,我们根据基本模型的输出添加我们自己的全局平均池化层,然后是一个每类一个单元的密集输出层,使用softmax 激活函数。最后,我们创建 Keras Model

base_model = keras.applications.xception.Xception(weights="imagenet",
                                                  include_top=False)
avg = keras.layers.GlobalAveragePooling2D()(base_model.output)
output = keras.layers.Dense(n_classes, activation="softmax")(avg)
model = keras.Model(inputs=base_model.input, outputs=output)

第 11 章所述,冻结预训练层的权重通常是一个好主意,至少在训练开始时是这样:

for layer in base_model.layers:
    layer.trainable = False

笔记

由于我们的模型直接使用基础模型的层,而不是base_model对象本身,因此设置base_model.trainable=False将不起作用。

最后,我们可以编译模型并开始训练:

optimizer = keras.optimizers.SGD(lr=0.2, momentum=0.9, decay=0.01)
model.compile(loss="sparse_categorical_crossentropy", optimizer=optimizer,
              metrics=["accuracy"])
history = model.fit(train_set, epochs=5, validation_data=valid_set)

警告

这会很慢,除非你有 GPU。如果你不这样做,那么你应该在 Colab 中使用 GPU 运行时运行本章的 notebook(它是免费的!)。请参阅GitHub - ageron/handson-ml2: A series of Jupyter notebooks that walk you through the fundamentals of Machine Learning and Deep Learning in Python using Scikit-Learn, Keras and TensorFlow 2.上的说明。

在对模型进行了几个 epoch 的训练后,其验证准确率应该会达到 75-80% 左右,并且不会取得太大进展。这意味着顶层现在训练得很好,所以我们准备解冻所有层(或者你可以尝试只解冻顶层)并继续训练(不要忘记在冻结或解冻层时编译模型)。这次我们使用低得多的学习率来避免损坏预训练的权重:

for layer in base_model.layers:
    layer.trainable = True

optimizer = keras.optimizers.SGD(lr=0.01, momentum=0.9, decay=0.001)
model.compile(...)
history = model.fit(...)

这需要一段时间,但这个模型应该在测试集上达到 95% 左右的准确率。有了它,你就可以开始训练惊人的图像分类器了!但计算机视觉不仅仅是分类。例如,如果您还想知道花在图片中的位置怎么办?现在让我们看看这个。

分类和本地化

本地化图片中的对象可以表示为回归任务,如第 10 章所述:预测对象周围的边界框,一种常见的方法是预测对象中心的水平和垂直坐标,以及它的高度和宽度。这意味着我们有四个数字要预测。它不需要对模型进行太多更改;我们只需要添加具有四个单元的第二个密集输出层(通常在全局平均池化层之上),并且可以使用 MSE 损失对其进行训练:

base_model = keras.applications.xception.Xception(weights="imagenet",
                                                  include_top=False)
avg = keras.layers.GlobalAveragePooling2D()(base_model.output)
class_output = keras.layers.Dense(n_classes, activation="softmax")(avg)
loc_output = keras.layers.Dense(4)(avg)
model = keras.Model(inputs=base_model.input,
                    outputs=[class_output, loc_output])
model.compile(loss=["sparse_categorical_crossentropy", "mse"],
              loss_weights=[0.8, 0.2], # depends on what you care most about
              optimizer=optimizer, metrics=["accuracy"])

但是现在我们遇到了一个问题:花朵数据集没有围绕花朵的边界框。所以,我们需要自己添加它们。这通常是机器学习项目中最困难和最昂贵的部分之一:获取标签。花时间寻找合适的工具是个好主意。要使用边界框注释图像,您可能需要使用开源图像标记工具,如 VGG Image Annotator、LabelImg、OpenLabeler 或 ImgLab,或者可能需要使用商业工具,如 LabelBox 或 Supervisely。如果您有大量图像要注释,您可能还需要考虑众包平台,例如 Amazon Mechanical Turk。但是,搭建一个众包平台,准备表格发给工人,监督他们,确保他们生产的包围盒质量好,是相当多的工作,因此,请确保值得付出努力。如果只有几千张图像要标记,并且您不打算经常这样做,那么最好自己做。阿德里安娜科瓦什卡等人。写了一个很实用的关于计算机视觉众包的论文24 。我建议您检查一下,即使您不打算使用众包。

假设您已经获得了鲜花数据集中每个图像的边界框(现在我们假设每个图像都有一个边界框)。然后,您需要创建一个数据集,其项目将是成批的预处理图像及其类标签和边界框。每个项目应该是表单的一个元组(images, (class_labels, bounding_boxes))。然后你就可以训练你的模型了!

小费

边界框应该被归一化,以便水平和垂直坐标以及高度和宽度都在 0 到 1 的范围内。此外,通常预测高度和宽度的平方根而不是高度和宽度直接:这样,大边界框的 10 像素错误不会像小边界框的 10 像素错误那样受到惩罚。

MSE 通常作为训练模型的成本函数效果很好,但它并不是评估模型预测边界框的好坏的一个很好的指标。最常见的度量是联合交集(IoU):预测边界框和目标边界框之间的重叠面积除以它们的联合面积(见图14-23)。在 tf.keras 中,它是由tf.keras.metrics.MeanIoU类实现的。

图 14-23。边界框的交并联合 (IoU) 度量

对单个对象进行分类和定位很不错,但是如果图像包含多个对象(花朵数据集中经常出现这种情况)怎么办?

物体检测

对图像中的多个对象进行分类和定位的任务称为对象检测。直到几年前,一种常见的方法是采用经过训练的 CNN 对单个对象进行分类和定位,然后将其滑过图像,如图 14-24所示. 在这个例子中,图像被切割成 6 × 8 的网格,我们展示了一个 CNN(厚黑色矩形)在所有 3 × 3 区域上滑动。当 CNN 查看图像的左上角时,它检测到最左边玫瑰的一部分,然后当它第一次向右移动一步时,它再次检测到同一朵玫瑰。在下一步,它开始检测最上面的玫瑰的一部分,然后一旦它向右移动了一步,它就会再次检测到它。然后,您将继续在整个图像中滑动 CNN,查看所有 3 × 3 区域。此外,由于对象可以有不同的大小,您还可以在不同大小的区域之间滑动 CNN。例如,一旦您完成了 3 × 3 区域,您可能还希望在所有 4 × 4 区域上滑动 CNN。

图 14-24。通过在图像上滑动 CNN 来检测多个对象

这种技术相当简单,但正如您所见,它会在稍微不同的位置多次检测到同一个对象。然后需要进行一些后处理来消除所有不必要的边界框。一个对此的常用方法称为非最大抑制。这是你如何做到的:

  1. 首先你需要向你的 CNN 添加一个额外的objectness输出,以估计图像中确实存在花的概率(或者,你可以添加一个“no-flower”类,但这通常也不起作用)。它必须使用 sigmoid 激活函数,你可以使用二元交叉熵损失来训练它。然后去掉所有对象分数低于某个阈值的边界框:这将丢弃所有实际上不包含花的边界框。

  2. 找到objectness score最高的bounding box,并去掉所有其他与它重叠很多的bounding box(例如,IoU大于60%)。例如,在图 14-24中,objectness score 最大的边界框是最上面玫瑰上方的厚边界框(objectness score 由边界框的厚度表示)。同一朵玫瑰上的另一个边界框与最大边界框重叠很多,所以我们将去掉它。

  3. 重复第二步,直到没有更多的边界框可以摆脱。

这种简单的目标检测方法效果很好,但需要多次运行 CNN,因此速度很慢。幸运的是,有一种更快的方法可以在图像上滑动 CNN:使用完全卷积网络(FCN)。

全卷积网络

这个想法FCNs 最早是在Jonathan Long 等人2015 年的一篇论文25中引入的,用于语义分割(根据图像所属对象的类别对图像中的每个像素进行分类的任务)。作者指出,您可以用卷积层替换 CNN 顶部的密集层。为了理解这一点,让我们看一个例子:假设有 200 个神经元的密集层位于输出 100 个特征图的卷积层之上,每个特征图大小为 7 × 7(这是特征图大小,而不是内核大小)。每个神经元将计算来自卷积层的所有 100 × 7 × 7 激活的加权和(加上一个偏置项)。现在让我们看看如果我们使用 200 个过滤器(每个大小为 7 × 7)将密集层替换为卷积层会发生什么,并且使用"valid"填充。该层将输出 200 个特征图,每个 1 × 1(因为内核正是输入特征图的大小,并且我们使用了"valid"填充)。换句话说,它会输出 200 个数字,就像密集层一样;如果您仔细观察卷积层执行的计算,您会注意到这些数字将与密集层产生的数字完全相同。唯一的区别是密集层的输出是一个形状为 [ batch size , 200] 的张量,而卷积层将输出一个形状为 [ batch size , 1, 1, 200] 的张量。

小费

要将密集层转换为卷积层,卷积层中的过滤器数量必须等于密集层中的单元数量,过滤器大小必须等于输入特征图的大小,并且必须使用"valid"填充。正如我们稍后将看到的,步幅可以设置为 1 或更大。

为什么这很重要?好吧,虽然密集层需要特定的输入大小(因为它对每个输入特征有一个权重),但卷积层会很乐意处理任何大小为26的图像(但是,它确实希望其输入具有特定数量的通道,因为每个内核为每个输入通道包含一组不同的权重)。由于 FCN 仅包含卷积层(和具有相同属性的池化层),因此可以在任何大小的图像上进行训练和执行!

例如,假设我们已经训练了一个用于花卉分类和定位的 CNN。它在 224 × 224 图像上进行训练,输出 10 个数字:输出 0 到 4 通过softmax 激活函数,这给出了类概率(每个类一个);输出 5 通过逻辑激活函数发送,这给出了客观性分数;输出 6 到 9 不使用任何激活函数,它们表示边界框的中心坐标,以及它的高度和宽度。我们现在可以将其密集层转换为卷积层。事实上,我们甚至不需要重新训练它;我们可以将权重从密集层复制到卷积层!或者,我们可以在训练之前将 CNN 转换为 FCN。

现在假设当网络输入 224 × 224 图像时,输出层(也称为瓶颈层)之前的最后一个卷积层输出 7 × 7 特征图(见图 14-25的左侧)。如果我们向 FCN 提供 448 × 448 的图像(见图 14-25的右侧),瓶颈层现在将输出 14 × 14 的特征图。27由于密集输出层被卷积层取代,卷积层使用 10 个大小为 7 × 7 的滤波器,其中"valid"padding 和 stride 1,输出将由 10 个特征图组成,每个特征图大小为 8 × 8(因为 14 – 7 + 1 = 8)。换句话说,FCN 将只处理整个图像一次,它会输出一个 8 × 8 的网格,其中每个单元格包含 10 个数字(5 个类别概率、1 个对象得分和 4 个边界框坐标)。这就像使用原始 CNN 并使用每行 8 步和每列 8 步在图像上滑动它一样。为了可视化这一点,想象一下将原始图像切成 14 × 14 的网格,然后在该网格上滑动一个 7 × 7 的窗口;窗口将有 8 × 8 = 64 个可能的位置,因此有 8 × 8 个预测。然而,FCN 方法效率更高,因为网络只查看图像一次。事实上,你只看一次(YOLO) 是一种非常流行的对象检测架构的名称,我们将在接下来介绍它。

图 14-25。处理小图像(左)和大图像(右)的同一个全卷积网络

你只看一次 (YOLO)

优洛是 Joseph Redmon 等人提出的一种极其快速和准确的目标检测架构。在2015 年的一篇论文中,28和随后在 2016 年29(YOLOv2)和2018 年30(YOLOv3)中得到了改进。正如 Redmon 的演示中所见,它是如此之快,以至于它可以在视频上实时运行。

YOLOv3 的架构与我们刚刚讨论的非常相似,但有一些重要的区别:

  • 它为每个网格单元输出五个边界框(而不仅仅是一个),每个边界框都带有一个对象性分数。它还为每个网格单元输出 20 个类别概率,因为它是在包含 20 个类别的 PASCAL VOC 数据集上进行训练的。每个网格单元总共有 45 个数字:5 个边界框,每个边界框有 4 个坐标,加上 5 个对象分数,加上 20 个类别概率。

  • YOLOv3 不是预测边界框中心的绝对坐标,而是预测相对于网格单元坐标的偏移量,其中 (0, 0) 表示该单元格的左上角, (1, 1) 表示右下角。对于每个网格单元,YOLOv3 被训练为仅预测其中心位于该单元中的边界框(但边界框本身通常远远超出网格单元)。YOLOv3 将逻辑激活函数应用于边界框坐标,以确保它们保持在 0 到 1 的范围内。

  • 在训练神经网络之前,YOLOv3 找到五个有代表性的边界框尺寸,称为锚框(或边界框先验)。它通过应用 K-Means 算法来做到这一点(见第 9 章)) 到训练集边界框的高度和宽度。例如,如果训练图像包含许多行人,则其中一个锚框可能具有典型行人的尺寸。然后当神经网络预测每个网格单元五个边界框时,它实际上预测了每个锚框要重新缩放多少。例如,假设一个锚框高 100 像素,宽 50 像素,并且网络预测,例如,垂直缩放因子为 1.5,水平缩放因子为 0.9(对于其中一个网格单元)。这将产生大小为 150 × 45 像素的预测边界框。更准确地说,对于每个网格单元和每个锚框,网络都会预测垂直和水平缩放因子的对数。

  • 网络使用不同尺度的图像进行训练:在训练过程中每隔几批,网络随机选择一个新的图像尺寸(从 330 × 330 到 608 × 608 像素)。这允许网络学习检测不同尺度的对象。此外,它可以在不同的尺度上使用 YOLOv3:较小的尺度会不太准确,但比较大的尺度更快,因此您可以为您的用例选择正确的权衡。

还有一些您可能感兴趣的创新,例如使用跳过连接来恢复 CNN 中丢失的一些空间分辨率(我们将在稍后讨论语义分割时对此进行讨论)。在 2016 年的论文中,作者介绍了使用层次分类的 YOLO9000 模型:模型预测称为WordTree的视觉层次结构中每个节点的概率。这使得网络可以高度自信地预测图像代表例如狗,即使它不确定是什么特定类型的狗。我鼓励您继续阅读所有三篇论文:它们读起来非常愉快,并且提供了如何逐步改进深度学习系统的优秀示例。

平均精度 (MAP)

一个对象检测任务中使用的非常常见的指标是平均平均精度(mAP)。“平均平均值”听起来有点多余,不是吗?为了理解这个指标,让我们回到我们在第 3 章讨论过的两个分类指标:精度和召回率。记住权衡:召回率越高,精度越低。您可以在精确率/召回率曲线中对此进行可视化(参见图 3-5)。要将这条曲线总结为一个数字,我们可以计算其曲线下面积 (AUC)。但请注意,准确率/召回率曲线可能包含一些部分,当召回率增加时,准确率实际上会上升,尤其是在召回率较低的情况下(您可以在图 3-5的左上角看到这一点))。这是 mAP 指标的动机之一。

假设分类器在 10% 的召回率下有 90% 的准确率,但在 20% 的召回率下有 96% 的准确率。这里真的没有权衡:在 20% 的召回率而不是 10% 的召回率下使用分类器更有意义,因为您将获得更高的召回率和更高的精度。因此,与其关注 10% 召回率的精度,我们应该真正关注分类器可以提供至少10% 召回率最大精度。这将是 96%,而不是 90%。因此,对模型性能有一个公平了解的一种方法是计算在至少 0% 召回率、10% 召回率、20% 直至 100% 的情况下可以获得的最大精度,然后计算平均值这些最大精度。这个称为平均精度(AP) 指标。现在当有两个以上的类时,我们可以计算每个类的 AP,然后计算平均 AP (mAP)。而已!

在对象检测系统中,还有一个额外的复杂度:如果系统检测到了正确的类别,但在错误的位置(即边界框完全关闭)怎么办?当然,我们不应将此视为积极的预测。一种方法是定义 IOU 阈值:例如,我们可以认为只有当 IOU 大于 0.5 并且预测的类别正确时,预测才是正确的。对应的mAP一般记为mAP@0.5(或mAP@50%,或者有时只是AP 50)。在某些比赛中(例如 PASCAL VOC 挑战赛),就是这样做的。在其他(例如 COCO 竞赛)中,mAP 是针对不同的 IOU 阈值(0.50、0.55、0.60、...、0.95)计算的,最终的度量是所有这些 mAP 的平均值(记为 mAP@[.50:. 95] 或 mAP@[.50:0.05:.95])。是的,这是平均平均数。

GitHub 上提供了几个使用 TensorFlow 构建的 YOLO 实现。特别是,请查看Zihao Zang 的 TensorFlow 2 implementation。TensorFlow Models 项目中提供了其他对象检测模型,其中许多具有预训练的权重;有些甚至被移植到了 TF Hub,比如SSD 31Faster-RCNN32都非常流行。SSD 也是一种“单次”检测模型,类似于 YOLO。Faster R-CNN 更复杂:图像首先经过一个 CNN,然后输出被传递到区域建议网络(RPN),该网络建议最有可能包含对象的边界框,并根据 CNN 的裁剪输出为每个边界框运行分类器。

检测系统的选择取决于许多因素:速度、准确性、可用的预训练模型、训练时间、复杂性等。论文包含指标表,但测试环境存在很大的可变性,技术也在不断发展很快,很难做出一个公平的比较,这对大多数人来说都是有用的,并且在几个月内仍然有效。

因此,我们可以通过在对象周围绘制边界框来定位对象。伟大的!但也许你想更精确一点。让我们看看如何下到像素级别。

语义分割

 语义分割像素根据其所属对象的类别(如道路、汽车、行人、建筑物等)进行分类,如图14-26所示。请注意,区分同一类的不同对象。例如,分割图像右侧的所有自行车最终都是一大块像素。这项任务的主要困难在于,当图像通过常规 CNN 时,它们会逐渐失去其空间分辨率(由于跨度大于 1 的层);所以,一个普通的 CNN 可能最终会知道图像左下角的某个地方有一个人,但它不会比这更精确。

就像对象检测一样,有许多不同的方法可以解决这个问题,有些方法相当复杂。然而,Jonathan Long 等人在 2015 年的论文中提出了一个相当简单的解决方案。我们之前讨论过。作者首先采用预训练的 CNN 并将其转换为 FCN。CNN 对输入图像应用了 32 的整体步幅(即,如果将所有步幅加起来大于 1),这意味着最后一层输出的特征图比输入图像小 32 倍。这显然太粗糙了,因此他们添加了一个将分辨率乘以 32 的上采样层。

图 14-26。语义分割

有几种可用于上采样(增加图像大小)的解决方案,例如双线性插值,但仅在高达 ×4 或 ×8 的情况下工作得相当好。相反,他们使用转置卷积层33相当于先通过插入空行和空列(全为零)来拉伸图像,然后执行常规卷积(见图 14-27)。或者,有些人更愿意将其视为使用分数步幅的常规卷积层(例如,图 14-27中的 1/2 )。转置卷积层可以初始化以执行接近线性插值的操作,但由于它是可训练的层,它会在训练期间学习做得更好。在 tf.keras 中,您可以使用该Conv2DTranspose层。

图 14-27。使用转置卷积层进行上采样

笔记

在转置卷积层中,步幅定义了输入将被拉伸多少,而不是滤波器步长的大小,因此步幅越大,输出越大(与卷积层或池化层不同)。

TENSORFLOW 卷积运算

TensorFlow还提供了一些其他类型的卷积层:

keras.layers.Conv1D

为一维输入创建卷积层,例如时间序列或文本(字母或单词的序列),我们将在第 15 章中看到。

keras.layers.Conv3D

为 3D 输入创建卷积层,例如 3D PET 扫描。

dilation_rate

dilation_rate任何卷积层的超参数设置为 2 或更大的值会创建一个à-trous 卷积层(“à trous”是法语中“有孔”的意思)。这等效于使用带有通过插入零的行和列(即孔)来扩展的过滤器的常规卷积层。例如,一个等于 1 × 3 的过滤器[[1,2,3]]可能会以4 的膨胀率进行膨胀,从而产生 的膨胀过滤[[1, 0, 0, 0, 2, 0, 0, 0, 3]]。这让卷积层在没有计算成本并且不使用额外参数的情况下拥有更大的感受野。

tf.nn.depthwise_conv2d()

可用于创建深度卷积层(但您需要自己创建变量)。它独立地将每个过滤器应用于每个单独的输入通道。因此,如果有n 个过滤器和n ' 输入通道,那么这将输出n × n '特征图。

这个解决方案是可以的,但仍然太不精确。为了做得更好,作者添加了来自较低层的跳过连接:例如,他们将输出图像上采样了 2 倍(而不是 32 倍),并且他们添加了具有这种双分辨率的较低层的输出。然后他们将结果上采样了 16 倍,总上采样因子为 32(见图 14-28)。这恢复了早期池化层中丢失的一些空间分辨率。在他们最好的架构中,他们使用了第二个类似的跳过连接来从更低的层恢复更精细的细节。简而言之,原始CNN的输出经过以下额外步骤:upscale×2,添加较低层(适当规模)的输出,upscale×2,添加更底层的输出,最后upscale ×8。甚至可以放大到超过原始图像的大小:这可以用来增加图像的分辨率,这是一种称为超分辨率的技术。

图 14-28。跳过层从较低层恢复一些空间分辨率

再次,许多 GitHub 存储库提供语义分割的 TensorFlow 实现(现在是 TensorFlow 1),您甚至可以在 TensorFlow 模型项目中找到预训练的实例分割模型。实例分割类似于语义分割,但不是将同一类的所有对象合并为一个大块,而是将每个对象与其他对象区分开来(例如,它识别每辆自行车)。现在,TensorFlow 模型项目中可用的实例分割模型基于Mask R-CNN架构,该架构在2017 年的一篇论文中提出:34它通过为每个边界框额外生成像素掩码来扩展 Faster R-CNN 模型。因此,您不仅可以获得每个对象周围的边界框,以及一组估计的类概率,而且您还可以获得一个像素掩码,用于定位边界框中属于该对象的像素。

如您所见,深度计算机视觉领域广阔且发展迅速,每年都会涌现出各种基于卷积神经网络的架构。这短短几年内取得的进展令人震惊,研究人员现在正专注于越来越难的问题,例如对抗性学习(试图使网络更能抵抗旨在欺骗它的图像)、可解释性(了解网络为什么一个特定的分类)、逼真的图像生成(我们将在第 17 章中再次讨论)和单次学习(一个可以在对象只看到一次后识别它的系统)。有些人甚至探索了全新的架构,例如 Geoffrey Hinton 的胶囊网络35(我在几个视频中介绍了它们),在笔记本中带有相应的代码)。现在进入下一章,我们将了解如何使用循环神经网络和卷积神经网络处理时序数据,例如时间序列。

练习

  1. 在图像分类方面,CNN 与全连接 DNN 相比有哪些优势?

  2. 考虑一个由三个卷积层组成的 CNN,每个卷积层有 3 × 3 的内核、步长为 2 和"same"填充。最低层输出 100 个特征图,中间层输出 200 个,顶层输出 400 个。输入图像是 200×300 像素的 RGB 图像。

    CNN 中的参数总数是多少?如果我们使用 32 位浮点数,那么在对单个实例进行预测时,该网络至少需要多少 RAM?在小批量 50 张图像上进行训练时会怎样?

  3. 如果您的 GPU 在训练 CNN 时内存不足,您可以尝试解决哪五件事?

  4. 为什么要添加最大池化层而不是具有相同步幅的卷积层?

  5. 您什么时候想添加本地响应规范化层?

  6. 与 LeNet-5 相比,您能说出 AlexNet 的主要创新吗?GoogLeNet、ResNet、SENet 和 Xception 的主要创新是什么?

  7. 什么是全卷积网络?如何将密集层转换为卷积层?

  8. 语义分割的主要技术难点是什么?

  9. 从头开始构建您自己的 CNN,并尝试在 MNIST 上实现尽可能高的准确度。

  10. 使用迁移学习进行大图像分类,通过以下步骤:

    1. 创建一个训练集,每个类至少包含 100 个图像。例如,您可以根据位置(海滩、山脉、城市等)对自己的图片进行分类,或者您可以使用现有数据集(例如,来自 TensorFlow Datasets)。

    2. 将其拆分为训练集、验证集和测试集。

    3. 构建输入管道,包括适当的预处理操作,并可选择添加数据增强。

    4. 在此数据集上微调预训练模型。

  11. 浏览 TensorFlow 的风格迁移教程。这是一种使用深度学习生成艺术的有趣方式。

附录 A中提供了这些练习的解决方案。

1David H. Hubel,“无拘无束的猫的纹状皮层中的单单位活动” ,生理学杂志147(1959):226–238。

2David H. Hubel 和 Torsten N. Wiesel,“猫纹状皮层中单个神经元的感受野” ,生理学杂志148 (1959): 574–591。

3David H. Hubel 和 Torsten N. Wiesel,“猴子纹状皮层的感受野和功能结构” ,生理学杂志195 (1968): 215–243。

4Kunihiko Fukushima,“Neocognitron:一种不受位置变化影响的模式识别机制的自组织神经网络模型”,生物控制论36(1980):193-202。

5Yann LeCun 等人,“应用于文档识别的基于梯度的学习” ,IEEE 86 会议论文集,第 3 期。11 (1998): 2278–2324。

6卷积是一种数学运算,它将一个函数滑到另一个函数上并测量它们的逐点乘法的积分。它与傅里叶变换和拉普拉斯变换有很深的联系,并在信号处理中大量使用。卷积层实际上使用互相关,这与卷积非常相似(有关更多详细信息,请参见https://homl.info/76)。

7具有 150 × 100 个神经元的全连接层,每个神经元连接到所有 150 × 100 × 3 个输入,将有 1502 × 1002× 3 = 6.75 亿个参数!

8在国际单位制 (SI) 中,1 MB = 1,000 KB = 1,000 × 1,000 字节 = 1,000 × 1,000 × 8 位。

9到目前为止我们讨论过的其他内核都有权重,但池化内核没有:它们只是无状态的滑动窗口。

10Yann LeCun 等人,“应用于文档识别的基于梯度的学习” ,IEEE 86 会议论文集,第 3 期。11 (1998): 2278–2324。

11Alex Krizhevsky 等人,“使用深度卷积神经网络的 ImageNet 分类”,_第 25 届神经信息处理系统国际会议论文集1(2012 年):1097-1105。

12Matthew D. Zeiler 和 Rob Fergus,“可视化和理解卷积网络” ,欧洲计算机视觉会议论文集(2014):818-833。

13Christian Szegedy 等人,“Going Deeper with Convolutions” ,IEEE 计算机视觉和模式识别会议论文集(2015 年):1-9。

142010年的电影《盗梦空间》中,人物不断深入多层梦境;因此这些模块的名称。

15Karen Simonyan 和 Andrew Zisserman,“用于大规模图像识别的非常深的卷积网络”,arXiv 预印本 arXiv:1409.1556 (2014)。

16Kaiming He 等人,“用于图像识别的深度残差学习”,arXiv 预印本 arXiv:1512:03385 (2015)。

17在描述神经网络时,只计算带参数的层是一种常见的做法。

18Christian Szegedy 等人,“Inception-v4、Inception-ResNet 和残差连接对学习的影响”,arXiv 预印本 arXiv:1602.07261 (2016)。

19François Chollet,“Xception:深度可分离卷积的深度学习”,arXiv 预印本 arXiv:1610.02357 (2016)。

20这个名称有时可能是模棱两可的,因为空间上可分离的卷积通常也被称为“可分离的卷积”。

21Xingyu Zeng 等人,“Crafting GBD-Net for Object Detection”,IEEE Transactions on Pattern Analysis and Machine Intelligence 40,不。9 (2018): 2109–2123。

22Jie Hu 等人,“Squeeze-and-Excitation Networks” ,IEEE 计算机视觉和模式识别会议论文集(2018 年):7132-7141。

23在 ImageNet 数据集中,每个图像都与WordNet 数据集中的一个词相关联:类 ID 只是一个 WordNet ID。

24Adriana Kovashka 等人,“计算机视觉中的众包” ,计算机图形和视觉的基础和趋势10,第 10 期。3 (2014): 177–243。

25Jonathan Long 等人,“用于语义分割的完全卷积网络” ,IEEE 计算机视觉和模式识别会议论文集(2015 年):3431-3440。

26有一个小例外:"valid"如果输入大小小于内核大小,使用填充的卷积层会报错。

27这假设我们"same"在网络中只使用了填充:实际上,"valid"填充会减小特征图的大小。此外,448 可以整齐地除以 2 几次,直到我们达到 7,没有任何舍入误差。如果任何层使用的步幅与 1 或 2 不同,则可能存在一些舍入误差,因此特征图可能最终会变小。

28Joseph Redmon 等人,“You Only Look Once:Unified, Real-Time Object Detection” ,IEEE 计算机视觉和模式识别会议论文集(2016 年):779–788。

29Joseph Redmon 和 Ali Farhadi,“YOLO9000:更好、更快、更强” ,IEEE 计算机视觉和模式识别会议论文集(2017 年):6517–6525。

30Joseph Redmon 和 Ali Farhadi,“YOLOv3:增量改进”,arXiv 预印本 arXiv:1804.02767 (2018)。

31Wei Liu 等人,“SSD:Single Shot Multibox Detector” ,第 14 届欧洲计算机视觉会议论文集1(2016 年):21-37。

32Shaoqing Ren 等人,“Faster R-CNN:Towards Real-Time Object Detection with Region Proposal Networks” ,第 28 届神经信息处理系统国际会议论文集1(2015):91-99。

33这种类型的层有时被称为反卷积层,但它不执行数学家所说的反卷积,因此应避免使用此名称。

34Kaiming He 等人,“Mask R-CNN”,arXiv 预印本 arXiv:1703.06870 (2017)。

35Geoffrey Hinton 等人,“带有 EM 路由的矩阵胶囊” ,国际学习表示会议论文集(2018 年)。

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sonhhxg_柒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值