SKNet讲解

SKNet讲解

  • 0. 引言
  • 1. 网络结构
    • 1.1 Split部分
    • 1.2 Fuse部分
    • 1.3 Select部分
    • 1.4 三分支的情况
  • 2. SKNet网络体系结构
  • 3. 分析与解释
  • 4. 代码
  • 总结

0. 引言

视皮层神经元的感受野大小受刺激的调节,即对不同刺激卷积核的大小应该不同,但在构建CNN时一般在同一层只采用一种卷积核,很少考虑因图片大小不同采用不同卷积核。
于是提出了SKNet。在SKNet中,不同大小的感受视野(卷积核)对于不同尺度的目标会有不同的效果。尽管在Inception中使用多个卷积核来适应不同尺度图像,但是卷积核权重相同,参数就是被计算好的了。而SKNet 对不同输入使用的卷积核感受野不同,参数权重也不同,可以根据输入大小自适应的进行处理。
SKNet中,提出了一种动态选择机制,允许每个神经元根据输入信息的多个尺度自适应调整其接受野的大小。设计了一种称为选择性内核(SK)单元的构建模块,在该模块中,由不同内核大小的多个分支的信息引导,使用softmax的注意力进行融合。对这些分支的不同关注导致融合层神经元有效感受野的大小不同。

论文地址:https://arxiv.org/pdf/1903.06586.pdf
代码地址:https://github.com/implus/SKNet

1. 网络结构

SKNet讲解_第1张图片
SKNet网络主要由三个部分组成:SplitFuseSelect。其中,Split部分将输入信息X分别输入不同的核大小(这里是2个卷积核,卷积核大小分别为:3*35*5);Fuse部分进行特征融合;Select部分根据计算得到的权重对对应的特征进行选择操作。

1.1 Split部分

对于输入信息X:[h,w,C],在Split中,分别输入两个卷积层(默认为2个,根据需要可以设计多个),两个卷积层分别为3*35*5。其中,每个卷积层都是由高效的分组/深度卷积批处理归一化ReLU函数依次组成的。另外,为了进一步提高效率,将具有5*5核的传统卷积替换为具有3×3核膨胀大小为2扩展卷积。最终得到的输出分别为 U ~ \widetilde{U} U U ^ \hat{U} U^

1.2 Fuse部分

基本思想是使用门来控制来自多个分支的信息流,这些分支携带不同尺度的信息到下一层的神经元中。为实现这一目标,门需要整合来自所有分支的信息。我们首先通过element-wise summation融合来自多个分支的结果:
U = U ~ + U ^ U=\widetilde{U}+\hat{U} U=U +U^

F g p F_{gp} Fgp全局平均池化操作 F f c F_{fc} Ffc先降维再升维的两层全连接层。需要注意的是输出的两个矩阵ab,其中矩阵b为冗余矩阵,在如图两个分支的情况下b=1-a

通过简单地使用全局平均池化以生成channel-wise统计信息 s ∈ R C s\in{R^C} sRC 来生成全局信息。具体来说,s的第C个元素是通过空间尺寸h×w收缩U来计算的:
s c = F g p ( U c ) = 1 h × w ∑ i = 1 h ∑ j = 1 w U c ( i , j ) s_c = F_{gp}(U_c) = \frac{1}{h\times{w}}\sum_{i=1}^{h}\sum_{j=1}^{w}{U_c(i,j)} sc=Fgp(Uc)=h×w1i=1hj=1wUc(i,j)

此外,还创建了一个紧凑的特征 z ∈ R d × 1 z\in{R^{d\times1}} zRd×1,以便为精确和自适应选择提供指导。这是通过一个简单的完全连接(fc)层实现的,降低了维度以提高效率:
z = F f c ( s ) = δ ( B ( W s ) ) z = F_{fc}(s)=\delta(B(Ws)) z=Ffc(s)=δ(B(Ws))
其中 δ \delta δReLU函数, B B B 表示批量标准化, W ∈ R d × C W\in{R^{d\times C}} WRd×C。为了研究d对模型效率的影响,我们使用reduction ratio (r)来控制其值:
d = m a x ( C / r , L ) d=max(C/r, L) d=max(C/r,L)

其中L表示d的最小值(L=32是我们实验中的典型设置)。

1.3 Select部分

Select操作使用ab两个权重矩阵对 U ~ \widetilde{U} U U ^ \hat{U} U^ 进行加权操作,然后求和得到最终的输出向量V

跨通道的软关注用于自适应地选择信息的不同空间尺度,空间尺度由紧凑特征描述符z引导。具体而言,softmax运算符应用于channel-wise数字:
a c = e A c z e A c z + e B c z , b c = e B c z e A c z + e B c z a_c = \frac{e^{A_cz}}{e^{A_cz}+e^{B_cz}},b_c = \frac{e^{B_cz}}{e^{A_cz}+e^{B_cz}} ac=eAcz+eBczeAcz,bc=eAcz+eBczeBcz
其中 A , B ∈ R C × d A,B\in{R^{C\times d}} A,BRC×d ,a、b表示 U ~ \widetilde{U} U U ^ \hat{U} U^soft attention A c A_c AcA的第c行,$a_c $ 是a的第c个元素。在两个分支的情况下,矩阵B是冗余的,因为 a c + b c = 1 a_c +b_c = 1 ac+bc=1。最终的特征映射V是通过各种卷积核的注意力权重获得的:
V c = a c ∗ U ~ + b c ∗ U ^ , a c + b c = 1 V_c = a_c * \widetilde{U} + b_c * \hat{U}, a_c +b_c = 1 Vc=acU +bcU^,ac+bc=1
其中 V = [ V 1 , V 2 , . . . , V C ] V=[V_1,V_2,...,V_C] V=[V1,V2,...,VC], V c ∈ R h × w V_c∈R^{h×w} VcRh×w,注意,这里我们提供了一个双分支情况的公式,并且可以通过扩展轻松推断出具有更多分支的情况。

1.4 三分支的情况

这里以三分支为例,绘制网络结构。后续多分支结构与此类似。
SKNet讲解_第2张图片

2. SKNet网络体系结构

SKNet讲解_第3张图片

  1. 与ResNeXt相似,SKNet主要由一堆重复的瓶颈(bottleneck)块组成,称为“ SK单元”。

  2. 每个SK单元由1×1卷积,SK卷积和1×1卷积的序列组成。

  3. 通常,ResNeXt中原始瓶颈块中的所有大内核卷积都将由SK卷积代替

  4. 与ResNeXt-50相比,SKNet-50仅会使参数数量增加10%,计算成本增加5%。

3. 分析与解释

为什么SKNet会取得这么好的效果?
为了理解自适应卷积核选择的工作原理,我们通过输入相同的目标对象但在不同的尺度上分析注意力。我们从ImageNet验证集中获取所有图像实例,并通过中心裁剪和随后的大小调整逐步将中心对象从1.0倍扩大到2.0倍。

SKNet讲解_第4张图片
图1 第一个例子
SKNet讲解_第5张图片
图2 第二个例子

由上述两个例子对比可知:在大多数通道中,当目标对象扩大时,大核(5×5)的注意力权重就会增加,这表明神经元的RF大小会自适应地变大,这与预期相符。
关于跨深度的自适应选择的作用,还有一个令人惊讶的特点:目标对象越大,通过低级和中级阶段(例如,SK 23,SK 34)的“选择性内核”机制,对更大内核的关注就越多。 但是,在更高的层上(例如,SK 5_3),所有比例尺信息都丢失了,这种模式消失了。

SKNet讲解_第6张图片
随着目标规模的增长5×5内核的重要性不断提高。在网络的底层部分(前部),可以根据对象大小的语义意识选择合适的内核大小,从而有效地调整这些神经元的RF大小。但是,这种模式在像SK 5_3这样的较高层中并不存在,因为对于高级表示,“比例”部分编码在特征向量中,并且与较低层的情况相比,内核大小的重要性较小

4. 代码

最后,附上SKNet的代码实现。

import torch
import torch.nn as nn


class SKConv(nn.Module):
    def __init__(self, features, M: int = 2, G: int = 32, r: int = 16, stride: int = 1, L: int = 32):
        super().__init__()
        d = max(features / r, L)
        self.M = M
        self.features = features
        # 1.split
        self.convs = nn.ModuleList([])
        for i in range(M):
            self.convs.append(nn.Sequential(
                nn.Conv2d(features, features, kernel_size=3, stride=stride, padding=1+i, dilation=1+i, groups=G, bias=False),
                nn.BatchNorm2d(features),
                nn.ReLU(inplace=True)
            ))
        # 2.fuse
        self.gap = nn.AdaptiveAvgPool2d(1)
        self.fc = nn.Sequential(nn.Conv2d(features, d, kernel_size=1, stride=1, bias=False),
                                nn.BatchNorm2d(d),
                                nn.ReLU(inplace=True))
        # 3.select
        self.fcs = nn.ModuleList([])
        for i in range(M):
            self.fcs.append(
                 nn.Conv2d(d, features, kernel_size=1, stride=1)
            )
        self.softmax = nn.Softmax(dim=1)
        
    def forward(self, x):
        batch_size = x.shape[0]
        # 1.split
        feats = [conv(x) for conv in self.convs]
        feats = torch.cat(feats, dim=1)
        feats = feats.view(batch_size, self.M, self.features, feats.shape[2], feats.shape[3])
        print('feats.shape', feats.shape)
        # 2.fuse
        feats_U = torch.sum(feats, dim=1)
        feats_S = self.gap(feats_U)
        feats_Z = self.fc(feats_S)
        print('feats_U.shape', feats_U.shape)
        print('feats_S.shape', feats_S.shape)
        print('feats_Z.shape', feats_Z.shape)
        # 3.select
        attention_vectors = [fc(feats_Z) for fc in self.fcs]
        attention_vectors = torch.cat(attention_vectors, dim=1)
        print('attention_vectors.shape', attention_vectors.shape)
        attention_vectors = attention_vectors.view(batch_size, self.M, self.features, 1, 1)
        print('attention_vectors.shape', attention_vectors.shape)
        attention_vectors = self.softmax(attention_vectors)
        feats_V = torch.sum(feats * attention_vectors, dim=1)
        print('feats_V.shape', feats_V.shape)
        return feats_V
    
    
if __name__ == '__main__':
    inputs = torch.randn(4, 64, 512, 512)
    net = SKConv(64)
    outputs = net(inputs)

得到的输出如下所示:

feats.shape torch.Size([4, 2, 64, 512, 512])
feats_U.shape torch.Size([4, 64, 512, 512])
feats_S.shape torch.Size([4, 64, 1, 1])
feats_Z.shape torch.Size([4, 32, 1, 1])
attention_vectors.shape torch.Size([4, 128, 1, 1]) #a, b
attention_vectors.shape torch.Size([4, 2, 64, 1, 1]) #a, b
feats_V.shape torch.Size([4, 64, 512, 512])

总结

SKNet中使用了不同的卷积核,且卷积核权重是不同的,该创新点是突出的!相信一定能在某些情况下取得不错的效果。最后,如果有什么疑问欢迎在评论区提出,对于共性问题可能会后续添加到文章介绍中。

你可能感兴趣的:(机器视觉,深度学习,深度学习,计算机视觉,神经网络,python,cnn)