转载:CVPR2023 | SCConv:即插即用的高效卷积模块,减少冗余计算并提升特征学习
希望可以用上这个论文提供的idea。
论文信息:
文章地址:https://openaccess.thecvf.com/content/CVPR2023/papers/Li_SCConv_Spatial_and_Channel_Reconstruction_Convolution_for_Feature_Redundancy_CVPR_2023_paper.pdf
代码复现:https://github.com/cheng-haha/ScConv
转载网址: CVPR2023 | SCConv:即插即用的高效卷积模块,减少冗余计算并提升特征学习
| 导言
本文尝试利用特征之间的空间和通道冗余来进行CNN压缩,并提出了一种高效的卷积模块,称为SCConv (spatial and channel reconstruction convolution),以减少冗余计算并促进代表性特征的学习。提出的SCConv由空间重构单元(SRU)和信道重构单元(CRU)两个单元组成。
(1)SRU根据权重分离冗余特征并进行重构,以抑制空间维度上的冗余,增强特征的表征。
(2)CRU采用分裂变换和融合策略来减少信道维度的冗余以及计算成本和存储。
(3)SCConv是一种即插即用的架构单元,可直接用于替代各种卷积神经网络中的标准卷积。实验结果表明,scconvo嵌入模型能够通过减少冗余特征来获得更好的性能,并且显著降低了复杂度和计算成本。
| 方法
SCConv如图1所示,它由两个单元组成,空间重建单元(SRU)和通道重建单元(CRU),以顺序的方式放置。具体而言,对于瓶颈残差块中的中间输入特征X,首先通过SRU运算获得空间细化特征Xw,然后利用CRU运算获得信道细化特征Y。SCConv模块充分利用了特征之间的空间冗余和通道冗余,可以无缝集成到任何CNN架构中,以减少中间特征映射之间的冗余并增强CNN的特征表示。
1)SRU单元用于空间冗余
为了利用特征的空间冗余,引入了空间重构单元(SRU),如图2所示,它利用了分离和重构操作。
分离操作 的目的是将信息丰富的特征图与空间内容对应的信息较少的特征图分离开来。我们利用组归一化(GN)层中的比例因子来评估不同特征图的信息内容。具体来说,给定一个中间特征映射X∈R N×C×H×W,首先通过减去平均值µ并除以标准差σ来标准化输入特征X,如下所示:
其中µ和σ是X的均值和标准差,ε是为了除法稳定性而加入的一个小的正常数,γ和β是可训练的仿射变换。
GN层中的可训练参数用于测量每个批次和通道的空间像素方差。更丰富的空间信息反映了空间像素的更多变化,从而导致更大的γ。归一化相关权重由下面公式2得到,表示不同特征映射的重要性。
然后将经Wγ重新加权的特征映射的权值通过sigmoid函数映射到(0,1)范围,并通过阈值进行门控。我们将阈值以上的权重设置为1,得到信息权重W1,将其设置为0,得到非信息权重W2(实验中阈值设置为0.5)。获取W的整个过程可以用公式3表示。
最后将输入特征X分别乘以W1和W2,得到两个加权特征:信息量较大的特征和信息量较小的特征。这样就成功地将输入特征分为两部分:具有信息量和表达性的空间内容,而几乎没有信息,被认为是冗余的。
重构操作 将信息丰富的特征与信息较少的特征相加,生成信息更丰富的特征,从而节省空间空间。采用交叉重构运算,将加权后的两个不同的信息特征充分结合起来,加强它们之间的信息流。然后将交叉重构的特征和进行拼接,得到空间精细特征映射。从后过程表示如下:
其中⊗是逐元素的乘法,⊕是逐元素的求和,∪是串联。将SRU应用于中间输入特征X后,不仅将信息特征与信息较少的特征分离,而且对其进行重构,增强代表性特征,抑制空间维度上的冗余特征。然而,空间精细特征映射在通道维度上仍然是冗余的。
2)CRU单元用于通道冗余
分割 操作将输入的空间细化特征分割成两个部分,一部分通道数是,另一部分通道数是,随后对两组特征的通道数使用1 * 1卷积核进行压缩,分别得到和。
转换 操作将输入的作为“富特征提取”的输入,分别进行GWC和PWC,然后相加得到输出Y1,将输入作为“富特征提取”的补充,进行PWC,得到的记过和原来的输入取并集得到Y2。
融合 操作使用简化的SKNet方法来自适应合并Y1和Y2。具体说是首先使用全局平均池化将全局空间信息和通道统计信息结合起来,得到经过池化的S1和S2。然后对S1和S2做Softmax得到特征权重向量和,最后使用特征权重向量得到输出,Y即为通道提炼的特征。
| 实验结果
1)图像分类
在所有的情况下,SCConv-embedded 模型的准确性都优于先前所有的网络。在某些模型中,对比同类模型在减少参数和 FLOPs 的同时还实现了更高的准确率。
2)目标检测
在PASCAL VOC和MS COCO数据集上,scconvr - r50比slimconvr - r50分别高出近0.8%和0.6%。这些结果证明SCConv模块不仅带来了性能的提高,而且帮助网络用更少的参数学习到更好的代表性特征,使得在资源有限的设备上部署目标检测成为可能。
复现代码
import torch # 导入 PyTorch 库
import torch.nn.functional as F # 导入 PyTorch 的函数库
import torch.nn as nn # 导入 PyTorch 的神经网络模块
# 自定义 GroupBatchnorm2d 类,实现分组批量归一化
class GroupBatchnorm2d(nn.Module):
def __init__(self, c_num:int, group_num:int = 16, eps:float = 1e-10):
super(GroupBatchnorm2d,self).__init__() # 调用父类构造函数
assert c_num >= group_num # 断言 c_num 大于等于 group_num
self.group_num = group_num # 设置分组数量
self.gamma = nn.Parameter(torch.randn(c_num, 1, 1)) # 创建可训练参数 gamma
self.beta = nn.Parameter(torch.zeros(c_num, 1, 1)) # 创建可训练参数 beta
self.eps = eps # 设置小的常数 eps 用于稳定计算
def forward(self, x):
N, C, H, W = x.size() # 获取输入张量的尺寸
x = x.view(N, self.group_num, -1) # 将输入张量重新排列为指定的形状
mean = x.mean(dim=2, keepdim=True) # 计算每个组的均值
std = x.std(dim=2, keepdim=True) # 计算每个组的标准差
x = (x - mean) / (std + self.eps) # 应用批量归一化
x = x.view(N, C, H, W) # 恢复原始形状
return x * self.gamma + self.beta # 返回归一化后的张量
# 自定义 SRU(Spatial and Reconstruct Unit)类
class SRU(nn.Module):
def __init__(self,
oup_channels:int, # 输出通道数
group_num:int = 16, # 分组数,默认为16
gate_treshold:float = 0.5, # 门控阈值,默认为0.5
torch_gn:bool = False # 是否使用PyTorch内置的GroupNorm,默认为False
):
super().__init__() # 调用父类构造函数
# 初始化 GroupNorm 层或自定义 GroupBatchnorm2d 层
self.gn = nn.GroupNorm(num_channels=oup_channels, num_groups=group_num) if torch_gn else GroupBatchnorm2d(c_num=oup_channels, group_num=group_num)
self.gate_treshold = gate_treshold # 设置门控阈值
self.sigomid = nn.Sigmoid() # 创建 sigmoid 激活函数
def forward(self, x):
gn_x = self.gn(x) # 应用分组批量归一化
w_gamma = self.gn.gamma / sum(self.gn.gamma) # 计算 gamma 权重
reweights = self.sigomid(gn_x * w_gamma) # 计算重要性权重
# 门控机制
info_mask = reweights >= self.gate_treshold # 计算信息门控掩码
noninfo_mask = reweights < self.gate_treshold # 计算非信息门控掩码
x_1 = info_mask * x # 使用信息门控掩码
x_2 = noninfo_mask * x # 使用非信息门控掩码
x = self.reconstruct(x_1, x_2) # 重构特征
return x
def reconstruct(self, x_1, x_2):
x_11, x_12 = torch.split(x_1, x_1.size(1) // 2, dim=1) # 拆分特征为两部分
x_21, x_22 = torch.split(x_2, x_2.size(1) // 2, dim=1) # 拆分特征为两部分
return torch.cat([x_11 + x_22, x_12 + x_21], dim=1) # 重构特征并连接
# 自定义 CRU(Channel Reduction Unit)类
class CRU(nn.Module):
def __init__(self, op_channel:int, alpha:float = 1/2, squeeze_radio:int = 2, group_size:int = 2, group_kernel_size:int = 3):
super().__init__() # 调用父类构造函数
self.up_channel = up_channel = int(alpha * op_channel) # 计算上层通道数
self.low_channel = low_channel = op_channel - up_channel # 计算下层通道数
self.squeeze1 = nn.Conv2d(up_channel, up_channel // squeeze_radio, kernel_size=1, bias=False) # 创建卷积层
self.squeeze2 = nn.Conv2d(low_channel, low_channel // squeeze_radio, kernel_size=1, bias=False) # 创建卷积层
# 上层特征转换
self.GWC = nn.Conv2d(up_channel // squeeze_radio, op_channel, kernel_size=group_kernel_size, stride=1, padding=group_kernel_size // 2, groups=group_size) # 创建卷积层
self.PWC1 = nn.Conv2d(up_channel // squeeze_radio, op_channel, kernel_size=1, bias=False) # 创建卷积层
# 下层特征转换
self.PWC2 = nn.Conv2d(low_channel // squeeze_radio, op_channel - low_channel // squeeze_radio, kernel_size=1, bias=False) # 创建卷积层
self.advavg = nn.AdaptiveAvgPool2d(1) # 创建自适应平均池化层
def forward(self, x):
# 分割输入特征
up, low = torch.split(x, [self.up_channel, self.low_channel], dim=1)
up, low = self.squeeze1(up), self.squeeze2(low)
# 上层特征转换
Y1 = self.GWC(up) + self.PWC1(up)
# 下层特征转换
Y2 = torch.cat([self.PWC2(low), low], dim=1)
# 特征融合
out = torch.cat([Y1, Y2], dim=1)
out = F.softmax(self.advavg(out), dim=1) * out
out1, out2 = torch.split(out, out.size(1) // 2, dim=1)
return out1 + out2
# 自定义 ScConv(Squeeze and Channel Reduction Convolution)模型
class ScConv(nn.Module):
def __init__(self, op_channel:int, group_num:int = 16, gate_treshold:float = 0.5, alpha:float = 1/2, squeeze_radio:int = 2, group_size:int = 2, group_kernel_size:int = 3):
super().__init__() # 调用父类构造函数
self.SRU = SRU(op_channel, group_num=group_num, gate_treshold=gate_treshold) # 创建 SRU 层
self.CRU = CRU(op_channel, alpha=alpha, squeeze_radio=squeeze_radio, group_size=group_size, group_kernel_size=group_kernel_size) # 创建 CRU 层
def forward(self, x):
x = self.SRU(x) # 应用 SRU 层
x = self.CRU(x) # 应用 CRU 层
return x
if __name__ == '__main__':
x = torch.randn(1, 32, 16, 16) # 创建随机输入张量
model = ScConv(32) # 创建 ScConv 模型
print(model(x).shape) # 打印模型输出的形状
更多推荐
所有评论(0)