一、说明
图 1:使用 CNN 运行图像分割的结果。按从上到下的顺序,输入图像、地面实况分割掩码、预测分割掩码。
二、文章大纲
在本文中,我们将实现一个名为SegNet的基于卷积神经网络(CNN)的架构,它将输入图像中的每个像素分配给相应的宠物,如猫或狗。不属于任何宠物的像素将被归类为背景像素。我们将使用 PyTorch 在牛津宠物数据集上构建和训练此模型,以了解交付成功的图像分割任务所需的条件。模型构建过程将是动手操作的,我们将详细讨论模型中每一层的作用。本文将包含大量对研究论文和文章的参考,以供进一步学习。
在本文中,我们将引用此笔记本中的代码和结果。如果要重现结果,则需要 GPU 来确保笔记本在合理的时间内完成运行。
三、本系列文章
本系列面向所有深度学习经验水平的读者。如果您想了解深度学习和视觉AI的实践以及一些扎实的理论和实践经验,那么您来对地方了!这将是一个由 4 部分组成的系列,包含以下文章:
- 概念和想法
- 基于 CNN 的模型(本文)
- 深度可分离卷积
- 基于视觉变压器的模型
让我们从对卷积层和其他一些通常一起用作卷积块的层的简短介绍开始讨论。
四、Conv-BatchNorm-ReLU 和 Max Pooling/Unpooling
卷积、批量归一化、ReLU 块是视觉 AI 的三位一体。你会看到它经常与基于 CNN 的视觉 AI 模型一起使用。这些术语中的每一个都代表在 PyTorch 中实现的不同层。卷积层负责对输入张量执行学习过滤器的互相关操作。批量归一化将批次中的元素居中为零均值和单位方差,ReLU 是一种非线性激活函数,仅保留输入中的正值。
典型的CNN随着层的堆叠而逐渐减小输入空间维度。下一节将讨论缩小空间维度背后的动机。这种减少是通过使用简单的函数(如最大值或平均值)汇集相邻值来实现的。我们将在最大池化部分进一步讨论这个问题。在分类问题中,转换-BN-ReLU-池块堆栈后跟一个分类头,用于预测输入属于目标类之一的概率。某些问题集(如语义分割)需要按像素进行预测。对于这种情况,在下采样块之后附加一堆上采样块,以将其输出投影到所需的空间维度。上采样块只不过是 Conv-BN-ReLU-Unpool 块,它们用非池化层替换池化层。我们将在最大池化部分详细讨论取消池化。
现在,让我们进一步阐述卷积层背后的动机。
五、卷积
卷积是视觉 AI 模型的基本构建块。它们在计算机视觉中大量使用,历史上一直用于实现视觉转换,例如:
- 边缘检测
- 图像模糊和锐化
- 凹凸
- 强化
卷积运算是两个矩阵的元素乘法和聚合。卷积操作示例如图 2 所示。
图 2:卷积操作的图示。来源:作者
在深度学习上下文中,卷积是在称为过滤器的 n 维参数矩阵或较大输入上的内核之间进行的。这是通过在输入上滑动滤波器并将卷积应用于相应部分来实现的。幻灯片的范围是使用步幅参数配置的。一步意味着内核滑过一步以执行下一部分。与使用固定滤波器的传统方法相反,深度学习使用反向传播从数据中学习过滤器。
那么卷积如何帮助深度学习呢?
在深度学习中,卷积层用于检测视觉特征。典型的CNN模型包含一堆这样的层。堆栈中的底层检测简单的特征,例如线条和边缘。当我们在堆栈中向上移动时,这些层会检测到越来越复杂的特征。堆栈中的中间层检测线条和边缘的组合,顶层检测复杂的形状,如汽车、面部或飞机。图 3 直观地显示了经过训练的模型的顶层和底层的输出。
图 3:卷积过滤器学习识别的内容。来源:卷积深度信念网络,用于分层表示的可扩展无监督学习
卷积层具有一组可学习的过滤器,这些过滤器作用于输入中的小区域,以为每个区域生成具有代表性的输出值。例如,3x3 筛选器在 3x3 大小的区域上运行,并生成代表该区域的值。在输入区域重复应用滤波器会产生一个输出,该输出成为堆栈中下一层的输入。直观地说,较高的层可以“看到”输入的更大区域。例如,第二个卷积层中的 3x3 滤波器对第一个卷积层的输出进行操作,其中每个单元都包含有关输入中 3x3 大小区域的信息。如果我们假设 stride=1 的卷积操作,那么第二层中的过滤器将“看到”原始输入的 5x5 大小区域。这称为卷积的感受野。卷积层的重复应用逐渐减小了输入图像的空间维度,并增加了滤波器的视野,使它们能够“看到”复杂的形状。图 4 显示了卷积网络对一维输入的处理。输出层中的元素代表相对较大的输入块。
图 4:内核大小 = 1 的一维卷积的感受野,应用 3 次。假设步幅=3 且没有填充。在卷积核连续第三次应用后,单个像素能够在原始输入图像中看到 1 个像素。来源:作者
一旦卷积层可以检测到这些对象并能够生成它们的表示,我们就可以使用这些表示进行图像分类、图像分割以及对象检测和定位。从广义上讲,CNN遵循以下一般原则:
- 卷积层要么保持输出通道©的数量不变,要么将其加倍。
- 它使用 步幅=1 保持空间维度不变,或使用步幅=2 将其减少到一半。
- 通常将卷积块的输出池化以更改图像的空间维度。
卷积层将内核独立地应用于每个输入。这可能会导致其输出因不同的输入而异。批处理规范化层通常遵循卷积层来解决此问题。让我们在下一节中详细了解它的作用。
六、批量规范化
批次归一化图层将批次输入中的通道值归一化为零均值和单位方差。此归一化对批处理中的每个通道独立执行,以确保输入的通道值具有相同的分布。批量规范化具有以下优点:
- 它通过防止梯度变得太小时来稳定训练过程。
- 它在我们的任务上实现了更快的收敛。
如果我们所拥有的只是一堆卷积层,由于线性变换的级联效应,它基本上等同于单个卷积层网络。换句话说,一系列线性变换可以替换为具有相同效果的单个线性变换。直观地说,如果我们将一个常数 k₁ 的向量乘以另一个常数 k₂,则相当于单次乘以常数 k₁k₂。因此,为了使网络具有现实深度,它们必须具有非线性以防止其崩溃。我们将在下一节中讨论经常用作非线性的ReLU。
七、激活函数Qdot
ReLU是一个简单的非线性激活函数,它削波最低输入值以大于或等于0。它还有助于解决将输出限制为大于或等于 0 的梯度消失问题。ReLU 图层后通常跟一个池化层,用于缩小缩小子网中的空间维度,或一个非池化层,用于提升子网中的空间维度。下一节将提供详细信息。
八、池化
池化层用于缩小输入的空间维度。步幅 = 2 的池化会将空间维度为 (H, W) 的输入转换为 (H/2, W/2)。最大池化是深度 CNN 中最常用的池化技术。它将网格中的最大值(例如)2x2 投影到输出上。然后,我们根据类似于卷积的步幅将 2x2 池化窗口滑动到下一部分。使用 step=2 重复执行此操作会导致输出高度的一半和宽度的一半。另一个常用的池化层是平均池化层,它计算平均值而不是最大值。
池化层的反面称为非池化层。它采用 (H, W) 尺寸输入并将其转换为 (2H, 2W) 尺寸输出,步幅 = 2。此转换的必要组成部分是在输出的 2x2 部分中选择位置以投影输入值。为此,我们需要一个最大解池索引映射,它告诉我们输出部分中的目标位置。此取消池化映射由之前的最大池化操作生成。图 5 显示了池化和取消池化操作的示例。
图 5:最大池化和取消池化。来源:DeepPainter:使用深度卷积自动编码器的Painter分类
我们可以将最大池化视为一种非线性激活函数。但是,据报道,使用它来替换诸如ReLU之类的非线性会影响网络的性能。相反,平均池化不能被视为非线性函数,因为它使用其所有输入来产生其输入的线性组合输出。
这涵盖了深度CNN的所有基本构建块。现在,让我们将它们放在一起以创建一个模型。我们为本练习选择的模型称为SegNet。我们接下来会讨论它。
九、SegNet:基于CNN的模型
SegNet是一个基于我们在本文中讨论的基本块的深度CNN模型。它有两个不同的部分。底部(也称为编码器)对输入进行下采样,以生成代表输入的特征。顶部解码器部分对特征进行上采样,以创建每像素分类。每个部分由一系列 Conv-BN-ReLU 块组成。这些模块还分别在下采样和上采样路径中包含池化或取消池化层。图 6 更详细地显示了各层的排列。SegNet 使用编码器中最大池化操作中的池化索引来确定在解码器中的最大池化操作期间要复制哪些值。虽然激活张量的每个元素都是 4 字节(32 位),但 2x2 正方形内的偏移量可以仅使用 2 位存储。这在使用内存方面更有效,因为这些激活(或 SegNet 中的索引)需要在模型运行时存储。
图 6:用于图像分割的 SegNet 模型架构。来源:SegNet:用于图像分割的深度卷积编码器-解码器架构
此笔记本包含此部分的所有代码。
此模型具有 15.27M 的可训练参数。
在模型训练和验证期间使用了以下配置。
- 随机水平翻转和颜色抖动数据增强应用于训练集以防止过度拟合
- 在非宽高比保留调整大小操作中将图像大小调整为 128x128 像素
- 不会对图像应用输入规范化;而是使用批量归一化层作为模型的第一层
- 该模型使用 LR 为 20.0 的 Adam 优化器和每 001 个 epoch 将学习率衰减 0.7 的 StepLR 调度器训练 7 个 epoch
- 交叉熵损失函数用于将像素分类为属于宠物、背景或宠物边框
该模型在 88 个训练周期后实现了 28.20% 的验证准确率。
我们绘制了一个 gif,显示了模型如何学习预测验证集中 21 张图像的分割掩码。
图 6:一个 gif,显示了 SegNet 模型如何学习预测验证集中 21 张图像的分割掩码。来源:作者
本系列的第 1 部分介绍了所有验证指标的定义。
如果你想看到一个使用Tensorflow实现的用于分割宠物图像的全卷积模型,请参阅第4章:高效深度学习的高效架构一书。
十、模型学习的观察结果
基于训练模型在每个纪元之后做出的预测的发展,我们可以观察到以下内容。
- 该模型能够学习足够的知识,使输出看起来在图像中宠物的正确球场上,甚至早在 1 个训练时期
- 边框像素更难分割,因为我们使用的是未加权损失函数,该函数平等地对待每个成功(或失败),因此弄错边框像素不会使模型在损失方面付出太多代价。我们鼓励您对此进行调查,并检查您可以尝试哪些策略来解决此问题。尝试使用焦点损失,看看它的表现如何
- 即使在 20 个训练时期之后,该模型似乎仍在学习。这表明,如果我们训练模型的时间更长,我们可以提高验证准确性。
- 有些真实标签本身很难搞清楚——比如中间一排狗的面具,最后一列在狗的身体被植物遮挡的区域有很多未知像素。这对于模型来说很难弄清楚,因此人们应该始终期望此类示例的准确性下降。但是,这并不意味着该模型表现不佳。除了查看整体验证指标外,还应始终抽查预测以了解模型的行为。
图 7:包含大量未知像素的地面实况分割掩码示例。对于任何 ML 模型来说,这都是非常困难的输入。来源:作者
十一、结论
在本系列的第 2 部分中,我们了解了用于视觉 AI 的深度 CNN 的基本构建块。我们看到了如何在 PyTorch 中从头开始实现 SegNet 模型,并可视化了在连续时期训练的模型如何在 21 张验证图像上执行。这应该可以帮助您了解模型可以多快地学习到足以使输出看起来在正确的球场上。在这种情况下,我们可以看到分割掩码,它早在第一个训练时期就与实际分割掩码大致相似!