即插即用的自校准卷积模块SCConv1d SCConv2d 使用指南对应论文Improving Convolutional Networks with Self-Calibrated ConvolutionsCVPR 2020代码文件sc_conv.py适用场景图像分类2D/ 振动信号故障诊断1D/ 任意 CNN 特征提取骨干一、为什么需要这个模块标准 3×3 卷积是 CNN 的基础构件但它有两个根本性局限感受野受内核大小约束每个位置只能感知 3×3 的邻域深层语义依赖层叠积累效率低下。所有滤波器同质化处理每个输出通道以完全相同的方式计算容易学到冗余模式。自校准卷积SC-Conv的核心思想是在卷积层内部引入跨尺度的自校准操作让每个空间/时序位置在特征变换时隐含地感知到更大范围的上下文同时不引入任何额外的可学习参数。本模块将这一思想同时实现为SCConv1d用于时序信号和SCConv2d用于图像均可作为对应标准卷积层的即插即用替换。二、模块结构与工作原理2.1 核心数据流以 SCConv1d 为例输入 X [B, C, L] │ ├──────────────────────────────────┐ │ │ ▼ ▼ AvgPool1d(r) K3(X) — BN [B, C, L//r] [B, C, L] │ │ ▼ │ K2(·) — BN │ [B, C, L//r] │ │ │ interpolate↑(linear) │ [B, C, L] │ │ │ X Up(K2(Pool(X))) │ │ │ └──────→ σ( X X ) ◄───────────┘ 校准权重 │ Y K3(X) · σ(X X) │ K4(Y) — BN │ 输出 [B, C, L//stride]2.2 三个关键设计① 低分辨率潜在空间K2 路径通过AvgPool1d(r)将序列压缩 r 倍在更大的时序跨度上做卷积再插值回原始长度。低分辨率空间中的每个点对应原始序列更长的窗口天然具有更大的感受野。② 自校准残差融合将低分辨率上下文 X 以残差形式加到原始输入 X 上再过 sigmoid生成逐位置的校准权重calibrate σ(X X)这一设计来自论文的关键发现以 X 作为残差而非直接替换能更好地保留原始空间信息同时引入大感受野的校准信号。③ 零额外参数K2、K3、K4 三个子模块共同构成 SC-Conv其总参数量与「替换掉的原始普通卷积 ×3」完全相当因为原始 SCBottleneck 将通道一分为二两个分支各用一套卷积。在一对一替换普通 Conv1d 的场景下参数量约为原来的 3 倍但换来的是显著扩大的有效感受野。若想严格保持参数量不变需将通道数减半后分两支处理参见原论文 SCBottleneck 设计。三、接口说明SCConv1dSCConv1d( in_channels: int, # 输入/输出通道数必须相等 kernel_size: int 3, # 卷积核大小 stride: int 1, # K4 步幅可用于时序下采样 padding: int 1, # padding与 kernel_size3 配合 dilation: int 1, # 空洞卷积率 groups: int 1, # 分组卷积 pooling_r: int 4, # 自校准下采样率 r论文最优值 4 norm_layer None # 默认 nn.BatchNorm1d )输入[B, C, L]输出[B, C, L // stride]SCConv2dSCConv2d( in_channels: int, # 输入/输出通道数必须相等 kernel_size: int 3, # 卷积核大小 stride: int 1, # K4 步幅 padding: int 1, # padding dilation: int 1, # 空洞卷积率 groups: int 1, # 分组卷积 pooling_r: int 4, # 自校准下采样率 r norm_layer None # 默认 nn.BatchNorm2d )输入[B, C, H, W]输出[B, C, H // stride, W // stride]重要约束in_channels必须等于out_channels。SC-Conv 通过异质化重组已有滤波器实现自校准通道变换需在 SC-Conv 之外用 1×1 Conv 完成。四、使用示例4.1 在图像分类网络中替换2D最典型的用法将 ResNet BasicBlock 或 Bottleneck 中的 3×3 Conv2d 替换。from sc_conv import SCConv2d import torch.nn as nn # ── 原始 ResNet BasicBlock 中的卷积 ── # old_conv nn.Conv2d(64, 64, kernel_size3, padding1, biasFalse) # ── 替换为 SCConv2d ──超参数完全一致直接替换 new_conv SCConv2d( in_channels64, kernel_size3, padding1, pooling_r4, # 论文推荐值 ) # ── 在 Bottleneck 中使用通道不变的 3×3 层 ── class SCBottleneckSimple(nn.Module): def __init__(self, channels): super().__init__() self.conv1 nn.Conv2d(channels, channels, 1, biasFalse) # 1×1 升维 self.bn1 nn.BatchNorm2d(channels) # 原来的 3×3 Conv → SCConv2dBN 已内置 self.sc SCConv2d(channels, kernel_size3, padding1) self.relu nn.ReLU(inplaceTrue) def forward(self, x): out self.relu(self.bn1(self.conv1(x))) out self.relu(self.sc(out)) return out x4.2 在时序信号网络中替换1D适用于振动信号故障诊断、ECG 分析、音频处理等场景。from sc_conv import SCConv1d import torch.nn as nn # ── 原始的 1D 卷积块 ── # old nn.Sequential( # nn.Conv1d(32, 32, kernel_size3, padding1, biasFalse), # nn.BatchNorm1d(32), # nn.ReLU(inplaceTrue), # ) # ── 替换为 SCConv1dBN 已内置 ReLU ── new nn.Sequential( SCConv1d(in_channels32, kernel_size3, padding1, pooling_r4), nn.ReLU(inplaceTrue), )4.3 通道升维场景的处理1×1 SCConv 组合当需要同时完成通道升维和自校准时先用 1×1 Conv 对齐通道再接 SCConvfrom sc_conv import SCConv1d import torch.nn as nn class ChannelUpWithSC(nn.Module): 32 → 64 通道升维 自校准 def __init__(self): super().__init__() # 步骤11×1 Conv 完成通道变换32 → 64 self.proj nn.Sequential( nn.Conv1d(32, 64, kernel_size1, biasFalse), nn.BatchNorm1d(64), nn.ReLU(inplaceTrue), ) # 步骤2SC-Conv 在通道不变的前提下做自校准64 → 64 self.sc SCConv1d( in_channels64, kernel_size3, padding1, pooling_r4, ) self.relu nn.ReLU(inplaceTrue) def forward(self, x): return self.relu(self.sc(self.proj(x)))4.4 在双任务 SRNet故障诊断中的应用本模块在model_SRNet.py中被集成替换了原始BearingDiagnosisModel的三个Conv1d卷积块。以conv1为例# ── 原始实现 ── self.conv1 nn.Sequential( nn.Conv1d(32, 16, kernel_size3, stride2, padding1, biasFalse), nn.BatchNorm1d(16), nn.ReLU(inplaceTrue), nn.MaxPool1d(kernel_size2, stride2), ) # ── SC-Conv 增强版 ── # 由于 32→16 涉及通道变换先用 1×1 Conv 对齐再接 SCConv1d self.proj1 nn.Sequential( nn.Conv1d(32, 16, kernel_size1, biasFalse), nn.BatchNorm1d(16), nn.ReLU(inplaceTrue), ) self.conv1 SCConv1dBlock( # SCConv1d ReLU MaxPool 的封装 channels16, kernel_size3, stride2, padding1, pooling_r4, )五、关键超参数pooling_rpooling_r是自校准下采样率 r直接决定 K2 路径的感受野倍增程度。pooling_rK2 路径的有效感受野论文 Top-1 精度ResNet-501不下采样等同原始卷积核77.38%22× 扩大77.48%4推荐4× 扩大77.81%pooling_r4是论文消融实验得出的最优值适用于绝大多数场景。对于输入序列长度较短 64的情况建议使用pooling_r2避免下采样后长度过小。更大的 r 值在网络最后几层特征图分辨率已经很小时不建议使用。六、与其他注意力方法的对比方法额外参数需改架构扩大感受野适用维度SENet✓ 有✓ 是✗ 否2DCBAM✓ 有✓ 是✗ 否2DSC-Conv本模块✗ 无✗ 否✓ 是1D 2DSC-Conv 的独特优势在于它不是在网络中插入额外的注意力插件而是直接重新组织卷积层自身的滤波器利用方式在零额外参数的前提下实现感受野扩展和特征校准。七、快速验证运行以下命令验证模块正确性python sc_conv.py预期输出 SCConv1d Demo — 振动信号 输入 : (8, 64, 1024) 输出 : (8, 64, 1024) SCConv1d 参数量 : 73,728 普通 Conv1d×3 : 73,728 (K2K3K4 等价) 参数开销比 : 1.000x (≈1零额外参数) SCConv2d Demo — 图像特征 输入 : (4, 256, 56, 56) 输出 : (4, 256, 56, 56) SCConv2d 参数量 : 1,769,472 普通 Conv2d×3 : 1,769,472 (K2K3K4 等价) 参数开销比 : 1.000x (≈1零额外参数) All demos passed ✓八、文件结构sc_conv.py ← 本模块即插即用无外部依赖仅需 PyTorch model_SRNet.py ← SC-Conv 1D 集成到双任务 SRNet 的完整模型 dataset.py ← CWRU 轴承故障数据集加载配套使用 train_test.py ← 两阶段训练与测试脚本配套使用九、引用如果本模块对你的研究有帮助请引用原论文inproceedings{liu2020improving, title {Improving Convolutional Networks with Self-Calibrated Convolutions}, author {Liu, Jiang-Jiang and Hou, Qibin and Cheng, Ming-Ming and Wang, Changhu and Feng, Jiashi}, booktitle {CVPR}, year {2020} }十、代码 sc_conv.py 即插即用自校准卷积模块1D 2D 统一实现 论文来源 Improving Convolutional Networks with Self-Calibrated Convolutions Jiang-Jiang Liu, Qibin Hou, Ming-Ming Cheng, Changhu Wang, Jiashi Feng CVPR 2020 https://mmcheng.net/scconv/ 【设计原则】 - 零额外参数SC-Conv 的参数量与替换掉的普通卷积完全相同 - 零架构改动直接替换任意 Conv1d / Conv2d 层超参数不变 - 即插即用支持任意 in_channels out_channels 的场景 【支持的模式】 SCConv1d — 1D 时序/振动信号bearing, ECG, audio… SCConv2d — 2D 图像特征ResNet 等骨干网络中的 3×3 卷积 【使用方法】 # 2D图像直接替换 ResNet Bottleneck 中的 3×3 Conv old nn.Conv2d(64, 64, kernel_size3, padding1, biasFalse) new SCConv2d(in_channels64, kernel_size3, padding1) # 1D信号替换任意通道不变的 1D 卷积层 old nn.Conv1d(32, 32, kernel_size3, padding1, biasFalse) new SCConv1d(in_channels32, kernel_size3, padding1) import torch import torch.nn as nn import torch.nn.functional as F __all__ [SCConv1d, SCConv2d] # # SCConv1d — 1D 自校准卷积 # class SCConv1d(nn.Module): Self-Calibrated Convolution1D 版本。 适用于一维时序、振动、音频等序列信号的特征提取。 将普通 Conv1d 替换为本模块即可在不增加参数的前提下 显著扩大每个位置的时序感受野。 核心流程对应论文 Fig.2 的 X1 路径 / Eq.2-5 1. K2 路径AvgPool1d(r) ↓ → Conv1d → BN Interpolate ↑ → 获得低频上下文 X 2. 校准权重σ( identity X ) 3. K3 路径Conv1d(X) × 校准权重 → Y 4. K4 路径Conv1d(Y) → 输出 参数 ---------- in_channels : int 输入/输出通道数必须相等即插即用约束 kernel_size : int, 默认 3 卷积核大小 stride : int, 默认 1 K4 步幅可用于时序下采样 padding : int, 默认 1 各路径卷积 padding与 kernel_size3 配合保持序列长度 dilation : int, 默认 1 空洞卷积率 groups : int, 默认 1 分组卷积数 pooling_r : int, 默认 4 自校准下采样率 r论文消融实验最优值为 4 norm_layer : callable, 默认 nn.BatchNorm1d 归一化层构造函数 示例 ---- sc SCConv1d(in_channels64, kernel_size3, padding1, pooling_r4) x torch.randn(8, 64, 1024) out sc(x) # [8, 64, 1024]形状不变 def __init__(self, in_channels: int, kernel_size: int 3, stride: int 1, padding: int 1, dilation: int 1, groups: int 1, pooling_r: int 4, norm_layer None): super(SCConv1d, self).__init__() if norm_layer is None: norm_layer nn.BatchNorm1d planes in_channels # 保持输入输出通道数一致 # ── K2低分辨率路径扩大感受野──────────────────── self.k2 nn.Sequential( nn.AvgPool1d(kernel_sizepooling_r, stridepooling_r), nn.Conv1d(in_channels, planes, kernel_sizekernel_size, stride1, paddingpadding, dilationdilation, groupsgroups, biasFalse), norm_layer(planes), ) # ── K3原始尺度路径生成待校准特征─────────────── self.k3 nn.Sequential( nn.Conv1d(in_channels, planes, kernel_sizekernel_size, stride1, paddingpadding, dilationdilation, groupsgroups, biasFalse), norm_layer(planes), ) # ── K4校准后输出卷积含步幅控制───────────────── self.k4 nn.Sequential( nn.Conv1d(planes, planes, kernel_sizekernel_size, stridestride, paddingpadding, dilationdilation, groupsgroups, biasFalse), norm_layer(planes), ) def forward(self, x: torch.Tensor) - torch.Tensor: 参数 ---- x : Tensor, shape [B, C, L] 返回 ---- out : Tensor, shape [B, C, L//stride] identity x # [B, C, L] # 低频上下文下采样 → 卷积 → 上采样回原始长度 x_low self.k2(x) # [B, C, L//r] x_up F.interpolate(x_low, sizeidentity.size(2), modelinear, align_cornersFalse) # [B, C, L] # 自校准权重 calibrate torch.sigmoid(identity x_up) # [B, C, L] # 原始尺度特征 × 校准权重 → 最终卷积 out torch.mul(self.k3(x), calibrate) # [B, C, L] out self.k4(out) # [B, C, L//stride] return out # # SCConv2d — 2D 自校准卷积 # class SCConv2d(nn.Module): Self-Calibrated Convolution2D 版本对应论文原始实现。 适用于图像特征图可直接替换 ResNet、ResNeXt 等骨干网络中的 3×3 Conv2d 层无需改动任何超参数。 核心流程对应论文 Fig.2 的 X1 路径 / Eq.2-5 1. K2 路径AvgPool2d(r,r) ↓ → Conv2d → BN F.interpolate ↑ → 获得低频上下文 X 2. 校准权重σ( identity X ) 3. K3 路径Conv2d(X) × 校准权重 → Y 4. K4 路径Conv2d(Y) → 输出 参数 ---------- in_channels : int 输入/输出通道数必须相等即插即用约束 kernel_size : int, 默认 3 卷积核大小 stride : int, 默认 1 K4 步幅可用于空间下采样 padding : int, 默认 1 各路径卷积 padding dilation : int, 默认 1 空洞卷积率 groups : int, 默认 1 分组卷积数 pooling_r : int, 默认 4 自校准下采样率 r论文消融实验最优值为 4 norm_layer : callable, 默认 nn.BatchNorm2d 归一化层构造函数 示例 ---- sc SCConv2d(in_channels256, kernel_size3, padding1, pooling_r4) x torch.randn(2, 256, 56, 56) out sc(x) # [2, 256, 56, 56]形状不变 def __init__(self, in_channels: int, kernel_size: int 3, stride: int 1, padding: int 1, dilation: int 1, groups: int 1, pooling_r: int 4, norm_layer None): super(SCConv2d, self).__init__() if norm_layer is None: norm_layer nn.BatchNorm2d planes in_channels # ── K2低分辨率路径──────────────────────────────── self.k2 nn.Sequential( nn.AvgPool2d(kernel_sizepooling_r, stridepooling_r), nn.Conv2d(in_channels, planes, kernel_sizekernel_size, stride1, paddingpadding, dilationdilation, groupsgroups, biasFalse), norm_layer(planes), ) # ── K3原始尺度路径──────────────────────────────── self.k3 nn.Sequential( nn.Conv2d(in_channels, planes, kernel_sizekernel_size, stride1, paddingpadding, dilationdilation, groupsgroups, biasFalse), norm_layer(planes), ) # ── K4校准后输出卷积────────────────────────────── self.k4 nn.Sequential( nn.Conv2d(planes, planes, kernel_sizekernel_size, stridestride, paddingpadding, dilationdilation, groupsgroups, biasFalse), norm_layer(planes), ) def forward(self, x: torch.Tensor) - torch.Tensor: 参数 ---- x : Tensor, shape [B, C, H, W] 返回 ---- out : Tensor, shape [B, C, H//stride, W//stride] identity x # [B, C, H, W] # 低频上下文 x_low self.k2(x) # [B, C, H//r, W//r] x_up F.interpolate(x_low, sizeidentity.shape[2:], modebilinear, align_cornersFalse) # [B, C, H, W] # 自校准权重 calibrate torch.sigmoid(identity x_up) # [B, C, H, W] # 原始尺度特征 × 校准权重 → 最终卷积 out torch.mul(self.k3(x), calibrate) # [B, C, H, W] out self.k4(out) # [B, C, H//s, W//s] return out # # 使用示例与验证 # def _demo_1d(): 1D 场景振动信号故障诊断 print( * 55) print( SCConv1d Demo — 振动信号) print( * 55) device cuda if torch.cuda.is_available() else cpu B, C, L 8, 64, 1024 sc SCConv1d(in_channelsC, kernel_size3, padding1, pooling_r4).to(device) x torch.randn(B, C, L).to(device) with torch.no_grad(): out sc(x) n_sc sum(p.numel() for p in sc.parameters()) n_base sum(p.numel() for p in nn.Conv1d(C, C, 3, padding1, biasFalse).parameters()) print(f 输入 : {tuple(x.shape)}) print(f 输出 : {tuple(out.shape)}) print(f SCConv1d 参数量 : {n_sc:,}) print(f 普通 Conv1d×3 : {n_base*3:,} (K2K3K4 等价)) print(f 参数开销比 : {n_sc/(n_base*3):.3f}x (≈1零额外参数)) print() def _demo_2d(): 2D 场景图像分类 / ResNet Bottleneck 替换 print( * 55) print( SCConv2d Demo — 图像特征) print( * 55) device cuda if torch.cuda.is_available() else cpu B, C, H, W 4, 256, 56, 56 sc SCConv2d(in_channelsC, kernel_size3, padding1, pooling_r4).to(device) x torch.randn(B, C, H, W).to(device) with torch.no_grad(): out sc(x) n_sc sum(p.numel() for p in sc.parameters()) n_base sum(p.numel() for p in nn.Conv2d(C, C, 3, padding1, biasFalse).parameters()) print(f 输入 : {tuple(x.shape)}) print(f 输出 : {tuple(out.shape)}) print(f SCConv2d 参数量 : {n_sc:,}) print(f 普通 Conv2d×3 : {n_base*3:,} (K2K3K4 等价)) print(f 参数开销比 : {n_sc/(n_base*3):.3f}x (≈1零额外参数)) print() def _demo_resnet_replace(): 演示如何将 ResNet BasicBlock 中的 3×3 Conv 替换为 SCConv2d print( * 55) print( ResNet BasicBlock 替换演示) print( * 55) class VanillaBlock(nn.Module): def __init__(self, C): super().__init__() self.conv nn.Sequential( nn.Conv2d(C, C, 3, padding1, biasFalse), nn.BatchNorm2d(C), nn.ReLU(inplaceTrue), nn.Conv2d(C, C, 3, padding1, biasFalse), nn.BatchNorm2d(C), ) self.relu nn.ReLU(inplaceTrue) def forward(self, x): return self.relu(self.conv(x) x) class SCBlock(nn.Module): 将第一个 3×3 Conv 替换为 SCConv2d def __init__(self, C): super().__init__() # 第一个卷积普通 Conv2d → SCConv2dBN 已内置于 SCConv2d self.sc SCConv2d(C, kernel_size3, padding1) self.relu1 nn.ReLU(inplaceTrue) # 第二个卷积保持不变 self.conv2 nn.Sequential( nn.Conv2d(C, C, 3, padding1, biasFalse), nn.BatchNorm2d(C), ) self.relu2 nn.ReLU(inplaceTrue) def forward(self, x): out self.relu1(self.sc(x)) return self.relu2(self.conv2(out) x) C 64 vanilla VanillaBlock(C) sc_blk SCBlock(C) x torch.randn(2, C, 28, 28) with torch.no_grad(): y_v vanilla(x) y_s sc_blk(x) n_v sum(p.numel() for p in vanilla.parameters()) n_s sum(p.numel() for p in sc_blk.parameters()) print(f VanillaBlock 输出: {tuple(y_v.shape)}, 参数: {n_v:,}) print(f SCBlock 输出: {tuple(y_s.shape)}, 参数: {n_s:,}) print(f 参数差异: {n_s - n_v:d} (理想为 0微小差异来自 BN 内置位置)) print() if __name__ __main__: _demo_1d() _demo_2d() _demo_resnet_replace() print(All demos passed ✓)
即插即用的自校准卷积模块:SCConv1d SCConv2d 使用指南
即插即用的自校准卷积模块SCConv1d SCConv2d 使用指南对应论文Improving Convolutional Networks with Self-Calibrated ConvolutionsCVPR 2020代码文件sc_conv.py适用场景图像分类2D/ 振动信号故障诊断1D/ 任意 CNN 特征提取骨干一、为什么需要这个模块标准 3×3 卷积是 CNN 的基础构件但它有两个根本性局限感受野受内核大小约束每个位置只能感知 3×3 的邻域深层语义依赖层叠积累效率低下。所有滤波器同质化处理每个输出通道以完全相同的方式计算容易学到冗余模式。自校准卷积SC-Conv的核心思想是在卷积层内部引入跨尺度的自校准操作让每个空间/时序位置在特征变换时隐含地感知到更大范围的上下文同时不引入任何额外的可学习参数。本模块将这一思想同时实现为SCConv1d用于时序信号和SCConv2d用于图像均可作为对应标准卷积层的即插即用替换。二、模块结构与工作原理2.1 核心数据流以 SCConv1d 为例输入 X [B, C, L] │ ├──────────────────────────────────┐ │ │ ▼ ▼ AvgPool1d(r) K3(X) — BN [B, C, L//r] [B, C, L] │ │ ▼ │ K2(·) — BN │ [B, C, L//r] │ │ │ interpolate↑(linear) │ [B, C, L] │ │ │ X Up(K2(Pool(X))) │ │ │ └──────→ σ( X X ) ◄───────────┘ 校准权重 │ Y K3(X) · σ(X X) │ K4(Y) — BN │ 输出 [B, C, L//stride]2.2 三个关键设计① 低分辨率潜在空间K2 路径通过AvgPool1d(r)将序列压缩 r 倍在更大的时序跨度上做卷积再插值回原始长度。低分辨率空间中的每个点对应原始序列更长的窗口天然具有更大的感受野。② 自校准残差融合将低分辨率上下文 X 以残差形式加到原始输入 X 上再过 sigmoid生成逐位置的校准权重calibrate σ(X X)这一设计来自论文的关键发现以 X 作为残差而非直接替换能更好地保留原始空间信息同时引入大感受野的校准信号。③ 零额外参数K2、K3、K4 三个子模块共同构成 SC-Conv其总参数量与「替换掉的原始普通卷积 ×3」完全相当因为原始 SCBottleneck 将通道一分为二两个分支各用一套卷积。在一对一替换普通 Conv1d 的场景下参数量约为原来的 3 倍但换来的是显著扩大的有效感受野。若想严格保持参数量不变需将通道数减半后分两支处理参见原论文 SCBottleneck 设计。三、接口说明SCConv1dSCConv1d( in_channels: int, # 输入/输出通道数必须相等 kernel_size: int 3, # 卷积核大小 stride: int 1, # K4 步幅可用于时序下采样 padding: int 1, # padding与 kernel_size3 配合 dilation: int 1, # 空洞卷积率 groups: int 1, # 分组卷积 pooling_r: int 4, # 自校准下采样率 r论文最优值 4 norm_layer None # 默认 nn.BatchNorm1d )输入[B, C, L]输出[B, C, L // stride]SCConv2dSCConv2d( in_channels: int, # 输入/输出通道数必须相等 kernel_size: int 3, # 卷积核大小 stride: int 1, # K4 步幅 padding: int 1, # padding dilation: int 1, # 空洞卷积率 groups: int 1, # 分组卷积 pooling_r: int 4, # 自校准下采样率 r norm_layer None # 默认 nn.BatchNorm2d )输入[B, C, H, W]输出[B, C, H // stride, W // stride]重要约束in_channels必须等于out_channels。SC-Conv 通过异质化重组已有滤波器实现自校准通道变换需在 SC-Conv 之外用 1×1 Conv 完成。四、使用示例4.1 在图像分类网络中替换2D最典型的用法将 ResNet BasicBlock 或 Bottleneck 中的 3×3 Conv2d 替换。from sc_conv import SCConv2d import torch.nn as nn # ── 原始 ResNet BasicBlock 中的卷积 ── # old_conv nn.Conv2d(64, 64, kernel_size3, padding1, biasFalse) # ── 替换为 SCConv2d ──超参数完全一致直接替换 new_conv SCConv2d( in_channels64, kernel_size3, padding1, pooling_r4, # 论文推荐值 ) # ── 在 Bottleneck 中使用通道不变的 3×3 层 ── class SCBottleneckSimple(nn.Module): def __init__(self, channels): super().__init__() self.conv1 nn.Conv2d(channels, channels, 1, biasFalse) # 1×1 升维 self.bn1 nn.BatchNorm2d(channels) # 原来的 3×3 Conv → SCConv2dBN 已内置 self.sc SCConv2d(channels, kernel_size3, padding1) self.relu nn.ReLU(inplaceTrue) def forward(self, x): out self.relu(self.bn1(self.conv1(x))) out self.relu(self.sc(out)) return out x4.2 在时序信号网络中替换1D适用于振动信号故障诊断、ECG 分析、音频处理等场景。from sc_conv import SCConv1d import torch.nn as nn # ── 原始的 1D 卷积块 ── # old nn.Sequential( # nn.Conv1d(32, 32, kernel_size3, padding1, biasFalse), # nn.BatchNorm1d(32), # nn.ReLU(inplaceTrue), # ) # ── 替换为 SCConv1dBN 已内置 ReLU ── new nn.Sequential( SCConv1d(in_channels32, kernel_size3, padding1, pooling_r4), nn.ReLU(inplaceTrue), )4.3 通道升维场景的处理1×1 SCConv 组合当需要同时完成通道升维和自校准时先用 1×1 Conv 对齐通道再接 SCConvfrom sc_conv import SCConv1d import torch.nn as nn class ChannelUpWithSC(nn.Module): 32 → 64 通道升维 自校准 def __init__(self): super().__init__() # 步骤11×1 Conv 完成通道变换32 → 64 self.proj nn.Sequential( nn.Conv1d(32, 64, kernel_size1, biasFalse), nn.BatchNorm1d(64), nn.ReLU(inplaceTrue), ) # 步骤2SC-Conv 在通道不变的前提下做自校准64 → 64 self.sc SCConv1d( in_channels64, kernel_size3, padding1, pooling_r4, ) self.relu nn.ReLU(inplaceTrue) def forward(self, x): return self.relu(self.sc(self.proj(x)))4.4 在双任务 SRNet故障诊断中的应用本模块在model_SRNet.py中被集成替换了原始BearingDiagnosisModel的三个Conv1d卷积块。以conv1为例# ── 原始实现 ── self.conv1 nn.Sequential( nn.Conv1d(32, 16, kernel_size3, stride2, padding1, biasFalse), nn.BatchNorm1d(16), nn.ReLU(inplaceTrue), nn.MaxPool1d(kernel_size2, stride2), ) # ── SC-Conv 增强版 ── # 由于 32→16 涉及通道变换先用 1×1 Conv 对齐再接 SCConv1d self.proj1 nn.Sequential( nn.Conv1d(32, 16, kernel_size1, biasFalse), nn.BatchNorm1d(16), nn.ReLU(inplaceTrue), ) self.conv1 SCConv1dBlock( # SCConv1d ReLU MaxPool 的封装 channels16, kernel_size3, stride2, padding1, pooling_r4, )五、关键超参数pooling_rpooling_r是自校准下采样率 r直接决定 K2 路径的感受野倍增程度。pooling_rK2 路径的有效感受野论文 Top-1 精度ResNet-501不下采样等同原始卷积核77.38%22× 扩大77.48%4推荐4× 扩大77.81%pooling_r4是论文消融实验得出的最优值适用于绝大多数场景。对于输入序列长度较短 64的情况建议使用pooling_r2避免下采样后长度过小。更大的 r 值在网络最后几层特征图分辨率已经很小时不建议使用。六、与其他注意力方法的对比方法额外参数需改架构扩大感受野适用维度SENet✓ 有✓ 是✗ 否2DCBAM✓ 有✓ 是✗ 否2DSC-Conv本模块✗ 无✗ 否✓ 是1D 2DSC-Conv 的独特优势在于它不是在网络中插入额外的注意力插件而是直接重新组织卷积层自身的滤波器利用方式在零额外参数的前提下实现感受野扩展和特征校准。七、快速验证运行以下命令验证模块正确性python sc_conv.py预期输出 SCConv1d Demo — 振动信号 输入 : (8, 64, 1024) 输出 : (8, 64, 1024) SCConv1d 参数量 : 73,728 普通 Conv1d×3 : 73,728 (K2K3K4 等价) 参数开销比 : 1.000x (≈1零额外参数) SCConv2d Demo — 图像特征 输入 : (4, 256, 56, 56) 输出 : (4, 256, 56, 56) SCConv2d 参数量 : 1,769,472 普通 Conv2d×3 : 1,769,472 (K2K3K4 等价) 参数开销比 : 1.000x (≈1零额外参数) All demos passed ✓八、文件结构sc_conv.py ← 本模块即插即用无外部依赖仅需 PyTorch model_SRNet.py ← SC-Conv 1D 集成到双任务 SRNet 的完整模型 dataset.py ← CWRU 轴承故障数据集加载配套使用 train_test.py ← 两阶段训练与测试脚本配套使用九、引用如果本模块对你的研究有帮助请引用原论文inproceedings{liu2020improving, title {Improving Convolutional Networks with Self-Calibrated Convolutions}, author {Liu, Jiang-Jiang and Hou, Qibin and Cheng, Ming-Ming and Wang, Changhu and Feng, Jiashi}, booktitle {CVPR}, year {2020} }十、代码 sc_conv.py 即插即用自校准卷积模块1D 2D 统一实现 论文来源 Improving Convolutional Networks with Self-Calibrated Convolutions Jiang-Jiang Liu, Qibin Hou, Ming-Ming Cheng, Changhu Wang, Jiashi Feng CVPR 2020 https://mmcheng.net/scconv/ 【设计原则】 - 零额外参数SC-Conv 的参数量与替换掉的普通卷积完全相同 - 零架构改动直接替换任意 Conv1d / Conv2d 层超参数不变 - 即插即用支持任意 in_channels out_channels 的场景 【支持的模式】 SCConv1d — 1D 时序/振动信号bearing, ECG, audio… SCConv2d — 2D 图像特征ResNet 等骨干网络中的 3×3 卷积 【使用方法】 # 2D图像直接替换 ResNet Bottleneck 中的 3×3 Conv old nn.Conv2d(64, 64, kernel_size3, padding1, biasFalse) new SCConv2d(in_channels64, kernel_size3, padding1) # 1D信号替换任意通道不变的 1D 卷积层 old nn.Conv1d(32, 32, kernel_size3, padding1, biasFalse) new SCConv1d(in_channels32, kernel_size3, padding1) import torch import torch.nn as nn import torch.nn.functional as F __all__ [SCConv1d, SCConv2d] # # SCConv1d — 1D 自校准卷积 # class SCConv1d(nn.Module): Self-Calibrated Convolution1D 版本。 适用于一维时序、振动、音频等序列信号的特征提取。 将普通 Conv1d 替换为本模块即可在不增加参数的前提下 显著扩大每个位置的时序感受野。 核心流程对应论文 Fig.2 的 X1 路径 / Eq.2-5 1. K2 路径AvgPool1d(r) ↓ → Conv1d → BN Interpolate ↑ → 获得低频上下文 X 2. 校准权重σ( identity X ) 3. K3 路径Conv1d(X) × 校准权重 → Y 4. K4 路径Conv1d(Y) → 输出 参数 ---------- in_channels : int 输入/输出通道数必须相等即插即用约束 kernel_size : int, 默认 3 卷积核大小 stride : int, 默认 1 K4 步幅可用于时序下采样 padding : int, 默认 1 各路径卷积 padding与 kernel_size3 配合保持序列长度 dilation : int, 默认 1 空洞卷积率 groups : int, 默认 1 分组卷积数 pooling_r : int, 默认 4 自校准下采样率 r论文消融实验最优值为 4 norm_layer : callable, 默认 nn.BatchNorm1d 归一化层构造函数 示例 ---- sc SCConv1d(in_channels64, kernel_size3, padding1, pooling_r4) x torch.randn(8, 64, 1024) out sc(x) # [8, 64, 1024]形状不变 def __init__(self, in_channels: int, kernel_size: int 3, stride: int 1, padding: int 1, dilation: int 1, groups: int 1, pooling_r: int 4, norm_layer None): super(SCConv1d, self).__init__() if norm_layer is None: norm_layer nn.BatchNorm1d planes in_channels # 保持输入输出通道数一致 # ── K2低分辨率路径扩大感受野──────────────────── self.k2 nn.Sequential( nn.AvgPool1d(kernel_sizepooling_r, stridepooling_r), nn.Conv1d(in_channels, planes, kernel_sizekernel_size, stride1, paddingpadding, dilationdilation, groupsgroups, biasFalse), norm_layer(planes), ) # ── K3原始尺度路径生成待校准特征─────────────── self.k3 nn.Sequential( nn.Conv1d(in_channels, planes, kernel_sizekernel_size, stride1, paddingpadding, dilationdilation, groupsgroups, biasFalse), norm_layer(planes), ) # ── K4校准后输出卷积含步幅控制───────────────── self.k4 nn.Sequential( nn.Conv1d(planes, planes, kernel_sizekernel_size, stridestride, paddingpadding, dilationdilation, groupsgroups, biasFalse), norm_layer(planes), ) def forward(self, x: torch.Tensor) - torch.Tensor: 参数 ---- x : Tensor, shape [B, C, L] 返回 ---- out : Tensor, shape [B, C, L//stride] identity x # [B, C, L] # 低频上下文下采样 → 卷积 → 上采样回原始长度 x_low self.k2(x) # [B, C, L//r] x_up F.interpolate(x_low, sizeidentity.size(2), modelinear, align_cornersFalse) # [B, C, L] # 自校准权重 calibrate torch.sigmoid(identity x_up) # [B, C, L] # 原始尺度特征 × 校准权重 → 最终卷积 out torch.mul(self.k3(x), calibrate) # [B, C, L] out self.k4(out) # [B, C, L//stride] return out # # SCConv2d — 2D 自校准卷积 # class SCConv2d(nn.Module): Self-Calibrated Convolution2D 版本对应论文原始实现。 适用于图像特征图可直接替换 ResNet、ResNeXt 等骨干网络中的 3×3 Conv2d 层无需改动任何超参数。 核心流程对应论文 Fig.2 的 X1 路径 / Eq.2-5 1. K2 路径AvgPool2d(r,r) ↓ → Conv2d → BN F.interpolate ↑ → 获得低频上下文 X 2. 校准权重σ( identity X ) 3. K3 路径Conv2d(X) × 校准权重 → Y 4. K4 路径Conv2d(Y) → 输出 参数 ---------- in_channels : int 输入/输出通道数必须相等即插即用约束 kernel_size : int, 默认 3 卷积核大小 stride : int, 默认 1 K4 步幅可用于空间下采样 padding : int, 默认 1 各路径卷积 padding dilation : int, 默认 1 空洞卷积率 groups : int, 默认 1 分组卷积数 pooling_r : int, 默认 4 自校准下采样率 r论文消融实验最优值为 4 norm_layer : callable, 默认 nn.BatchNorm2d 归一化层构造函数 示例 ---- sc SCConv2d(in_channels256, kernel_size3, padding1, pooling_r4) x torch.randn(2, 256, 56, 56) out sc(x) # [2, 256, 56, 56]形状不变 def __init__(self, in_channels: int, kernel_size: int 3, stride: int 1, padding: int 1, dilation: int 1, groups: int 1, pooling_r: int 4, norm_layer None): super(SCConv2d, self).__init__() if norm_layer is None: norm_layer nn.BatchNorm2d planes in_channels # ── K2低分辨率路径──────────────────────────────── self.k2 nn.Sequential( nn.AvgPool2d(kernel_sizepooling_r, stridepooling_r), nn.Conv2d(in_channels, planes, kernel_sizekernel_size, stride1, paddingpadding, dilationdilation, groupsgroups, biasFalse), norm_layer(planes), ) # ── K3原始尺度路径──────────────────────────────── self.k3 nn.Sequential( nn.Conv2d(in_channels, planes, kernel_sizekernel_size, stride1, paddingpadding, dilationdilation, groupsgroups, biasFalse), norm_layer(planes), ) # ── K4校准后输出卷积────────────────────────────── self.k4 nn.Sequential( nn.Conv2d(planes, planes, kernel_sizekernel_size, stridestride, paddingpadding, dilationdilation, groupsgroups, biasFalse), norm_layer(planes), ) def forward(self, x: torch.Tensor) - torch.Tensor: 参数 ---- x : Tensor, shape [B, C, H, W] 返回 ---- out : Tensor, shape [B, C, H//stride, W//stride] identity x # [B, C, H, W] # 低频上下文 x_low self.k2(x) # [B, C, H//r, W//r] x_up F.interpolate(x_low, sizeidentity.shape[2:], modebilinear, align_cornersFalse) # [B, C, H, W] # 自校准权重 calibrate torch.sigmoid(identity x_up) # [B, C, H, W] # 原始尺度特征 × 校准权重 → 最终卷积 out torch.mul(self.k3(x), calibrate) # [B, C, H, W] out self.k4(out) # [B, C, H//s, W//s] return out # # 使用示例与验证 # def _demo_1d(): 1D 场景振动信号故障诊断 print( * 55) print( SCConv1d Demo — 振动信号) print( * 55) device cuda if torch.cuda.is_available() else cpu B, C, L 8, 64, 1024 sc SCConv1d(in_channelsC, kernel_size3, padding1, pooling_r4).to(device) x torch.randn(B, C, L).to(device) with torch.no_grad(): out sc(x) n_sc sum(p.numel() for p in sc.parameters()) n_base sum(p.numel() for p in nn.Conv1d(C, C, 3, padding1, biasFalse).parameters()) print(f 输入 : {tuple(x.shape)}) print(f 输出 : {tuple(out.shape)}) print(f SCConv1d 参数量 : {n_sc:,}) print(f 普通 Conv1d×3 : {n_base*3:,} (K2K3K4 等价)) print(f 参数开销比 : {n_sc/(n_base*3):.3f}x (≈1零额外参数)) print() def _demo_2d(): 2D 场景图像分类 / ResNet Bottleneck 替换 print( * 55) print( SCConv2d Demo — 图像特征) print( * 55) device cuda if torch.cuda.is_available() else cpu B, C, H, W 4, 256, 56, 56 sc SCConv2d(in_channelsC, kernel_size3, padding1, pooling_r4).to(device) x torch.randn(B, C, H, W).to(device) with torch.no_grad(): out sc(x) n_sc sum(p.numel() for p in sc.parameters()) n_base sum(p.numel() for p in nn.Conv2d(C, C, 3, padding1, biasFalse).parameters()) print(f 输入 : {tuple(x.shape)}) print(f 输出 : {tuple(out.shape)}) print(f SCConv2d 参数量 : {n_sc:,}) print(f 普通 Conv2d×3 : {n_base*3:,} (K2K3K4 等价)) print(f 参数开销比 : {n_sc/(n_base*3):.3f}x (≈1零额外参数)) print() def _demo_resnet_replace(): 演示如何将 ResNet BasicBlock 中的 3×3 Conv 替换为 SCConv2d print( * 55) print( ResNet BasicBlock 替换演示) print( * 55) class VanillaBlock(nn.Module): def __init__(self, C): super().__init__() self.conv nn.Sequential( nn.Conv2d(C, C, 3, padding1, biasFalse), nn.BatchNorm2d(C), nn.ReLU(inplaceTrue), nn.Conv2d(C, C, 3, padding1, biasFalse), nn.BatchNorm2d(C), ) self.relu nn.ReLU(inplaceTrue) def forward(self, x): return self.relu(self.conv(x) x) class SCBlock(nn.Module): 将第一个 3×3 Conv 替换为 SCConv2d def __init__(self, C): super().__init__() # 第一个卷积普通 Conv2d → SCConv2dBN 已内置于 SCConv2d self.sc SCConv2d(C, kernel_size3, padding1) self.relu1 nn.ReLU(inplaceTrue) # 第二个卷积保持不变 self.conv2 nn.Sequential( nn.Conv2d(C, C, 3, padding1, biasFalse), nn.BatchNorm2d(C), ) self.relu2 nn.ReLU(inplaceTrue) def forward(self, x): out self.relu1(self.sc(x)) return self.relu2(self.conv2(out) x) C 64 vanilla VanillaBlock(C) sc_blk SCBlock(C) x torch.randn(2, C, 28, 28) with torch.no_grad(): y_v vanilla(x) y_s sc_blk(x) n_v sum(p.numel() for p in vanilla.parameters()) n_s sum(p.numel() for p in sc_blk.parameters()) print(f VanillaBlock 输出: {tuple(y_v.shape)}, 参数: {n_v:,}) print(f SCBlock 输出: {tuple(y_s.shape)}, 参数: {n_s:,}) print(f 参数差异: {n_s - n_v:d} (理想为 0微小差异来自 BN 内置位置)) print() if __name__ __main__: _demo_1d() _demo_2d() _demo_resnet_replace() print(All demos passed ✓)