一、深度学习的3大分层模型结构

1、起源、分模块的目的

        前面的文章我们已经讲过了【神经网络NN】、【卷积神经网络CNN】,那么现在试想一下让你去调优最底层的卷积神经网络的代码你能调吗?但是如果我们把这些代码操作分成各个模块,只对简单的外层的模块进行调整不就可以了?因此深度学习中,模型又被结构模块化,共分为了3大主要部分:【Backbone主干网络】、【Neck颈部】、【Head头部】

2、介绍【Backbone主干网络】、【Neck颈部】、【Head头部】

1)Backbone(骨干网络)

        Backbone 是指模型的主干网络,通常是一个预训练的卷积神经网络(CNN),在大规模图像分类任务中训练,它是整个模型的基础部分,作用是提取特征的网络

  • 从输入图像中提取特征(主要作用):通过卷积层、池化层等操作,逐渐提取出具有高级语义信息的特征图(边缘、纹理、形状、明暗对比这些最基础的图像信息)
  • 预训练权重:使用预训练的 backbone 可以加速模型收敛,提高性能,因为预训练权重已经从大量数据中学习到了一些通用特征

常见模型:

  • VGG:简单但有效的深度网络,主要由卷积层和池化层组成。
  • ResNet:通过引入残差连接,解决了深层网络中的梯度消失问题。
  • Inception:通过多尺度卷积和池化提高模型的表示能力。
  • EfficientNet:通过复合缩放方法在保持高效的同时提升模型性能。
  • MobileNet:轻量级网络,适用于移动设备和嵌入式系统。

2)Neck(颈部)

         Neck 是指在 backbone 和 head 之间的连接部分,通常用于进一步多尺度特征融合,将 backbone 提取的特征进行整合和调整,侧重于加强不同尺度的空间和语义信息。由于图像中物体的尺寸可能非常不同,目标检测和图像分割模型需要同时处理大物体和小物体。Neck 通过在多个尺度上提取特征,确保模型能够处理各种尺寸的目标。:

  • (对Backbone的输出)特征融合:对来自不同层级的特征图进行融合,结合低层次的细节信息和高层次的语义信息。
  • (为了head理解)降维或升维:通过卷积操作或其他方法调整特征图的维度,使其适应后续 head 的输入要求。
  • 上下文信息聚合:有些neck模块会使用上下文信息聚合技术,以更好地捕捉多尺度特征。

常见模型:

  • FPN:在不同尺度的特征图上进行融合,使得模型能够处理不同尺度的对象。
  • PANet:进一步增强了 FPN,通过路径聚合的方式提高特征信息的流动。
  • BiFPN:双向特征融合网络,常用于 EfficientDet。

3)Head(头部)

        Head 是指模型的输出部分,负责对 neck 提供的特征进行具体任务的处理,如分类、检测、分割等,包括分类头(确定候选框属于哪个类别)和回归头(预测候选框的位置):

  • 任务特定处理:根据具体任务(如目标检测、图像分类、语义分割等)对特征进行处理并生成最终的输出。
  • 预测结果:head 的输出通常是预测结果,如类别概率、边界框坐标、分割掩码等。

常见模型:

  • 分类头:通常是全连接层(FC)或 GAP(Global Average Pooling)层,如 ImageNet 分类任务中的 ResNet。
  • 检测头:如YOLO、SSD 的检测头,直接预测对象的位置和类别;R-CNN 的 RPN和 RoI Head,用于生成候选区域和最终的边界框预测;(YOLO是属于检测头)
  • 分割头:如 FCN、U-Net,用于像素级的语义分割任务。

4)总结

  1. Backbone:提取特征
  2. Neck:对特征进行进一步处理和融合
  3. Head:根据具体任务生成最终的预测结果(如分类头、检测头、分割头)

二、YOLO的这3部分的优化改进

        那么现在回归到YOLO,我们终究是要到YOLO代码里修改,所以得了解在YOLO里这3部分是啥样的,由哪些模块组成。

1、骨干网络(Backbone):给图片 “提炼特征”

  • 卷积(Conv)操作:通过多个卷积层逐步提取图像的浅层到深层特征,比如边缘、纹理、形状等信息。
    • CBS 模块:把基础Conv卷积包装一下的模块,也叫 “下采样”,就是
    • C3k2 模块:是网络的核心组件之一,YOLO系列经典的“特征提取单元”
      • 有 c3k=Falsec3k=True 两种模式。
      • 它通过 “分割(Split)- 瓶颈(Bottleneck)/ 子C3k模块 - 拼接(Concat)” 的流程,对特征进行更精细的处理,既减少计算量,又能保持特征的表达能力。
    • SPPF 模块:“快速空间金字塔池化”
      • 通过多次最大池化(MaxPool2d)和拼接(Concat),增大感受野(比如同时用大、中、小窗口提取特征),能更好地捕捉图像中远距离的特征依赖关系(比如整个鸡蛋的形状和近距离鸡蛋的裂纹),同时减少计算复杂度。
      • 不明白 “池化” 的看我往期文章讲解
    • C2PSA 模块:结合了“某种注意力机制”
      • 通过注意力机制强化【对关键特征的关注】,提升特征提取的针对性。

2、颈部(Neck):把特征 “混在一起优化”

会输出多个尺度特征图

  • 上采样(Upsample)拼接(Concat):上采样就是将不同尺度的特征图进行上采样后拼接,实现多尺度特征融合。这样能结合浅层的细节特征(如小目标的边缘)和深层的语义特征(如目标的类别信息),让网络对不同大小的目标都有较好的检测能力。
  • C3k2 模块再次作用:在 Neck 中继续使用 C3k2 模块,进一步细化和融合特征,确保特征在传递到 Head 前足够丰富和准确。

3、检测头(Detection Head):输出 “最终检测结果”

        基于融合后的特征,直接输出目标的“位置、类别、置信度”(比如图片左上角有鸡蛋裂纹,可信度90%),对多个尺度特征图分别预测,每个特征点会有多个锚框,通过非极大值抑制NMS筛选最准确的框,最终输出结果(比如目标的类别、位置坐标、置信度等)

  • 卷积(Conv) 与 深度卷积(DWConv) :得出最终【用于检测特征
  • 损失计算
    • CIoU:是一种边界框回归的损失函数,考虑了边界框的重叠度、中心点距离和长宽比,能更精准地优化边界框位置。
    • CLSLoss:是分类损失函数,用于优化目标类别的预测精度。
  • 分为几种设计模式:

    • 1、【解耦头模式】:
      • 体现了"分而治之"的思想,通过独立的分支处理不同的任务,避免了任务间的相互干扰,各司其职,提高了整体检测精度
        • 【分类】分支专注于【"是什么"】

        • 【回归】分支专注于【"在哪里"】

        • 【置信度】分支专注于【"有没有"】

    • 2、【多尺度检测头模式】:

      • 就像是为不同大小的目标配备了不同的"放大镜",这种设计确保了模型能够准确检测各种尺寸的目标:

        • 小目标】使用【高分辨率特征图(大放大镜)

        • 大目标】使用【低分辨率特征图(小放大镜)

  • YOLO11 系列的检测头同时融合了【解耦检测头】和【多尺度检测头】的设计思路,这也是当前 YOLO 家族的核心技术路线。

YOLO11网络模型的整体结构图

三、细分Backbone部分的模块

        接下来再细解Backbone主干部分的几个最核心的模块,看下图可以发现YOLO11的Backbone有这么几个反复出现的模块:CBS、C3K2、SPPF、C2PSA,那么这几个模块是啥呢?

只要记住这个结构图,会画一个一样的,那就可以随便调YOLO网络模型了

1、YOLO的Backbone历史

        我不像别的博主那么多废话,把YOLO所有历史的模块一个一个研究,我们tmd简单看一下这些逼专业术语到底有什么鸟关系,以下是我叫豆包整理的,你只需知道从上到下就是用老历史版本的结构一步步改进搭造到现在的最新的C3k2、C2PSA....等等模块的

这些C3、C2f、C3k....的C都是啥,有啥关联?

具体怎么分支?卷积具体多大?0人在意!!!!我鸟都不鸟!!!!我写代码又不看这些鸟东西!!!!!

2、C3k2模块细解

现在我们知道C3k2是YOLO11的核心模块,那就还是得了解一下他的机制

        从下面图中我们可以看到,C3K2模块其实就是C2F模块转变出来的,它代码中有一个设置:

  • c3k这个参数为false的时候:
    • 它中间的Bottleneck还是【普通的Bottleneck】
    • C3K2模块就是C2F模块
    • 侧重「高效轻量化」,适合需要低计算开销的场景,特征融合以并行分支为主
  • 反之当它为true的时候:
    • 将它中间的Bottleneck模块替换成【C3模块】
    • C3K2模块才是自己新的模块
    • 侧重「强特征表达」,C3 模块的残差连接让梯度流动更顺畅,适合复杂场景或更深的网络结构,特征提取更具鲁棒性

注意:

  • 两个模块都不会缩放图片尺寸✅:
    • 输入的图片是多大尺寸,输出就是多少,不做改变

3、C2PSA模块细解

        C2PSA是对 C2f 模块的扩展,它结合了PSA(Pointwise Spatial Attention)块,用于增强特征提取和注意力机制。通过在标准 C2f 模块中引入 【PSA 块】,C2PSA实现了【更强大的注意力机制】,通过多头注意力机制和前馈神经网络来增强特征提取能力。

        它可以选择性地添加残差结构(shortcut)以优化梯度传播和网络训练效果。同时,使用FFN 可以将输入特征映射到更高维的空间,捕获输入特征的复杂非线性关系,允许模型学习更丰富的特征表示,从而提高了模型对重要特征的捕捉能力。

我们只需要知道加入了【注意力机制(让网络更关心重点特征,少受无关特征影响)】

注意:

  • 1、也不会缩放图片尺寸
  • 2、输出通道也始终不变

4、SPPF模块细解

        说人话就是:【效率高】+【捕捉细节更多】的池化层,你要了解清楚也可以看看下面豆包解释

注意:

  • 1、也不会缩放图片尺寸
  • 2、输出通道也始终不变

5、CBS模块(升级版Conv)

        最后,这些Bottleneck、CK3、C3k2......结构图里反复出现最多的【CBS】是啥?就是最最基础的小组件模块!你就可以把它宏观简单理解成一个 “升级版卷积神经网络结构”,各个C3k2、C2f、C2PSA、SPPF模块,通通都是由多个CBS的不同尺寸、不同组合来构成的

  • 而1个CBS的结构就是:【Conv卷积层】+【BN归一化层】+【SiLU激活函数】

注意注意注意:

  • 【尺寸变化】
    • 当 CBS 的步长 = 2时
      • 就是【下采样层】,也就是把输入图片【缩小】再输出
      • 特征图尺寸会直接除以步长(例如 640×640→320×320、40×40→20×20)。
    • 当 CBS 的步长 = 1时
      • 尺寸保持不变(例如 20×20→20×20)

6、代码层面讲解

现在就可以到代码里讲解了

1)我们可以修改的【yolo11.yaml】文件

位置在下图所示,或者你也可以自己找到本地ultralytics安装目录下的这个路径

2)对应CBS(Conv)的代码传参

官方文件路径在这

官方代码(中文注释我自己加的)

class Conv(nn.Module):
    """Standard convolution module with batch normalization and activation.

    Attributes:
        conv (nn.Conv2d): Convolutional layer.
        bn (nn.BatchNorm2d): Batch normalization layer.
        act (nn.Module): Activation function layer.
        default_act (nn.Module): Default activation function (SiLU).
    """

    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 parameters.

        Args:
            c1 (int): 自动获取(上一个模块的输出通道).
            c2 (int): 输出的特征通道数.
            k (int): 卷积核尺寸(n×n).
            s (int): 卷积步长(步长n = 图片尺寸缩放n倍).
            p (int, optional): 填充(让卷积后尺寸匹配)                      【默认(由autopad自动计算)】
            g (int): 分组数(1 = 普通卷积,g=c1=Depth-wise 卷积)            【默认1】
            d (int): 膨胀率(1 = 普通卷积,>1 = 膨胀卷积)                    【默认1】
            act (bool | nn.Module): 激活函数(True = 用 SiLU,False = 不用)【默认True】
        """
        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.

        Args:
            x (torch.Tensor): Input tensor.

        Returns:
            (torch.Tensor): Output tensor.
        """
        return self.act(self.bn(self.conv(x)))

    def forward_fuse(self, x):
        """Apply convolution and activation without batch normalization.

        Args:
            x (torch.Tensor): Input tensor.

        Returns:
            (torch.Tensor): Output tensor.
        """
        return self.act(self.conv(x))

3)对应C3k2的代码传参

官方文件路径在这

class C3k2(C2f):
    """Faster Implementation of CSP Bottleneck with 2 convolutions."""

    def __init__(
        self, c1: int, c2: int, n: int = 1, c3k: bool = False, e: float = 0.5, g: int = 1, shortcut: bool = True
    ):
        """Initialize C3k2 module.

        Args:
            c1 (int): 输入的特征通道数(比如上一个模块输出 128,c1=128)
            c2 (int): 模块最终的输出通道数
            n (int): 中间模块(Bottleneck/C3k)的重复次数
            c3k (bool): 中间模块的“切换开关”【False:用Bottleneck / True:用C3k】
            e (float): 通道比例(控制分支的通道数)
            g (int): 卷积分组                                              【默认值(g=1)】
            shortcut (bool): 是否保留短路连接                                【默认值(shortcut=True)】
        """
        super().__init__(c1, c2, n, shortcut, g, e)
        self.m = nn.ModuleList(
            # c3k=False,中间重复的模块是Bottleneck→ 此时C3k2完全等价于C2f模块
            # c3k=Ture,中间重复的模块换成C3k→ 此时C3k2是 “C2f的结构 + C3k的核心”(也就是之前说的 C3k2)
            C3k(self.c, self.c, 2, shortcut, g) if c3k else Bottleneck(self.c, self.c, shortcut, g) for _ in range(n)
        )

4)对应C2PSA和SPPF的代码传参

官方文件路径和C3k2同在block.py

【总结】

现在可以对应结构图和代码的含义了

四、[颈部neck] + [头部head]部分

这块会和backbone部分结合,很复杂,需要耐心看我的图来理解

1、结构图分析

各个教材的YOLO网络结构图会有所不同,但是都是包含了【backbone】【neck】【head】的:

  • 这就是标准的图
    • 分成【backbone】【neck】【head】三个部分
  • 下面这图就是把【neck颈部】放到了【head头部】的图,那我们需要知道【neck颈部】在哪
    • 【neck颈部】就是————>【上采样 + concat + C3k2】【CBS + concat + C3k2】结合
    • 因为neck的作用就是把 Backbone 输出的 “不同尺度特征”(小图、中图、大图)concat融合起来,让模型既能看到 “小目标的细节”,又能看到 “大目标的全局信息”

【上采样】、【下采样】是啥?

说人话:上采样=图片缩小下采样=图片放大

backbone的下采样没有显性展示,而是通过步长stride参数变化、通道数变化而缩小图片尺寸的;neck的上采样则是直接定义了nn.Upsample这个模块来放大图片

2、【neck部分】对应代码

neck和head的代码统一都在【yolo11.yaml】文件的【head】这部分

1)先看head部分第一段的第1行

  • 第一段指的是第一块【neck部分】
  • 第一段的第1行就是下图这些意思,仔细放大理解一下

2)再看head部分第一段的第2行

  • 第一段指的是第一块【neck部分】
  • 第一段的第2行就是下图这些意思,仔细放大理解一下

3)再看head部分第一段的第3行

  • 第一段指的是第一块【neck部分】
  • 第一段的第3行就是下图这些意思,仔细放大理解一下

然后下面每一段都是按这个逻辑对照着推理就行了

3、【heah部分】对应代码

4、检测头Detect分析

1)原理细节分析

这就不得不再解释一下【CBS】、【Conv2d】、【DSConv】、【DWConv】

提一下,所需参数量就是【卷积多大多少】,比如1个3×3×3卷积核,参数量就是它的体积3×3×3

【普通卷积Conv2d】结构:

  • 【输入图】×【卷积核Filter】=【输出特征图featuremap】
    • 另外回顾一下卷积的一些基本运算

【深度可分离卷积DSConv】和【深度卷积DWConv】

  • 深度可分离卷积(Depthwise separable convolution, DSC)由逐深度卷积和逐点卷积组成。深度可分离卷积在特征维度上分组卷积,对每个channel进行独立的逐深度卷积(depthwise convolution),并在输出前使用一个1x1卷积(pointwise convolution)将所有通道进行聚合。
    • 【深度卷积DWConv】:用于提取空间特征
      • 和普通卷积Conv不一样的是,Conv是如果是1个卷积核,不管输入通道数和卷积核通道数是多少,最终只生产1个特征图,输出通道是1(【输入通道数】=【1个卷积核通道数】≠【输出通道数(始终1)】)
      • 而深度卷积是把输入图按通道分组,1组卷积核也按通道数分成单个1组,然后有几个通道就对应生成几个特征图,也就是(【输入通道数】=【1个卷积核通道数】=【输出通道数】)
    • 【逐点卷积PWConv】:用于提取通道特征。
      • 特点是卷积核尺寸只有1×1,这样一来就能对应输入图的每个点
      • 但是和卷积一样,不管卷积核通道数是多少、输入通道数是多少,依旧输出一张图,输出通道是1(【输入通道数】=【1个卷积核通道数】≠【输出通道数(始终1)】)
    • 那么【深度可分离卷积】是怎么做到让计算参数减少的?我们前面说过,【深度可分离卷积】=【深度卷积】+【逐点卷积】,那么【深度可分离卷积所需的参数量】就是【深度卷积所需参数量】+【逐点卷积所需参数量】我们对照前面【普通卷积Conv】的那个图,同样是8×8×3的输入图,同样输出通道是256,而【普通卷积Conv2d】要6912个参数!!!而【深度可分离卷积DSConv】只要795个参数!!!

【CBS卷积】

  • 这个没什么可说的,其实前面说过很多次了,就只是在【普通卷积Conv2d】基础上:
    • 加了【BN层】、【SiLU激活函数】,至于这两干啥的不用管,牛逼就完事了
    • 整体结构如下图:不过既然他包含【普通卷积Conv2d】,那肯定也是超多参数的

所以1个检测头的简单图

3个检测头详细图

2)代码分析

Detect检测头的官方文件路径和【block.py】、【conv.py】同级(ultralytics\nn\modules\head.py),然后找到代码的这部分

我们需要知道一点,YOLO检测头并行设计了 “分类分支” 和 “回归分支”::

  • 分类分支(输出类别):任务是 “判断目标属于哪一类”,语义区分难度较低,因此用轻量化结构(参数少);
  • 回归分支(输出坐标 / 宽高):任务是 “精准预测目标的位置形状”,空间定位难度更高,因此用更复杂的结构(参数多)
  • 所以对应代码结构就是如下图

五、应用实践进入加深理解

1、理解小目标层、中目标层、大目标层

还要用代码了解各个层的尺寸、通道数

【如果你使用CPU跑模型】

        使用的是yolo11n的模型查看,如果你要换成yolo11s或其他模型,你可以自行修改yolo11n.pt为其他模型的pt文件即可;同时我设置的是

import torch
import os
from ultralytics import YOLO

# 解决OMP冲突(Windows必加)
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"

# 1. 加载你的YOLO11n模型(带CBAM的也可以)
model = YOLO("yolo11n.pt")
# 把模型设为eval模式,避免BatchNorm干扰
model.eval()

# 2. 创建一个和你训练时一样尺寸的测试张量(1张图,3通道,640×640)
dummy_input = torch.randn(1, 3, 640, 640).to("cuda" if torch.cuda.is_available() else "cpu")

# 3. 遍历模型每一层,打印输出尺寸(关键!)
print("=== 各层输出尺寸(尺寸越大=小目标层)===")
x = dummy_input
for i, layer in enumerate(model.model.model):
    x = layer(x)
    # 只打印4维特征图(排除分类/回归头的一维输出)
    if len(x.shape) == 4:
        # 修改点:使用三引号 """ 将字符串包裹起来,允许内部换行
        print(f"""层{i}:输出尺寸 = {x.shape} → 对应目标类型:{
            '极小目标(P2)' if x.shape[2] == 160 else
            '中小目标(P3)' if x.shape[2] == 80 else
            '大目标(P4)' if x.shape[2] == 40 else '其他层'
        }""")

【如果你使用GPU跑模型】

import torch
import os
from ultralytics import YOLO

os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"

# 1. 加载模型
model = YOLO("yolo11n.pt")
device = "cuda" if torch.cuda.is_available() else "cpu"
model.model.to(device)  # 移动模型到 GPU

# 2. 定义字典存储输出
outputs = {}


# 3. 定义钩子函数
def get_output(idx):
    def hook(module, input, output):
        # 只保存4维特征图
        if isinstance(output, torch.Tensor) and len(output.shape) == 4:
            outputs[idx] = output

    return hook


# 4. 【关键修改】直接遍历 model.model.model 列表
# model.model 是 DetectionModel
# model.model.model 才是真正的 nn.Sequential 列表
try:
    # 尝试获取模型中的Sequential列表
    model_list = model.model.model
    if not isinstance(model_list, torch.nn.Sequential):
        # 如果获取的不是Sequential,尝试直接遍历model.model(虽然通常不行,但做个兼容)
        print("警告:未能直接找到 model.model.model 列表,尝试直接遍历 model.model...")
        model_list = model.model

    for i, layer in enumerate(model_list):
        # 注册钩子,使用索引 i 作为 key
        layer.register_forward_hook(get_output(i))

except Exception as e:
    print(f"遍历模型层出错: {e}")
    print("尝试打印模型结构以调试:")
    print(model.model)

# 5. 运行推理
dummy_input = torch.randn(1, 3, 640, 640).to(device)
with torch.no_grad():
    _ = model.model(dummy_input)

# 6. 格式化打印
print("-" * 80)
print(f"{'Layer Index':<12} | {'Output Shape (C, H, W)':<25} | {'Target Type'}")
print("-" * 80)

if not outputs:
    print("未捕获到任何输出,请检查模型结构或钩子注册逻辑。")
else:
    # 按索引排序
    for idx in sorted(outputs.keys()):
        out = outputs[idx]
        c, h, w = out.shape[1], out.shape[2], out.shape[3]

        target_type = (
            '极小目标层' if h == 160 else
            '中小目标层' if h == 80 else
            '大目标层' if h == 40 else '其他层'
        )

        print(f"{idx:<12} | {str((c, h, w)):<25} | {target_type}")

print("-" * 80)

极小目标层、中小目标层通常是我们所关心的捕捉特征的层

2、学会计算并画出结构图

或许上面你没看懂,那么我再捋一下流程分析一下

1)初始通道数

        首先,根据你选择的是yolo11n、yolo11s、yolo11m.....不同型号的模型,会有对应不同初始化通道数(和输入图像的尺寸、通道数无关)

2)然后回忆各个模块的特点

【backbone部分】

  • 结构:
    • CBC + CBS + C2k2(C2f) + CBS + C2k2(C2f) + CBSC2k2 + CBS + C2k2 + SPPF + C2PSA
  • 尺寸:
    • CBS:步长s=1时尺寸不变;s=2时为下采样,图像尺寸缩小2倍
    • C3k2或C2f:图片尺寸不变
    • C2PSA:图片尺寸不变
    • SPPF:图片尺寸不变
  • 通道数:
    • 这个由代码规定好,在yolo的yaml配置文件里,backbone和head那写了多少通道数,那一层就有多少多少层3×3的卷积核,而实际通道数则是【卷积核数量 × 该yolo模型的宽度系数】,比如 [-1, 1, Conv, [128, 3, 2]] 就代表这一层有128个3×3的卷积核,输出通道是128
  • 于是我们就可以画图了
    • 假如:根据yolo11n模型,输入640×640×3的图像
    • 那么初始通道数是64×0.25=16、第一层CBS下采样缩小尺寸到320×320
    • 然后到下一层的CBS,依旧尺寸缩小一半、通道数是代码设置的通道数×宽度系数
    • 以此类推,backbone的结构图你已经会画了(图片里打错字,是yolo11n)

【neck部分】

  • 结构:称对称结构,记住【对称】
    • 先是2个:【Upsample上采样 + concat合并C2k2(C2f)
    • 然后对应2个:【CBS下采样 + concat合并C2k2(C2f)
  • 尺寸:
    • Upsample上采样时,尺寸乘2
    • CBS下采样时,依旧步长=2,缩小2倍
    • concat 和 C3k2(C2f):尺寸不变
  • 通道数:
    • concat合并:会把两个输入路线的输入通道数相加,作为自己的输出通道数
    • C3k2(C2f):又会把concat输入的合并通道数减少,具体减少到多少取决于yaml代码配置的通道数(代码设置的通道数 × yolo模型宽度系数)
  • 整体结构图
    • 遵循1组【Upsample上采样 + concat合并C2k2(C2f)】、对应1组【CBS下采样 + concat合并C2k2(C2f)

【head (detect检测头)】

  • 结构:
    • 不用记,因为通常调整yaml文件不用看detect内部结构,就一个detect检测头就行
  • 尺寸:
    • yolo的多尺度检测头按捕获图像小、中、大目标,分为
      • 极小目标层:neck传过来是多少尺寸就是多少尺寸,尺寸最大(80×80)
      • 中小目标层:neck传过来是多少尺寸就是多少尺寸,尺寸中等(40×40)
      • 大目标层:neck传过来是多少尺寸就是多少尺寸,尺寸最小(20×20)
  • 通道数:
  • 整体结构图                            

3)整个yolo11网络结构图(以640×640×3输入、yolo11n为例子)

        同时留意,看图中路线,最终某个检测头输出的图片尺寸和backbone的对应输入的下采样CBS的尺寸一样

自己对照代码可以推算一下

3、代码验证

不确定自己画结构、各层尺寸通道对不对的,可以用我下面的代码验证:

【如果你使用CPU跑模型】

import torch
import os
from ultralytics import YOLO

# 解决OMP冲突(Windows必加)
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"

# 1. 加载你的YOLO11s模型(带CBAM的也可以)
model = YOLO("yolo11s.pt")
# 把模型设为eval模式,避免BatchNorm干扰
model.eval()

# 2. 创建一个和你训练时一样尺寸的测试张量(1张图,3通道,1280×1280)
dummy_input = torch.randn(1, 3, 1280, 1280).to("cuda" if torch.cuda.is_available() else "cpu")

# 3. 遍历模型每一层,打印输出尺寸(关键!)
print("=== 各层输出尺寸(尺寸越大=小目标层)===")
x = dummy_input
for i, layer in enumerate(model.model.model):
    x = layer(x)
    # 只打印4维特征图(排除分类/回归头的一维输出)
    if len(x.shape) == 4:
        # 修改点:使用三引号 """ 将字符串包裹起来,允许内部换行
        print(f"""层{i}:输出尺寸 = {x.shape} → 对应目标类型:{
            '极小目标(P2)' if x.shape[2] == 160 else
            '中小目标(P3)' if x.shape[2] == 80 else
            '大目标(P4)' if x.shape[2] == 40 else '其他层'
        }""")

【如果你使用GPU跑模型】

import torch
import os
from ultralytics import YOLO

os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"

# 1. 加载模型
model = YOLO("yolo11s.pt")
device = "cuda" if torch.cuda.is_available() else "cpu"
model.model.to(device)  # 移动模型到 GPU

# 2. 定义字典存储输出
outputs = {}


# 3. 定义钩子函数
def get_output(idx):
    def hook(module, input, output):
        # 只保存4维特征图
        if isinstance(output, torch.Tensor) and len(output.shape) == 4:
            outputs[idx] = output

    return hook


# 4. 【关键修改】直接遍历 model.model.model 列表
# model.model 是 DetectionModel
# model.model.model 才是真正的 nn.Sequential 列表
try:
    # 尝试获取模型中的Sequential列表
    model_list = model.model.model
    if not isinstance(model_list, torch.nn.Sequential):
        # 如果获取的不是Sequential,尝试直接遍历model.model(虽然通常不行,但做个兼容)
        print("警告:未能直接找到 model.model.model 列表,尝试直接遍历 model.model...")
        model_list = model.model

    for i, layer in enumerate(model_list):
        # 注册钩子,使用索引 i 作为 key
        layer.register_forward_hook(get_output(i))

except Exception as e:
    print(f"遍历模型层出错: {e}")
    print("尝试打印模型结构以调试:")
    print(model.model)

# 5. 运行推理
dummy_input = torch.randn(1, 3, 1280, 1280).to(device)
with torch.no_grad():
    _ = model.model(dummy_input)

# 6. 格式化打印
print("-" * 80)
print(f"{'Layer Index':<12} | {'Output Shape (C, H, W)':<25} | {'Target Type'}")
print("-" * 80)

if not outputs:
    print("未捕获到任何输出,请检查模型结构或钩子注册逻辑。")
else:
    # 按索引排序
    for idx in sorted(outputs.keys()):
        out = outputs[idx]
        c, h, w = out.shape[1], out.shape[2], out.shape[3]

        target_type = (
            '极小目标层' if h == 160 else
            '中小目标层' if h == 80 else
            '大目标层' if h == 40 else '其他层'
        )

        print(f"{idx:<12} | {str((c, h, w)):<25} | {target_type}")

print("-" * 80)

输出网络模型结构结果:

下一篇写手动修改yolo11网络模型代码

更多推荐