GCN-经典分割算法(清华和旷世 CVPR2017)附复现代码

文章探讨了为何使用小卷积核替代大卷积核的原因,以及卷积方式的多种变体,如空洞卷积、非对称卷积和组卷积。GCN被提出用于解决语义分割中的分类和定位问题,结合ResNet进行特征提取,并通过多尺度结构和全局上下文嵌入来提高性能。实验在PASCALVOC2012和Cityscapes数据集上取得了良好结果。
摘要由CSDN通过智能技术生成

一 前置知识

1.1 为什么要用小卷积核代替大卷积核?

大卷积核:

  • 优点:感受野范围大
  • 缺点:参数量多,计算量大

小卷积核:

  • 优点:参数量小,计算量小,整合三个非线性激活层代替单一非线性激活层,增加模型判别能力
  • 缺点:感受野不足,深度堆叠卷积容易出现不可控的因素

在参数计算(输入输出图片通道数必须保持一致才能比较)
设输入尺寸为5X5X10,输出尺寸为1X1X10
在前面的ResNet上说过这个问题,可以去前面参考一下前面的文章
请添加图片描述
如果输入和输出通道数不同,那么上面的参数量减少的情况还存在吗?当然不存在,一个简单的栗子:
如果输入尺寸是15X15X3,输出尺寸为11X11X64
1个5X5卷积 5X5X3X64
2个3X3卷积 3X3X3X64 + 3X3X64X64

卷积核的感受野的计算方法:
RF(后一个感受野) = RF(前一个感受野) + (卷积核大小 - 1) X 步长

栗子:
一个5X5卷积:RF = 5
两个3X3卷积:3+(3-1) X 1 = 5

1.2 卷积方式汇总

能否让固定大小的卷积核看到更大范围的区域?
当然得是空洞卷积 标准的3X3卷积核只能看到对应区域3X3大小,但是为了让卷积核看到更大范围,空洞卷积成为可能,池化操作导致的信息丢失是不可逆的,这不利于像素级任务,用空洞卷积代替pool的作用(成倍的增加感受野)更适用于语义分割。
请添加图片描述
卷积核一定得是正方形吗?(非对称卷积)
将标准3X3卷积拆分成为1X3卷积和3X1卷积,在不改变感受野大小的情况下,可减少计算量
标准卷积计算量:9X9=81次乘法
非对称卷积计算量:3X15 + 3X9 = 72次乘法
标准卷积与非对称卷积感受野对比
请添加图片描述
卷积只能在同一组进行吗?(组卷积&深度可分离卷积)
组卷积是对输入特征图进行分组,每组分别进行卷积。
请添加图片描述
分组卷积能否对通道进行随机分组?(shffleNet)
为达到特征之间的相互通信,除了采用dense pointwise convolution,还可以使用channel shuffle,就是对分组卷积之后特征图进行重组,这样可以保证下面的卷积其输入来自不同的组,因此信息可以在不同组之间流转。请添加图片描述
每层卷积只能用一种尺寸的卷积核吗?inception结构
请添加图片描述
通道间的特征都是平等的吗?(SE Net)
无论是在Inception、DenseNet或者ShuffleNet里面,我们对所有通道产生的特 征都是不分权重直接结合的,那为什么要认为所有通道的特征对模型的作用都是相等 的呢?一个卷积层中往往有数以千计的卷积核,每个卷积核都对应了特征,于是那么 多特征要怎么区分?这个方法就是通过学习的方式来自动获取到每个特征通道的重要 程度,然后依照计算出来的重要程度去提升有用的特征并抑制对当前任务用处不大的 特征。
请添加图片描述
卷积核形状得一定是矩形吗?(可变形卷积)
规则形状的卷积核(比如一般用的正方形3*3卷积)可能会限制特征的提取,如果 赋予卷积核形变的特性,让网络根据label反传下来的误差自动的调整卷积核的形状, 适应网络重点关注的感兴趣的区域,就可以提取更好的特征。例如,网络会根据原位 置(a),学习一个offset偏移量,得到新的卷积核(b)©(d),那么一些特殊 情况就会成为这个更泛化的模型的特例,例如图©表示从不同尺度物体的识别,图 (d)表示旋转物体的识别
请添加图片描述
总结:

  • 卷积核方面:
    • 大卷积核可以用多个小卷积核代替
    • 单一尺寸卷积核用多尺寸卷积代替
    • 固定形状卷积核趋于使用可变形卷积核
    • 使用1X1卷积核
  • 卷积层连接方面
    • 使用跳跃连接 让模型更深
    • 稠密连接
  • 卷积层通道方面:
    • 标准卷积用depthwise卷积代替
    • 使用分组卷积
    • 分组卷积前使用channel shuffle
    • 通道加权

二 本论文的成果

  • 在分类和定位之间找到了平衡点
  • 缓解了大卷积核参数多的问题

三 GCN摘要

  • 背景叙述:网络架构设计的一个最新趋势是在整个网络中堆叠小过滤器
  • 本文贡献:当必须同时执行分类和定位任务时,卷积核扮演着重要角色,我们提出一个全局卷积网络来解决语义分割的分类和定位问题,还基于残差结构,重新定义对象边界
  • 分别的PASCAL VOC 2012 和 Cityscapes数据集中取得好成绩

四 引言和相关工作

  1. 语义分割可以看成是一个逐像素分类任务,包含分类和定位两个挑战。一个好的语义分割模型应该同时兼顾以上两个任务。
  2. 语义分割的两个方面天然对立。对于分类任务,模型必须是具有不变形的,以适应目标的各种形式,如平移和反转;而对于定位任务,模型应该是对变换敏感的,即能够精确定位语义类别的每一个像素
  3. 以上两个方面出发,可以引申出设计网络的两个原则:
    • 一 从定位出发,应该采用全卷积结构,去掉全链接层和全局池化层
    • 二 从分类的角度出发,应该采用较大的卷积核,使得像素和特征图的结合更加紧密,增强处理不同变换的能力,而且,卷积核一旦过小,造成感受野过小,覆盖不了较大的目标,不利于分类。

4.1 Context Embedding

  • Zoom-out 提出了一个手工构建的层次化上下文特征
  • ParseNet添加了一个全局池化分支来提取上下文信息
  • Dilated-Net在score map后添加了几个层,以嵌入多尺度上下文
  • DeeplabV2 使用ASPP,通过卷积结果的组合,总结从特征映射中只和上下文信息

4.2 Resolution Enlarging

  • FCN使用反卷积来提高 小尺寸分数图的分辨率
  • DeconvNet和SegNet引入了反池化操作和一个glass-like的网络来学习上采样过程
  • LRR认为上采样特征图要好于上采样分数图
  • DeepLab和Dilated-Net没有学习上采样的过程,而是使用空洞卷积来直接增加小尺寸特征映射的空间大小,从而得到更大尺寸的分数映射。

4.3 CNN特性

  • 平移不变形:CNN的平移不变形更适用于分类任务,无论待分类物体处于图片中的哪个位置,其分类结果都不应该改变
  • 平移同变形:针对定位任务,CNN理论上应具有很好的平移同变形才能保持对目标位置的敏感,然而CNN并不具有这样的特性
  • CNN弊端:以卷积操作为例,卷积只是记忆物体形状,并没有考虑不同轮廓的空间位置。

五 论文算法结构

  1. 基础网络使用ResNet作为特征提取路径,使用FCN作为语义分割框架
  2. 使用了ResNet中不同的stage的特征图,因此是多尺度架构
  3. GCN模块则用于产生低分辨率的score map 并上采样与更高分辨率的score map相加产生新的score map
  4. 经过最后上采样,输出预测结果。
    请添加图片描述

代码部分

首先导入需要使用的库

import torch
import torch.nn as nn
from torchvision import models

我们这里使用的是ResNet152,直接通过torchvision.models导入

resnet152_pretrained = models.resnet152(pretrained=False)

请添加图片描述
首先从小的模块开始实现,这个就是总体结构图中的GCN部分,主要作用是对resnet的不同特征图使用非对称卷积,进行特征提取,然后进行特征融合。

class GCM(nn.Module):
    def __init__(self, in_channels, num_class, k=15):
        super(GCM, self).__init__()
        # 为了保持输入和输出图像的大小一致,我们需要对输入图片进行填充
        pad = (k-1) // 2

        self.conv1 = nn.Sequential(nn.Conv2d(in_channels, num_class, kernel_size=(1, k), padding=(0, pad), bias=False),
                                   nn.Conv2d(num_class, num_class, kernel_size=(k, 1), padding=(pad, 0), bias=False))

        self.conv2 = nn.Sequential(nn.Conv2d(in_channels, num_class, kernel_size=(k, 1), padding=(pad, 0), bias=False),
                                   nn.Conv2d(num_class, num_class, kernel_size=(1, k), padding=(0, pad), bias=False))

    def forward(self, x):
        x1 = self.conv1(x)
        x2 = self.conv2(x)
		# 特征进行相加的话,得保持两根线输出的特征图的大小是一样维度的
        assert x1.shape == x2.shape
        return x1 + x2

接着实现BR层
请添加图片描述
可能是为了防止特征提取过程中,造成像素的损失,采用了跳层连接的结构,在提取特征的同时,能保持平移同变性。

class BR(nn.Module):
    def __init__(self, num_class):
        super(BR, self).__init__()

        self.shortcut = nn.Sequential(nn.Conv2d(num_class, num_class, 3, padding=1, bias=False),
                                      nn.ReLU(),
                                      nn.Conv2d(num_class, num_class, 3, padding=1, bias=False))

    def forward(self, x):
        return x + self.shortcut(x)

请添加图片描述
接着实现每一个上采样模块,从下往上看,我们可以看出,在每一层都存在GCN + BR 然后最后一层比较特殊,没有相加这个模块,然后我们可以在反向传播模块,传入两个参数,作为判断条件,如果第二个参数为空的话,就是最后一层,如果不为空,就是前面两个GCN+BR然后和下面一层的上采样部分进行相加特征融合,融合之后经过BR和Deconv,依次这样恢复原来的尺寸。

class GCN_BR_BR_Deconv(nn.Module):
    def __init__(self, in_channels, num_class, k=15):
        super(GCN_BR_BR_Deconv, self).__init__()

        self.gcn = GCM(in_channels, num_class, k)
        self.br = BR(num_class)

        self.deconv = nn.ConvTranspose2d(num_class, num_class, 4, 2, 1, bias=False)

    def forward(self, x1, x2=None):

        x1 = self.gcn(x1)
        x1 = self.br(x1)
        if x2 is None:
            x = self.deconv(x1)
        else:
            x = x1 + x2
            x = self.br(x)
            x = self.deconv(x)
        return x

最后我们总的来实现一个GCN网络,为了清晰,我们从forward看:

def forward(self, input):
        x0 = self.layer0(input); print('x0:', x0.size())    # x0: torch.Size([1, 64, 176, 240])
        x1 = self.layer1(x0); print('x1:', x1.size())       # x1: torch.Size([1, 256, 88, 120])
        x2 = self.layer2(x1); print('x2:', x2.size())       # x2: torch.Size([1, 512, 44, 60])
        x3 = self.layer3(x2); print('x3:', x3.size())       # x3: torch.Size([1, 1024, 22, 30])
        x4 = self.layer4(x3); print('x4:', x4.size())       # x4: torch.Size([1, 2048, 11, 15])
        branch4 = GCN_BR_BR_Deconv(x4.shape[1], self.num_class, self.k)
        branch3 = GCN_BR_BR_Deconv(x3.shape[1], self.num_class, self.k)
        branch2 = GCN_BR_BR_Deconv(x2.shape[1], self.num_class, self.k)
        branch1 = GCN_BR_BR_Deconv(x1.shape[1], self.num_class, self.k)
        branch4 = branch4(x4); print('branch4:', branch4.size())    # torch.Size([1, 12, 22, 30])
        branch3 = branch3(x3, branch4); print('branch3:', branch3.size())   # torch.Size([1, 12, 44, 60])
        branch2 = branch2(x2, branch3); print('branch2:', branch2.size())   # torch.Size([1, 12, 88, 120])
        branch1 = branch1(x1, branch2); print('branch1:', branch1.size())   # torch.Size([1, 12, 176, 240])
        x = self.br(branch1)
        x = self.deconv(x)
        x = self.br(x)
        return x

__init__部分

def __init__(self, num_classes, k=15):
        super(GCN, self).__init__()
        self.num_class = num_classes
        self.k = k

        self.layer0 = nn.Sequential(resnet152_pretrained.conv1, resnet152_pretrained.bn1, resnet152_pretrained.relu)
        self.layer1 = nn.Sequential(resnet152_pretrained.maxpool, resnet152_pretrained.layer1)
        self.layer2 = resnet152_pretrained.layer2
        self.layer3 = resnet152_pretrained.layer3
        self.layer4 = resnet152_pretrained.layer4

        self.br = BR(self.num_class)
        # 一般卷积核为4,步长为2,padding为1时,是将原始图像通过转置卷积放大为原来的两倍
        self.deconv = nn.ConvTranspose2d(self.num_class, self.num_class, 4, 2, 1, bias=False) 

最后是测试,我们用torch模拟一张图片的输入,然后打印出每一层的size大小,可以更直观的看到图像尺寸的变化。

if __name__ == "__main__":
    import torch as t
    rgb = t.randn(1, 3, 512, 512)

    net = GCN(21)

    out = net(rgb)

    print(out.shape)

输出结果:
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值