YOLOv11改进系列 | 引入CVPR2024 DCMPNet的C3k2_LEGM模块,局部增强全局建模补强C3k2,适合精度优先分割实验

YOLOv11-seg改进 | C3k2_LEGM局部增强全局建模替换C3k2全流程指南

    • 一、本文简介
      • [原始 C3k2 的局限性](#原始 C3k2 的局限性)
      • 本文改进核心
      • [改进前后参数量 / GFLOPs 对比](#改进前后参数量 / GFLOPs 对比)
    • 二、模块原理详解
      • [2.1 层级结构总览](#2.1 层级结构总览)
      • [2.2 基础组件](#2.2 基础组件)
      • [2.3 Att:窗口注意力局部全局融合算子(核心)](#2.3 Att:窗口注意力局部全局融合算子(核心))
      • [2.4 LEGM:动态归一化 + 注意力 + MLP 残差块](#2.4 LEGM:动态归一化 + 注意力 + MLP 残差块)
      • [2.5 C3k2_LEGM:完整 Bottleneck 替换逻辑](#2.5 C3k2_LEGM:完整 Bottleneck 替换逻辑)
    • 三、改进思想与创新点
      • [3.1 背景与动机](#3.1 背景与动机)
      • [3.2 核心创新点](#3.2 核心创新点)
      • [3.3 与现有方案的对比](#3.3 与现有方案的对比)
      • [3.4 在 YOLOv11 框架中的适配设计](#3.4 在 YOLOv11 框架中的适配设计)
    • 四、完整代码
      • [4.1 来自 `ultralytics/nn/extra_modules/DCMPNet.py`](#4.1 来自 ultralytics/nn/extra_modules/DCMPNet.py)
      • [4.2 来自 `ultralytics/nn/extra_modules/block.py`](#4.2 来自 ultralytics/nn/extra_modules/block.py)
    • 五、手把手配置步骤(三步法)
      • [Step 1:确认 `extra_modules/init.py` 导入(已自动导入,跳过)](#Step 1:确认 extra_modules/__init__.py 导入(已自动导入,跳过))
      • [Step 2:确认 `tasks.py` 注册(已注册,跳过)](#Step 2:确认 tasks.py 注册(已注册,跳过))
      • [Step 3:训练代码](#Step 3:训练代码)
    • [六、YAML 配置文件](#六、YAML 配置文件)
      • [变体一:全面替换(Backbone + Head 全部替换,推荐作为首选配置)](#变体一:全面替换(Backbone + Head 全部替换,推荐作为首选配置))
      • [变体二:仅替换 Backbone(Head 保留原版 C3k2)](#变体二:仅替换 Backbone(Head 保留原版 C3k2))
      • [变体三:精度优先模式(深层全部使用 c3k=True 强力配置)](#变体三:精度优先模式(深层全部使用 c3k=True 强力配置))
      • [变体四:混合模式(浅层保持原版 C3k2,深层引入 LEGM)](#变体四:混合模式(浅层保持原版 C3k2,深层引入 LEGM))
      • [变体五:P2 四尺度版(增加 P2 输出层,适合小目标密集场景)](#变体五:P2 四尺度版(增加 P2 输出层,适合小目标密集场景))
    • 七、常见问题(FAQ)
      • [7.1 `NameError: name 'C3k2_LEGM' is not defined`](#7.1 NameError: name 'C3k2_LEGM' 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 替换为 C3k2_LEGM,引入 DCMPNet 中的 LEGM 模块,以窗口注意力、动态重标定归一化和残差 MLP 联合增强局部与全局特征建模能力,在参数量与 GFLOPs 小幅增加的前提下,提升复杂纹理、目标边界与多尺度场景下的实例分割表达能力。


一、本文简介

本文引入 CVPR 2024 DCMPNet 中的 LEGM 模块,将其嵌入 YOLOv11 的 C3k2 框架,形成 C3k2_LEGM。根据仓库文档 YOLOV11配置文件.md303 项说明,当前配置的来源为 CVPR2024 DCMPNet 中的 LEGM 。从实现上看,LEGM 不是单纯卷积替换,而是将窗口注意力、动态归一化重标定与卷积式 MLP 组合到一个残差块中,用更强的局部增强全局建模能力替换原始 C3k2 的基础 Bottleneck

原始 C3k2 的局限性

YOLOv11 默认 C3k2 模块在效率上很稳,但在实例分割任务里仍有几个典型局限:

  1. 感受野有限 :内部 Bottleneck 主要依赖固定深度卷积堆叠,对复杂背景和长程依赖捕获不够充分。
  2. 全局建模不足:标准卷积更擅长局部邻域聚合,在多目标、遮挡和大尺度目标场景中容易缺少全局上下文。
  3. 特征自适应能力偏弱:缺少动态重标定和显式注意力机制,对细粒度结构和纹理差异建模能力有限。

本文改进核心

C3k2_LEGM 的核心思路是在不破坏 C2f/C3k2 外层结构的前提下,把原始内部单元替换成 LEGM,主要引入三点增强:

  1. 窗口注意力建模 :通过 WATT 在局部窗口内做多头注意力,补足卷积对全局上下文建模的不足。
  2. 动态归一化重标定LayNormal 不仅做归一化,还额外预测 rescalerebias,增强特征动态调节能力。
  3. 残差 MLP 增强 :在注意力分支后接入卷积式 Mlp,进一步提升通道交互和特征表达能力。

改进前后参数量 / GFLOPs 对比

下表结果来自项目环境下的 model.info() 实测:

python 复制代码
from ultralytics import YOLO
model = YOLO("ultralytics/cfg/models/11-seg/light_impro/yolo11-seg-C3k2-LEGM.yaml")
model.info()
模型 参数量 GFLOPs 说明
yolo11n-seg 2.46M2,460,112 9.6 基线
yolo11n-seg-C3k2-LEGM 2.71M2,713,360 10.3 2026-04-21 实测
变化 +253,248+10.29% +0.7+7.29% 精度改进方向

当前环境下,该模型的 model.info() 实测结果为 10.3 GFLOPs

从机制和指标上看,C3k2_LEGM 更适合作为精度增强型替换块,而不是轻量化模块。


二、模块原理详解

2.1 层级结构总览

text 复制代码
C3k2_LEGM
├─ 继承自 C3k2
│  ├─ 继承自 C2f
│  │  ├─ cv1: Conv(c1, 2c, 1)
│  │  ├─ cv2: Conv((2+n)c, c2, 1)
│  │  └─ m: ModuleList(...)
│  └─ 将原始 Bottleneck 替换为 LEGM
├─ C3k_LEGM
│  └─ 在 C3k 内部堆叠 LEGM
└─ LEGM
   ├─ norm1: LayNormal
   ├─ attn: Att
   │  ├─ QK: 1x1 Conv
   │  ├─ V: 1x1 Conv
   │  ├─ WATT: 窗口多头注意力
   │  └─ proj: 1x1 Conv
   ├─ 残差连接 1
   ├─ norm2: Identity(默认 mlp_norm=False)
   ├─ mlp: Conv1x1 -> ReLU -> Conv1x1
   └─ 残差连接 2

2.2 基础组件

window_partition / window_reverse

这两个函数负责把输入特征图切分成固定大小的窗口,再在注意力完成后恢复回原始特征图布局。

设输入特征为:

X ∈ R B × H × W × C X \in \mathbb{R}^{B \times H \times W \times C} X∈RB×H×W×C

窗口大小为 M M M,则窗口切分后得到:

X w ∈ R ( B ⋅ H M ⋅ W M ) × M 2 × C X_w \in \mathbb{R}^{(B \cdot \frac{H}{M} \cdot \frac{W}{M}) \times M^2 \times C} Xw∈R(B⋅MH⋅MW)×M2×C

这使得注意力复杂度从全局的:

O ( ( H W ) 2 ) O((HW)^2) O((HW)2)

变为窗口内的:

O ( H W M 2 ⋅ M 4 ) = O ( H W M 2 ) O\left(\frac{HW}{M^2} \cdot M^4\right)=O(HWM^2) O(M2HW⋅M4)=O(HWM2)

get_relative_positions

该函数提前构建窗口内 token 之间的相对位置编码,用于注意力偏置项:

B i j = f ( Δ x i j , Δ y i j ) B_{ij} = f(\Delta x_{ij}, \Delta y_{ij}) Bij=f(Δxij,Δyij)

其中 f ( ⋅ ) f(\cdot) f(⋅) 由后续 meta 网络拟合。

WATT

WATT 是窗口多头注意力模块。它先将 qkv 重新组织成多头形式,再计算:

Attn ( Q , K , V ) = Softmax ( Q K T d + B ) V \text{Attn}(Q, K, V)=\text{Softmax}\left(\frac{QK^T}{\sqrt{d}} + B\right)V Attn(Q,K,V)=Softmax(d QKT+B)V

其中:

  1. d d d 是每个 head 的维度;
  2. B B B 是相对位置偏置;
  3. softmax 在窗口内部完成。

2.3 Att:窗口注意力局部全局融合算子(核心)

AttLEGM 的关键算子。当前 LEGM 默认参数里:

  • use_attn=True
  • conv_type=None
  • window_size=8
  • shift_size=0

因此它的实际流程是:

  1. 通过 QK 得到查询和键;
  2. 通过 V 得到值分支;
  3. 切分窗口;
  4. 在每个窗口内部执行 WATT
  5. 通过 proj 做输出投影。

其核心公式可写成:

Q K = C o n v 1 × 1 ( X ) , V = C o n v 1 × 1 ( X ) QK = \mathrm{Conv}{1\times1}(X), \quad V = \mathrm{Conv}{1\times1}(X) QK=Conv1×1(X),V=Conv1×1(X)

X ′ = W A T T ( P a r t i t i o n ( Q , K , V ) ) X' = \mathrm{WATT}(\mathrm{Partition}(Q,K,V)) X′=WATT(Partition(Q,K,V))

Y = P r o j ( R e v e r s e ( X ′ ) ) Y = \mathrm{Proj}(\mathrm{Reverse}(X')) Y=Proj(Reverse(X′))

因为这里采用固定窗口注意力,所以它比全局自注意力更适合高分辨率检测/分割特征图。

2.4 LEGM:动态归一化 + 注意力 + MLP 残差块

LEGM 的前向传播非常清楚:

python 复制代码
identity = x
if self.use_attn: x, rescale, rebias = self.norm1(x)
x = self.attn(x)
if self.use_attn: x = x * rescale + rebias
x = identity + x

identity = x
if self.use_attn and self.mlp_norm: x, rescale, rebias = self.norm2(x)
x = self.mlp(x)
if self.use_attn and self.mlp_norm: x = x * rescale + rebias
x = identity + x

如果把它抽象成公式,可以写成:

X ^ , α , β = L a y N o r m a l ( X ) \hat{X}, \alpha, \beta = \mathrm{LayNormal}(X) X^,α,β=LayNormal(X)

Y = A t t ( X ^ ) Y = \mathrm{Att}(\hat{X}) Y=Att(X^)

X 1 = X + ( α ⊙ Y + β ) X_1 = X + (\alpha \odot Y + \beta) X1=X+(α⊙Y+β)

X 2 = X 1 + M l p ( X 1 ) X_2 = X_1 + \mathrm{Mlp}(X_1) X2=X1+Mlp(X1)

与普通 Transformer block 相比,这里的区别在于 LayNormal 不只是归一化,还引入了可学习的动态缩放与偏置重构。

2.5 C3k2_LEGM:完整 Bottleneck 替换逻辑

C3k2_LEGM 保留 C3k2 的 CSP 聚合方式,只把内部单元从 Bottleneck 替换为 LEGM。因此它整体仍然符合 C2f 系列的前向逻辑:

y 0 , y 1 = S p l i t ( c v 1 ( x ) ) y_0, y_1 = \mathrm{Split}(\mathrm{cv1}(x)) y0,y1=Split(cv1(x))

y i + 1 = L E G M ( y i ) y_{i+1} = \mathrm{LEGM}(y_i) yi+1=LEGM(yi)

o u t = c v 2 ( C o n c a t ( y 0 , y 1 , ... ) ) \mathrm{out} = \mathrm{cv2}(\mathrm{Concat}(y_0, y_1, \ldots)) out=cv2(Concat(y0,y1,...))

这种设计的价值在于:

  1. 兼容原始 C3k2 的输入输出格式;
  2. 替换成本低;
  3. YAML 层面即可切换。

三、改进思想与创新点

3.1 背景与动机

实例分割任务对特征表达的要求比普通检测更高。一方面,需要对局部边界、纹理与小目标轮廓保持敏感;另一方面,又要求对复杂背景、遮挡关系和多尺度目标具备更强的全局上下文建模能力。原始 C3k2 的优势是高效,但它内部仍以卷积堆叠为主,在跨区域依赖建模上相对保守。

LEGM 的价值在于,它不是简单把注意力直接塞进 YOLO,而是采用了一种更平衡的做法:

  1. 用窗口注意力引入局部范围内的全局关系建模;
  2. LayNormal 的动态重标定增强特征适应性;
  3. 用轻量 MLP 做通道补充建模。

3.2 核心创新点

创新点一:窗口注意力替代纯卷积式局部建模

Att 在固定大小窗口中做多头注意力:

A t t n ( Q , K , V ) = S o f t m a x ( Q K T d + B ) V \mathrm{Attn}(Q,K,V)=\mathrm{Softmax}\left(\frac{QK^T}{\sqrt{d}}+B\right)V Attn(Q,K,V)=Softmax(d QKT+B)V

相比原始卷积,这种机制能在局部区域内直接建模更长距离的 token 关系。

创新点二:动态重标定归一化

LayNormal 的输出不是单纯归一化结果,而是:

( X ^ , α , β ) (\hat{X}, \alpha, \beta) (X^,α,β)

然后在注意力后执行:

Y ′ = α ⊙ Y + β Y' = \alpha \odot Y + \beta Y′=α⊙Y+β

这种做法等价于给注意力输出提供了一层动态的特征校准,更适合复杂场景下的特征恢复与增强。

创新点三:注意力后接卷积式 MLP

Mlp 不是标准全连接,而是:

C o n v 1 × 1 → R e L U → C o n v 1 × 1 \mathrm{Conv}{1\times1} \rightarrow \mathrm{ReLU} \rightarrow \mathrm{Conv}{1\times1} Conv1×1→ReLU→Conv1×1

它在保留空间结构的前提下增强通道交互能力,相比直接只做注意力更完整。

创新点四:以最小侵入方式融入 YOLOv11

当前仓库的适配代码非常克制:

python 复制代码
self.m = nn.ModuleList(C3k_LEGM(self.c, self.c, n, shortcut, g) if c3k else LEGM(self.c) for _ in range(n))

这意味着:

  1. 输入输出维度不变;
  2. Neck 和 Head 不需要大改;
  3. 更适合做可控消融实验。

3.3 与现有方案的对比

方法 局部建模 全局上下文 动态重标定 通道增强 复杂度方向 适用场景
C3k2 原版 常规卷积 效率优先 通用检测 / 分割
C3k2_LFE Shift 位移卷积 轻度精度增强 边缘和纹理细节
C3k2_MambaOut 门控卷积 精度增强 多尺度上下文建模
C3k2_LEGM 窗口注意力 + 残差 MLP 精度增强 多尺度、遮挡、复杂背景实例分割
ViT 式全局注意力块 很强 可选 高复杂度 高算力场景

3.4 在 YOLOv11 框架中的适配设计

当前仓库里的导入和注册已经完成:

  1. ultralytics/nn/extra_modules/__init__.py 已包含 from .block import *from .DCMPNet import *
  2. ultralytics/nn/tasks.pyC3K2_CLASS 已经注册 C3k2_LEGM
  3. 目标 YAML 可直接被 YOLO() 正常构建。

因此在本文的配置步骤里,这两步都应明确写成"已存在,跳过即可"。


四、完整代码

为保持和仓库源码逐字一致,下面代码块不在内部加入人工中文注释,解释放在代码块外。

4.1 来自 ultralytics/nn/extra_modules/DCMPNet.py

python 复制代码
import torch, math
import torch.nn as nn
import torch.nn.functional as F
from timm.layers import to_2tuple, trunc_normal_
from ..modules.conv import Conv

__all__ = ['MFM', 'LEGM']

def window_partition(x, window_size):
    B, H, W, C = x.shape
    x = x.view(B, H // window_size, window_size, W // window_size, window_size, C)
    windows = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(-1, window_size**2, C)
    return windows


def window_reverse(windows, window_size, H, W):
    B = int(windows.shape[0] / (H * W / window_size / window_size))
    x = windows.view(B, H // window_size, W // window_size, window_size, window_size, -1)
    x = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(B, H, W, -1)
    return x

def get_relative_positions(window_size):
    coords_h = torch.arange(window_size)
    coords_w = torch.arange(window_size)
    # coords = torch.stack(torch.meshgrid(coords_h, coords_w, indexing='xy'))
    coords = torch.stack(torch.meshgrid([coords_h, coords_w]))
    coords_flatten = torch.flatten(coords, 1)
    relative_positions = coords_flatten[:, :, None] - coords_flatten[:, None, :]
    relative_positions = relative_positions.permute(1, 2, 0).contiguous()
    relative_positions_log  = torch.sign(relative_positions) * torch.log(1. + relative_positions.abs())
    return relative_positions_log

class WATT(nn.Module):
    def __init__(self, dim, window_size, num_heads):

        super().__init__()
        self.dim = dim
        self.window_size = window_size
        self.num_heads = num_heads
        head_dim = dim // num_heads
        self.scale = head_dim ** -0.5

        relative_positions = get_relative_positions(self.window_size)
        self.register_buffer("relative_positions", relative_positions)
        self.meta = nn.Sequential(
            nn.Linear(2, 256, bias=True),
            nn.ReLU(True),
            nn.Linear(256, num_heads, bias=True)
        )

        self.softmax = nn.Softmax(dim=-1)

    def forward(self, qkv):
        B_, N, _ = qkv.shape
        qkv = qkv.reshape(B_, N, 3, self.num_heads, self.dim // self.num_heads).permute(2, 0, 3, 1, 4)
        q, k, v = qkv[0], qkv[1], qkv[2]
        q = q * self.scale
        attn = (q @ k.transpose(-2, -1))
        relative_position_bias = self.meta(self.relative_positions)
        relative_position_bias = relative_position_bias.permute(2, 0, 1).contiguous()
        attn = attn + relative_position_bias.unsqueeze(0)
        attn = self.softmax(attn)
        x = (attn @ v).transpose(1, 2).reshape(B_, N, self.dim)
        return x

class Att(nn.Module):
    def __init__(self, dim, num_heads, window_size, shift_size, use_attn=False, conv_type=None):
        super().__init__()
        self.dim = dim
        self.head_dim = int(dim // num_heads)
        self.num_heads = num_heads
        self.window_size = window_size
        self.shift_size = shift_size
        self.use_attn = use_attn
        self.conv_type = conv_type

        if self.conv_type == 'Conv':
            self.conv = nn.Sequential(
                nn.Conv2d(dim, dim, kernel_size=3, padding=1, padding_mode='reflect'),
                nn.ReLU(True),
                nn.Conv2d(dim, dim, kernel_size=3, padding=1, padding_mode='reflect')
            )

        if self.conv_type == 'DWConv':
            self.conv = nn.Conv2d(dim, dim, kernel_size=5, padding=2, groups=dim, padding_mode='reflect')
        if self.conv_type == 'DWConv' or self.use_attn:
            self.V = nn.Conv2d(dim, dim, 1)
            self.proj = nn.Conv2d(dim, dim, 1)
        if self.use_attn:
            self.QK = nn.Conv2d(dim, dim * 2, 1)
            self.attn = WATT(dim, window_size, num_heads)

    def check_size(self, x, shift=False):
        _, _, h, w = x.size()
        mod_pad_h = (self.window_size - h % self.window_size) % self.window_size
        mod_pad_w = (self.window_size - w % self.window_size) % self.window_size

        if shift:
            x = F.pad(x, (self.shift_size, (self.window_size-self.shift_size+mod_pad_w) % self.window_size,
                          self.shift_size, (self.window_size-self.shift_size+mod_pad_h) % self.window_size), mode='reflect')
        else:
            x = F.pad(x, (0, mod_pad_w, 0, mod_pad_h), 'reflect')
        return x

    def forward(self, X):
        B, C, H, W = X.shape

        if self.conv_type == 'DWConv' or self.use_attn:
            V = self.V(X)

        if self.use_attn:
            QK = self.QK(X)
            QKV = torch.cat([QK, V], dim=1)

            # shift
            shifted_QKV = self.check_size(QKV, self.shift_size > 0)
            Ht, Wt = shifted_QKV.shape[2:]

            # partition windows
            shifted_QKV = shifted_QKV.permute(0, 2, 3, 1)
            qkv = window_partition(shifted_QKV, self.window_size)  # nW*B, window_size**2, C

            attn_windows = self.attn(qkv)

            # merge windows
            shifted_out = window_reverse(attn_windows, self.window_size, Ht, Wt)  # B H' W' C

            # reverse cyclic shift
            out = shifted_out[:, self.shift_size:(self.shift_size+H), self.shift_size:(self.shift_size+W), :]
            attn_out = out.permute(0, 3, 1, 2)

            if self.conv_type in ['Conv', 'DWConv']:
                conv_out = self.conv(V)
                out = self.proj(conv_out + attn_out)
            else:
                out = self.proj(attn_out)

        else:
            if self.conv_type == 'Conv':
                out = self.conv(X)
            elif self.conv_type == 'DWConv':
                out = self.proj(self.conv(V))

        return out

class Mlp(nn.Module):
    def __init__(self, in_features, hidden_features=None, out_features=None):
        super().__init__()
        out_features = out_features or in_features
        hidden_features = hidden_features or in_features
        self.mlp = nn.Sequential(
            nn.Conv2d(in_features, hidden_features, 1),
            nn.ReLU(True),
            nn.Conv2d(hidden_features, out_features, 1)
        )

    def forward(self, x):
        return self.mlp(x)

class LayNormal(nn.Module):
    def __init__(self, dim, eps=1e-5, detach_grad=False):
        super(LayNormal, self).__init__()
        self.eps = eps
        self.detach_grad = detach_grad
        self.weight = nn.Parameter(torch.ones((1, dim, 1, 1)))
        self.bias = nn.Parameter(torch.zeros((1, dim, 1, 1)))
        self.meta1 = nn.Conv2d(1, dim, 1)
        self.meta2 = nn.Conv2d(1, dim, 1)
        trunc_normal_(self.meta1.weight, std=.02)
        nn.init.constant_(self.meta1.bias, 1)
        trunc_normal_(self.meta2.weight, std=.02)
        nn.init.constant_(self.meta2.bias, 0)

    def forward(self, input):
        mean = torch.mean(input, dim=(1, 2, 3), keepdim=True)
        std = torch.sqrt((input - mean).pow(2).mean(dim=(1, 2, 3), keepdim=True) + self.eps)
        normalized_input = (input - mean) / std
        if self.detach_grad:
            rescale, rebias = self.meta1(std.detach()), self.meta2(mean.detach())
        else:
            rescale, rebias = self.meta1(std), self.meta2(mean)
        out = normalized_input * self.weight + self.bias
        return out, rescale, rebias

class LEGM(nn.Module):
    def __init__(self, dim, num_heads=8, mlp_ratio=4.,
                 norm_layer=LayNormal, mlp_norm=False,
                 window_size=8, shift_size=0, use_attn=True, conv_type=None):
        super().__init__()
        self.use_attn = use_attn
        self.mlp_norm = mlp_norm

        self.norm1 = norm_layer(dim) if use_attn else nn.Identity()
        self.attn = Att(dim, num_heads=num_heads, window_size=window_size,
                              shift_size=shift_size, use_attn=use_attn, conv_type=conv_type)

        self.norm2 = norm_layer(dim) if use_attn and mlp_norm else nn.Identity()
        self.mlp = Mlp(dim, hidden_features=int(dim * mlp_ratio))

    def forward(self, x):
        identity = x
        if self.use_attn: x, rescale, rebias = self.norm1(x)
        x = self.attn(x)
        if self.use_attn: x = x * rescale + rebias
        x = identity + x

        identity = x
        if self.use_attn and self.mlp_norm: x, rescale, rebias = self.norm2(x)
        x = self.mlp(x)
        if self.use_attn and self.mlp_norm: x = x * rescale + rebias
        x = identity + x
        return x

4.2 来自 ultralytics/nn/extra_modules/block.py

python 复制代码
######################################## CVPR2024 DCMPNet start ########################################
        
class C3k_LEGM(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(*(LEGM(c_) for _ in range(n)))

class C3k2_LEGM(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_LEGM(self.c, self.c, n, shortcut, g) if c3k else LEGM(self.c) for _ in range(n))

######################################## CVPR2024 DCMPNet end ########################################

五、手把手配置步骤(三步法)

Step 1:确认 extra_modules/__init__.py 导入(已自动导入,跳过)

当前仓库已经在 ultralytics/nn/extra_modules/__init__.py 中包含:

python 复制代码
from .DCMPNet import *
from .block import *

因此 LEGMC3k2_LEGM 所需导入已存在,这一步直接跳过即可。

Step 2:确认 tasks.py 注册(已注册,跳过)

当前仓库在 ultralytics/nn/tasks.pyC3K2_CLASS 中已经包含:

python 复制代码
C3k2_LEGM

因此无需再手动补注册。

Step 3:训练代码

python 复制代码
from ultralytics import YOLO

# 加载改进模型
model = YOLO("ultralytics/cfg/models/11-seg/light_impro/yolo11-seg-C3k2-LEGM.yaml")

# 查看参数量和 GFLOPs
model.info()

# 开始训练
model.train(
    data="coco8-seg.yaml",
    epochs=100,
    imgsz=640,
    batch=16,
    device=0
)

六、YAML 配置文件

变体一:全面替换(Backbone + Head 全部替换,推荐作为首选配置)

适用场景:追求最完整的 LEGM 表达能力,Backbone 与 Head 全部使用 C3k2_LEGM

实测参数量:2.71M / 10.3 GFLOPs(n scale)

yaml 复制代码
# YOLO11-seg C3k2_LEGM 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_LEGM, [256, False, 0.25]] # 2
  - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
  - [-1, 2, C3k2_LEGM, [512, False, 0.25]] # 4
  - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
  - [-1, 2, C3k2_LEGM, [512, True]] # 6
  - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
  - [-1, 2, C3k2_LEGM, [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_LEGM, [512, False]] # 13
  - [-1, 1, nn.Upsample, [None, 2, "nearest"]] # 14
  - [[-1, 4], 1, Concat, [1]] # 15 cat backbone P3
  - [-1, 2, C3k2_LEGM, [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_LEGM, [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_LEGM, [1024, True]] # 22 (P5/32-large)
  - [[16, 19, 22], 1, Segment, [nc, 32, 256]] # 23 Segment(P3, P4, P5)

变体二:仅替换 Backbone(Head 保留原版 C3k2)

适用场景:仅在 Backbone 引入 LEGM,优先观察主干侧的上下文增强收益。

yaml 复制代码
# YOLO11-seg C3k2_LEGM 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_LEGM, [256, False, 0.25]] # 2
  - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
  - [-1, 2, C3k2_LEGM, [512, False, 0.25]] # 4
  - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
  - [-1, 2, C3k2_LEGM, [512, True]] # 6
  - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
  - [-1, 2, C3k2_LEGM, [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)

变体三:精度优先模式(深层全部使用 c3k=True 强力配置)

适用场景:进一步增强内部堆叠深度,追求更强表达能力。

yaml 复制代码
# YOLO11-seg C3k2_LEGM 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_LEGM, [256, True, 0.25]] # 2
  - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
  - [-1, 2, C3k2_LEGM, [512, True, 0.25]] # 4
  - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
  - [-1, 2, C3k2_LEGM, [512, True]] # 6
  - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
  - [-1, 2, C3k2_LEGM, [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_LEGM, [512, True]] # 13
  - [-1, 1, nn.Upsample, [None, 2, "nearest"]] # 14
  - [[-1, 4], 1, Concat, [1]] # 15 cat backbone P3
  - [-1, 2, C3k2_LEGM, [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_LEGM, [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_LEGM, [1024, True]] # 22 (P5/32-large)
  - [[16, 19, 22], 1, Segment, [nc, 32, 256]] # 23 Segment(P3, P4, P5)

变体四:混合模式(浅层保持原版 C3k2,深层引入 LEGM)

适用场景:兼顾速度与精度,只在中深层加入 LEGM。

yaml 复制代码
# YOLO11-seg C3k2_LEGM 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_LEGM, [512, False, 0.25]] # 4
  - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
  - [-1, 2, C3k2_LEGM, [512, True]] # 6
  - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
  - [-1, 2, C3k2_LEGM, [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_LEGM, [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_LEGM, [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_LEGM, [1024, True]] # 22 (P5/32-large)
  - [[16, 19, 22], 1, Segment, [nc, 32, 256]] # 23 Segment(P3, P4, P5)

变体五:P2 四尺度版(增加 P2 输出层,适合小目标密集场景)

适用场景:小目标较多,希望引入四尺度分割输出。

注意:P2 版层索引重新计算,Segment 输出使用 [19, 22, 25, 28]

yaml 复制代码
# YOLO11-seg C3k2_LEGM 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_LEGM, [256, False, 0.25]] # 2
  - [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
  - [-1, 2, C3k2_LEGM, [512, False, 0.25]] # 4
  - [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
  - [-1, 2, C3k2_LEGM, [512, True]] # 6
  - [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
  - [-1, 2, C3k2_LEGM, [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_LEGM, [512, False]] # 13
  - [-1, 1, nn.Upsample, [None, 2, "nearest"]] # 14
  - [[-1, 4], 1, Concat, [1]] # 15 cat backbone P3
  - [-1, 2, C3k2_LEGM, [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_LEGM, [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_LEGM, [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_LEGM, [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_LEGM, [1024, True]] # 28 (P5/32-large)
  - [[19, 22, 25, 28], 1, Segment, [nc, 32, 256]] # 29 Segment(P2, P3, P4, P5)

七、常见问题(FAQ)

7.1 NameError: name 'C3k2_LEGM' is not defined

优先检查以下三项:

  1. ultralytics/nn/extra_modules/block.py 中是否存在 C3k2_LEGM
  2. ultralytics/nn/extra_modules/__init__.py 是否保留 from .DCMPNet import *from .block import *
  3. ultralytics/nn/tasks.pyC3K2_CLASS 是否仍包含 C3k2_LEGM

7.2 需要安装哪些第三方依赖

LEGM 这条链路主要依赖:

bash 复制代码
pip install timm

因为 ultralytics/nn/extra_modules/DCMPNet.py 里使用了 timm.layers 中的 to_2tupletrunc_normal_

7.3 Segment 改成 Detect 怎么写

只需要替换最后一层:

  1. 三尺度版改为 [[16, 19, 22], 1, Detect, [nc]]
  2. P2 四尺度版改为 [[19, 22, 25, 28], 1, Detect, [nc]]

7.4 YAML 参数怎么理解

C3k2_LEGM 的参数与普通 C3k2 保持一致:

写法 含义 示例
[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_LEGM 堆叠;
  3. e 是隐藏通道扩展比。

八、总结

对比维度 原始 C3k2 C3k2_LEGM
参数量(n scale) 2.46M 2.71M(+10.29%)
GFLOPs(n scale) 9.6 10.3(+7.29%)
局部建模方式 常规卷积堆叠 窗口划分后的局部多头注意力
全局上下文能力 较弱 通过窗口注意力与相对位置偏置增强
特征重标定机制 LayNormal 动态生成 rescale + rebias
通道增强方式 基础卷积映射 残差 Mlp(Conv1x1 -> ReLU -> Conv1x1)
归一化格式 BN(BCHW) 动态 LayNormal(BCHW)
论文来源 YOLOv11 原版 DCMPNet(CVPR 2024)
适用场景 通用目标检测 / 分割 复杂背景、多尺度目标、遮挡场景与精细实例分割

总结C3k2_LEGM 将 CVPR 2024 DCMPNet 中的 LEGM 引入 YOLOv11,核心是用窗口注意力补足卷积对长程依赖建模的不足,再通过 LayNormal 的动态重标定与残差 Mlp 进一步增强特征表达。在 n scale 下参数量增加 10.29%(+0.25M),GFLOPs 增加 7.29%(+0.7),换来更强的局部增强全局建模能力。对于复杂背景、遮挡、多尺度目标以及边界要求更高的实例分割场景,这种替换通常比单纯卷积 Bottleneck 更有潜力。

如果这篇文章对你有帮助,欢迎点赞、收藏、在评论区交流你实际训练后的涨点情况,也欢迎关注我的 YOLOv11 改进专栏,后面还会继续更新更多能直接落地的检测/分割结构改法。

相关推荐
思绪无限2 小时前
YOLOv5至YOLOv12升级:水下目标检测系统的设计与实现(完整代码+界面+数据集项目)
人工智能·深度学习·yolo·目标检测·水下目标检测·yolov12·yolo全家桶
三毛的二哥10 小时前
BEV:典型BEV算法总结
人工智能·算法·计算机视觉·3d
<-->11 小时前
Megatron(全称 Megatron-LM,由 NVIDIA 开发)和 DeepSpeed(由 Microsoft 开发)
人工智能·pytorch·python·深度学习·transformer
Yuanxl90311 小时前
神经网络-Sequential 应用与实战
人工智能·深度学习·神经网络
真·skysys13 小时前
On-Policy Distillation
人工智能·深度学习·机器学习
懷淰メ15 小时前
【AI加持】基于PyQt+YOLO+DeepSeek的口罩佩戴检测系统(详细介绍)
yolo·计算机视觉·pyqt·口罩检测·deepseek·ai加持
AI医影跨模态组学17 小时前
Cancer Letters(IF=10.1)中科院自动化研究所田捷等团队:整合纵向MRI与活检全切片图像用于乳腺癌新辅助治疗反应的早期预测及个体化管理
人工智能·深度学习·论文·医学·医学影像
王飞飞不会飞17 小时前
Mac 安装Hermes Agent 过程记录
运维·深度学习·机器学习
是梦终空17 小时前
计算机毕业设计271—基于python+深度学习+YOLOV7的车牌识别系统(源代码+数据库+3万字论文)
python·深度学习·opencv·yolo·毕业设计·pyqt5·车牌识别系统