admin管理员组文章数量:1568307
题目:C3模块-空洞可分离卷积存在的问题及轻量化语义分割模型架构技巧
❝ 轻量化语义分割网络多采用深度可分离卷积和空洞卷积结合的方式,提升性能的同时保持尽可能大的感受野,但是前者对于标准卷积的不恰当近似和后者存在的空格效应,使得特征图信息丢失,模型表现不佳。为此文章提出C3模块即"Concentrated-Comprehensive Convolution Block",用于构建新型的轻量化语义分割网络。
论文地址:https://arxiv/abs/1812.04920
工程地址:https://github/clovaai/c3_sinet
深度可分离空洞卷积的问题
空洞卷积是语义分割中的常用操作,实现感受野扩张的同时不会减小特征图尺寸,为了降低复杂度,通常会为其结合深度可分离卷积。令分别表示:输入和输出的特征图通道数,输入/输出特征图的高和宽,卷积核的尺寸,输入特征图,卷积,扩张率为d的扩张卷积的输出特征图的计算如下所示:
为该扩张卷积应用深度可分类卷积,有:
上述过程并没有计算跨通道的信息,也就是通道之间没有实现有效的信息交流,仅仅是在空间维度进行了计算。这里参数量从减少到了(当),而浮点数运算则从减少到了。
但是这种对标准空洞卷积的近似会造成明显的模型表现衰退,况且标准空洞卷积本身也并非“完人”(一是空洞卷积本质上是空间离散的操作,这会导致信息的丢失;二是空洞卷积跳过了邻近特征反而会受到更远的区域的影响,这使得对于小目标或者窄目标的分割容易失误。如下图所示)。
一、Concentrated-Comprehensive Convolution
基于上面的观察提出C3模块,包含两个阶段,concentration阶段和comprehensive convolution阶段。如下图所示:前者通过在空洞卷积之前应用一个简单的深度卷积来消除特征信息的损失,这能够压缩跳跃的特征信息并且增强局部一致性。相较于使用常规的深度卷积参数量和FLOPS分别为和,论文使用了两个不对称的的深度卷积,将计算复杂度从降低到了降低到了,并且在二者之间加入了PReLU和BN;后者则使用深度空洞卷积来扩张感受野,之后通过一个1×1的点卷积进行跨通道混合。简单来看,C3综合了深度可分离卷积和空洞卷积的优势,使得模型参数量和计算复杂度降低的同时能够实现较好的模型表现。
二、C3模块
基于上述的C3 block为轻量分割模型设计了一个C3模块, 如下图所示,首先通过一个1×1点卷积降低通道数,然后通过一个并行结构,经过一个分层特征融合(扩张率从小到大的融合),与跳跃结构形成最后的输出。
YOlOv5中的C3代码实现
"""
定义自动padding,为了保持图像大小卷积前后一致,就需要用到自动padding
"""
def autopad(k, p=None): # kernel padding 根据卷积核大小k自动计算卷积核padding数(0填充)
"""
:param k: 卷积核的 kernel_size
:param p: 卷积的padding 一般是None
:return: 自动计算的需要pad值(0填充)
"""
if p is None:
# k 是 int 整数则除以2, 若干的整数值则循环整除
p = k // 2 if isinstance(k, int) else [x // 2 for x in k]
return p
"""
C3模块中的基本Conv模块,方便调取复用,提高代码的复用性
"""
class Conv(nn.Module):
def __init__(self, c1, c2, k=1, s=1, p=None, act=True, g=1):
"""
:param c1: 输入的channel值
:param c2: 输出的channel值
:param k: 卷积的kernel_size
:param s: 卷积的stride
:param p: 卷积的padding 一般是None
:param act: 激活函数类型 True就是SiLU(), False就是不使用激活函数
:param g: 卷积的groups数 =1就是普通的卷积 >1就是深度可分离卷积
"""
super(Conv, self).__init__()
self.conv_1 = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=True)
self.bn = nn.BatchNorm2d(c2)
self.act = nn.SiLU() if act else nn.Identity() # 若act=True, 则激活, act=False, 不激活
def forward(self, x):
return self.act(self.bn(self.conv_1(x)))
"""
Bottleneck模块中包含一个残差连接结构(左),和不包含的残差结构(右),就需要传入参数,来判断是否需要使用残差结构
"""
class Bottleneck(nn.Module):
def __init__(self, c1, c2, e=0.5, shortcut=True, g=1):
"""
:param c1: 整个Bottleneck的输入channel
:param c2: 整个Bottleneck的输出channel
:param e: expansion ratio c2*e 就是第一个卷积的输出channel=第二个卷积的输入channel
:param shortcut: bool Bottleneck中是否有shortcut,默认True
:param g: Bottleneck中的3x3卷积类型 =1普通卷积 >1深度可分离卷积
"""
super(Bottleneck, self).__init__()
c_ = int(c2*e) # 使通道减半, c_具体多少取决于e
self.conv_1 = Conv(c1, c_, 1, 1)
self.conv_2 = Conv(c_, c2, 3, 1, g=g)
self.add = shortcut and c1 == c2
def forward(self, x):
return x + self.conv_2(self.conv_1(x)) if self.add else self.conv_2(self.conv_1(x))
"""
定义C3结构
"""
class C3(nn.Module):
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
"""
:param c1: 整个 C3 的输入channel
:param c2: 整个 C3 的输出channel
:param n: 有n个Bottleneck
:param shortcut: bool Bottleneck中是否有shortcut,默认True
:param g: C3中的3x3卷积类型 =1普通卷积 >1深度可分离卷积
:param e: expansion ratio
"""
super(C3, self).__init__()
c_ = int(c2 * e)
self.cv_1 = Conv(c1, c_, 1, 1)
self.cv_2 = Conv(c1, c_, 1, 1)
# *操作符可以把一个list拆开成一个个独立的元素,然后再送入Sequential来构造m,相当于m用了n次Bottleneck的操作
self.m = nn.Sequential(*[Bottleneck(c_, c_, e=1, shortcut=True, g=1) for _ in range(n)])
self.cv_3 = Conv(2*c_, c2, 1, 1)
def forward(self, x):
return self.cv_3(torch.cat((self.m(self.cv_1(x)), self.cv_2(x)), dim=1))
实验结果
参考:C3模块-空洞可分离卷积存在的问题及轻量化语义分割模型架构技巧_cnn中c3模块中文全称_不会算命的赵半仙的博客-程序员宅基地 - 程序员宅基地 (its301)
本文标签: ComprehensiveConcentratedConvolutionSegmentationSemantic
版权声明:本文标题:C3: Concentrated-Comprehensive Convolution and its application to semantic segmentation 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/xitong/1725896593a1047714.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论