SCConv:一种减少CNN计算负担的创新方法

论文复述

这篇论文提出了一种新的卷积神经网络(CNN)模块,名为SCConv(Spatial and Channel Reconstruction Convolution),旨在通过减少特征的空间和通道冗余来提高CNN的压缩效率和特征表示能力。SCConv由两个单元组成:空间重构单元(SRU)和通道重构单元(CRU)。SRU通过分离和重构操作来抑制空间冗余,而CRU则采用分割-转换-融合策略来减少通道冗余。SCConv是一个即插即用的架构单元,可以直接替换各种CNN中的标凑卷积。

论文地址:https://openaccess.thecvf.com/content/CVPR2023/papers/Li_SCConv_Spatial_and_Channel_Reconstruction_Convolution_for_Feature_Redundancy_CVPR_2023_paper.pdf

概要

这篇论文介绍了一种新的卷积神经网络(CNN)模块,SCConv(Spatial and Channel Reconstruction Convolution),它通过减少空间和通道冗余来提高CNN的压缩效率和特征表示能力。SCConv包含两个关键单元:空间重构单元(SRU)和通道重构单元(CRU)。SRU通过分离和重构方法减少空间冗余,而CRU采用分割-转换-融合策略来降低通道冗余。SCConv作为一个即插即用的模块,能够替换现有CNN架构中的标凑卷积,无需额外的模型调整。

引言

引言部分讨论了CNN在计算机视觉任务中的广泛应用和它们在计算资源和存储方面的高需求,这在资源受限的环境中构成了挑战。为了解决这些挑战,研究者探索了模型压缩策略和网络设计,包括网络剪枝、权重量化、低秩分解和知识蒸馏等。同时,也提出了一些轻量级网络设计,如ResNet、DenseNet、MobileNet等,它们通过改进网络拓扑结构来减少参数冗余。然而,现有方法要么只关注通道冗余,要么只关注空间冗余,而SCConv则尝试同时减少这两方面的冗余。

总结

总结部分强调了SCConv模块的两个主要贡献:空间重构单元(SRU)和通道重构单元(CRU),它们通过减少特征图的空间和通道冗余,显著提高了模型性能,同时降低了计算负载。SCConv作为一个高效的、可即插即用的架构单元,能够轻松集成到各种CNN架构中,无需对现有模型架构进行调整。广泛的实验结果表明,与现有的最先进方法相比,SCConv嵌入的模型在图像分类和目标检测任务中提供了更好的性能与效率之间的权衡。作者希望这项工作能够激发更多关于高效CNN架构设计的研究。

全局要点

  1. 双重冗余减少

    • 传统CNN在特征提取过程中存在空间和通道两个维度的冗余,这导致计算资源的浪费。SCConv通过同时针对这两个维度进行优化,减少了特征表示中的冗余信息,提高了特征的表达效率。
  2. 空间重构单元(SRU)的创新

    • SRU利用Group Normalization(GN)层的可训练参数γ来评估不同特征图的空间信息量。通过GN层的标准化处理,可以区分出信息丰富和信息较少的特征图。
    • 通过Sigmoid函数和阈值门控机制,SRU能够分离出具有表达性的特征,并抑制那些空间上冗余的特征,从而增强了特征的空间表示能力。
  3. 通道重构单元(CRU)的创新

    • CRU采用分割-转换-融合策略,首先将输入特征图的通道进行分割,然后通过1x1的卷积来降低通道维度,以减少计算量。
    • 利用分组卷积(GWC)和逐点卷积(PWC)的组合,CRU在减少参数和计算量的同时,能够提取出高质量的特征表示,并通过特征重用来进一步提高效率。
  4. 即插即用模块的设计

    • SCConv作为一个独立的模块,可以方便地集成到现有的CNN架构中,替换掉标准的卷积操作,而不需要对整体网络结构进行大规模的修改或重新设计。
  5. 参数和计算效率的提升

    • 通过减少卷积核的参数数量和优化特征图的处理方式,SCConv显著降低了模型的参数量和浮点运算次数(FLOPs),使得模型更加轻量化,适合在资源受限的环境中部署。
  6. 性能提升的实证

    • 论文通过在多个标准数据集上的实验,展示了SCConv模块在减少计算资源消耗的同时,能够保持或提升模型的准确率。
    • 实验结果表明,SCConv模块在图像分类和目标检测任务中均能实现性能上的提升,证明了其有效性和优越性。

SCConv模块通过其创新的空间和通道重构单元,有效减少了CNN中的冗余,提升了模型性能和计算效率。作为一个灵活的即插即用组件,SCConv在保持或提升准确率的同时,显著降低了模型的参数量和计算负载,为CNN的优化设计提供了新思路。

SCConv模块

SCConv(Spatial and Channel Reconstruction Convolution)是一种新颖的卷积模块,旨在通过减少空间和通道维度上的特征冗余来提高卷积神经网络(CNN)的效率和性能。以下是SCConv的关键特点和其在PyTorch中的一个基本实现示例。

SCConv的关键特点:

  1. 空间重构单元(SRU):通过分离和重构操作减少空间冗余,利用Group Normalization(GN)层的可训练参数来评估特征图的空间信息量。

  2. 通道重构单元(CRU):采用分割-转换-融合策略减少通道冗余,使用分组卷积(GWC)和逐点卷积(PWC)来提取高级特征信息。

  3. 即插即用模块:SCConv设计为可轻松集成到现有CNN架构中的模块,无需对模型架构进行调整。

  4. 性能提升:实验结果表明,SCConv能够在减少计算资源消耗的同时,提高模型的准确率。

空间重构单元(SRU)

空间重构单元(SRU)是SCConv模块的一个关键组成部分,它专注于减少特征图中的空间冗余。以下是SRU的主要特点和工作原理:

  1. 基于权重的分离:SRU使用Group Normalization(GN)层中的可训练参数γ来评估每个特征图的空间信息量。这些参数能够反映不同空间像素的方差,从而衡量特征的重要性。

  2. 信息量评估:通过GN层的标准化处理,SRU能够区分出信息丰富和信息较少的特征图。GN层的输出通过Sigmoid函数和阈值(通常设为0.5)处理,将特征图的权重分为两类:信息丰富的权重和信息较少的权重。

  3. 特征重构:SRU将输入特征X乘以这两类权重,得到两部分特征:信息丰富的特征Xw1和信息较少的特征Xw2。然后,SRU采用交叉重构操作,将这两部分特征结合起来,以增强信息流并生成更丰富的空间特征。

  4. 减少空间冗余:通过这种分离和重构过程,SRU能够有效地减少空间维度上的冗余,同时保留和强化那些对模型性能有重要贡献的特征。

  5. 增强特征表达:SRU的设计不仅减少了冗余,还通过重构操作增强了特征的表达能力,这对于提升模型在复杂视觉任务中的表现至关重要。

  6. 灵活性和兼容性:作为SCConv的一部分,SRU可以与其他模块(如CRU)结合使用,以实现更全面的冗余减少和特征优化。

SRU的创新之处在于它能够智能地识别和处理空间冗余,而不是简单地减少卷积核的大小或深度,这为提高CNN模型的效率和性能提供了一种新的方法。

通道重构单元(CRU)

通道重构单元(CRU)是SCConv模块中的另一个核心组件,专注于减少特征表示中的通道冗余。以下是CRU的关键特点和工作机制:

  1. 分割操作(Split)

    • CRU首先将输入特征图的通道分成两部分,根据分割比例α,一部分包含αC个通道,另一部分包含(1-α)C个通道。这种分割允许网络专注于提取和优化不同子集的特征。
  2. 通道压缩(Squeeze)

    • 利用1x1的卷积操作来压缩特征图的通道,通过引入挤压比例r来平衡CRU的计算成本。这有助于减少模型参数和计算量,同时保持特征表达能力。
  3. 转换操作(Transform)

    • 对分割后的特征进行转换,其中“Rich Feature Extractor”利用分组卷积(GWC)和逐点卷积(PWC)的组合来提取高级特征信息。GWC减少了参数和计算量,而PWC则补偿了由于GWC导致的信息流中断。
  4. 特征融合(Fuse)

    • CRU通过一个简化的SKNet方法来适应性地合并来自转换阶段的上下两部分特征。首先使用全局平均池化来收集全局空间信息,然后通过通道注意力机制生成特征重要性向量,最后根据这些向量在通道维度上合并特征。
  5. 减少通道冗余

    • 通过分割和转换操作,CRU有效地减少了特征图在通道维度上的冗余,同时通过特征重用和轻量级卷积操作来提取丰富且具有代表性的信息。
  6. 提升特征表示

    • CRU的设计不仅减少了冗余,还通过特征融合增强了特征的表达能力,这有助于提高模型对数据的理解力和分类性能。
  7. 灵活性和模块化

    • 作为SCConv的一部分,CRU可以独立使用或与其他模块(如SRU)结合使用,为CNN提供更全面的优化策略。

CRU的创新之处在于其智能的通道管理策略和特征融合机制,这些策略和机制共同作用于减少CNN模型中的通道冗余,同时提升模型的效率和性能。

pytorch代码实现

# coding=utf-8
import torch
import torch.nn as nn

class GN(nn.Module):
    # 初始化
    def __init__(self, groups: int, channels: int, eps=1e-5, affine=True):
        super(GN, self).__init__()
        # 通道数要整除组数
        assert channels % groups == 0, 'channels should be evenly divisible by groups'
        self.groups = groups  # 把通道分成多少组
        self.channels = channels  # 通道数
        self.eps = eps  # 防止分母为0
        self.affine = affine  # 是否使用可学习的线性变化参数
        if self.affine:
            self.scale = nn.Parameter(torch.ones(channels))  # 缩放因子
            self.shift = nn.Parameter(torch.zeros(channels))  # 偏置

    # 前向传播
    def forward(self, x):
        x_shape = x.shape  # 输入特征的维度 [b,c,w,h]
        batch_size = x_shape[0]  # 样本量
        assert self.channels == x.shape[1]  # 预设通道数和输入特征的通道数要保持一致
        # [b,c,w,h]-->[b,g,w*h*c/g]
        x = x.view(batch_size, self.groups, -1)
        # 在最后一个维度上做标准化
        mean = x.mean(dim=[-1], keepdim=True)  # [b,g,1]
        mean_x2 = (x ** 2).mean(dim=[-1], keepdim=True)  # [b,g,1]
        var = mean_x2 - mean ** 2
        x_norm = (x - mean) / torch.sqrt(var + self.eps)  # [b,g,w*h*c/g]
        # 线性变化
        if self.affine:
            x_norm = x_norm.view(batch_size, self.channels, -1)  # [b,c,w*h]
            x_norm = self.scale.view(1, -1, 1) * x_norm + self.shift.view(1, -1, 1)  # [1,c,1]*[b,c,w*h]+[1,c,1]
        # [b,c,w*h]-->[b,c,w,h]
        return x_norm.view(x_shape)
class SRU(nn.Module):
    def __init__(self, channels, groups=32, threshold=0.5):
        super(SRU, self).__init__()
        self.gn = GN(groups=groups, channels=channels, affine=True)
        self.sigmoid = nn.Sigmoid()
        self.threshold = threshold
        self.splitter = nn.Conv2d(channels, 2*channels, kernel_size=1, stride=1, padding=0)

    def forward(self, x):
        weights = self.sigmoid(self.gn(x))

        w1, w2 = weights.clone(), weights.clone()
        w1[w1 > self.threshold] = 1  # 使用副本进行操作
        w2[w2 < self.threshold] = 0  # 使用副本进行操作

        x1 = x * w1
        x2 = x * w2

        x11, x12 = torch.split(x1, split_size_or_sections=x.size(1) // 2, dim=1)
        x21, x22 = torch.split(x2, split_size_or_sections=x.size(1) // 2, dim=1)

        x_refined = torch.cat([x11+x22, x12+x21], dim=1)

        return x_refined


class CRU(nn.Module):
    def __init__(self, in_channels, squeeze_ratio=2, alpha=0.5, groups=16):
        super(CRU, self).__init__()
        self.alpha = alpha
        self.k = int(in_channels * alpha)

        self.squeeze = nn.Conv2d(self.k, in_channels // squeeze_ratio, kernel_size=1, stride=1, padding=0)

        self.up_gwc = nn.Conv2d(in_channels // squeeze_ratio, self.k, kernel_size=3, stride=1, padding=1, groups=self.k // groups)
        self.up_pwc = nn.Conv2d(in_channels // squeeze_ratio, self.k, kernel_size=1, stride=1, padding=0)

        self.low_pwc = nn.Conv2d(in_channels - self.k, in_channels - self.k, kernel_size=1, stride=1, padding=0)

        self.softmax = nn.Softmax(dim=1)

        self.final_conv = nn.Conv2d(2 * in_channels - self.k, in_channels, kernel_size=3, stride=1, padding=1)

    def forward(self, x):
        split_size = int(self.alpha * x.shape[1])
        x_up, x_low = torch.split(x, [split_size, x.shape[1] - split_size], dim=1)

        x_up = self.squeeze(x_up)
        x_up_gwc = self.up_gwc(x_up)
        x_up_pwc = self.up_pwc(x_up)
        y1 = x_up_gwc + x_up_pwc

        x_low_transformed = self.low_pwc(x_low)
        y2 = torch.cat([x_low_transformed, x_low], dim=1)

        global_pool_up = torch.mean(y1, [2, 3], keepdim=True)
        global_pool_low = torch.mean(y2, [2, 3], keepdim=True)

        attention_up = self.softmax(global_pool_up)
        attention_low = self.softmax(global_pool_low)


        x_fused = torch.cat([attention_up * y1, attention_low * y2], dim=1)
        channel_refined = self.final_conv(x_fused)

        return channel_refined

class SCConv(nn.Module):
    def __init__(self, channels, stride=1, groups=32, threshold=0.5, alpha=0.5):
        super(SCConv, self).__init__()
        self.sru = SRU(channels, groups=groups, threshold=threshold)
        self.cru = CRU(channels, alpha=alpha, groups=groups)
        self.conv1 = nn.Conv2d(channels, channels, kernel_size=1, stride=1, padding=0, bias=False)
        self.conv2 = nn.Conv2d(channels, channels, kernel_size=1, stride=1, padding=0, bias=False)
        self.bn1 = nn.BatchNorm2d(channels)
        self.relu = nn.ReLU(inplace=True)
        self.stride = stride


    def forward(self, x):
        residual = x
        out = self.conv1(x)
        out = self.bn1(self.sru(self.cru(out)))
        out = self.conv2(self.relu(out))
        out += residual
        out = self.relu(out)
        return out


x = torch.randn((4, 64, 128, 128))
model = SCConv(channels=64)
out = model(x)
print(out.shape)

注:此代码为手搓的代码,可能会有所偏差!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值