YOLOv10改进 | Conv/卷积篇 | 轻量化多尺度异构卷积(MSHC)优化YOLOv10精度(全网独家首发)

张开发
2026/4/20 2:44:57 15 分钟阅读

分享文章

YOLOv10改进 | Conv/卷积篇 | 轻量化多尺度异构卷积(MSHC)优化YOLOv10精度(全网独家首发)
一、本文介绍本文给大家带来的最新改进机制是由HyPCA-Net提出的MSHC结构本文将系统介绍多尺度空间异构卷积模块 MSHC 的核心思想与实际用法。首先从模块设计原理出发解析其如何借助异构卷积、多尺度特征提取和通道混洗在较低计算成本下增强特征表示能力随后结合 PyTorch 实现代码详细说明该模块在 YOLO10中的集成步骤包括文件添加、模块注册、YAML 配置与训练流程帮助读者从理论理解到代码复现再到工程落地完成完整上手利用该模块可以集成在你得网络模型中进而发表论文该模块为我个人全网首发独家整理。欢迎大家订阅我的专栏一起学习YOLO购买专栏读者联系读者入群获取进阶项目文件文字学不会的读者作者可提供视频学习方法.专栏回顾YOLOv10改进系列专栏——本专栏持续复习各种顶会内容——科研必备目录一、本文介绍二、原理介绍2.1MSHC卷积模块原理解析一种兼顾轻量化与多尺度表达的异构卷积设计2.1.1MSHC的核心思想2.1.2.MSHC为什么需要“异构多尺度”2.1.3.MSHC的结构组成2.2 多分支异构卷积提取2.2.1多分支特征融合2.2.2 通道混洗增强交互2.2.3 轻量卷积进一步精炼2.2.4 MSHC的本质优势2.2.5.MSHC在整个网络中的作用2.2.6. 对MSHC的直观理解3. 总结三、核心代码四、添加方法4.1 修改一4.2 修改二4.3 修改三4.4 修改四五、正式训练5.1 yaml文件5.2 训练代码5.3 训练过程截图五、本文总结二、原理介绍论文链接官方论文链接点击此处即可跳转代码链接官方代码链接点击此处即可跳转2.1MSHC卷积模块原理解析一种兼顾轻量化与多尺度表达的异构卷积设计在轻量级视觉网络设计中一个始终存在的矛盾是如何在尽量少的参数量和计算量下提升特征表达能力尤其是多尺度空间信息的建模能力。传统卷积虽然具有较强的局部表征能力但在面对复杂纹理、小目标、尺度变化和长距离空间依赖时往往需要通过堆叠更多层或引入更重的结构来弥补这会显著增加模型开销。正是在这样的背景下MSHCMulti-scaleSpatialHeterogeneousConvolution多尺度空间异构卷积被提出用于以更高效的方式增强特征提取过程中的多尺度空间建模能力。从整体定位上看MSHC并不是一个单纯追求“大感受野”的卷积块而是一个强调多尺度、异构化、轻量化和高效融合的卷积模块。论文中指出它被用于解决已有方法中存在的三个问题其一单阶段多尺度处理能力有限其二分支结构过于同构导致表征多样性不足其三缺乏对空间信息与通道信息的有效协同建模。MSHC正是围绕这三个问题展开设计通过多分支异构卷积结构在较低成本下获得更丰富的空间表征。2.1.1MSHC的核心思想MSHC的核心思想可以概括为一句话利用不同类型、不同尺度的轻量卷积分支并行提取特征再通过通道重组与后续卷积融合将分散的多尺度信息重新组织为更强的空间表示。这种设计并不依赖大规模标准卷积堆叠而是通过“异构分支并行通道交互轻量精炼”的方式在效率和性能之间取得平衡。这里的“异构”有两层含义。第一不同分支采用了不同类型的卷积算子例如组逐点卷积、深度卷积和空洞深度卷积第二不同分支对应了不同尺度的感受野例如1×1、3×3、5×5以及扩张卷积带来的更大空间覆盖范围。通过这种设计模块内部的每个分支都承担着不同的功能最终形成互补的信息提取机制。2.1.2.MSHC为什么需要“异构多尺度”在很多轻量网络中为了降低开销常常会大量使用深度卷积或分组卷积。虽然这样能显著减少参数量但也容易带来两个问题一是不同通道之间的信息交互不足二是单一路径的空间感受野有限难以同时兼顾局部细节和全局上下文。MSHC针对这两个问题提出了更有针对性的解决思路。首先它不再依赖单一路径提取特征而是将输入特征图送入多个并行分支。不同分支负责捕获不同尺度、不同性质的空间模式。例如小卷积核更适合建模局部纹理和边缘细节大卷积核更适合提取中尺度结构信息而空洞卷积则能在不显著增加参数的情况下扩大感受野增强对远距离空间依赖的感知能力。通过这种多尺度并行建模MSHC能够在同一层内同时整合细粒度信息和较大范围上下文信息。其次MSHC强调“异构分支设计”也就是说不同分支不是简单地做相同操作而是采用不同卷积机制使每条路径提取出的特征具有明显差异。这种差异性本质上增强了特征空间中的表征多样性使得模块不再局限于单一视角的空间感知而是能够从多个方向理解输入特征。论文明确指出这种branch-wiseheterogeneity的设计目标就是提升representationaldiversity也就是增强表示的丰富性与互补性。2.1.3.MSHC的结构组成从结构上看MSHC通常由四类关键操作构成异构多分支卷积、分支融合、通道混洗、后续轻量卷积精炼。论文图示中MSHC包含组逐点卷积GPC、深度卷积DWC、空洞深度卷积DDC、通道混洗CS、步长深度卷积SDWC等操作并在这些操作之间完成多尺度信息聚合与表达增强。2.2 多分支异构卷积提取输入特征首先进入多个并行分支。常见的分支包括1×1组逐点卷积用于进行轻量级通道变换3×3深度卷积用于提取局部空间细节5×5深度卷积用于捕获更大感受野的中尺度结构3×3空洞深度卷积用于在参数基本不变的情况下进一步扩大感受野。这些分支的共同作用是让模块在一次前向传播中同时感知不同范围的空间模式从而避免单尺度特征建模的局限。2.2.1多分支特征融合各分支提取出的特征随后会被融合。论文公式中用符号θ表示这种融合操作本质上是将多分支提取出的不同尺度空间信息聚合起来使模块输出包含更丰富的上下文信息。这里的关键不在于某一种单一融合方式而在于融合后的特征不再是单尺度、单路径的局部表示而是多分支信息叠加后的综合表示。2.2.2 通道混洗增强交互由于模块中使用了大量分组卷积、深度卷积等轻量操作虽然运算代价下降了但也容易造成通道之间、分支之间的信息隔离。为了解决这一问题MSHC引入了通道混洗ChannelShuffle操作。论文明确指出这一步是为了加强inter-channelcommunication。也就是说它的作用不是简单打乱顺序而是主动打破原有分组边界使来自不同分支、不同卷积路径的特征在后续处理中重新交叉融合。通道混洗可以看作是MSHC中的一个关键“桥梁”前面的多分支操作负责“分工提特征”而通道混洗则负责“让这些特征重新交流”。如果没有这一步那么轻量卷积带来的分组隔离问题可能会削弱多分支设计的收益。2.2.3 轻量卷积进一步精炼在通道混洗之后MSHC还会通过一系列后续卷积对融合特征进行精炼。论文中提到这部分由GPC、DWC和SDWC构成其作用包括恢复通道维度、进一步建模通道依赖关系以及对融合后的空间表示进行再次细化。换句话说前面的分支结构负责“多尺度感知”而后面的卷积序列负责“重新组织并强化这些信息”最终形成更稳定、更具判别力的输出特征。2.2.4 MSHC的本质优势从方法论角度看MSHC的优势主要体现在三个方面。第一它通过多尺度并行建模提升了空间特征表达的完整性。传统单卷积路径往往只能关注单一尺度而MSHC能够在一个模块内部同时整合局部细节、中尺度结构和更大范围上下文因此对尺度变化较大的目标和复杂场景更友好。第二它通过异构分支设计增强了特征表示的多样性。不同分支使用不同卷积算子不同算子擅长提取不同性质的空间模式这使得输出特征更加丰富也更有利于后续任务进行判别。第三它通过轻量卷积与通道混洗结合兼顾了效率和性能。MSHC并没有依赖昂贵的标准大卷积或复杂注意力堆叠而是通过深度卷积、分组逐点卷积和空洞深度卷积来控制计算量再借助通道混洗弥补轻量结构的信息交互不足问题。这种设计使其在保持较低参数量和GFLOPs的同时仍具备较强的空间建模能力。2.2.5.MSHC在整个网络中的作用在HyPCA-Net中MSHC并不是孤立存在的它是SCALA模块中的卷积分支部分并进一步服务于RALA模块。RALA的目标是以较低开销逐步细化特征表示而MSHC承担的正是其中“多尺度空间异构建模”的任务。论文给出的结构关系是RALA通过SCALA完成表征优化而SCALA又由MSHC与SCPFA共同组成。前者主要负责多尺度空间卷积建模后者则负责空间—通道并行注意力建模。也就是说MSHC更偏向“卷积式特征增强”而SCPFA更偏向“注意力式特征重标定”两者组合后形成更完整的特征优化流程。从这个角度看MSHC的意义不仅仅是替换一个普通卷积块而是在整个网络中承担了丰富空间表示、增强尺度适应性、为后续注意力模块提供高质量输入特征的作用。没有MSHC后续的空间—通道融合注意力难以建立在足够丰富的特征基础之上。2.2.6. 对MSHC的直观理解如果用更直观的话来解释MSHC就像是一组“分工明确的小型观察器”。有的分支擅长看细节有的分支擅长看较大的区域有的分支擅长跨更远的空间距离寻找关联。它们各自完成观察后并不会各管各的而是通过融合和通道混洗把彼此看到的信息重新整合到一起。最后再通过轻量卷积对这些信息进行提纯得到一张既包含局部纹理又具备上下文感知能力的特征图。因此MSHC真正解决的问题不是“怎么让卷积核变多”而是“怎么在轻量约束下让网络看到更多、更远、更丰富的空间信息”。这正是它区别于普通卷积块的地方。3. 总结总体而言MSHC是一种面向轻量视觉网络设计的高效卷积模块。它通过异构卷积分支并行提取多尺度空间特征利用通道混洗打破分组隔离再通过后续轻量卷积完成特征融合与精炼从而在较低计算成本下显著增强空间表征能力。其本质可以概括为以异构性提升表示多样性以多尺度并行提升空间感知能力以轻量化结构守住效率边界。对于目标检测、语义分割、小目标识别等任务而言这种设计尤其有价值。因为这些任务往往既需要关注细粒度局部信息又依赖较强的多尺度上下文建模能力而MSHC正是在这两者之间提供了一种较为均衡且高效的解决方案。三、核心代码核心代码的使用方式看章节四import torch import torch.nn as nn __all__ [MSHC] def _to_2tuple(x): return x if isinstance(x, tuple) else (x, x) def channel_shuffle(x, groups: int): x: [B, C, H, W] b, c, h, w x.size() if c % groups ! 0: raise ValueError(fchannel_shuffle: channels ({c}) must be divisible by groups ({groups}).) x x.view(b, groups, c // groups, h, w) x x.transpose(1, 2).contiguous() x x.view(b, c, h, w) return x class ChannelSpatialAttention(nn.Module): PyTorch equivalent of the active Keras ChannelSpatialAttention in your file. Effective behavior preserved: - Channel attention: avg max min sum over spatial dims, then 1x1 conv sigmoid - Spatial attention: avg max min sum over channel dim, then 7x7 conv sigmoid - Final gate: sigmoid(channel_info spatial_info) - Output: x * attention_map def __init__(self, channels: int): super().__init__() self.channel_conv nn.Conv2d(channels, channels, kernel_size1, biasTrue) self.spatial_conv nn.Conv2d(1, 1, kernel_size7, padding3, biasTrue) def forward(self, x): # Channel holistic information attention avg_pool x.mean(dim(2, 3), keepdimTrue) max_pool x.amax(dim(2, 3), keepdimTrue) min_pool x.amin(dim(2, 3), keepdimTrue) sum_pool x.sum(dim(2, 3), keepdimTrue) pooled avg_pool max_pool min_pool sum_pool channel_info torch.sigmoid(self.channel_conv(pooled)) # Spatial holistic information attention avg_spatial x.mean(dim1, keepdimTrue) max_spatial x.amax(dim1, keepdimTrue) min_spatial x.amin(dim1, keepdimTrue) sum_spatial x.sum(dim1, keepdimTrue) spatial_input avg_spatial max_spatial min_spatial sum_spatial spatial_info torch.sigmoid(self.spatial_conv(spatial_input)) attention_map torch.sigmoid(channel_info spatial_info) return x * attention_map class MSHC(nn.Module): PyTorch equivalent of your active multi_kernel_groupwise_conv1. Keras logic mapped to PyTorch: 1) Four parallel branches: - 1x1 Conv2d - out_channels // 4 - 3x3 DepthwiseConv2d - 5x5 DepthwiseConv2d - 3x3 Dilated DepthwiseConv2d (dilation2) 2) Concatenate 3) Channel shuffle 4) 3x3 depthwise conv 5) 1x1 grouped pointwise conv - out_channels 6) 3x3 depthwise downsample 7) ChannelSpatialAttention 8) Shortcut: 1x1 grouped PW - 3x3 depthwise downsample - 1x1 grouped PW 9) Residual add ReLU def __init__( self, in_channels: int, out_channels: int, groups: int 4, stride2, use_attention: bool True, ): super().__init__() stride _to_2tuple(stride) if out_channels % 4 ! 0: raise ValueError(fout_channels ({out_channels}) must be divisible by 4.) if out_channels % groups ! 0: raise ValueError(fout_channels ({out_channels}) must be divisible by groups ({groups}).) concat_channels out_channels // 4 3 * in_channels if concat_channels % groups ! 0: raise ValueError( fConcatenated channels ({concat_channels}) must be divisible by groups ({groups}) ffor channel shuffle. ) self.groups groups # Four heterogeneous branches self.branch1 nn.Conv2d( in_channels, out_channels // 4, kernel_size1, stride1, padding0, biasTrue ) self.branch2 nn.Conv2d( in_channels, in_channels, kernel_size3, stride1, padding1, groupsin_channels, biasTrue ) self.branch3 nn.Conv2d( in_channels, in_channels, kernel_size5, stride1, padding2, groupsin_channels, biasTrue ) self.branch4 nn.Conv2d( in_channels, in_channels, kernel_size3, stride1, padding2, dilation2, groupsin_channels, biasTrue ) # Fusion path self.fuse_dw nn.Conv2d( concat_channels, concat_channels, kernel_size3, stride1, padding1, groupsconcat_channels, biasTrue ) self.fuse_pw nn.Conv2d( concat_channels, out_channels, kernel_size1, stride1, padding0, groupsgroups, biasTrue ) self.down_dw nn.Conv2d( out_channels, out_channels, kernel_size3, stridestride, padding1, groupsout_channels, biasTrue ) self.attention ChannelSpatialAttention(out_channels) if use_attention else nn.Identity() # Shortcut path self.short_pw1 nn.Conv2d( in_channels, out_channels, kernel_size1, stride1, padding0, groupsgroups, biasTrue ) self.short_dw nn.Conv2d( out_channels, out_channels, kernel_size3, stridestride, padding1, groupsout_channels, biasTrue ) self.short_pw2 nn.Conv2d( out_channels, out_channels, kernel_size1, stride1, padding0, groupsgroups, biasTrue ) self.act nn.ReLU(inplaceTrue) def forward(self, x): # Multi-branch extraction conv1x1 self.branch1(x) conv3x3 self.branch2(x) conv5x5 self.branch3(x) conv_dilated self.branch4(x) # Concatenate shuffle y torch.cat([conv1x1, conv3x3, conv5x5, conv_dilated], dim1) y channel_shuffle(y, self.groups) # Fusion path y self.fuse_dw(y) y self.fuse_pw(y) y self.down_dw(y) y self.attention(y) # Shortcut path s self.short_pw1(x) s self.short_dw(s) s self.short_pw2(s) # Residual add out self.act(s y) return out四、添加方法4.1 修改一第一还是建立文件我们找到如下ultralytics/nn文件夹下建立一个目录名字呢就是Addmodules文件夹(用群内的文件的话已经有了无需新建)然后在其内部建立一个新的py文件将核心代码复制粘贴进去即可。​4.2 修改二第二步我们在该目录下创建一个新的py文件名字为__init__.py(用群内的文件的话已经有了无需新建)然后在其内部导入我们的检测头如下图所示。​​​4.3 修改三第三步找到如下文件ultralytics/nn/tasks.py进行导入和注册我们的模块(用群内的文件的话已经有了无需重新导入直接开始第四步即可)​​​4.4 修改四找到文件到如下文件ultralytics/nn/tasks.py在其中的parse_model方法中添加即可根据周围代码进行定位即可如果不会入群内有视频讲解。​​​到此就修改完成了大家可以复制下面的yaml文件运行,如果不会添加可联系作者入群观看视频教程。五、正式训练5.1 yaml文件训练信息YOLOv10n-MSHC summary: 442 layers, 3150184 parameters, 3150168 gradients, 10.8 GFLOPs# Ultralytics YOLO , AGPL-3.0 license # YOLOv10 object detection model. For Usage examples see https://docs.ultralytics.com/tasks/detect # Parameters nc: 80 # number of classes scales: # model compound scaling constants, i.e. modelyolov10n.yaml will call yolov10.yaml with scale n # [depth, width, max_channels] n: [0.33, 0.25, 1024] backbone: # [from, repeats, module, args] - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2 - [-1, 1, MSHC, [128]] # 1-P2/4 - [-1, 3, C2f, [128, True]] - [-1, 1, MSHC, [256]] # 3-P3/8 - [-1, 6, C2f, [256, True]] - [-1, 1, MSHC, [512]] # 5-P4/16 - [-1, 6, C2f, [512, True]] - [-1, 1, MSHC, [1024]] # 7-P5/32 - [-1, 3, C2f, [1024, True]] - [-1, 1, SPPF, [1024, 5]] # 9 - [-1, 1, PSA, [1024]] # 10 # YOLOv10.0n head head: - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 6], 1, Concat, [1]] # cat backbone P4 - [-1, 3, C2f, [512]] # 13 - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 4], 1, Concat, [1]] # cat backbone P3 - [-1, 3, C2f, [256]] # 16 (P3/8-small) - [-1, 1, MSHC, [256]] - [[-1, 13], 1, Concat, [1]] # cat head P4 - [-1, 3, C2f, [512]] # 19 (P4/16-medium) - [-1, 1, MSHC, [512]] - [[-1, 10], 1, Concat, [1]] # cat head P5 - [-1, 3, C2fCIB, [1024, True, True]] # 22 (P5/32-large) - [[16, 19, 22], 1, v10Detect, [nc]] # Detect(P3, P4, P5)5.2 训练代码大家可以创建一个py文件将我给的代码复制粘贴进去配置好自己的文件路径即可运行。import warnings warnings.filterwarnings(ignore) from ultralytics import YOLO if __name__ __main__: model YOLO(替换你的模型配置文件yaml文件地址) # 如何切换模型版本, 上面的ymal文件可以改为 yolov11s.yaml就是使用的v11s, # 类似某个改进的yaml文件名称为yolov11-XXX.yaml那么如果想使用其它版本就把上面的名称改为yolov11l-XXX.yaml即可改的是上面YOLO中间的名字不是配置文件的 # model.load(yolo11n.pt) # 是否加载预训练权重,科研不建议大家加载否则很难提升精度 model.train(datar替换你的数据集配置文件地址, # 如果大家任务是其它的ultralytics/cfg/default.yaml找到这里修改task可以改成detect, segment, classify, pose cacheFalse, imgsz640, epochs150, single_clsFalse, # 是否是单类别检测 batch16, close_mosaic0, workers0, device0, optimizerSGD, # using SGD # resumeruns/train/exp21/weights/last.pt, # 如过想续训就设置last.pt的地址 ampFalse, # 如果出现训练损失为Nan可以关闭amp projectruns/train, nameexp, )5.3 训练过程截图​​​五、本文总结到此本文的正式分享内容就结束了在这里给大家推荐我的YOLOv10改进有效涨点专栏本专栏目前为新开的平均质量分98分后期我会根据各种最新的前沿顶会进行论文复现也会对一些老的改进机制进行补充如果大家觉得本文帮助到你了订阅本专栏关注后续更多的更新~专栏回顾YOLOv10改进系列专栏——本专栏持续复习各种顶会内容——科研必备

更多文章