文章目录
论文地址:https://arxiv.org/abs/2104.06403
一、预备知识
1.1 卷积
深度可分离卷积(Depthwise separable Convolution)
深度可分离卷积depthwise separable convolution较多的应用在轻量级的网络,由depthwise(DW)和pointwise(PW)两个部分结合起来提取特征feature map。相比常规的卷积操作,其参数数量和运算成本比较低。
Depthwise separable convolution将卷积操作分为两步,分别是逐点卷积(PointWise Convolution,PW)和逐通道卷积(DepthWise Convolution,DW)。
-
DepthWise Convolution
标准卷积操作将卷积核用于所有的输入通道上,如输入为3×224×224时,一个卷积核在3个通道上同时卷积操作,最终一个卷积核对应输出一个feature map,而DepthWise Convolution不同,它针对每个输入通道应用不同的卷积核,一个卷积核对应一个输入通道。DW卷积完全在二维平面内运算,卷积核数量与上一层的通道数相同,如下图所示
Depthwise Convolution完成后的Feature map数量与输入层的通道数相同,无法扩展Feature map。而且这种运算对输入层的每个通道独立进行卷积运算,没有有效的利用不同通道在相同空间位置上的特征信息。因此需要PointWise Convolution来将这些Feature map进行组合生成新的Feature map。 -
PointWise Convolution
PointWise Convolution就是卷积核大小为1×1×M的标准卷积,M为上一层的输出通道数。PW卷积在ResNet中已经用到,使用1×1卷积降低维度。PW卷积运算会将上一步的feature map在深度方向上进行加权组合,生成新的feature map,与普通卷积相同,输出的feature map数量与卷积核数量保持一致,如下图所示,上一层输出深度为3,则卷积核大小为1×1×3 。
PW卷积的作用是对DW后各通道的特征进行融合,使得不同通道在相同空间位置上的特征得到有效利用。
综合来看,Depthwise separable convolution运算过程如下。
深度可分离卷积降低了参数数量和运算成本,以3×5×5的输入,要得到输出为4×3×3的输出为例,分别计算使用标准卷积和深度可分离卷积的参数量与运算量。
- 标准卷积
由[3, 5, 5]获得[4, 3, 3],需要4个filter,每个大小为3×3,参数量为卷积核W x 卷积核H x 输入通道数 x 输出通道数 = 4×3×3×3 = 108,计算量为卷积核W x 卷积核H x (图片W-卷积核W+1) x (图片H-卷积核H+1) x 输入通道数 x 输出通道数 = 3×3×(5-2)×(5-2)×3×4 = 972 。 - Depthwise separable convolution
首先考虑DW卷积,维度与输入通道数保持一致,则需要3个卷积核,每个大小为3×3,则参数量=27,计算量为243;
再考虑PW卷积,卷积核大小为1×1×3,,共有4个,则参数量为1×1×3×4=12,运算量为1×1×3×3×3×4=108;
相加可以得到,深度可分离卷积共计参数量为39,运算量为351,与标准卷积的108,972相比缩小了很多。
分组卷积(Group Convolution)
分组卷积对输入进行分组,每组分别卷积,若输入为
C
×
H
×
W
C×H×W
C×H×W,卷积核数量为N,分为
G
G
G个组,每组需处理的输入为
C
G
\frac{C}{G}
GC个,每组输出为
N
G
\frac{N}{G}
GN,设每个卷积核大小为
C
G
×
K
×
K
\frac{C}{G}×K×K
GC×K×K,卷积核共有
N
N
N个,参数量为
N
×
C
G
×
K
×
K
N×\frac{C}{G}×K×K
N×GC×K×K。
即常规卷积输出的特征图上,每一个点是由输入特征图
C
×
H
×
W
C×H×W
C×H×W个点计算得到的,分组卷积输出的特征图上,每一个点是由输入特征图
C
G
×
H
×
W
\frac{C}{G}×H×W
GC×H×W个点计算得到的,所以分组卷积的参数量是标准卷积的
1
G
\frac{1}{G}
G1
因为每组的卷积核只与该组的输入进行卷积,不会与其他组的输入计算,故运算量也大大减小。下图清晰的展示了分组卷积的运算。
1.2 MobileNet
MobileNet的基本单元是depthwise separable convolution,它们对参数规模和计算量的改进我们在上面已经有所解释,根据论文中进一步的推导,比较depthwise separable convolution和标准卷积可以得到如下公式:
其中
N
N
N为输出特征图深度,
D
K
D_K
DK为卷积核大小,
N
N
N值一般较大,所以可以粗略地认为depthwise separable convolution相较于标准卷积,其参数量可减少至后者的
1
D
K
2
\frac{1}{D^2_K}
DK21。
MobileNet基本结构如下图右,
基本单元即为可分离卷积单元,从代码角度来看MobileNet的Block更为清晰。
class Block(nn.Module):
'''Depthwise conv + Pointwise conv'''
def __init__(self, in_planes, out_planes, stride=1):
super(Block, self).__init__()
# DW卷积, 卷积核大小3×3, 分为 in_planes,各层单独进行卷积
# 输入输出深度相同均为in_planes
self.conv1 = nn.Conv2d(in_planes, in_planes, kernel_size=3, stride=stride, padding=1, groups=in_planes, bias=False)
self.bn1 = nn.BatchNorm2d(in_planes)
# PW 卷积, 1*1 的卷积核
self.conv2 = nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=1, padding=0, bias=False)
self.bn2 = nn.BatchNorm2d(out_planes)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = F.relu(self.bn2(self.conv2(out)))
return out
MobileNet共有28层,第一层为标准3×3卷积,总体结构如下图
模型在训练时使用了非常少的正则化以及数据增强技术,因为小模型很少有过拟合的问题,也没有使用side heads或者标签平滑操作,因为深度卷积中参数量很小,所以也没有进行权重衰减。
MobileNet为了进一步缩小模型,引入了两个超参数——width multiplier和resolution multiplier,前者按比例减少通道数,记为 α \alpha α,几个典型的取值为1, 0.75, 0.5和0.25,结合此参数,计算量为
后者按比例降低特征图大小,记为
ρ
\rho
ρ,例如原来输入特征图是224x224,可以减少为192x192,引入resolution multiplier后,参数计算为
引入width multiplier和resolution multiplier势必会降低MobileNet的性能,所以根据需要的accuracy和model size来选择其取值。
一个问题
在很多文章中提到了此时计算量的bottleneck是conv1x1,这是我现在还没能理解的地方,需要以后继续学习。
1.3 ShuffleNet
前面提到在MobileNet中,1×1卷积已成为继续缩减计算量的瓶颈,这时出现了ShuffleNet,它在Xception和ResNeXt基础上,使用pointwise group convolutions减少1×1卷积的复杂性,并且为了克服分组卷积带来的副作用(分组之间的信息不流通),提出一种新的操作——channel shuffle来帮助不同特征分组之间的信息“交流”。
ShuffleNet除了已经介绍的depthwise separable convolution,还使用了channel shuffle、pointwise group convolutions来改进ResNet。
channel shuffle
channel shuffle操作基于分组卷积(group convolution),分组卷积已在上面介绍过,如下图(a)所示,每个卷积核仅与其对应分组的feature map做卷积,这样大大减少了计算量,但是会有“副作用”,对于输出特征,它仅仅关注学习所在分组的特征,某个通道的输出仅来自一小部分输入通道,这样具有很大的局限性,故作者提出channel shuffle。
在(b)图中,在一次分组卷积GConv1后,对所得结果的feature划分为subgroups(在代码中可通过reshape和 transpose来实现),将subgroups顺序打乱后输入到GConv2的分组卷积核中,而( c )图的思想与(b)图是一致的,这样就可以在一定程度上解决分组卷积通道信息不相关的问题。
对channel shuffle,原文中已解释的很明白
This can be efficiently and elegantly implemented by a channel shuffle operation (Fig 1 (c )): suppose a convolutional layer with g groups whose output has g × n channels; we first reshape the output channel dimension into (g; n), transposing and then flattening it back as the input of next layer.
即使两个卷积具有不同的组数,该操作仍然有效。此外,channel shuffle也是可微的,这意味着它可以嵌入到网络结构中进行端到端训练。
pointwise group convolutions
PW卷积为卷积核大小为1×1的卷积操作,pointwise group convolutions即为分组的PW卷积。
ShuffleNet unit
作者利用原始ResNet中的Bottleneck模块,逐步改进,最终形成ShuffleNet Unit。
图(a):用3×3的DW卷积代替Bottleneck中的3×3卷积;
图(b):用1×1的分组卷积代替原来的1×1卷积;
图(c):在short cut上添加3×3的平均池化并设置stride=2,改变原有ResNet的Add操作为concat,即按channel合并,这使得在不增加额外计算成本的情况下,很容易扩大通道尺寸。
ShuffleNet
基于ShuffleNet Unit,提出ShuffleNet结构如下表
ShuffleNet主要由ShuffleNet Unit堆叠而成,主体遵循ResNet的结构,分为3个stage,每个stage中用ShuffleNet Unit代替原有的残差块,在ShuffleNet Unit中使用分组数g控制PW卷积的复杂度,使用不同组数和输出通道数,以确保计算量大致不变。
将上表中的ShuffleNet作为ShuffleNet 1×,在通道上取一个缩放因子 s s s,将其模型称为ShuffleNet s×, s s s用于控制filters的数量,在整个ShuffleNet架构上,其复杂度的变化大概是 s 2 s^2 s2倍,不同参数的表现和计算量如下表所示
ShuffleNet的实现代码:ShuffleNet V1 神经网络简介与代码实战
对于卷积模型压缩的几个变形,这篇文章介绍的很详细,可作为参考阅读:
深入剖析MobileNet和它的变种(例如:ShuffleNet)为什么会变快?
1.4 SE模块
SENet即Squeeze-and-Excitation Networks,使用SE模块学习通道之间的相关性,通过对通道进行加权,强调有效信息,抑制无效信息。
SE模块分为Squeeze和Excitation两部分,很容易加载到已有的网络架构中,SE模块的结构如下图所示:
可以看到,输入X(
C
′
×
H
′
×
W
′
C'×H'×W'
C′×H′×W′)先经过一个
F
t
r
F_{tr}
Ftr变换(eg. 卷积)获得特征图U(
C
×
H
×
W
C×H×W
C×H×W),接着就要进行SE分支的操作。
Squeeze操作
Squeeze本质上是一个全局平均池化,顺着空间维度进行特征压缩,将每个二维的特征通道变成一个通道描述符(标量),这个描述符在某种程度上具有全局的感受野,并且输出的维度( 1 × 1 × C 1×1×C 1×1×C)与输入的特征通道数一致,它允许网络的所有层使用来自全局感受野的信息。
Excitation操作
输入为Squeeze的输出,维度为
1
×
1
×
C
1×1×C
1×1×C。
Excitation操作基于特征通道间的相关性,对每个特征通道生成了一个权重,用于代表特征通道的重要程度,故Excitation的输出维度仍是C个标量(
1
×
1
×
C
1×1×C
1×1×C)。
权重生成后,通过乘法逐通道地加权到之前的特征上,完成通道维度上对原始特征的特征重标定。
SE模块的加载
SE模块的简单性也使得它很容易加入到已有的网络结构中,原论文中举了两个例子,分别是Inception和ResNet并给出了详细的计算图,结合Inception网络的计算图,可以更好的理解SE模块的操作过程。
Squeeze操作即图中的Global Pooling,Excitation操作即后续的FC+RELU+FC+Sigmoid组合。
在进行Excitation时,第一次FC的输出维度上添加了一个衰减因子reduction,这个参数的目的是为了减少通道个数从而降低计算量。
对Squeeze中的池化选择、Excitation中reduction值大小和激活函数选择,原论文中也做了不同尝试,可以查看原论文进行了解。
将SE加载到ResNet中,结构是相似的
这时需要考虑的要更多一些,例如SE block是加在ResNet哪个stage或者放在残差单元的哪个位置,论文也对不同情况进行了实验,如下图,将SE放在残差单元不同位置的结构
不同实验的结果对比,参看原论文,不做介绍。
SE Block实现
根据结构图,使用pytorch可以快速完成SE模块的搭建,代码如下
class SELayer(nn.Module):
def __init__(self, channel, reduction=16):
super(SELayer, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Sequential(
nn.Linear(channel, channel // reduction, bias=False),
nn.ReLU(inplace=True),
nn.Linear(channel // reduction, channel, bias=False),
nn.Sigmoid()
)
def forward(self, x):
b, c, _, _ = x.size()
y = self.avg_pool(x).view(b, c)
y = self.fc(y).view(b, c, 1, 1)
return x * y.expand_as(x)
二、Lite-HRNet论文阅读
篇幅和时间原因,Lite-HRNet论文阅读记录放在下一次。
阅读Lite-HRNet前,先看一眼在COCO数据集上的实验的对比结果,如下图
这个参数和运算量的压缩属实太狠了,在参数量相近模型中的表现对比也是碾压的。。。