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
网络主要由三个部分组成:Split
、Fuse
、Select
。其中,Split
部分将输入信息X
分别输入不同的核大小(这里是2个卷积核,卷积核大小分别为:3*3
和 5*5
);Fuse
部分进行特征融合;Select
部分根据计算得到的权重对对应的特征进行选择操作。
1.1 Split部分
对于输入信息X:[h,w,C]
,在Split
中,分别输入两个卷积层(默认为2个,根据需要可以设计多个),两个卷积层分别为3*3
和 5*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 为先降维再升维的两层全连接层
。需要注意的是输出的两个矩阵a
和b
,其中矩阵b为冗余矩阵
,在如图两个分支的情况下b=1-a
。
通过简单地使用全局平均池化以生成channel-wise
统计信息 s ∈ R C s\in{R^C} s∈RC 来生成全局信息。具体来说,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=1∑hj=1∑wUc(i,j)
此外,还创建了一个紧凑的特征 z ∈ R d × 1 z\in{R^{d\times1}} z∈Rd×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}} W∈Rd×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
操作使用a
和b
两个权重矩阵对 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,B∈RC×d ,a、b表示 U ~ \widetilde{U} U 和 U ^ \hat{U} U^ 的soft attention
, A c A_c Ac 是A
的第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=ac∗U +bc∗U^,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} Vc∈Rh×w,注意,这里我们提供了一个双分支情况的公式,并且可以通过扩展轻松推断出具有更多分支的情况。
1.4 三分支的情况
这里以三分支为例,绘制网络结构。后续多分支结构与此类似。
2. SKNet网络体系结构
-
与ResNeXt相似,
SKNet
主要由一堆重复的瓶颈(bottleneck)块组成,称为“ SK单元”。 -
每个SK单元由1×1卷积,SK卷积和1×1卷积的序列组成。
-
通常,ResNeXt中原始瓶颈块中的所有大内核卷积都将由
SK卷积代替
。 -
与ResNeXt-50相比,SKNet-50仅会使参数数量增加10%,计算成本增加5%。
3. 分析与解释
为什么SKNet会取得这么好的效果?
为了理解自适应卷积核选择的工作原理,我们通过输入相同的目标对象但在不同的尺度上分析注意力。我们从ImageNet验证集中获取所有图像实例,并通过中心裁剪和随后的大小调整逐步将中心对象从1.0倍扩大到2.0倍。
| |
由上述两个例子对比可知:在大多数通道
中,当目标对象扩大
时,大核(5×5)的注意力权重就会增加
,这表明神经元的RF大小会自适应
地变大,这与预期相符。
关于跨深度的自适应选择的作用,还有一个令人惊讶的特点:目标对象越大,通过低级和中级阶段
(例如,SK 23,SK 34)的“选择性内核”
机制,对更大内核的关注就越多
。 但是,在更高的层
上(例如,SK 5_3),所有比例尺信息都丢失了
,这种模式消失了。
随着目标规模的增长
,5×5内核的重要性不断提高
。在网络的底层部分
(前部),可以根据对象大小的语义意识
来选择
合适的内核大小,从而有效地调整这些神经元的RF大小
。但是,这种模式在像SK 5_3这样的较高层中并不存在
,因为对于高级表示,“比例”部分编码在特征向量中,并且与较低层的情况相比,内核大小的重要性较小
。
4. 代码
最后,附上SKNet
的代码实现。
import torch
import torch.nn as nnclass 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 = Mself.features = features# 1.splitself.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.fuseself.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.selectself.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.splitfeats = [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.fusefeats_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.selectattention_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_Vif __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
中使用了不同的卷积核,且卷积核权重
是不同的,该创新点是突出的!相信一定能在某些情况下取得不错的效果。最后,如果有什么疑问欢迎在评论区提出,对于共性问题可能会后续添加到文章介绍中。