yolov26改进 | Conv/卷积篇 | 轻量化多尺度异构卷积(MSHC)优化YOLOv26精度(附独家网络结构图)
开始讲解之前推荐一下我的专栏,本专栏的内容支持(分类、检测、分割、追踪、关键点检测),专栏目前为限时折扣,欢迎大家订阅本专栏,本专栏每周更新5-7篇最新机制,更有包含我所有改进的文件和交流群提供给大家,本人定期在群内分享发表论文方法和经验。
一、本文介绍
本文给大家带来的最新改进机制是由HyPCA-Net提出的MSHC结构,本文将系统介绍多尺度空间异构卷积模块 MSHC 的核心思想与实际用法。首先从模块设计原理出发,解析其如何借助异构卷积、多尺度特征提取和通道混洗,在较低计算成本下增强特征表示能力;随后结合 PyTorch 实现代码,详细说明该模块在 YOLO26中的集成步骤,包括文件添加、模块注册、YAML 配置与训练流程,帮助读者从理论理解到代码复现再到工程落地完成完整上手,利用该模块可以集成在你得网络模型中进而发表论文,该模块为我个人全网首发,独家整理。
欢迎大家订阅我的专栏一起学习YOLO,购买专栏读者联系读者入群获取进阶项目文件!
(文字学不会的读者,作者可提供视频学习方法.)
专栏链接:YOLOv26有效涨点专栏包含:Conv、注意力机制、主干/Backbone、损失函数、优化器、后处理等改进机制
目录
一、本文介绍
二、原理介绍
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 修改四
4.5 修改五
4.6 修改六
五、正式训练
5.1 yaml文件
5.2 训练代码
5.3 训练过程截图
五、本文总结
二、原理介绍
论文链接:官方论文链接点击此处即可跳转
代码链接:官方代码链接点击此处即可跳转
2.1MSHC卷积模块原理解析:一种兼顾轻量化与多尺度表达的异构卷积设计
在轻量级视觉网络设计中,一个始终存在的矛盾是:如何在尽量少的参数量和计算量下,提升特征表达能力,尤其是多尺度空间信息的建模能力。传统卷积虽然具有较强的局部表征能力,但在面对复杂纹理、小目标、尺度变化和长距离空间依赖时,往往需要通过堆叠更多层或引入更重的结构来弥补,这会显著增加模型开销。正是在这样的背景下,MSHC(Multi-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(f"channel_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_size=1, bias=True) self.spatial_conv = nn.Conv2d(1, 1, kernel_size=7, padding=3, bias=True) def forward(self, x): # Channel holistic information attention avg_pool = x.mean(dim=(2, 3), keepdim=True) max_pool = x.amax(dim=(2, 3), keepdim=True) min_pool = x.amin(dim=(2, 3), keepdim=True) sum_pool = x.sum(dim=(2, 3), keepdim=True) 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(dim=1, keepdim=True) max_spatial = x.amax(dim=1, keepdim=True) min_spatial = x.amin(dim=1, keepdim=True) sum_spatial = x.sum(dim=1, keepdim=True) 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 (dilation=2) 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, stride=2, use_attention: bool = True, ): super().__init__() stride = _to_2tuple(stride) if out_channels % 4 != 0: raise ValueError(f"out_channels ({out_channels}) must be divisible by 4.") if out_channels % groups != 0: raise ValueError(f"out_channels ({out_channels}) must be divisible by groups ({groups}).") concat_channels = out_channels // 4 + 3 * in_channels if concat_channels % groups != 0: raise ValueError( f"Concatenated channels ({concat_channels}) must be divisible by groups ({groups}) " f"for channel shuffle." ) self.groups = groups # Four heterogeneous branches self.branch1 = nn.Conv2d( in_channels, out_channels // 4, kernel_size=1, stride=1, padding=0, bias=True ) self.branch2 = nn.Conv2d( in_channels, in_channels, kernel_size=3, stride=1, padding=1, groups=in_channels, bias=True ) self.branch3 = nn.Conv2d( in_channels, in_channels, kernel_size=5, stride=1, padding=2, groups=in_channels, bias=True ) self.branch4 = nn.Conv2d( in_channels, in_channels, kernel_size=3, stride=1, padding=2, dilation=2, groups=in_channels, bias=True ) # Fusion path self.fuse_dw = nn.Conv2d( concat_channels, concat_channels, kernel_size=3, stride=1, padding=1, groups=concat_channels, bias=True ) self.fuse_pw = nn.Conv2d( concat_channels, out_channels, kernel_size=1, stride=1, padding=0, groups=groups, bias=True ) self.down_dw = nn.Conv2d( out_channels, out_channels, kernel_size=3, stride=stride, padding=1, groups=out_channels, bias=True ) self.attention = ChannelSpatialAttention(out_channels) if use_attention else nn.Identity() # Shortcut path self.short_pw1 = nn.Conv2d( in_channels, out_channels, kernel_size=1, stride=1, padding=0, groups=groups, bias=True ) self.short_dw = nn.Conv2d( out_channels, out_channels, kernel_size=3, stride=stride, padding=1, groups=out_channels, bias=True ) self.short_pw2 = nn.Conv2d( out_channels, out_channels, kernel_size=1, stride=1, padding=0, groups=groups, bias=True ) self.act = nn.ReLU(inplace=True) 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], dim=1) 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'文件夹!
4.2 修改二
然后在Addmodules文件夹内建立一个新的py文件,将本文章节三中的“核心代码"复制粘贴进去。
4.3 修改三
第二步我们在该目录下创建一个新的py文件名字为'__init__.py',然后在其内部导入我们的文件,如下图所示。
4.4 修改四
第三步我门中到如下文件'ultralytics/nn/tasks.py'进行导入和注册我们的模块(此处只需要添加一次即可,如果你用我其它的改进机制这里的步骤只需要添加一次)!
4.5 修改五
在'ultralytics/nn/tasks.py'文件内的parse_model方法函数内(位置大概在1500+行左右),按照图示位置添加即可(此处需要自己有一定的判别能力,如果不会可联系作者获得视频教程)。
4.6 修改六
在'ultralytics/nn/tasks.py'文件内的parse_model方法函数内(位置大概在1600+行左右),按照图示位置进行代码的替换即可(此处不改如果你yaml文件中的所有C3k2都被改名了,则检测头会使用老版本的v8检测头参数量会大幅度增加,但不影响运行很多人都忽略了这一步)。
if "C3k2" in getattr(m, "__name__", str(m)): legacy = False if scale in "mlx": args[3] = True到此就修改完成了,大家可以复制下面的yaml文件运行,更多使用方式可以联系作者获得使用视频,本文仅列出常见的使用方式。
五、正式训练
5.1 yaml文件
训练信息:YOLO26-MSHC summary: 326 layers, 2,128,960 parameters, 2,128,960 gradients, 5.9 GFLOPs
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license # Ultralytics YOLO26 object detection model with P3/8 - P5/32 outputs # Model docs: https://docs.ultralytics.com/models/yolo26 # Task docs: https://docs.ultralytics.com/tasks/detect # Parameters nc: 80 # number of classes end2end: True # whether to use end-to-end mode reg_max: 1 # DFL bins scales: # model compound scaling constants, i.e. 'model=yolo26n.yaml' will call yolo26.yaml with scale 'n' # [depth, width, max_channels] n: [0.50, 0.25, 1024] # summary: 260 layers, 2,572,280 parameters, 2,572,280 gradients, 6.1 GFLOPs s: [0.50, 0.50, 1024] # summary: 260 layers, 10,009,784 parameters, 10,009,784 gradients, 22.8 GFLOPs m: [0.50, 1.00, 512] # summary: 280 layers, 21,896,248 parameters, 21,896,248 gradients, 75.4 GFLOPs l: [1.00, 1.00, 512] # summary: 392 layers, 26,299,704 parameters, 26,299,704 gradients, 93.8 GFLOPs x: [1.00, 1.50, 512] # summary: 392 layers, 58,993,368 parameters, 58,993,368 gradients, 209.5 GFLOPs # YOLO26n backbone backbone: # [from, repeats, module, args] - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2 - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4 - [-1, 2, C3k2_DualConv, [256, False, 0.25]] - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8 - [-1, 2, C3k2_DualConv, [512, False, 0.25]] - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16 - [-1, 2, C3k2_DualConv, [512, True]] - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32 - [-1, 2, C3k2_DualConv, [1024, True]] - [-1, 1, SPPF, [1024, 5, 3, True]] # 9 - [-1, 2, C2PSA, [1024]] # 10 # YOLO26n head head: - [-1, 1, nn.Upsample, [None, 2, "nearest"]] - [[-1, 6], 1, Concat, [1]] # cat backbone P4 - [-1, 2, C3k2, [512, True]] # 13 - [-1, 1, nn.Upsample, [None, 2, "nearest"]] - [[-1, 4], 1, Concat, [1]] # cat backbone P3 - [-1, 2, C3k2, [256, True]] # 16 (P3/8-small) - [-1, 1, Conv, [256, 3, 2]] - [[-1, 13], 1, Concat, [1]] # cat head P4 - [-1, 2, C3k2, [512, True]] # 19 (P4/16-medium) - [-1, 1, Conv, [512, 3, 2]] - [[-1, 10], 1, Concat, [1]] # cat head P5 - [-1, 1, C3k2, [1024, True, 0.5, True]] # 22 (P5/32-large) - [[16, 19, 22], 1, Detect, [nc]] # Detect(P3, P4, P5)5.2 训练代码
大家可以创建一个py文件将我给的代码复制粘贴进去,配置好自己的文件路径即可运行。
import warnings warnings.filterwarnings('ignore') from ultralytics import YOLO if __name__ == '__main__': model = YOLO('模型配置文件地址,也就是5.1你保存到本地文件的地址') # 如何切换模型版本, 上面的ymal文件可以改为 yolo26s.yaml就是使用的26s, # 类似某个改进的yaml文件名称为yolo26-XXX.yaml那么如果想使用其它版本就把上面的名称改为yolo26l-XXX.yaml即可(改的是上面YOLO中间的名字不是配置文件的)! # model.load('yolo26n.pt') # 是否加载预训练权重,科研不建议大家加载否则很难提升精度 model.train( data=r"数据集文件地址", # 如果大家任务是其它的'ultralytics/cfg/default.yaml'找到这里修改task可以改成detect, segment, classify, pose cache=False, imgsz=640, epochs=20, single_cls=False, # 是否是单类别检测 batch=16, close_mosaic=0, workers=0, device='0', optimizer='MuSGD', # using SGD/MuSGD # resume=, # 这里是填写last.pt地址 amp=True, # 如果出现训练损失为Nan可以关闭amp project='runs/train', name='exp', )5.3 训练过程截图
五、本文总结
到此本文的正式分享内容就结束了,在这里给大家推荐我的YOLOv26改进有效涨点专栏,本专栏目前为新开的平均质量分98分,后期我会根据各种最新的前沿顶会进行论文复现,也会对一些老的改进机制进行补充,如果大家觉得本文帮助到你了,订阅本专栏,关注后续更多的更新~
专栏链接:YOLOv26有效涨点专栏包含:Conv、注意力机制、主干/Backbone、损失函数、优化器、后处理等改进机制
