本文为 AI 研习社编译的技术博客,原标题 :
Diving into Deep Convolutional Semantic Segmentation Networks and Deeplab_V3
作者 | Thalles Silva
翻译 | 斯蒂芬·二狗子
校对 | 酱番梨 审核 | 约翰逊·李加薪 整理 | 立鱼王
原文链接:
https://medium.freecodecamp.org/diving-into-deep-convolutional-semantic-segmentation-networks-and-deeplab-v3-4f094fa387df
注:本文的相关链接请访问文末【阅读原文】
深度卷积神经网络(DCNN)在各种计算机视觉应用中取得了显着的成功。当然,语义分割的任务也不例外。
本文通过亲自动手做,用TensorFlow实现了语义分割任务。我们将讨论关于生活中常见物体的语义分割任务的相关论文 - Deeplab_v3。您可以在点击阅读原文复制一份github代码 。
用于图像分类任务的深度卷积神经网络模型DCNNs具有类似的结构。这些模型将图像作为输入并输出表示该图像的类别值。
通常,用于分类的DCNN有四个主要操作。卷积,激活函数,池化和全连接层。输入图像通过一系列这些操作后,模型输出包含每个类标签的概率的特征向量。需要注意的是,在此过程中,我们将图像进行整体分类。也就是说,我们为整个图像分配单个标签。
用于图像识别的标准深度学习模型。图片来源:卷积神经网络MathWorks
与图像分类任务不同,在语义分割中,我们想要为图像中的每个像素做出判断。因此,对于每个像素,模型需要将其划分为预定义的某一类别。换句话说,语义分割就是在像素级别理解图像。
请记住,语义分割不区分对象实例。在这里,我们尝试为图像的每个像素指派单独的标签。因此,如果我们有两个物体属于同一类,那么它们最终会有相同的类别标签。实例分割则是区分同类物体中不同个体的一类问题。
语义分割与实例分割的区别。(中图)虽然它们是同一个类物体(公交),但它们被归类为不同标签。(右图)相同类别物体有相同的标签。
然而,诸如AlexNet和VGG的常规深度卷积神经网络模型不适合于这类密集预测任务。首先,这些模型包含许多层,用于减少输入像素的空间维度。结果,这些层最终产生了高度抽象的特征向量,这些特征向量缺乏清晰的细节信息。其次,完全连接的层在针对固定的大小和松散的空间信息进行计算。
举个例子,想象一下 图像在一系列卷积层中传递,而不是通过池化和全连接层。我们可以将每个卷积设置的步长stride设置1 ,padding的填充类型设置为“SAME”。这样,每个卷积保留原图输入时的空间维度。我们可以用这样一堆堆叠的卷积层,构建分割模型。
用于密集预测任务的全卷积神经网络。请注意,不在使用池化层和全连接层。
该模型可以输出具有形状[W,H,C]的概率张量 ,其中W和H表示宽度和高度,C是类标签的数量。对这个输出用 argmax 函数(在第三轴上)给出了[W,H,1] 的张量形状的预测结果。之后,我们计算图像ground-truth(就是真实的分割图)的每个像素与我们的预测结果的交叉熵损失。最后,我们取损失的均值,并使用反向传播 (back prop.)来训练网络。
但是,这种方法存在一个问题。正如我们所提到的,使用带有步长为1,“SAME”填充的卷积是可以保证输入尺寸不变。但是,这样做带来模型在内存消耗大和计算复杂度高的代价。
为了解决这个问题,分割网络通常有三个主要组件:卷积,下采样和上采样层。
用于图像语义分割的编码器 - 解码器架构。
在神经网络中进行下采样有两种常用方法:使用 步长大于1的卷积
(convolution striding) 或常规的 池化 操作。通常,下采样主要目的是减少特征图(feature maps)的空间维度。出于这个考虑,下采样结构的使用使我们没有很大内存情况下,依然可以采用更深层的卷积的模型设计。此外,这样做法也是为了获得一些抽象特征。
同时,值得注意的是此模型架构的第一部分看起来像常见的DCNN分类模型,除了全连接层不同。
在第一部分之后,我们有一个形状为[W,H,D]的特征向量层,其中W,H和D是特征张量的宽度,高度和深度。请注意,此层的被压缩特征向量的空间维度比原始输入更小(但更密集)。
(上图)VGG-16网络的原始形式。请注意卷积层的顶部是3个完全连接的层的堆叠。(下图)把VGG-16模型在全连接层替换为1x1卷积。这种改变使得该网络可以输出粗略的热力图。图像来源:用于语义分割的全卷积网络Fully Convolutional Networks for Semantic Segmentation.
此时,常见的DCNN分类模型将输出包含每个类标签概率的(非空间)向量。在分割模型中不同的是,我们将此压缩的特征向量传送给一系列上采样层处理。这些上采样层用于改造网络第一部分的输出,其目标是增加特征图的空间分辨率,使输出具有与输入图像相同的尺寸的热力图标签。
通常,上采样层使用的是 步长大于1 的转置卷积,使模型层从深又窄层变为更宽更浅的层。在这里,我们使用转置卷积将特征向量的维度增加到所需的值。
在大多数论文中,分割网络的这两个组件称为编码器和解码器。简而言之, 编码器将信息“编码”成用于表示其输入的压缩的向量。第二个(解码器)则将该向量重建为期望的结果。
目前有许多基于编码器 - 解码器架构的实现的网络,如FCNs, SegNet和 UNET 是这些中最流行的。因此,我们在各个领域看到了许多有效的分割模型。
与大多数编码器 - 解码器设计不同,Deeplab提供了一种不同的语义分割方法。它提出了一种用于控制信号抽取和学习多尺度上下文特征的架构。
图像来源:重新思考用于语义图像分割的空洞卷积Rethinking Atrous Convolution for Semantic Image Segmentation.
Deeplab使用在ImageNet上预训练的残差网络ResNet作为其主要特征提取器网络。但是,它提出了一种用于多尺度特征学习的新残差块。最后一个ResNet块不使用常规卷积,而是使用 atrous(带孔)卷积。此外,每个3x3卷积(在此模块内)使用不同的扩张率来获取多尺度上下文信息。
此外,在这个新模块的顶部,它使用了空洞空间金字塔池化 Atrous Spatial Pyramid Pooling(ASPP)。ASPP使用不同速率的扩张卷积作为对任意尺度区域进行分类的尝试。
要了解deeplab架构,我们需要关注三个组件。(i)ResNet架构,(ii)空洞卷积和(iii)空洞空间金字塔池(ASPP)。让我们一个一个来了解。
ResNet是一个非常受欢迎的DCNN,赢得了 ILSVRC 2015 分类任务。ResNets的主要贡献之一是提供一个框架来简化深层模型的培训。
在其原始形式中,ResNets包含4个计算块。每个块包含不同数量的残差单元Residual Units. 。这些单元以特殊方式执行一系列卷积。此外,每个块都使用最大池操作以减少空间维度。
原始论文提出了两种类型的残差单位。baseline 基线块,bottleneck瓶颈块。
baseline 包含两个 3x3 卷积,具有批量标准化(BN)和ReLU激活。
ResNet构建块。(左)基线块; (右)瓶颈块。图片自:Deep Residual Learning for Image Recognition。
第二个是瓶颈单元,由三个操作堆叠组成。1x1, 3x3 和 1x1的 一序列卷积代替了以往的设计。这两个1x1卷积操作分别是减少和恢复尺寸。这使得中间的3x3卷积在较稀疏的特征向量上运行。此外,在每个卷积后和ReLU非线性之前 使用BN层。
为了进一步阐明,我们定义这些组操作的函数为 F 其输入为 X -F(x) .
x 在通过 F(x) 的非线性变换之后 ,该单元将变换结果F(x) 与原始输入x合并。通过添加两个函数来完成此合并过程。将原始输入x 与非线性函数 F(x) 合并 有一些优点。它使较早的层可以访问后面层的梯度信号。换句话说,F(x) 上的skipping跳跃操作使较早的层能够访问更强的梯度信号。因此,这种类型的连接已被证明可以简化对更深层网络的训练。
随着我们增加模型容量,非瓶颈单元也显示出准确性的提高的优势。然而,瓶颈残差单元具有一些实际优势。首先,它们执行了比具有几乎相同数量参数更多的计算;其次,它们的执行速度却与其类似。
在实际中, 瓶颈 单元更适合于训练更深的模型,因为需要更少的训练时间和计算资源。
对于我们的实施,我们将使用 完整的预激活残差单元 full pre-activation Residual Unit。与标准瓶颈单元bottleneck unit 的唯一区别在于BN和ReLU激活的顺序。对于完全预激活,BN和ReLU在卷积前。
不同的ResNet构建块的架构。(最左边)原始ResNet块。(最右边)改进的完整预激活版本。图片来源:Identity Mappings in Deep Residual Networks 。
如 Identity Mappings in Deep Residual Networks 所示,完整的预激活单元比其他变体表现更好。
注意,这些设计之间的唯一区别是卷积堆栈中BN和RELu的顺序。
(空洞或扩张)卷积是带有扩张因子的传统卷积,其中扩张因子扩展了我们滤波器的视野。
例如,考虑一个 3x3 卷积滤波器。当扩张率等于1时,它和标准卷积一样。但是,如果我们将扩张系数设置为2,它会产生扩大卷积核的效果。
从理论上讲,它就是这样的。首先,它根据扩张率来扩展卷积滤波器范围。其次,它用零填充空白的空间 - 创建稀疏的过滤器。最后,它使用扩张的滤波器执行常规卷积。
各种扩张率的扩张卷积。
因此,使用扩张率的2的3x3 滤波器进行卷积 可以使其能够覆盖相当于5x5的面积。然而,因为它的作用类似于稀疏滤波器,所以只有原始的 3x3 单元才能进行计算并产生结果。我之所以说其“行为”像一个稀疏的滤波器,是因为大多数框架都没有使用稀疏的滤波器来实现扩张卷积的效果(因为内存问题)。
以类似的方式,将扩张因子设置为3,是常规的3x3 卷积从对应7x7 的区域获得信号。
此效果允许我们控制计算特征时所得到分辨率。此外,扩张卷积在不增加参数数量或计算量的情况下增加了更大的上下文信息。
Deeplab还表明,必须根据特征图的大小调整扩张率。他们研究了在小特征图上使用大的扩张率的后果。
为较小的要素图设置较大的扩张率的副作用。对于14x14输入图像,扩散率为15的3x3滤波器使得扩张卷积的行为类似于常规的1x1卷积。
当扩张速率非常接近特征图的大小时,常规的3x3 空洞滤波器充当了标准的1x1 卷积。
换句话说,空洞卷积的效率取决于扩张率的选择是否正确。因此,了解神经网络中输出步长stride的概念非常重要。
输出步长解释了输入图像大小与输出特征图大小的比率。它定义了输入向量在通过网络时所受的信号抽取量。
对于16的输出步长,224x224x3 的图像尺寸 输出尺寸为缩小了16倍的特征向量,即 14x14。
此外,Deeplab还讨论了不同输出步长对分割模型的影响。它认为过度的信号抽取对密集预测任务是有害的。总之,具有较小输出步长的模型 - 较少的信号抽取 - 倾向于输出更精细的分割结果。然而,具有较小输出量的训练模型需要更多的训练时间。
Deeplab报告了设置两种输出步长的实验,8和16.正如预期的那样,输出stride = 8能够产生稍好的结果。这里我们选择输出stride = 16出于实际原因。
此外,由于空洞卷积块没有实现完全的下采样,因此采用空洞金字塔池化ASPP来改变不同空洞卷积得出的图的大小。因此,ASPP结构允许使用相对较大的扩张率的空洞卷积从多尺度图像中学习特征。
新的Atrous残余块包含三个残差单元。总共3个单元有3个 3x3 卷积。在 多重网格 方法的推动下,Deeplab为每个卷积提出了不同的扩张率。总之,多重网格定义了三个卷积中每个卷积的扩张率。
在实验中:
对于新的block4,当输出stride = 16且Multi Grid =(1,2,4)时,三个卷积的分别具有扩张率 rates = 2·(1,2,4)=(2,4,8) 。
对于ASPP,其想法是为模型提供多尺度信息。为此,ASPP增加了一系列具有不同扩张率的空洞卷积。这些不同扩张率是为了获取较大上下文图像语义信息。此外,为了添加全局上下文信息,ASPP通过全局平均池( Global Average Pooling,GAP)合并了图像级的特征。
此版本的ASPP包含4个并行操作,包括1一个1x1卷积和3个3x3 卷积,扩张率 rates=(6,12,18)。正如我们所提到的,此时,特征图的标准步长等于16。
基于最初的实施,我们使用513x513的 裁剪尺寸进行训练和测试。因此,使用16的输出步长后,ASPP接收大小为32x32的特征向量 。
此外,为了添加更多全局上下文信息,ASPP合并不同图像级的特征。首先,它将 全局平均池用在最后一个空洞卷积模块的特征输出上。其次,将得到的特征送入到具有256个滤波器的1x1卷积。最后,结果以双线性上采样到正确的尺寸。
@slim.add_arg_scope
def atrous_spatial_pyramid_pooling(net, scope, depth=256):
"""
ASPP consists of (a) one 1×1 convolution and three 3×3 convolutions with rates = (6, 12, 18) when output stride = 16
(all with 256 filters and batch normalization), and (b) the image-level features as described in https://arxiv.org/abs/1706.05587
:param net: tensor of shape [BATCH_SIZE, WIDTH, HEIGHT, DEPTH]
:param scope: scope name of the aspp layer
:return: network layer with aspp applyed to it.
"""
with tf.variable_scope(scope):
feature_map_size = tf.shape(net)
# apply global average pooling
image_level_features = tf.reduce_mean(net, [1, 2], name='image_level_global_pool', keep_dims=True)
image_level_features = slim.conv2d(image_level_features, depth, [1, 1], scope="image_level_conv_1x1", activation_fn=None)
image_level_features = tf.image.resize_bilinear(image_level_features, (feature_map_size[1], feature_map_size[2]))
at_pool1x1 = slim.conv2d(net, depth, [1, 1], scope="conv_1x1_0", activation_fn=None)
at_pool3x3_1 = slim.conv2d(net, depth, [3, 3], scope="conv_3x3_1", rate=6, activation_fn=None)
at_pool3x3_2 = slim.conv2d(net, depth, [3, 3], scope="conv_3x3_2", rate=12, activation_fn=None)
at_pool3x3_3 = slim.conv2d(net, depth, [3, 3], scope="conv_3x3_3", rate=18, activation_fn=None)
net = tf.concat((image_level_features, at_pool1x1, at_pool3x3_1, at_pool3x3_2, at_pool3x3_3), axis=3,
name="concat")
net = slim.conv2d(net, depth, [1, 1], scope="conv_1x1_output", activation_fn=None)
return net
最后,来自所有分支的特征通过连接组合成单个向量。然后使用BN和256个filters 将此输出与另一个1x1内核进行卷积。
在ASPP之后,我们将结果提供给另一个 1x1 卷积 - 以产生最终的分割概率图。
Deeplab_v3使用ResNet-50作为特征提取器,并采用以下网络配置:
输出步长stride = 16
将多网格空洞卷积的扩张率从(1,2,4)修改为新的值(block4)。
ASPP在最后空洞残差块的扩张率为(6,12,18)。
将输出步长设置为16极大加快训练。与输出步长8相比,16的步幅使得空洞残差Atrous Residual块处理后的特征映射图比其之前处理的小四倍。
多网格膨胀率应用于空洞残差块内的3个卷积。
最后, ASPP中三个并行3x3 卷积中的每一个都有不同的扩张率 - (6,12,18)。
在计算 交叉熵损失之前,我们将输出概率图logits的大小调整resize为输入的大小。正如论文中所论述的那样,最好调整logits的大小而不是
ground-truth标签以保持分辨率细节。
基于原始训练程序,我们使用从0.5到2的随机因子来放缩每张图像。此外,我们将随机左右翻转应用于放缩后的图像。(图像增强过程)
最后,我们为训练和测试输入图被裁剪为513x513 的patch。
def deeplab_v3(inputs, args, is_training, reuse):
# mean subtraction normalization
inputs = inputs - [_R_MEAN, _G_MEAN, _B_MEAN]
# inputs has shape [batch, 513, 513, 3]
with slim.arg_scope(resnet_utils.resnet_arg_scope(args.l2_regularizer, is_training,
args.batch_norm_decay,
args.batch_norm_epsilon)):
resnet = getattr(resnet_v2, args.resnet_model)
_, end_points = resnet(inputs,
args.number_of_classes,
is_training=is_training,
global_pool=False,
spatial_squeeze=False,
output_stride=args.output_stride,
reuse=reuse)
with tf.variable_scope("DeepLab_v3", reuse=reuse):
# get block 4 feature outputs
net = end_points[args.resnet_model + '/block4']
net = atrous_spatial_pyramid_pooling(net, "ASPP_layer", depth=256, reuse=reuse)
net = slim.conv2d(net, args.number_of_classes, [1, 1], activation_fn=None,
normalizer_fn=None, scope='logits')
size = tf.shape(inputs)[1:3]
# resize the output logits to match the labels dimensions
# net = tf.image.resize_nearest_neighbor(net, size)
net = tf.image.resize_bilinear(net, size)
return net
为了在resnet的block4中使用多网格实现空洞卷积,我们只是在resnet_utils.py 文件中更改了这个部分。
...
with tf.variable_scope('unit_%d' % (i + 1), values=[net]):
# If we have reached the target output_stride, then we need to employ
# atrous convolution with stride=1 and multiply the atrous rate by the
# current unit's stride for use in subsequent layers.
if output_stride is not None and current_stride == output_stride:
# Only uses atrous convolutions with multi-graid rates in the last (block4) block
if block.scope == "block4":
net = block.unit_fn(net, rate=rate * multi_grid[i], **dict(unit, stride=1))
else:
net = block.unit_fn(net, rate=rate, **dict(unit, stride=1))
rate *= unit.get('stride', 1)
...
为了训练网络,我们决定使用 Semantic contours from inverse 提供的增强的Pascal VOC数据集 。
训练数据由8,252个图像组成。训练集中有5,623个,验证集中有2,299个。为了测试模型,使用了原始VOC 2012 验证数据集,我从2,299验证集中删除了558个图像。这558个样品也出现在官方VOC验证集上。此外,我添加了来自VOC 2012训练集中的的330张图像,这些图像在5,623和2,299数据集中都不存在。最后,8,252张图像中的10%(约825个样本)被保留用于验证,剩下的用于训练。
请注意,这与原始论文不同:此实现未在COCO数据集中预先训练。此外,文件中描述的一些用于培训和评估的技术尚未实施。
该模型能够在PASCAL VOC验证集上获得不错的结果。
像素精度Pixel accuracy:~91%
平均准确度Mean Accuracy:~82%
平均交并比(mIoU):~74%
频率加权交并比 :~86%。
这里,您可以从PASCAL VOC验证集中查看各种图像中的一些结果。
语义分割领域无疑是计算机视觉领域最热门的领域之一。Deeplab提供了传统编码器 - 解码器架构的替代方案。它主张在多尺度环境中使用空洞卷积进行特征学习。开源代码,可以调整模型以获得更接近原始实现的结果。完整的代码在这里:
https://github.com/sthalles/deeplab_v3
希望你喜欢阅读!
最初发表于sthalles.github.io。
想要继续查看该篇文章相关链接和参考文献?
点击底部【阅读原文】即可访问:
https://ai.yanxishe.com/page/TextTranslation/1584