文献阅读笔记:全卷积神经网络

摘要

本周学习了全卷积神经网络,全卷积神经网络(Fully Convolutional Network,FCN)是深度学习在语义分割领域的开山之作,其特点在于将传统卷积神经网络(CNN)中的全连接层替换为卷积层,使得网络的输出为热力图而非类别,这种设计使得FCN可以接受任意尺寸的输入图像,并对其进行像素级的分类,从而解决了语义级别的图像分割问题,FCN的主要优势在于其灵活性和高效性。由于去除了全连接层,FCN可以适应任意尺寸的输入,这在处理不同尺寸的图像时非常有用。本文将详细介绍全卷积神经网络。

Abstract

This week, we learned about Fully Convolutional Neural Networks (FCN), which is the groundbreaking work of deep learning in the field of semantic segmentation, and is characterized by replacing the fully-connected layer in traditional Convolutional Neural Networks (CNNs) with a convolutional layer so that the output of the network is a heat map instead of a category, and this design allows FCNs to accept input images of arbitrary size and classify them at the pixel level, thus solving the problem of image segmentation at the semantic level.The main advantages of FCN are its flexibility and efficiency. Due to the removal of the fully connected layer, FCNs can adapt to inputs of arbitrary size, which is very useful when dealing with images of different sizes. In this paper, we will introduce Full Convolutional Neural Networks in detail.

Translated with DeepL.com (free version)

1. 全卷积神经网络

文献链接:

1.1 文献摘要

作者在本文中提出了全卷积神经网络,该网络接受任意大小的输入并通过有效的推理和学习产生相应大小的输出。作者在本文中定义并详细介绍了全卷积网络的空间,解释了它们在空间密集预测任务中的应用,并与先前的模型建立了联系。该网络中还有跳跃架构,它将来自深层、粗糙层的语义信息与来自浅层、精细层的外观信息相结合,以产生准确而详细的分割。

1.2 全卷积神经网络

卷积网络中的每一层数据都是一个大小为 h × w × d 的三维数组,其中 h 和 w 是空间维度,d 是特征或通道维度。第一层是图像,像素大小为 h × w,有 d 个颜色通道。较高层中的位置对应于它们路径连接到的图像中的位置,称为它们的感受野。

卷积网络建立在平移不变性的基础上。它们的基本组件(卷积、池化和激活函数)在局部输入区域上运行,并且仅依赖于相对空间坐标。将 x i j x_{ij} xij 写入特定层中位置 (i, j) 处的数据向量,并将 y i j y_{ij} yij 写入后续层,这些函数通过以下方式计算输出 y i j y_{ij} yij

1.2.1 网络结构

FCN网络结构主要分为两个部分:全卷积部分和反卷积部分。其中全卷积部分为一些经典的CNN网络(如VGG,ResNet等),用于提取特征;反卷积部分则是通过上采样得到原尺寸的语义分割图像。FCN的输入可以为任意尺寸的彩色图像,输出与输入尺寸相同,通道数为n(目标类别数)+1(背景)。FCN网络结构如下:
在这里插入图片描述

1.2.0 从分类器到密集 FCN

对于GoogLeNet,作者仅使用最终的损失层,并通过丢弃最终的平均池化层来提高性能。我们通过丢弃最终的分类器层来斩首每个网络,并将所有全连接层转换为卷积层。 作者附加一个通道维度为 21 的 1 × 1 卷积来预测每个粗略输出位置处每个 PASCAL 类(包括背景)的分数,然后是一个反卷积层,将粗略输出双线性上采样为像素密集输出,下表比较了初步验证结果以及每个网络的基本特征。
在这里插入图片描述
从分类到分割的微调为每个网络提供了合理的预测。即使是最差的模型也能达到最先进性能的 75%。

尽管完全卷积分类器可以像上述所示进行微调以进行分割,甚至在标准指标上得分很高,但它们的输出却非常粗糙(见图 4)。最终预测层的 32 像素步长限制了上采样输出中的细节比例。作者通过添加跳跃融合连接来解决这个问题,该跳跃将最终预测层与具有更精细步长的较低层结合起来。这会将线型拓扑转变为 DAG,其边缘从较低层跳到较高层。由于他们看到的像素更少,更精细的尺度预测应该需要更少的层,因此从更浅的网络输出中制作它们是有意义的。结合精细层和粗略层,模型可以做出尊重全局结构的局部预测。
在这里插入图片描述

1.2.2 上采样 Upsampling

在卷积过程的卷积操作和池化操作会使得特征图的尺寸变小,为得到原图像大小的稠密像素预测,需要对得到的特征图进行上采样操作。可通过双线性插值(Bilinear)实现上采样,且双线性插值易于通过固定卷积核的转置卷积(transposed convolution)实现,转置卷积即为反卷积(deconvolution)。在论文中,作者并没有固定卷积核,而是让卷积核变成可学习的参数。转置卷积操作过程如下:

在这里插入图片描述

1.2.3 跳级结构

如果仅对最后一层的特征图进行上采样得到原图大小的分割,最终的分割效果往往并不理想。因为最后一层的特征图太小,这意味着过多细节的丢失。因此,通过跳级结构将最后一层的预测(富有全局信息)和更浅层(富有局部信息)的预测结合起来,在遵守全局预测的同时进行局部预测。

将底层(stride 32)的预测(FCN-32s)进行2倍的上采样得到原尺寸的图像,并与从pool4层(stride 16)进行的预测融合起来(相加),这一部分的网络被称为FCN-16s。随后将这一部分的预测再进行一次2倍的上采样并与从pool3层得到的预测融合起来,这一部分的网络被称为FCN-8s。图示如下:
在这里插入图片描述

1.2.4 FCN训练

阶段1:以经典的分类网络为初始化,最后两级为全连接(红色),参数弃去不用。
在这里插入图片描述
阶段2:FCN-32s 网络—从特征小图预测分割小图,之后直接升采样为大图。
在这里插入图片描述
阶段3:FCN-16s 网络—上采样分为两次完成。在第二次升采样前,把第4个pooling层的预测结果融合进来,使用跳级结构提升精确性。
在这里插入图片描述
阶段4:FCN-8s 网络—升采样分为三次完成。 进一步融合了第3个pooling层的预测结果。
在这里插入图片描述

1.3 实验

作者在语义分割和场景解析上测试 FCN,探索 PASCAL VOC、NYUDv2 和 SIFT Flow。尽管这些任务历史上区分了对象和区域,但作者将它们统一视为像素预测。我们在每个数据集上评估我们的 FCN 跳跃架构,然后将其扩展到 NYUDv2 的多模态输入以及 SIFT Flow 的语义和几何标签的多任务预测。下表给出了作者提出的 FCN-8 在 PASCAL VOC 2011 和 2012 测试集上的性能,并将其与之前最先进的 SDS和著名的 R-CNN。我们在平均 IU8 上取得了最佳结果,相对优势为 20%。推理时间减少了 114 倍(仅限卷积网络,忽略提案和细化)或 286 倍(总体)。
在这里插入图片描述
NYUDv2 是使用 Microsoft Kinect 收集的 RGB-D 数据集。它有 1449 张 RGB-D 图像,带有像素标签,已被 Gupta 等人合并为 40 类语义分割任务。我们报告了 795 个训练图像和 654 个测试图像的标准分割结果。 (注:所有模型选择均在 PASCAL 2011 val 上进行。)下表给出了我们的模型在几种变体中的性能。首先,我们在 RGB 图像上训练未经修改的粗略模型 (FCN-32s)。为了添加深度信息,我们对升级后的模型进行训练,以采用四通道 RGB-D 输入(早期融合)。
在这里插入图片描述

1.4 总结

全卷积网络是一类丰富的模型,现代分类卷积网络是其中的一个特例。认识到这一点,将这些分类网络扩展到分段,并通过多分辨率层组合改进架构,可以显着提高最先进的水平,同时简化和加速学习和推理。全卷积神经网络(Fully Convolutional Network,FCN)是深度学习在语义分割领域的开山之作,其特点在于将传统卷积神经网络(CNN)中的全连接层替换为卷积层,使得网络的输出为热力图而非类别。这种设计使得FCN可以接受任意尺寸的输入图像,并对其进行像素级的分类,从而解决了语义级别的图像分割问题。FCN的主要优势在于其灵活性和高效性。由于去除了全连接层,FCN可以适应任意尺寸的输入,这在处理不同尺寸的图像时非常有用。此外,FCN使用转置卷积(也称为反卷积)层对特征图进行上采样,使其恢复到输入图像相同的尺寸,从而可以对每一个像素都产生一个预测。这种设计保留了原始输入图像中的空间信息,使得预测结果更加精确。在实际应用中,FCN已被广泛应用于目标分割、目标检测、目标分类等研究领域,并取得了令人瞩目的成果。例如,在医学图像分割中,FCN可以精确地识别出病变区域;在自动驾驶领域,FCN可以用于识别道路、车辆和行人等关键元素。

然而,值得注意的是,虽然FCN在图像分割方面取得了显著进展,但它仍然存在一些挑战和限制。例如,对于某些复杂的场景或物体,FCN可能难以准确地进行分割;此外,由于FCN需要进行像素级的预测,因此其计算复杂度相对较高,可能需要大量的计算资源和时间。

2. 代码实现

此处使用paddle深度学习框架进行网络模型的搭建,以下为网络结构代码:

class FCN8s(nn.Layer):
    def __init__(self, num_classes=21):
        super(FCN8s, self).__init__()
        # num_classes要包含背景,如果是PASCAL VOC则是20+1
        self.layer1 = self.make_block(num=2, in_channels=3, out_channels=64)
        self.layer2 = self.make_block(num=2, in_channels=64, out_channels=128)
        self.layer3 = self.make_block(num=3, in_channels=128, out_channels=256)
        self.layer4 = self.make_block(num=3, in_channels=256, out_channels=512)
        self.layer5 = self.make_block(num=3, in_channels=512, out_channels=512)
        # 下面的两个卷积层代替了原来VGG网络的全连接层(原本为4096,此处可根据gpu性能,设置为其他数,此处设为2048)
        mid_channels = 2048
        self.conv6 = nn.Conv2D(in_channels=512, out_channels=mid_channels, kernel_size=7, padding=3)
        self.conv7 = nn.Conv2D(in_channels=mid_channels, out_channels=mid_channels, kernel_size=1)
        
        # 3个1*1的卷积,用于改变pool的通道数,为了后续融合语义信息
        self.score32 = nn.Conv2D(in_channels=mid_channels, out_channels=num_classes, kernel_size=1)
        self.score16 = nn.Conv2D(in_channels=512, out_channels=num_classes, kernel_size=1)
        self.score8 = nn.Conv2D(in_channels=256, out_channels=num_classes, kernel_size=1)
        
        # 3个转置卷积,用于扩大特征图
        # 若参数kernel_size:stride:padding=4:2:1,此时stride为扩大倍数
        weight_8x = paddle.ParamAttr(
            initializer=paddle.nn.initializer.Assign(bilinear_kernel(num_classes, num_classes, 16))
        )
        self.up_sample8x = nn.Conv2DTranspose(
            in_channels=num_classes,
            out_channels=num_classes,
            kernel_size=16, stride=8, padding=4,
            weight_attr=weight_8x
        )
        
        weight_16x = paddle.ParamAttr(
            initializer=paddle.nn.initializer.Assign(bilinear_kernel(num_classes, num_classes, 4))
        )
        self.up_sample16x = nn.Conv2DTranspose(
            in_channels=num_classes,
            out_channels=num_classes,
            kernel_size=4, stride=2, padding=1,
            weight_attr=weight_16x
        )
        
        weight_32x = paddle.ParamAttr(
            initializer=paddle.nn.initializer.Assign(bilinear_kernel(num_classes, num_classes, 4))
        )
        self.up_sample32x = nn.Conv2DTranspose(
            in_channels=num_classes,
            out_channels=num_classes,
            kernel_size=4, stride=2, padding=1,
            weight_attr=weight_32x
        )  
        
    def make_block(self, num: int, in_channels: int, out_channels: int, padding=1):
        """根据传入的in,out和需要构建的块数搭建网络块"""
        blocks = []
        blocks.append(nn.Conv2D(in_channels=in_channels, out_channels=out_channels, kernel_size=3, padding=padding))
        blocks.append(nn.ReLU())
        for i in range(num-1):
            blocks.append(nn.Conv2D(in_channels=out_channels, out_channels=out_channels, kernel_size=3, padding=1))
            blocks.append(nn.ReLU())
        blocks.append(nn.MaxPool2D(kernel_size=2, stride=2, ceil_mode=True))
        
        return nn.Sequential(*blocks)
    
    def forward(self, inputs):
        # inputs [3, 1, 1],以原始输入图像尺寸为1
        # features
        out = self.layer1(inputs)  # [64, 1/2, 1/2]
        out = self.layer2(out)  # [128, 1/4, 1/4]
        pool3 = self.layer3(out)  # [256, 1/8, 1/8]
        pool4 = self.layer4(pool3)  # [512, 1/16, 1/16]
        pool5 = self.layer5(pool4)  # [512, 1/32, 1/32]
        x = self.conv6(pool5)  # [mid_channels, 1/32, 1/32]
        x = self.conv7(x)  # [mid_channels, 1/32, 1/32]
        score32 = self.score32(x)  # [num_classes, 1/32, 1/32]
        
        up_pool16 = self.up_sample32x(score32)  # [num_classes, 1/16, 1/16]
        score16 = self.score16(pool4)  # [num_classes, 1/16, 1/16]
        fuse_16 = paddle.add(up_pool16, score16)
        
        up_pool8 = self.up_sample16x(fuse_16)  # [num_classes, 1/8, 1/8]
        score8 = self.score8(pool3)  # [num_classes, 1/8, 1/8]
        fuse_8 = paddle.add(up_pool8, score8)
        heatmap = self.up_sample8x(fuse_8)
        
        return heatmap

相关推荐

  1. 神经网络

    2024-03-11 13:16:03       45 阅读
  2. 神经网络

    2024-03-11 13:16:03       38 阅读
  3. 神经网络

    2024-03-11 13:16:03       31 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-11 13:16:03       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-11 13:16:03       106 阅读
  3. 在Django里面运行非项目文件

    2024-03-11 13:16:03       87 阅读
  4. Python语言-面向对象

    2024-03-11 13:16:03       96 阅读

热门阅读

  1. gitlab的CI/CD的Job作业

    2024-03-11 13:16:03       46 阅读
  2. Redis高可用方案

    2024-03-11 13:16:03       35 阅读
  3. 力扣496. 下一个更大元素 I

    2024-03-11 13:16:03       37 阅读
  4. 5.52 BCC工具之dbslower.py解读

    2024-03-11 13:16:03       47 阅读
  5. Linux开发的常用桌面界面有哪些?以及使用推荐

    2024-03-11 13:16:03       38 阅读
  6. linux安全配置规范

    2024-03-11 13:16:03       34 阅读
  7. 【c++11线程库的使用】

    2024-03-11 13:16:03       28 阅读