其他分享
首页 > 其他分享> > Lite-HRNet学习记录(一)

Lite-HRNet学习记录(一)

作者:互联网

文章目录


Lite-HRNet
论文地址: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 separable convolution运算过程如下。
深度可分离卷积
深度可分离卷积降低了参数数量和运算成本,以3×5×5的输入,要得到输出为4×3×3的输出为例,分别计算使用标准卷积和深度可分离卷积的参数量与运算量。

  1. 标准卷积
    由[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 。
  2. 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} DK2​1​。

MobileNet基本结构如下图右,
MoblieNet基本结构
基本单元即为可分离卷积单元,从代码角度来看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卷积,总体结构如下图
MobileNet结构
模型在训练时使用了非常少的正则化以及数据增强技术,因为小模型很少有过拟合的问题,也没有使用side heads或者标签平滑操作,因为深度卷积中参数量很小,所以也没有进行权重衰减。

MobileNet为了进一步缩小模型,引入了两个超参数——width multiplier和resolution multiplier,前者按比例减少通道数,记为 α \alpha α,几个典型的取值为1, 0.75, 0.5和0.25,结合此参数,计算量为

计算量a
后者按比例降低特征图大小,记为 ρ \rho ρ,例如原来输入特征图是224x224,可以减少为192x192,引入resolution multiplier后,参数计算为

计算2
引入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。
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

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 s×
ShuffleNet的实现代码:ShuffleNet V1 神经网络简介与代码实战

对于卷积模型压缩的几个变形,这篇文章介绍的很详细,可作为参考阅读:
深入剖析MobileNet和它的变种(例如:ShuffleNet)为什么会变快?

1.4 SE模块

SENet即Squeeze-and-Excitation Networks,使用SE模块学习通道之间的相关性,通过对通道进行加权,强调有效信息,抑制无效信息。
SE模块分为Squeeze和Excitation两部分,很容易加载到已有的网络架构中,SE模块的结构如下图所示:
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模块的操作过程。
Inception+SE
Squeeze操作即图中的Global Pooling,Excitation操作即后续的FC+RELU+FC+Sigmoid组合。
在进行Excitation时,第一次FC的输出维度上添加了一个衰减因子reduction,这个参数的目的是为了减少通道个数从而降低计算量。

对Squeeze中的池化选择、Excitation中reduction值大小和激活函数选择,原论文中也做了不同尝试,可以查看原论文进行了解。

将SE加载到ResNet中,结构是相似的
ResNet+SE
这时需要考虑的要更多一些,例如SE block是加在ResNet哪个stage或者放在残差单元的哪个位置,论文也对不同情况进行了实验,如下图,将SE放在残差单元不同位置的结构
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数据集上的实验的对比结果,如下图
COCO VAL
COCO test
这个参数和运算量的压缩属实太狠了,在参数量相近模型中的表现对比也是碾压的。。。

标签:记录,卷积,self,ShuffleNet,分组,HRNet,Lite,SE,通道
来源: https://blog.csdn.net/qq_41533576/article/details/121165049