笔记:YoloV11

YOLOv11 模型架构深度梳理

YOLO 系列作为目标检测领域最经典的 one-stage 方法,经历了从 v1 到 v11 的持续演进。每一次迭代都在速度-精度权衡上做出改进:

  • YOLOv8(2023):引入 C2f 模块取代 C3,解耦头(Decoupled Head),Anchor-Free
  • YOLOv9(2024):提出 PGI(Programmable Gradient Information)和 GELAN 架构,解决信息瓶颈问题
  • YOLOv10(2024):提出 NMS-Free 训练,引入一致匹配度量,消除对 NMS 的后处理依赖
  • YOLOv11 (2024):在 v8 基础上进一步精简结构------用 C3k2 替代 C2f,新增 C2PSA 注意力模块,优化 Neck 路径,在同等参数量下取得更好的精度

YOLOv11 的设计哲学可以概括为:回归简洁。它没有像 v9/v10 那样引入复杂的训练策略(PGI、NMS-Free),而是在 v8 的成熟框架上做"减法"------简化模块、优化结构、提升效率。

下面我们从每个基础模块入手,逐层拆解 YOLOv11 的网络设计。


二、基础模块详解

Conv层

卷积层是 YOLOv11 中最基本的构建单元。与 YOLOv8 一致,采用 Conv + BatchNorm + SiLU 的三段式结构:

设计要点:

  • SiLU 激活函数 :f(x)=x⋅σ(x)f(x) = x \cdot \sigma(x)f(x)=x⋅σ(x),相比 ReLU 的硬截断,SiLU 在负半轴有平滑的非零梯度,有利于梯度流动。虽然计算量略高于 ReLU,但在深层网络中通常能带来更好的收敛效果
  • Bias=False :卷积层后紧跟 BN,BN 本身带有可学习的缩放(γ\gammaγ)和偏移(β\betaβ),卷积层的 bias 是冗余的,去掉可以节省参数
  • 自动 Paddingp = k // 2 保证 stride=1 时输入输出尺寸不变,这是典型的 "same" 填充策略

Conv 在 YOLOv11 中承担两个角色:特征提取 (k=3, s=1)和下采样(k=3, s=2),以及通道数调整(k=1, s=1)。


C3k2层

C3k2 是 YOLOv11 最核心的模块,它替代了 YOLOv8 中的 C2f。从命名上可以看出:"C3" 表示 CSP 结构的第三个变体,"k2" 表示可以选用 2 种不同的内部模块。

C2f vs C3k2 的关键区别:

特性 C2f (v8) C3k2 (v11)
内部结构 固定为 Bottleneck 可选 Bottleneck(c3k=True)或 Conv(c3k=False)
Bottleneck 数量 由 n 控制,输入输出通道均分 由 n 控制,输入输出通道均分
shortcut 默认 True 默认 False
灵活性 单一模式 双模式,可灵活配置

为什么默认 shortcut=False?

在 C2f 中,Bottleneck 默认启用残差连接。但 C3k2 的多数场景下(c3k=False),内部使用普通的 Conv 而非 Bottleneck,此时本身就无残差概念。即便是 c3k=True 时,也只在某些特定层(如 b3、b7)启用残差连接,避免不必要的梯度路径。

c3k 开关的设计意图:

  • c3k=True:用于 Backbone 中较深的层(b3: P3/8, b7: P5/32),这些层需要更强的特征提取能力,Bottleneck 的残差结构有助于训练更深网络
  • c3k=False:用于 Neck 和 Backbone 中的中间层,使用普通 Conv 堆叠,计算量更小,适合特征融合阶段的轻量处理

SPPF(快速空间金字塔池化)

SPPF 是 SPP(Spatial Pyramid Pooling)的快速版本,位于 Backbone 的最后一层(b8),用于扩大感受野。

核心思想:通过多个串联的 MaxPool2d(k=5) 获得不同感受野的特征,再将它们拼接起来。

为什么是串联而不是并联?

传统 SPP 使用并联的多个不同 kernel 大小的池化,计算量较大。SPPF 采用串联方式,感受野计算如下:

  • 一次池化:感受野 5x5
  • 两次池化:感受野 9x9(等效于 5 + 5 - 1 = 9)
  • 三次池化:感受野 13x13(等效于 9 + 5 - 1 = 13)

串联池化的计算量约为并联的 1/31/31/3,但效果等价。

通道处理流程

  1. cv1:C→C/2C \rightarrow C/2C→C/2,先降维减少后续计算量
  2. 三次串联 MaxPool:输出通道数不变,均为 C/2C/2C/2
  3. 拼接:C/2×4=2CC/2 \times 4 = 2CC/2×4=2C
  4. cv2:2C→C2C \rightarrow C2C→C,恢复原始通道数

C2PSA(部分空间注意力)

C2PSA 是 YOLOv11 新增的模块,将空间注意力机制融入 C2 结构,位于 Backbone 的最后一层(b9),接在 SPPF 之后。

PSA 内部机制

PSA(Partial Spatial Attention)的核心操作是特征门控(feature gating):

  1. 1x1 卷积将通道扩展为 2c2c2c,然后拆分为 a,ba, ba,b 两个分支
  2. 对 aaa 做 sigmoid 得到注意力权重:weight=σ(a)\text{weight} = \sigma(a)weight=σ(a)
  3. 用权重调制 bbb:b′=b⋅σ(a)b' = b \cdot \sigma(a)b′=b⋅σ(a)
  4. 拼接 a,b′a, b'a,b′ 后通过 1x1 卷积恢复通道

这里的 aaa 分支相当于"注意力生成器",bbb 分支相当于"特征携带者"。aaa 的 sigmoid 输出值在 (0,1) 范围内,作为软门控调制 bbb 的特征响应。

C2PSA 的 C2 结构

C2PSA 沿用了 C2 的 split 设计:输入通道一分为二,一半直接传递(shortcut),另一半经过 PSA 处理,最后再拼接融合。这种设计保证了梯度可以从两个路径传播,训练更加稳定。


三、网络整体架构

YOLOv11 延续了 Backbone + Neck + Head 的三段式设计,整体结构如下:

Backbone(特征提取网络)

模块 通道 输出尺寸 说明
b1 Conv k=3 s=2 3→16 320x320 P1/2 下采样
b2 Conv k=3 s=2 16→32 160x160 P2/4 下采样
b3 C3k2 n=2 c3k=True 32→64 80x80 P3/8 特征提取
b4 Conv k=3 s=2 64→128 40x40 下采样
b5 C3k2 n=2 c3k=False 128→128 40x40 P4/16 特征提取
b6 Conv k=3 s=2 128→256 20x20 下采样
b7 C3k2 n=2 c3k=True 256→256 20x20 P5/32 特征提取
b8 SPPF 256→256 20x20 感受野扩大
b9 C2PSA 256→256 20x20 空间注意力

三点值得注意的设计:

  1. C3k2 的交替配置:b3 和 b7 使用 c3k=True(瓶颈残差块),b5 使用 c3k=False(简单卷积)。这是因为 b3 和 b7 分别位于 P3 和 P5 这两个关键检测层之前,需要更强的特征提取能力
  2. C2PSA 的位置:放在 SPPF 之后而不是之前,因为 SPPF 已经扩大了感受野,PSA 可以在更大的感受野上做空间注意力,效果更好
  3. 通道数策略:YOLOv11n 的通道数较小(最大 256),而 v8n 最大为 256,实际参数量相当。但对更大的模型(s/m/l/x),v11 的通道数配置有所不同

Neck(特征融合网络)

Neck 采用 FPN + PAN 结构,即先上采样再下采样的双向融合路径:

上采样路径(自顶向下):

  • up1cat(d9, d5)n1(C3k2):大目标特征与中层特征融合,输出 40x40
  • up2cat(x1, d3)n2(C3k2):融合后输出 80x80,得到 P3 检测特征图(小目标)

下采样路径(自底向上):

  • down1cat(x2, x1)n3(C3k2):输出 40x40,得到 P4 检测特征图(中目标)
  • down2cat(x3, d9)n4(C3k2):输出 20x20,得到 P5 检测特征图(大目标)

与 YOLOv8 Neck 的区别:

YOLOv8 的 Neck 在上下采样路径中使用 C2f 模块,且通道数较多。YOLOv11 的 Neck 全部使用 c3k=False 的 C3k2,计算量更轻,且下采样路径中的拼接源不同------v11 将下采样结果与 Backbone 的原始输出(d9)拼接,保留了更多原始信息。

Head(检测头)

检测头接收三个尺度的特征图(P3: 80x80, P4: 40x40, P5: 20x20),输出分类和回归结果。

每个检测层包含两个分支:

  • cv2:生成中间特征(64通道),用于后续的框回归(Regression)
  • cv3:生成分类 + 框回归的原始输出(4 + nc 通道)

注意 :上述代码是简化版本。实际 YOLOv11 的检测头使用 DFL(Distribution Focal Loss) 进行框回归,输出通道为 reg_max * 4 = 16 * 4 = 64,而非简单的 4。分类分支使用 nc 个通道并通过 sigmoid 激活。完整实现可参考 ultralytics 源码。


四、损失函数与训练(概述)

YOLOv11 的训练流程与 v8 基本一致,主要包括:

损失函数三件套:

  1. 分类损失(BCE Loss):二值交叉熵损失,每个类别独立做二分类
  2. 框回归损失(CIoU / DFL Loss):DFL 将边框坐标建模为离散分布,通过 softmax 输出分布概率,再加权求和得到坐标值。CIoU 考虑重叠面积、中心点距离和宽高比
  3. 置信度损失(BCE Loss):控制 objectness 分数

数据增强:

  • Mosaic(前 10 个 epoch):将 4 张图拼接为一张,增强小目标检测能力
  • MixUp(仅在较大模型中使用)
  • HSV 颜色抖动、随机缩放、平移、翻转

训练策略:

  • 训练 500 epoch
  • Cosine LR Scheduler,初始 lr=0.01
  • EMA(Exponential Moving Average)平滑权重
  • 自动混合精度(AMP)训练

五、总结

YOLOv11 的核心改进可以概括为三点:

  1. 模块精简:C3k2 取代 C2f,统一了内部结构,通过 c3k 开关灵活切换 Conv/Bottleneck,代码更简洁,部署更友好
  2. 注意力增强:C2PSA 模块的引入提供了空间注意力机制,在不大幅增加计算量的前提下提升了特征表示能力
  3. 结构优化:Neck 路径的简化,下采样路径中与 Backbone 原始输出的拼接,保持了信息流的高效传递

与 v8 相比,YOLOv11 在 COCO 上的 mAP 有约 0.5-1.0 的提升,而推理速度几乎不变。这得益于其"做减法"的设计思路------不是堆砌复杂的模块,而是让每个模块都发挥最大效用。

对于实际工程部署,YOLOv11 的简洁架构使其更容易导出为 ONNX/TensorRT,且量化友好,是一个在精度和效率之间取得优秀平衡的目标检测方案。


完整代码实现

下面是一个 YOLOv11n 最小实现,主要用来看一代模型结构,包含了所有上述模块:

python 复制代码
import torch
import torch.nn as nn
import torch.nn.functional as F

# ---------- 辅助模块 ----------
class Conv(nn.Module):
    """标准卷积 + BN + SiLU"""
    # c1, c2:输入通道数 / 输出通道数
    # k:卷积核大小(默认 1,常用于调整通道数)
    # s:步长(默认 1,用于下采样时设为 2)
    # p:填充大小。若为 None,自动计算为 k//2(保持输入输出尺寸相同,前提是 s=1)
    # g:分组卷积的组数(默认 1,即普通卷积)
    # act:是否使用激活函数(默认 True 使用 SiLU,否则恒等映射)
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):
        super().__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, p if p else k//2, groups=g, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = nn.SiLU() if act else nn.Identity()

    def forward(self, x):
        return self.act(self.bn(self.conv(x)))

class Bottleneck(nn.Module):
    """标准瓶颈模块(残差块)"""
    # c1, c2:输入 / 输出通道数
    # shortcut:是否启用残差连接(默认 True)
    # g:分组卷积的组数,可用于深度可分离卷积或分组特征融合
    # k:第二个卷积层的核大小(默认为 3)
    # e:扩展因子(默认 0.5)。中间通道数 c_ = int(c2 * e),通常 e<1 实现"瓶颈"效果
    def __init__(self, c1, c2, shortcut=True, g=1, k=3, e=0.5):
        super().__init__()
        c_ = int(c2 * e)  # 中间通道数
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_, c2, k, 1, g=g)
        self.add = shortcut and c1 == c2

    def forward(self, x):
        return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))

# ---------- C3k2 模块(YOLOv11的核心) ----------
class C3k2(nn.Module):
    """
    C3k2模块:CSP结构,内部可以选择使用C3k瓶颈块(c3k=True)或普通卷积(c3k=False)
    实际内部堆叠n个Bottleneck或简单卷积
    """
    # c1, c2:输入/输出通道数
    # n:内部堆叠的模块数量(控制深度)
    # shortcut:当 c3k=True 时,传递给 Bottleneck 的 shortcut 参数,控制 Botteneck 内部是否启用残差连接
    # g:分组卷积参数,传递给 Bottleneck 中的分组卷积
    # e:扩展因子,隐藏通道数 c_ = int(c2 * e),通常 e=0.5(通道减半),形成瓶颈
    # c3k:核心开关
    # True:内部使用 Bottleneck(具有残差连接能力,卷积核固定 k=3)
    # False:内部使用普通 Conv(Conv+BN+SiLU,无残差)
    def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5, c3k=False):
        super().__init__()
        c_ = int(c2 * e)  # 隐藏通道数
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c1, c_, 1, 1)
        self.cv3 = Conv(2 * c_, c2, 1)  # 拼接后融合

        if c3k:
            # 使用C3k瓶颈块(带残差,卷积核3)
            self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, k=3) for _ in range(n)))
        else:
            # 使用简单卷积(无残差)
            self.m = nn.Sequential(*(Conv(c_, c_, 3, 1) for _ in range(n)))

    def forward(self, x):
        y1 = self.cv1(x)
        y2 = self.m(self.cv2(x))
        # 在通道维度上拼接
        return self.cv3(torch.cat((y1, y2), dim=1))

# ---------- SPPF 模块(空间金字塔池化) ----------
class SPPF(nn.Module):
    """快速空间金字塔池化"""
    # c1, c2:输入/输出通道数。
    # k:最大池化核大小,默认为 5。为了保持输出尺寸不变,设置 stride=1 和 padding=k//2,这是"same"填充策略。
    # c_:中间通道数 = c1 // 2,即先通过 1x1 卷积将通道减半,降低后续池化与拼接的计算量。
    def __init__(self, c1, c2, k=5):
        super().__init__()
        c_ = c1 // 2
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_ * 4, c2, 1, 1)
        self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2)

    def forward(self, x):
        x = self.cv1(x)
        y1 = self.m(x)
        y2 = self.m(y1)
        y3 = self.m(y2)
        return self.cv2(torch.cat((x, y1, y2, y3), dim=1))

# ---------- C2PSA 模块(部分空间注意力) ----------
class PSA(nn.Module):
    def __init__(self, c1, e=0.5):
        super().__init__()
        c_ = int(c1 * e)                     # 隐藏通道数(默认一半)
        self.cv1 = Conv(c1, 2 * c_, 1, 1)    # 1x1 卷积扩展通道
        self.cv2 = Conv(2 * c_, c1, 1)       # 1x1 卷积恢复原始通道

    def forward(self, x):
        x = self.cv1(x)                      # (B, 2*c_, H, W)
        a, b = x.chunk(2, dim=1)             # 分成两个分支,各 (B, c_, H, W)
        b = b * a.sigmoid()                  # 注意力:a 的 sigmoid 作为权重,调制 b
        return self.cv2(torch.cat((a, b), dim=1))  # 拼接后恢复通道
    
class C2PSA(nn.Module):
    """C2结构集成的空间注意力模块"""
    def __init__(self, c1, c2, n=1):
        super().__init__()
        c_ = c1 // 2                                          # 中间通道数(输入通道的一半)
        self.cv1 = Conv(c1, 2 * c_, 1, 1)                     # 1x1 卷积扩展通道
        self.cv2 = Conv(2 * c_, c2, 1)                        # 1x1 卷积融合输出
        self.m = nn.Sequential(*(PSA(c_) for _ in range(n)))  # 堆叠 n 个 PSA

    def forward(self, x):
        y = self.cv1(x)                  # (B, 2*c_, H, W)
        a, b = y.chunk(2, dim=1)         # 拆分为 a, b 各 (B, c_, H, W)
        b = self.m(b)                    # 对 b 分支应用 n 个 PSA
        return self.cv2(torch.cat((a, b), dim=1))  # 拼接后融合输出


# ---------- 检测头(简化版) ----------
class Detect(nn.Module):
    """YOLOv11的检测头(简化,仅展示结构)"""
    def __init__(self, nc=80, ch=()):
        super().__init__()
        self.nc = nc  # 类别数
        self.nl = len(ch)  # 检测层数(一般为3)
        self.stride = torch.tensor([8., 16., 32.])  # 相对于原图的步长

        # 每个检测层都需要预测:框回归(4) + 类别(nc) + 置信度(1)
        c_out = 4 + nc  # 注意:实际YOLO使用DFL,这里简化
        self.cv2 = nn.ModuleList(nn.Conv2d(ch[i], 64, 3, 1, 1) for i in range(self.nl))
        self.cv3 = nn.ModuleList(nn.Conv2d(ch[i], c_out, 3, 1, 1) for i in range(self.nl))

    def forward(self, x):
        # x 是三个特征图列表
        results = []
        for i in range(self.nl):
            # 先经过一些小卷积提取特征
            box = self.cv2[i](x[i])      # 中间特征(可进一步回归)
            cls = self.cv3[i](x[i])      # 类别+框原始输出
            results.append(cls)
        return results

# ---------- 完整的 YOLOv11n 网络 ----------
class YOLOv11n(nn.Module):
    """
    基于yolo11n.yaml配置的最小化实现
    """
    def __init__(self, nc=80):
        super().__init__()
        # -------- Backbone(类似ResNet,逐步下采样) --------
        self.b1 = Conv(3, 16, 3, 2)                     # P1/2
        self.b2 = Conv(16, 32, 3, 2)                    # P2/4
        self.b3 = C3k2(32, 64, n=2, c3k=True)           # P3/8   (输出80x80)
        self.b4 = Conv(64, 128, 3, 2)                   # 下采样
        self.b5 = C3k2(128, 128, n=2, c3k=False)        # P4/16  (输出40x40)
        self.b6 = Conv(128, 256, 3, 2)                  # 下采样
        self.b7 = C3k2(256, 256, n=2, c3k=True)         # P5/32  (输出20x20)
        self.b8 = SPPF(256, 256, k=5)                   # SPPF
        self.b9 = C2PSA(256, 256, n=1)                  # C2PSA(最后加入注意力)

        # -------- Neck(类似UNet,特征融合) --------
        self.up1 = nn.Upsample(scale_factor=2, mode='nearest')
        self.n1 = C3k2(256 + 128, 128, n=2, c3k=False)   # 融合P4
        self.up2 = nn.Upsample(scale_factor=2, mode='nearest')
        self.n2 = C3k2(128 + 64, 64, n=2, c3k=False)     # 融合P3

        # 下采样路径(可选,用于增强大目标)
        self.down1 = Conv(64, 128, 3, 2)
        self.n3 = C3k2(128 + 128, 128, n=2, c3k=False)   # 融合P4

        self.down2 = Conv(128, 256, 3, 2)
        self.n4 = C3k2(256 + 256, 256, n=2, c3k=False)   # 融合P5

        # -------- Head(三个检测头) --------
        # 三个输出特征图通道数:64, 128, 256
        self.detect = Detect(nc=nc, ch=(64, 128, 256))

    def forward(self, x):
        # Backbone
        d1 = self.b1(x)   # 160x160
        d2 = self.b2(d1)  # 80x80
        d3 = self.b3(d2)  # 80x80   → 用于小目标检测
        d4 = self.b4(d3)  # 40x40
        d5 = self.b5(d4)  # 40x40   → 用于中目标检测
        d6 = self.b6(d5)  # 20x20
        d7 = self.b7(d6)  # 20x20
        d8 = self.b8(d7)  # SPPF
        d9 = self.b9(d8)  # C2PSA  → 用于大目标检测

        # Neck
        x1 = self.up1(d9)                 # 40x40
        x1 = torch.cat([x1, d5], dim=1)   # 拼接
        x1 = self.n1(x1)                  # 40x40

        x2 = self.up2(x1)                 # 80x80
        x2 = torch.cat([x2, d3], dim=1)   # 拼接
        x2 = self.n2(x2)                  # 80x80  → 小目标检测特征图P3

        x3 = self.down1(x2)               # 40x40
        x3 = torch.cat([x3, x1], dim=1)
        x3 = self.n3(x3)                  # 40x40  → 中目标检测特征图P4

        x4 = self.down2(x3)               # 20x20
        x4 = torch.cat([x4, d9], dim=1)
        x4 = self.n4(x4)                  # 20x20  → 大目标检测特征图P5

        # Head
        out = self.detect([x2, x3, x4])   # 三个尺度的预测结果
        return out

# ---------- 测试网络 ----------
if __name__ == "__main__":
    # 创建模型
    model = YOLOv11n(nc=80)  # 80类(COCO)
    print(f"总参数量: {sum(p.numel() for p in model.parameters()):,}")

    # 模拟输入(batch=1, 3通道, 640x640)
    x = torch.randn(1, 3, 640, 640)
    out = model(x)

    print(f"输出个数: {len(out)}")
    for i, o in enumerate(out):
        print(f"尺度{i+1}: {o.shape}")  # 应为 [1, 84, 80, 80] 等(84=4+80)

参考:

deepseek

相关推荐
大鱼>16 小时前
地平线BPU部署实战:YOLOv8在J5/X3上的算法适配与性能优化
算法·yolo·性能优化
stsdddd17 小时前
YOLO系列目标检测数据集大全【第二十九期】
yolo·目标检测·目标跟踪
大鱼>17 小时前
YOLO边缘部署深度指南:从YOLOv8n到NPU加速的全链路优化
yolo·aiot
AI棒棒牛17 小时前
第 03 讲《监督学习:数据、标签、Loss与训练循环》
人工智能·学习·yolo·目标检测·yolo26
FL162386312918 小时前
国内快递面单识别检测数据集VOC+YOLO格式422张6类别
人工智能·yolo·机器学习
stsdddd19 小时前
YOLO系列目标检测数据集大全【第三十期】
yolo·目标检测·目标跟踪
YOLO数据集集合19 小时前
无人机航拍地质灾害智能识别 山体滑坡实例分割数据集落地实战 | 泥石流监测 道路险情封堵 深度学习模型训练方案10296期
人工智能·深度学习·yolo·目标检测·无人机
音沐mu.20 小时前
【73】墙壁建筑缺陷数据集(有v5/v8模型)/YOLO墙壁建筑缺陷检测
yolo·目标检测·目标检测数据集·墙壁建筑缺陷数据集·墙壁建筑缺陷检测
前网易架构师-高司机20 小时前
带标注的辣椒病叶数据集,识别率95.9%,可识别三种病害和健康叶子,9916张图,支持yolo,coco json,voc xml,文末有模型训练代码
yolo·json·数据集·病害·叶病·病叶·辣椒
动物园猫1 天前
直升机停机坪目标检测数据集分享(适用于YOLO系列深度学习分类检测任务)
深度学习·yolo·目标检测