目录
前言
selective kernel networks(sknet)
来源:cvpr2019
官方代码:https://github.com/implus/sknet
什么是感受野?感受野(receptive field)是指在网络的前向传播过程中,每个神经元对输入数据的区域大小。换句话说,它表示了神经元在输入空间中接收信息的范围。在图像处理任务中,神经元的感受野大小通常与输入图像的像素大小有关。较小的感受野可以捕获局部细节,而较大的感受野则可以捕获更大范围的整体结构和语境信息。因此,设计合适大小的感受野对于不同的任务和网络架构至关重要。skattention能够根据输入动态选择不同大小的卷积核。这种设计使得网络可以根据输入自适应地调整其感受野,从而更有效地捕获不同尺度的特征。这在处理诸如图像分类和对象检测等任务中特别有用,这些任务中输入特征的尺度和大小可能有很大的变化。
一、sknet结构
sknet结构如图一所示。sk卷积由split,select和split三个操作来实现。split操作:使用多个不同大小的卷积核对输入特征进行卷积操作(每次卷积操作即一组cbr),得到多个尺度的特征表示,再将这些特征表示拼接起来;fuse操作:由两个全连接层、一个全局平均池化及relu激活函数组成,首先对多个分支元素求和,即相同形状的张量中的对应元素进行相加。然后进行全局平均池化,压缩为具有相同通道数的特征向量,捕捉全局信息。接着先降维再升维,得到 k 个尺度对应的通道描述符,并将升维后的特征向量重塑为与输入的大小相同,重塑后的特征向量按照第 0 维度(k 维度)进行堆叠,形成一个新的张量,通过 softmax 函数将每个尺度对应的权重进行归一化处理,使得它们的总和为 1。selecte操作:将每个尺度的权重与对应的之前卷积之后的结果加权求和,得到不同的分支权重组合,影响融合后的层级v的有效感受野大小。
精读:split操作:目的:为了捕获多尺度的特征信息,split操作首先将输入特征通过不同大小的卷积核处理。常见的配置可能包括使用3x3、5x5等不同尺寸的卷积核。
实现:每个卷积后接批归一化(batch normalization)和relu激活函数,形成一组卷积-批归一化-激活(cbr)单元。这些不同尺度的特征图接着被拼接在一起,形成一个更丰富的特征表示。
fuse 操作:目的:为了综合多尺度的信息并生成每个尺度的重要性权重,fuse操作处理拼接后的特征,通过全局信息来指导选择操作。
实现:求和:首先将所有分支的特征图进行逐元素相加。全局平均池化:接着对求和后的结果进行全局平均池化,从而压缩特征至一个全局描述符。降维与升维:通过两个全连接层(通常先降维后升维),处理池化后的特征,生成每个尺度的通道描述符。重塑与归一化:将通道描述符重塑成原始输入的尺寸,并通过softmax进行归一化,生成每个尺度的权重。
select 操作:目的:根据fuse操作生成的尺度权重,动态选择并融合不同尺度的特征。
实现:将每个尺度的权重应用于对应的卷积输出(从split操作得到),通过加权求和的方式,结合这些特征。这样,网络可以侧重于当前最有效的特征尺度,从而优化处理结果。
二、sknet计算流程
对于任意给定的特征映射,默认情况下我们首先进行两个变换
:x→
∈
和
:x→
∈
,核大小分别为3×3和5×5,将其分为
和
,并将
和
按元素求和,得到u:
然后通过全局平均池化将(b,c,h,w)压缩到(b,c),单个特征向量
:
然后经过全连接层进行降维和升维:
接下来通过softmax得到各个特征尺度的权重,并在select中将其与卷积后的结果加权求和。
三、sknet参数
利用thop库的profile函数计算flops和param。input:(512,7,7)。
module | flops | param |
skattention | 1079555584 | 22192192 |
四、代码详解
import torch
from torch import nn
from collections import ordereddict
class skattention(nn.module):
#通道数channel, 卷积核尺度kernels, 降维系数reduction, 分组数group, 降维后的通道数l
def __init__(self, channel=512, kernels=[1, 3, 5, 7], reduction=16, group=1, l=32):
super().__init__()
self.d = max(l, channel // reduction)
self.convs = nn.modulelist([])
#有几个kernels,就有几个尺度, 每个尺度对应的卷积层由conv-bn-relu实现
for k in kernels:
self.convs.append(
nn.sequential(ordereddict([
('conv', nn.conv2d(channel, channel, kernel_size=k, padding=k // 2, groups=group)),
('bn', nn.batchnorm2d(channel)),
('relu', nn.relu())
]))
)
self.fc = nn.linear(channel, self.d)
self.fcs = nn.modulelist([])
# 将降维后的通道数l通过k个全连接层得到k个尺度对应的通道描述符表示, 然后基于k个通道描述符计算注意力权重
for i in range(len(kernels)):
self.fcs.append(nn.linear(self.d, channel))
self.softmax = nn.softmax(dim=0)
def forward(self, x):
b, c, h, w = x.size()
# 存放多尺度的输出
conv_outs=[]
## split: 将输入特征x通过k个卷积层得到k个尺度的特征
for conv in self.convs:
scale = conv(x)
conv_outs.append(scale)
feats=torch.stack(conv_outs,0) # torch.stack()函数用于在新创建的维度上对输入的张量序列进行拼接, (b,c,h,w)-->(k,b,c,h,w), k为尺度数
## fuse: 首先将多尺度的信息进行相加,sum()默认在第一个维度进行求和
u=sum(conv_outs) # (k,b,c,h,w)-->sum-->(b,c,h,w)
# 全局平均池化操作: (b,c,h,w)-->mean-->(b,c,h)-->mean-->(b,c) 【mean操作等价于全局平均池化的操作】
s=u.mean(-1).mean(-1)
# 降低通道数,提高计算效率: (b,c)-->(b,d)
z=self.fc(s)
# 将紧凑特征z通过k个全连接层得到k个尺度对应的通道描述符表示, 然后基于k个通道描述符计算注意力权重
weights=[]
for fc in self.fcs:
weight=fc(z) #恢复预输入相同的通道数: (b,d)-->(b,c)
weights.append(weight.view(b,c,1,1)) # (b,c)-->(b,c,1,1)
scale_weight=torch.stack(weights,0) #将k个通道描述符在0个维度上拼接: (k,b,c,1,1)
scale_weight=self.softmax(scale_weight) #在第0个维度上执行softmax,获得每个尺度的权重: (k,b,c,1,1)
## select
v=(scale_weight*feats).sum(0) # 将每个尺度的权重与对应的特征进行加权求和,第一步是加权,第二步是求和:(k,b,c,1,1) * (k,b,c,h,w) = (k,b,c,h,w)-->sum-->(b,c,h,w)
return v
if __name__ == '__main__':
from torchsummary import summary
from thop import profile
model = skattention(channel=512, reduction=8)
# summary(model, (512, 7, 7), device='cpu', batch_size=1)
flops, params = profile(model, inputs=(torch.randn(1, 512, 7, 7),))
print(f"flops: {flops}, params: {params}")
发表评论