YOLOv11-seg改进 | C3k2_MambaOut门控大核卷积替换C3k2全流程指南
-
- 一、本文简介
-
- [1.1 原始 C3k2 的局限](#1.1 原始 C3k2 的局限)
- [1.2 C3k2_MambaOut 做了什么](#1.2 C3k2_MambaOut 做了什么)
- [1.3 实测参数量与 GFLOPs 对比](#1.3 实测参数量与 GFLOPs 对比)
- 二、模块原理详解
-
- [2.1 从底层到顶层的结构树](#2.1 从底层到顶层的结构树)
- [2.2 原始 C3k2 与 C3k2_MambaOut 的本质差异](#2.2 原始 C3k2 与 C3k2_MambaOut 的本质差异)
- [2.3 C3k2_MambaOut 的前向流程](#2.3 C3k2_MambaOut 的前向流程)
- [2.4 为什么它更适合实例分割](#2.4 为什么它更适合实例分割)
- 三、改进思想与创新点
-
- [3.1 背景与动机](#3.1 背景与动机)
- [3.2 核心创新点](#3.2 核心创新点)
- [3.3 与原始 C3k2 的对比](#3.3 与原始 C3k2 的对比)
- [3.4 在 YOLOv11 中的适配设计](#3.4 在 YOLOv11 中的适配设计)
- 四、完整代码
-
- [4.1 底层卷积封装](#4.1 底层卷积封装)
- [4.2 CSP 外壳与原始 C3k2 结构](#4.2 CSP 外壳与原始 C3k2 结构)
- [4.3 MambaOut 的底层门控块](#4.3 MambaOut 的底层门控块)
- [4.4 最终接入 YOLO 的封装类](#4.4 最终接入 YOLO 的封装类)
- 五、手把手配置步骤
-
- [5.1 第一步:检查 `extra_modules/init.py` 是否需要额外导入](#5.1 第一步:检查
extra_modules/__init__.py是否需要额外导入) - [5.2 第二步:检查 `tasks.py` 是否需要注册](#5.2 第二步:检查
tasks.py是否需要注册) - [5.3 第三步:训练命令示例](#5.3 第三步:训练命令示例)
- [5.1 第一步:检查 `extra_modules/init.py` 是否需要额外导入](#5.1 第一步:检查
- [六、YAML 配置文件](#六、YAML 配置文件)
-
- [6.1 变体一:全面替换](#6.1 变体一:全面替换)
- [6.2 变体二:仅替换 Backbone](#6.2 变体二:仅替换 Backbone)
- [6.3 变体三:精度优先模式](#6.3 变体三:精度优先模式)
- [6.4 变体四:混合模式](#6.4 变体四:混合模式)
- [6.5 变体五:P2 四尺度版](#6.5 变体五:P2 四尺度版)
- 七、常见问题
-
- [7.1 `NameError: name 'C3k2_MambaOut' is not defined`](#7.1
NameError: name 'C3k2_MambaOut' is not defined) - [7.2 需要安装哪些第三方依赖](#7.2 需要安装哪些第三方依赖)
- [7.3 如何把 `Segment` 版改成 `Detect` 版](#7.3 如何把
Segment版改成Detect版) - [7.4 YAML 参数怎么理解](#7.4 YAML 参数怎么理解)
- [7.1 `NameError: name 'C3k2_MambaOut' is not defined`](#7.1
- 八、总结
专栏系列 :YOLOv11 注意力/精度改进实战
改进点 :将 YOLOv11-seg 中的C3k2替换为基于 CVPR2025 MambaOut 的C3k2_MambaOut,核心是引入GatedCNNBlock_BCHW的门控通道混合、7x7深度卷积局部建模和LayerNormGeneral归一化增强,在参数量与 GFLOPs 小幅增加的前提下,提供更强的特征表达能力,更适合精度优先实验。
一、本文简介
本文引入 CVPR 2025 MambaOut《Do We Really Need Mamba for Vision?》 中的门控卷积思想,在当前仓库里对应的落地模块为 ultralytics/nn/backbone/MambaOut.py 中的 GatedCNNBlock_BCHW,并由 ultralytics/nn/extra_modules/block.py 封装成 C3k2_MambaOut。需要先说明一个关键事实:这个改进块虽然名字叫 MambaOut,但当前仓库接入的并不是状态空间扫描模块,而是 MambaOut 里的 BCHW 版门控卷积块 ,因此它本质上是一个更强的卷积混合器替换方案,而不是额外引入 mamba_ssm 的 SSM 结构。
1.1 原始 C3k2 的局限
YOLOv11-seg 默认的 C3k2 结构足够高效,但在实例分割任务里也有几个比较明显的瓶颈:
Bottleneck内部仍以固定深度卷积堆叠为主,局部感受野相对保守。- 通道混合主要依赖前后两次卷积,缺少显式的门控选择机制。
- 对复杂纹理、边界细节和中大目标区域的一致性建模能力有限。
1.2 C3k2_MambaOut 做了什么
当前仓库里的 C3k2_MambaOut 没有改动 C2f/C3k2 的外层拼接范式,而是在 self.m 内把原先的 Bottleneck 替换成了 GatedCNNBlock_BCHW。它带来三点核心变化:
- 门控混合 :先做通道扩展,再拆分出门控分支
g、直连分支i和卷积分支c。 - 大核局部建模 :卷积分支通过
7x7深度卷积增强局部空间感受野。 - BCHW 原生归一化 :通过
LayerNormGeneral直接在BCHW张量上完成归一化,避免多余张量布局转换。
1.3 实测参数量与 GFLOPs 对比
本文没有臆造精度结果,只对当前仓库环境下的模型结构进行了真实测量。测试命令如下:
python
from ultralytics import YOLO
YOLO("ultralytics/cfg/models/11-seg/yolo11-seg.yaml").info()
YOLO("ultralytics/cfg/models/11-seg/light_impro/yolo11-seg-C3k2-MambaOut.yaml").info()
实测结果如下:
| 模型 | 参数量 | GFLOPs | 测量说明 |
|---|---|---|---|
yolo11n-seg |
2.46M(2,460,112) |
9.6 |
2026-04-21 复测,与 2026-04-18 既有基线一致 |
yolo11n-seg-C3k2-MambaOut |
2.63M(2,627,218) |
10.3 |
2026-04-21 实测 |
| 变化 | +167,106(+6.79%) |
+0.7(+7.29%) |
典型精度优先型替换块 |
从结构与复杂度看,
C3k2_MambaOut明显不是轻量化方向,而是用少量额外计算换更强表征 的精度向改进。至于是否真正涨点,需要在你的实例分割数据集上训练后再看mAP和mask mAP。
二、模块原理详解
2.1 从底层到顶层的结构树
text
C3k2_MambaOut
├─ 继承自 C3k2
│ ├─ 继承自 C2f
│ │ ├─ cv1: Conv(c1, 2c, 1)
│ │ ├─ cv2: Conv((2+n)c, c2, 1)
│ │ └─ m: ModuleList(...)
│ └─ 将原始 Bottleneck 替换为 GatedCNNBlock_BCHW
├─ C3k_MambaOut
│ └─ 在 C3k 的内部堆叠 GatedCNNBlock_BCHW
└─ GatedCNNBlock_BCHW
├─ norm: LayerNormGeneral((C,1,1))
├─ fc1: 1x1 Conv,通道扩展到 2h
├─ split: [g, i, c]
├─ conv: 7x7 Depthwise Conv(c)
├─ act: GELU(g)
├─ fc2: 1x1 Conv,投影回 C
└─ residual: x + DropPath(...)
2.2 原始 C3k2 与 C3k2_MambaOut 的本质差异
原始 C3k2 的核心单元还是 Bottleneck:
B o t t l e n e c k ( x ) = x + D W C o n v 2 ( D W C o n v 1 ( x ) ) \mathrm{Bottleneck}(x)=x+\mathrm{DWConv}_2(\mathrm{DWConv}_1(x)) Bottleneck(x)=x+DWConv2(DWConv1(x))
而 C3k2_MambaOut 的核心单元换成了 GatedCNNBlock_BCHW:
h = ⌊ 8 3 C ⌋ , C conv = r C h=\left\lfloor \frac{8}{3}C \right\rfloor,\quad C_{\text{conv}}=rC h=⌊38C⌋,Cconv=rC
g , i , c \] = S p l i t ( C o n v 1 × 1 ( L N ( x ) ) , \[ h , h − C conv , C conv \] ) \[g,i,c\]=\\mathrm{Split}\\left(\\mathrm{Conv}_{1\\times1}(\\mathrm{LN}(x)),\\ \[h,\\ h-C_{\\text{conv}},\\ C_{\\text{conv}}\]\\right) \[g,i,c\]=Split(Conv1×1(LN(x)), \[h, h−Cconv, Cconv\]) c \~ = D W C o n v 7 × 7 ( c ) \\tilde{c}=\\mathrm{DWConv}_{7\\times7}(c) c\~=DWConv7×7(c) y = C o n v 1 × 1 ( G E L U ( g ) ⊙ C a t ( i , c \~ ) ) y=\\mathrm{Conv}_{1\\times1}\\left(\\mathrm{GELU}(g)\\odot \\mathrm{Cat}(i,\\tilde{c})\\right) y=Conv1×1(GELU(g)⊙Cat(i,c\~)) G a t e d C N N B l o c k _ B C H W ( x ) = x + D r o p P a t h ( y ) \\mathrm{GatedCNNBlock\\_BCHW}(x)=x+\\mathrm{DropPath}(y) GatedCNNBlock_BCHW(x)=x+DropPath(y) 这里有两个非常值得注意的点: 1. 当前仓库默认 `conv_ratio=1.0`,也就是卷积分支会使用完整的卷积分支通道,而不是只卷积少量通道。 2. `GatedCNNBlock_BCHW` 不依赖 `mamba_ssm`,而是一个标准卷积体系内可运行的门控块。 #### 2.3 C3k2_MambaOut 的前向流程 外层 `C3k2_MambaOut` 仍沿用 `C2f` 思路,流程可以写成: ```python def forward(x): y = list(cv1(x).chunk(2, 1)) y.extend(block(y[-1]) for block in m) return cv2(cat(y, 1)) ``` 把它展开成更直观的形式就是: \[ y 0 , y 1 \] = C h u n k ( C o n v 1 × 1 ( x ) , 2 ) \[y_0,y_1\]=\\mathrm{Chunk}(\\mathrm{Conv}_{1\\times1}(x),2) \[y0,y1\]=Chunk(Conv1×1(x),2) y k + 2 = G ( y k + 1 ) , G = G a t e d C N N B l o c k _ B C H W y_{k+2}=\\mathcal{G}(y_{k+1}),\\quad \\mathcal{G}=\\mathrm{GatedCNNBlock\\_BCHW} yk+2=G(yk+1),G=GatedCNNBlock_BCHW o u t = C o n v 1 × 1 ( C a t ( y 0 , y 1 , ... , y n + 1 ) ) \\mathrm{out}=\\mathrm{Conv}_{1\\times1}\\left(\\mathrm{Cat}(y_0,y_1,\\ldots,y_{n+1})\\right) out=Conv1×1(Cat(y0,y1,...,yn+1)) 也就是说,**外层的跨层聚合不变,变化发生在内部特征混合器**。这也是它最适合在 YOLOv11 里做"最小侵入替换"的原因。 #### 2.4 为什么它更适合实例分割 实例分割比纯检测更依赖边缘和区域一致性。`GatedCNNBlock_BCHW` 的优势恰好对应这类需求: 1. `7x7` 深度卷积比原来的固定小核更有利于整合局部区域结构。 2. 门控分支让哪些通道应该被强化、哪些通道应该更直接保留,拥有更细的选择能力。 3. `C2f` 外层拼接保留了 YOLO 系列一贯的梯度流优势,不会像大改主干那样破坏整体训练稳定性。 *** ** * ** *** ### 三、改进思想与创新点 #### 3.1 背景与动机 MambaOut 这篇工作的核心观点非常有代表性:**很多视觉任务里,真正有效的并不一定是"必须上状态空间模型",而是找到一种更合适的 token mixer** 。因此,当前仓库没有把完整 `MambaOut` 分类骨干直接搬进 `C3k2`,而是抽取了里面最关键的 `GatedCNNBlock_BCHW` 作为增强块,这个选择非常工程化: 1. 保留 YOLOv11 原有的 `C3k2/C2f` 外壳,兼容现有 YAML 写法。 2. 只替换内部混合单元,训练与部署成本更低。 3. 不额外引入 `mamba_ssm` 这类编译型依赖,更适合 Windows/通用 PyTorch 环境。 #### 3.2 核心创新点 **创新点一:门控式通道重组,而不是无条件卷积堆叠** 传统 `Bottleneck` 会对所有隐藏通道做相近形式的卷积处理;`GatedCNNBlock_BCHW` 则先把特征拆为 `g / i / c` 三部分,再通过门控进行选择性交互: m i x = G E L U ( g ) ⊙ C a t ( i , c \~ ) \\mathrm{mix}=\\mathrm{GELU}(g)\\odot \\mathrm{Cat}(i,\\tilde{c}) mix=GELU(g)⊙Cat(i,c\~) 这种设计意味着模型不是"把所有通道一视同仁地卷一遍",而是显式区分门控控制分支、保留分支和空间混合分支。 **创新点二:大核深度卷积增强局部空间建模** 卷积分支使用: c \~ = D W C o n v 7 × 7 ( c ) \\tilde{c}=\\mathrm{DWConv}_{7\\times7}(c) c\~=DWConv7×7(c) 相比原始 `Bottleneck` 中常见的小核深度卷积,`7x7` 更容易覆盖边缘、纹理和局部形状上下文,这对实例分割中的 mask 边界质量更友好。 **创新点三:BCHW 原生 LayerNorm 适配检测/分割骨干** 当前实现没有使用 `BHWC` 版本的块,而是采用 `GatedCNNBlock_BCHW`,核心归一化形式为: L N ( x ) = x − μ σ 2 + ϵ ⋅ γ + β \\mathrm{LN}(x)=\\frac{x-\\mu}{\\sqrt{\\sigma\^2+\\epsilon}}\\cdot \\gamma+\\beta LN(x)=σ2+ϵ x−μ⋅γ+β 其中均值与方差在 `(1,2,3)` 维上统计,更符合 YOLO 主体在 `BCHW` 布局下的工程使用习惯。 **创新点四:以最小改动融入 YOLOv11** 仓库里的适配代码非常克制: ```python self.m = nn.ModuleList(C3k_MambaOut(self.c, self.c, n, shortcut, g) if c3k else GatedCNNBlock_BCHW(self.c) for _ in range(n)) ``` 这意味着: 1. 输入输出张量形状不变。 2. 原有层索引体系保持稳定。 3. 可以只改 YAML,不需要重写 head。 #### 3.3 与原始 C3k2 的对比 | 对比维度 | 原始 `C3k2` | `C3k2_MambaOut` | 实际影响 | |------------|--------------|------------------------------------|------------| | 核心单元 | `Bottleneck` | `GatedCNNBlock_BCHW` | 内部混合器更强 | | 归一化方式 | `BN` 为主 | `LayerNormGeneral` + `BN` 外壳 | 通道统计方式更灵活 | | 局部感受野 | 常规小核深度卷积 | `7x7` 深度卷积 | 边缘/区域建模更充分 | | 通道交互 | 固定卷积映射 | 门控 + 分支重组 | 选择性更强 | | 复杂度方向 | 偏效率 | 偏表征 | 更适合精度优先实验 | | 参数与 GFLOPs | 更低 | 实测 `+6.79% params`、`+7.29% GFLOPs` | 需要接受小幅算力上涨 | | 适用场景 | 通用检测/分割 | 纹理复杂、边界要求高的分割任务 | 更建议做精度尝试 | #### 3.4 在 YOLOv11 中的适配设计 当前仓库的接入状态已经是完整可用的: 1. `ultralytics/nn/extra_modules/block.py` 已经定义 `C3k_MambaOut` 和 `C3k2_MambaOut`。 2. `ultralytics/nn/extra_modules/__init__.py` 已经通过 `from .block import *` 暴露该模块。 3. `ultralytics/nn/tasks.py` 已经导入 `from ultralytics.nn.backbone.MambaOut import *`,并把 `C3k2_MambaOut` 加入 `C3K2_CLASS`。 因此这篇文章的重点不是"如何手动补注册",而是**如何正确理解、正确替换、正确写 YAML**。 *** ** * ** *** ### 四、完整代码 > 下面代码块按"底层卷积封装 -\> CSP 外壳 -\> MambaOut 门控块 -\> YOLO 封装块"的顺序给出,代码内容与仓库源码保持一致,只在代码块外补充中文说明,便于你快速定位。 #### 4.1 底层卷积封装 文件路径:`ultralytics/nn/modules/conv.py` ```python import math import numpy as np import torch import torch.nn as nn def autopad(k, p=None, d=1): # kernel, padding, dilation """Pad to 'same' shape outputs.""" if d > 1: k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k] # actual kernel-size if p is None: p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad return p class Conv(nn.Module): """Standard convolution with args(ch_in, ch_out, kernel, stride, padding, groups, dilation, activation).""" default_act = nn.SiLU() # default activation def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True): """Initialize Conv layer with given arguments including activation.""" super().__init__() self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False) self.bn = nn.BatchNorm2d(c2) self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity() def forward(self, x): """Apply convolution, batch normalization and activation to input tensor.""" return self.act(self.bn(self.conv(x))) def forward_fuse(self, x): """Perform transposed convolution of 2D data.""" return self.act(self.conv(x)) class DWConv(Conv): """Depth-wise convolution.""" def __init__(self, c1, c2, k=1, s=1, d=1, act=True): # ch_in, ch_out, kernel, stride, dilation, activation """Initialize Depth-wise convolution with given parameters.""" super().__init__(c1, c2, k, s, g=math.gcd(c1, c2), d=d, act=act) ``` #### 4.2 CSP 外壳与原始 C3k2 结构 文件路径:`ultralytics/nn/modules/block.py` ```python class C2f(nn.Module): """Faster Implementation of CSP Bottleneck with 2 convolutions.""" def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5): """Initializes a CSP bottleneck with 2 convolutions and n Bottleneck blocks for faster processing.""" super().__init__() self.c = int(c2 * e) # hidden channels self.cv1 = Conv(c1, 2 * self.c, 1, 1) self.cv2 = Conv((2 + n) * self.c, c2, 1) # optional act=FReLU(c2) self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n)) def forward(self, x): """Forward pass through C2f layer.""" y = list(self.cv1(x).chunk(2, 1)) y.extend(m(y[-1]) for m in self.m) return self.cv2(torch.cat(y, 1)) def forward_split(self, x): """Forward pass using split() instead of chunk().""" y = list(self.cv1(x).split((self.c, self.c), 1)) y.extend(m(y[-1]) for m in self.m) return self.cv2(torch.cat(y, 1)) class C3(nn.Module): """CSP Bottleneck with 3 convolutions.""" def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): """Initialize the CSP Bottleneck with given channels, number, shortcut, groups, and expansion values.""" super().__init__() c_ = int(c2 * e) # hidden channels self.cv1 = Conv(c1, c_, 1, 1) self.cv2 = Conv(c1, c_, 1, 1) self.cv3 = Conv(2 * c_, c2, 1) # optional act=FReLU(c2) self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, k=((1, 1), (3, 3)), e=1.0) for _ in range(n))) def forward(self, x): """Forward pass through the CSP bottleneck with 2 convolutions.""" return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), 1)) class Bottleneck(nn.Module): """Standard bottleneck.""" def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5): """Initializes a standard bottleneck module with optional shortcut connection and configurable parameters.""" super().__init__() c_ = int(c2 * e) # hidden channels self.cv1 = DWConv(c1, c_, k[0], 1) self.cv2 = DWConv(c_, c2, k[1], 1) self.add = shortcut and c1 == c2 def forward(self, x): """Applies the YOLO FPN to input data.""" return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x)) class C3k2(C2f): """Faster Implementation of CSP Bottleneck with 2 convolutions.""" def __init__(self, c1, c2, n=1, c3k=False, e=0.5, g=1, shortcut=True): """Initializes the C3k2 module, a faster CSP Bottleneck with 2 convolutions and optional C3k blocks.""" super().__init__(c1, c2, n, shortcut, g, e) self.m = nn.ModuleList( C3k(self.c, self.c, 2, shortcut, g) if c3k else Bottleneck(self.c, self.c, shortcut, g) for _ in range(n) ) class C3k(C3): """C3k is a CSP bottleneck module with customizable kernel sizes for feature extraction in neural networks.""" def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5, k=3): """Initializes the C3k module with specified channels, number of layers, and configurations.""" super().__init__(c1, c2, n, shortcut, g, e) c_ = int(c2 * e) # hidden channels # self.m = nn.Sequential(*(RepBottleneck(c_, c_, shortcut, g, k=(k, k), e=1.0) for _ in range(n))) self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, k=(k, k), e=1.0) for _ in range(n))) ``` #### 4.3 MambaOut 的底层门控块 文件路径:`ultralytics/nn/backbone/MambaOut.py` ```python from functools import partial import torch import torch.nn as nn import torch.nn.functional as F from timm.layers import trunc_normal_, DropPath from timm.data import IMAGENET_DEFAULT_MEAN, IMAGENET_DEFAULT_STD class LayerNormGeneral(nn.Module): r""" General LayerNorm for different situations. Args: affine_shape (int, list or tuple): The shape of affine weight and bias. Usually the affine_shape=C, but in some implementation, like torch.nn.LayerNorm, the affine_shape is the same as normalized_dim by default. To adapt to different situations, we offer this argument here. normalized_dim (tuple or list): Which dims to compute mean and variance. scale (bool): Flag indicates whether to use scale or not. bias (bool): Flag indicates whether to use scale or not. We give several examples to show how to specify the arguments. LayerNorm (https://arxiv.org/abs/1607.06450): For input shape of (B, *, C) like (B, N, C) or (B, H, W, C), affine_shape=C, normalized_dim=(-1, ), scale=True, bias=True; For input shape of (B, C, H, W), affine_shape=(C, 1, 1), normalized_dim=(1, ), scale=True, bias=True. Modified LayerNorm (https://arxiv.org/abs/2111.11418) that is idental to partial(torch.nn.GroupNorm, num_groups=1): For input shape of (B, N, C), affine_shape=C, normalized_dim=(1, 2), scale=True, bias=True; For input shape of (B, H, W, C), affine_shape=C, normalized_dim=(1, 2, 3), scale=True, bias=True; For input shape of (B, C, H, W), affine_shape=(C, 1, 1), normalized_dim=(1, 2, 3), scale=True, bias=True. For the several metaformer baslines, IdentityFormer, RandFormer and PoolFormerV2 utilize Modified LayerNorm without bias (bias=False); ConvFormer and CAFormer utilizes LayerNorm without bias (bias=False). """ def __init__(self, affine_shape=None, normalized_dim=(-1, ), scale=True, bias=True, eps=1e-5): super().__init__() self.normalized_dim = normalized_dim self.use_scale = scale self.use_bias = bias self.weight = nn.Parameter(torch.ones(affine_shape)) if scale else None self.bias = nn.Parameter(torch.zeros(affine_shape)) if bias else None self.eps = eps def forward(self, x): c = x - x.mean(self.normalized_dim, keepdim=True) s = c.pow(2).mean(self.normalized_dim, keepdim=True) x = c / torch.sqrt(s + self.eps) if self.use_scale: x = x * self.weight if self.use_bias: x = x + self.bias return x class GatedCNNBlock_BCHW(nn.Module): r""" Our implementation of Gated CNN Block: https://arxiv.org/pdf/1612.08083 Args: conv_ratio: control the number of channels to conduct depthwise convolution. Conduct convolution on partial channels can improve practical efficiency. The idea of partial channels is from ShuffleNet V2 (https://arxiv.org/abs/1807.11164) and also used by InceptionNeXt (https://arxiv.org/abs/2303.16900) and FasterNet (https://arxiv.org/abs/2303.03667) """ def __init__(self, dim, expansion_ratio=8/3, kernel_size=7, conv_ratio=1.0, norm_layer=partial(LayerNormGeneral,eps=1e-6,normalized_dim=(1, 2, 3)), act_layer=nn.GELU, drop_path=0., **kwargs): super().__init__() self.norm = norm_layer((dim, 1, 1)) hidden = int(expansion_ratio * dim) self.fc1 = nn.Conv2d(dim, hidden * 2, 1) self.act = act_layer() conv_channels = int(conv_ratio * dim) self.split_indices = (hidden, hidden - conv_channels, conv_channels) self.conv = nn.Conv2d(conv_channels, conv_channels, kernel_size=kernel_size, padding=kernel_size//2, groups=conv_channels) self.fc2 = nn.Conv2d(hidden, dim, 1) self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity() def forward(self, x): shortcut = x # [B, H, W, C] x = self.norm(x) g, i, c = torch.split(self.fc1(x), self.split_indices, dim=1) # c = c.permute(0, 3, 1, 2) # [B, H, W, C] -> [B, C, H, W] c = self.conv(c) # c = c.permute(0, 2, 3, 1) # [B, C, H, W] -> [B, H, W, C] x = self.fc2(self.act(g) * torch.cat((i, c), dim=1)) x = self.drop_path(x) return x + shortcut ``` #### 4.4 最终接入 YOLO 的封装类 文件路径:`ultralytics/nn/extra_modules/block.py` ```python class C3k_MambaOut(C3k): def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5, k=3): super().__init__(c1, c2, n, shortcut, g, e, k) c_ = int(c2 * e) # hidden channels self.m = nn.Sequential(*(GatedCNNBlock_BCHW(c_) for _ in range(n))) class C3k2_MambaOut(C3k2): def __init__(self, c1, c2, n=1, c3k=False, e=0.5, g=1, shortcut=True): super().__init__(c1, c2, n, c3k, e, g, shortcut) self.m = nn.ModuleList(C3k_MambaOut(self.c, self.c, n, shortcut, g) if c3k else GatedCNNBlock_BCHW(self.c) for _ in range(n)) ``` *** ** * ** *** ### 五、手把手配置步骤 #### 5.1 第一步:检查 `extra_modules/__init__.py` 是否需要额外导入 当前仓库**不需要修改**。 原因如下: 1. `ultralytics/nn/extra_modules/__init__.py` 已经包含 `from .block import *`。 2. `ultralytics/nn/extra_modules/block.py` 的 `__all__` 已经包含 `C3k2_MambaOut`。 所以如果你直接使用当前仓库,**这一步跳过即可**。 #### 5.2 第二步:检查 `tasks.py` 是否需要注册 当前仓库**也不需要修改**。 你已经具备以下条件: 1. `ultralytics/nn/tasks.py` 已经导入 `from ultralytics.nn.backbone.MambaOut import *`。 2. `ultralytics/nn/tasks.py` 的 `C3K2_CLASS` 已经包含 `C3k2_MambaOut`。 也就是说,`YOLO("...yaml")` 已经能够正确解析这个模块名。 #### 5.3 第三步:训练命令示例 ```python from ultralytics import YOLO model = YOLO("ultralytics/cfg/models/11-seg/light_impro/yolo11-seg-C3k2-MambaOut.yaml") model.train( data="coco8-seg.yaml", epochs=100, imgsz=640, batch=16, device=0 ) ``` 如果你是自定义分割数据集,只需要把 `data` 换成自己的 `xxx-seg.yaml` 即可。 *** ** * ** *** ### 六、YAML 配置文件 > 下面给出 5 套可直接参考的配置方式。为了避免层索引错误,我把每个层号都重新核对了一遍,`Concat` 与 `Segment` 的输入索引也都按当前结构重新计算。 #### 6.1 变体一:全面替换 适用场景:你想直接复现当前仓库的 `light_impro` 版本,让 Backbone 和 Head 都统一换成 `C3k2_MambaOut`。 ```yaml # YOLO11-seg C3k2_MambaOut full replacement nc: 80 scales: n: [0.50, 0.25, 1024] s: [0.50, 0.50, 1024] m: [0.50, 1.00, 512] l: [1.00, 1.00, 512] x: [1.00, 1.50, 512] backbone: - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2 - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4 - [-1, 2, C3k2_MambaOut, [256, False, 0.25]] # 2 - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8 - [-1, 2, C3k2_MambaOut, [512, False, 0.25]] # 4 - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16 - [-1, 2, C3k2_MambaOut, [512, True]] # 6 - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32 - [-1, 2, C3k2_MambaOut, [1024, True]] # 8 - [-1, 1, SPPF, [1024, 5]] # 9 - [-1, 2, C2PSA, [1024]] # 10 head: - [-1, 1, nn.Upsample, [None, 2, "nearest"]] # 11 - [[-1, 6], 1, Concat, [1]] # 12 cat backbone P4 - [-1, 2, C3k2_MambaOut, [512, False]] # 13 - [-1, 1, nn.Upsample, [None, 2, "nearest"]] # 14 - [[-1, 4], 1, Concat, [1]] # 15 cat backbone P3 - [-1, 2, C3k2_MambaOut, [256, False]] # 16 (P3/8-small) - [-1, 1, Conv, [256, 3, 2]] # 17 - [[-1, 13], 1, Concat, [1]] # 18 cat head P4 - [-1, 2, C3k2_MambaOut, [512, False]] # 19 (P4/16-medium) - [-1, 1, Conv, [512, 3, 2]] # 20 - [[-1, 10], 1, Concat, [1]] # 21 cat head P5 - [-1, 2, C3k2_MambaOut, [1024, True]] # 22 (P5/32-large) - [[16, 19, 22], 1, Segment, [nc, 32, 256]] # 23 Segment(P3, P4, P5) ``` #### 6.2 变体二:仅替换 Backbone 适用场景:你想先验证主干侧的收益,而不动分割 Head,便于做消融实验。 ```yaml # YOLO11-seg C3k2_MambaOut backbone only nc: 80 scales: n: [0.50, 0.25, 1024] s: [0.50, 0.50, 1024] m: [0.50, 1.00, 512] l: [1.00, 1.00, 512] x: [1.00, 1.50, 512] backbone: - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2 - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4 - [-1, 2, C3k2_MambaOut, [256, False, 0.25]] # 2 - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8 - [-1, 2, C3k2_MambaOut, [512, False, 0.25]] # 4 - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16 - [-1, 2, C3k2_MambaOut, [512, True]] # 6 - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32 - [-1, 2, C3k2_MambaOut, [1024, True]] # 8 - [-1, 1, SPPF, [1024, 5]] # 9 - [-1, 2, C2PSA, [1024]] # 10 head: - [-1, 1, nn.Upsample, [None, 2, "nearest"]] # 11 - [[-1, 6], 1, Concat, [1]] # 12 cat backbone P4 - [-1, 2, C3k2, [512, False]] # 13 - [-1, 1, nn.Upsample, [None, 2, "nearest"]] # 14 - [[-1, 4], 1, Concat, [1]] # 15 cat backbone P3 - [-1, 2, C3k2, [256, False]] # 16 (P3/8-small) - [-1, 1, Conv, [256, 3, 2]] # 17 - [[-1, 13], 1, Concat, [1]] # 18 cat head P4 - [-1, 2, C3k2, [512, False]] # 19 (P4/16-medium) - [-1, 1, Conv, [512, 3, 2]] # 20 - [[-1, 10], 1, Concat, [1]] # 21 cat head P5 - [-1, 2, C3k2, [1024, True]] # 22 (P5/32-large) - [[16, 19, 22], 1, Segment, [nc, 32, 256]] # 23 Segment(P3, P4, P5) ``` #### 6.3 变体三:精度优先模式 适用场景:显存和算力比较充足,想把 `c3k=True` 也打开,让内部走 `C3k_MambaOut` 分支。 ```yaml # YOLO11-seg C3k2_MambaOut accuracy-first nc: 80 scales: n: [0.50, 0.25, 1024] s: [0.50, 0.50, 1024] m: [0.50, 1.00, 512] l: [1.00, 1.00, 512] x: [1.00, 1.50, 512] backbone: - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2 - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4 - [-1, 2, C3k2_MambaOut, [256, True, 0.25]] # 2 - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8 - [-1, 2, C3k2_MambaOut, [512, True, 0.25]] # 4 - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16 - [-1, 2, C3k2_MambaOut, [512, True]] # 6 - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32 - [-1, 2, C3k2_MambaOut, [1024, True]] # 8 - [-1, 1, SPPF, [1024, 5]] # 9 - [-1, 2, C2PSA, [1024]] # 10 head: - [-1, 1, nn.Upsample, [None, 2, "nearest"]] # 11 - [[-1, 6], 1, Concat, [1]] # 12 cat backbone P4 - [-1, 2, C3k2_MambaOut, [512, True]] # 13 - [-1, 1, nn.Upsample, [None, 2, "nearest"]] # 14 - [[-1, 4], 1, Concat, [1]] # 15 cat backbone P3 - [-1, 2, C3k2_MambaOut, [256, True]] # 16 (P3/8-small) - [-1, 1, Conv, [256, 3, 2]] # 17 - [[-1, 13], 1, Concat, [1]] # 18 cat head P4 - [-1, 2, C3k2_MambaOut, [512, True]] # 19 (P4/16-medium) - [-1, 1, Conv, [512, 3, 2]] # 20 - [[-1, 10], 1, Concat, [1]] # 21 cat head P5 - [-1, 2, C3k2_MambaOut, [1024, True]] # 22 (P5/32-large) - [[16, 19, 22], 1, Segment, [nc, 32, 256]] # 23 Segment(P3, P4, P5) ``` #### 6.4 变体四:混合模式 适用场景:想重点增强中深层语义与大目标区域,但又不希望浅层小目标分支全部换掉。 ```yaml # YOLO11-seg C3k2_MambaOut hybrid nc: 80 scales: n: [0.50, 0.25, 1024] s: [0.50, 0.50, 1024] m: [0.50, 1.00, 512] l: [1.00, 1.00, 512] x: [1.00, 1.50, 512] backbone: - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2 - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4 - [-1, 2, C3k2, [256, False, 0.25]] # 2 - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8 - [-1, 2, C3k2_MambaOut, [512, False, 0.25]] # 4 - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16 - [-1, 2, C3k2_MambaOut, [512, True]] # 6 - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32 - [-1, 2, C3k2_MambaOut, [1024, True]] # 8 - [-1, 1, SPPF, [1024, 5]] # 9 - [-1, 2, C2PSA, [1024]] # 10 head: - [-1, 1, nn.Upsample, [None, 2, "nearest"]] # 11 - [[-1, 6], 1, Concat, [1]] # 12 cat backbone P4 - [-1, 2, C3k2_MambaOut, [512, False]] # 13 - [-1, 1, nn.Upsample, [None, 2, "nearest"]] # 14 - [[-1, 4], 1, Concat, [1]] # 15 cat backbone P3 - [-1, 2, C3k2, [256, False]] # 16 (P3/8-small) - [-1, 1, Conv, [256, 3, 2]] # 17 - [[-1, 13], 1, Concat, [1]] # 18 cat head P4 - [-1, 2, C3k2_MambaOut, [512, False]] # 19 (P4/16-medium) - [-1, 1, Conv, [512, 3, 2]] # 20 - [[-1, 10], 1, Concat, [1]] # 21 cat head P5 - [-1, 2, C3k2_MambaOut, [1024, True]] # 22 (P5/32-large) - [[16, 19, 22], 1, Segment, [nc, 32, 256]] # 23 Segment(P3, P4, P5) ``` #### 6.5 变体五:P2 四尺度版 适用场景:你的小目标很多,想把 `P2/4` 也纳入分割输出。 ```yaml # YOLO11-seg C3k2_MambaOut P2 4-scale nc: 80 scales: n: [0.50, 0.25, 1024] s: [0.50, 0.50, 1024] m: [0.50, 1.00, 512] l: [1.00, 1.00, 512] x: [1.00, 1.50, 512] backbone: - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2 - [-1, 1, Conv, [128, 3, 2]] # 1-P2/4 - [-1, 2, C3k2_MambaOut, [256, False, 0.25]] # 2 - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8 - [-1, 2, C3k2_MambaOut, [512, False, 0.25]] # 4 - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16 - [-1, 2, C3k2_MambaOut, [512, True]] # 6 - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32 - [-1, 2, C3k2_MambaOut, [1024, True]] # 8 - [-1, 1, SPPF, [1024, 5]] # 9 - [-1, 2, C2PSA, [1024]] # 10 head: - [-1, 1, nn.Upsample, [None, 2, "nearest"]] # 11 - [[-1, 6], 1, Concat, [1]] # 12 cat backbone P4 - [-1, 2, C3k2_MambaOut, [512, False]] # 13 - [-1, 1, nn.Upsample, [None, 2, "nearest"]] # 14 - [[-1, 4], 1, Concat, [1]] # 15 cat backbone P3 - [-1, 2, C3k2_MambaOut, [256, False]] # 16 (P3/8-small) - [-1, 1, nn.Upsample, [None, 2, "nearest"]] # 17 - [[-1, 2], 1, Concat, [1]] # 18 cat backbone P2 - [-1, 2, C3k2_MambaOut, [128, False]] # 19 (P2/4-xsmall) - [-1, 1, Conv, [256, 3, 2]] # 20 - [[-1, 16], 1, Concat, [1]] # 21 cat head P3 - [-1, 2, C3k2_MambaOut, [256, False]] # 22 (P3/8-medium) - [-1, 1, Conv, [256, 3, 2]] # 23 - [[-1, 13], 1, Concat, [1]] # 24 cat head P4 - [-1, 2, C3k2_MambaOut, [512, False]] # 25 (P4/16-medium) - [-1, 1, Conv, [512, 3, 2]] # 26 - [[-1, 10], 1, Concat, [1]] # 27 cat head P5 - [-1, 2, C3k2_MambaOut, [1024, True]] # 28 (P5/32-large) - [[19, 22, 25, 28], 1, Segment, [nc, 32, 256]] # 29 Segment(P2, P3, P4, P5) ``` *** ** * ** *** ### 七、常见问题 #### 7.1 `NameError: name 'C3k2_MambaOut' is not defined` 优先检查三件事: 1. `ultralytics/nn/extra_modules/block.py` 中是否真的存在 `C3k2_MambaOut`。 2. `ultralytics/nn/extra_modules/__init__.py` 是否仍保留 `from .block import *`。 3. `ultralytics/nn/tasks.py` 中的 `C3K2_CLASS` 是否仍包含 `C3k2_MambaOut`。 如果你直接使用当前仓库,这三项都已经满足。 #### 7.2 需要安装哪些第三方依赖 就当前 `C3k2_MambaOut` 这条链路而言,重点是: ```bash pip install timm ``` 因为 `ultralytics/nn/backbone/MambaOut.py` 里依赖了 `timm.layers` 的 `DropPath`。 **不需要额外安装 `mamba_ssm`** ,因为这里调用的是 `GatedCNNBlock_BCHW`,不是 SSM 扫描模块。 #### 7.3 如何把 `Segment` 版改成 `Detect` 版 思路非常简单: 1. 保留 Backbone 和 Neck 不变。 2. 把最后一层 `Segment` 改成 `Detect`。 3. 三尺度版输出索引保持 `[[16, 19, 22], 1, Detect, [nc]]`。 4. P2 四尺度版则改成 `[[19, 22, 25, 28], 1, Detect, [nc]]`。 本质区别只有最后的任务头,前面的 `C3k2_MambaOut` 替换逻辑完全通用。 #### 7.4 YAML 参数怎么理解 `C3k2_MambaOut` 的 YAML 参数对应关系如下: | 写法 | 含义 | 示例 | |----------------------|-----------------------------|---------------| | `[256, False, 0.25]` | `c2=256, c3k=False, e=0.25` | Backbone 浅层常用 | | `[512, False]` | `c2=512, c3k=False, e=0.5` | Head 常用 | | `[512, True]` | `c2=512, c3k=True, e=0.5` | 精度优先模式 | 其中: 1. `c2` 是输出通道数。 2. `c3k` 为 `True` 时,内部走 `C3k_MambaOut` 分支。 3. `e` 是隐藏通道扩展比,默认 `0.5`。 *** ** * ** *** ### 八、总结 `C3k2_MambaOut` 这类模块很适合想在 **YOLOv11-seg 保持原有框架不大动** 的前提下,尝试更强特征混合器的同学。它的优势不在于"超轻量",而在于**用比较克制的改动换更强的局部建模和门控表达能力**。同时,当前仓库已经把导入、注册和 YAML 示例都准备得比较完整,真正落地时门槛并不高。 最后用一张总表收尾: | 总结项 | 结论 | |-----------|--------------------------------------------------------| | 来源 | CVPR2025 MambaOut《Do We Really Need Mamba for Vision?》 | | 当前仓库实际接入块 | `GatedCNNBlock_BCHW` | | 是否属于轻量化 | 否,更偏精度优先型替换 | | 基线实测 | `2,460,112 params / 9.6 GFLOPs` | | 改进版实测 | `2,627,218 params / 10.3 GFLOPs` | | 结构变化 | `+6.79% params`,`+7.29% GFLOPs` | | 部署依赖 | 需要 `timm`,不需要 `mamba_ssm` | | 适用场景 | 实例分割、边界细节较多、精度优先实验 | | 改动成本 | 低,当前仓库已完成导入与注册 | | 使用建议 | 先从"仅替换 Backbone"或"混合模式"开始做消融,再决定是否全面替换 | 如果这篇文章对你有帮助,欢迎点赞、收藏、在评论区交流你实际训练出来的涨点情况,也欢迎关注我的 YOLOv11 改进专栏,后面还会继续更新更多能直接落地的检测/分割结构改法。