YOLOv11-seg改进系列 | 引入CVPR2025 MambaOut的C3k2_MambaOut模块,门控大核卷积增强C3k2,适合精度优先实验

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 第三步:训练命令示例)
    • [六、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 参数怎么理解)
    • 八、总结

专栏系列 :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 结构足够高效,但在实例分割任务里也有几个比较明显的瓶颈:

  1. Bottleneck 内部仍以固定深度卷积堆叠为主,局部感受野相对保守。
  2. 通道混合主要依赖前后两次卷积,缺少显式的门控选择机制。
  3. 对复杂纹理、边界细节和中大目标区域的一致性建模能力有限。

1.2 C3k2_MambaOut 做了什么

当前仓库里的 C3k2_MambaOut 没有改动 C2f/C3k2 的外层拼接范式,而是在 self.m 内把原先的 Bottleneck 替换成了 GatedCNNBlock_BCHW。它带来三点核心变化:

  1. 门控混合 :先做通道扩展,再拆分出门控分支 g、直连分支 i 和卷积分支 c
  2. 大核局部建模 :卷积分支通过 7x7 深度卷积增强局部空间感受野。
  3. 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.46M2,460,112 9.6 2026-04-21 复测,与 2026-04-18 既有基线一致
yolo11n-seg-C3k2-MambaOut 2.63M2,627,218 10.3 2026-04-21 实测
变化 +167,106+6.79% +0.7+7.29% 典型精度优先型替换块

从结构与复杂度看,C3k2_MambaOut 明显不是轻量化方向,而是用少量额外计算换更强表征 的精度向改进。至于是否真正涨点,需要在你的实例分割数据集上训练后再看 mAPmask 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_MambaOutC3k2_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.pyC3K2_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 套可直接参考的配置方式。为了避免层索引错误,我把每个层号都重新核对了一遍,ConcatSegment 的输入索引也都按当前结构重新计算。

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.layersDropPath
不需要额外安装 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. c3kTrue 时,内部走 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 改进专栏,后面还会继续更新更多能直接落地的检测/分割结构改法。

相关推荐
探物 AI9 小时前
把 MambaOut 塞进 YOLOv11:会有什么样的反应
python·yolo·计算机视觉
快乐得小萝卜19 小时前
部署:YOLO V11 TensorRT 推理&前后处理
yolo
断眉的派大星19 小时前
YOLO26 完整学习笔记:从 Anchor-Free、TAL、STAL 到端到端无 NMS 部署
人工智能·笔记·学习·yolo·目标检测·计算机视觉·目标跟踪
stsdddd1 天前
YOLO系列目标检测数据集大全【第十三期】
yolo·目标检测·目标跟踪
动物园猫1 天前
无人机战场侦察6类军事目标检测数据集分享(适用于YOLO系列深度学习分类检测任务)
yolo·目标检测·无人机
羊羊小栈2 天前
老人摔倒检测系统(基于YOLO姿态估计)
yolo·毕业设计·创业创新·大作业
土星云SaturnCloud2 天前
土星云AI边缘计算SE110S系列模型部署实战-YOLOv5
服务器·人工智能·yolo·docker·边缘计算
YOLO数据集集合3 天前
配电站智能运维|变电一次设备识别|高压电气构件目标检测数据集|电力巡检
运维·人工智能·深度学习·yolo·目标检测·视觉检测
stsdddd3 天前
YOLO系列目标检测数据集大全【第八期】
yolo·目标检测·目标跟踪
YOLO数据集集合3 天前
航拍输电线路故障识别|线路金具缺陷判别|无人机电力巡检故障检测数据集10262期
人工智能·深度学习·yolo·目标检测·视觉检测·无人机