Ultralytics:解读BottleneckCSP模块

在这里插入图片描述

前言

相关介绍

Ultralytics 简介

Ultralytics 基于多年的计算机视觉和人工智能基础研究,创建了最先进的 (SOTA) YOLO 模型。我们的模型不断更新性能和灵活性,快速、准确且易于使用。他们擅长对象检测、跟踪、实例分割、语义分割、图像分类和姿势估计任务。

前提条件

  • 熟悉Python、Pytorch

实验环境

Package                  Version
------------------------ ------------
Python                   3.11.8
absl-py                  2.4.0
accelerate               1.13.0
annotated-doc            0.0.4
anyio                    4.13.0
calflops                 0.3.2
certifi                  2026.4.22
charset-normalizer       3.4.7
click                    8.3.3
colorama                 0.4.6
contourpy                1.3.3
cycler                   0.12.1
filelock                 3.29.0
flatbuffers              25.12.19
fonttools                4.62.1
fsspec                   2026.4.0
grpcio                   1.80.0
h11                      0.16.0
hf-xet                   1.5.0
httpcore                 1.0.9
httpx                    0.28.1
huggingface_hub          1.14.0
idna                     3.15
Jinja2                   3.1.6
kiwisolver               1.5.0
Markdown                 3.10.2
markdown-it-py           4.2.0
MarkupSafe               3.0.3
matplotlib               3.10.9
mdurl                    0.1.2
ml_dtypes                0.5.0
mpmath                   1.3.0
networkx                 3.6.1
numpy                    1.26.4
nvidia-cublas-cu12       12.8.3.14
nvidia-cuda-cupti-cu12   12.8.57
nvidia-cuda-nvrtc-cu12   12.8.61
nvidia-cuda-runtime-cu12 12.8.57
nvidia-cudnn-cu12        9.7.1.26
nvidia-cufft-cu12        11.3.3.41
nvidia-cufile-cu12       1.13.0.11
nvidia-curand-cu12       10.3.9.55
nvidia-cusolver-cu12     11.7.2.55
nvidia-cusparse-cu12     12.5.7.53
nvidia-cusparselt-cu12   0.6.3
nvidia-nccl-cu12         2.26.2
nvidia-nvjitlink-cu12    12.8.61
nvidia-nvtx-cu12         12.8.55
onnx                     1.19.0
onnxruntime-gpu          1.26.0
onnxslim                 0.1.94
opencv-python            4.6.0.66
packaging                26.2
pillow                   12.2.0
pip                      24.0
polars                   1.40.1
polars-runtime-32        1.40.1
protobuf                 7.34.1
psutil                   7.2.2
pycocotools              2.0.11
Pygments                 2.20.0
pyparsing                3.3.2
python-dateutil          2.9.0.post0
PyYAML                   6.0.3
regex                    2026.5.9
requests                 2.34.1
rich                     15.0.0
safetensors              0.7.0
scipy                    1.16.0
setuptools               65.5.0
shellingham              1.5.4
six                      1.17.0
sympy                    1.14.0
tabulate                 0.10.0
tensorboard              2.20.0
tensorboard-data-server  0.7.2
tokenizers               0.22.2
torch                    2.7.1+cu128
torchaudio               2.7.1+cu128
torchvision              0.22.1+cu128
tqdm                     4.67.3
transformers             5.8.1
triton                   3.3.1
typer                    0.25.1
typing_extensions        4.15.0
ultralytics              8.4.58
ultralytics-thop         2.0.19
urllib3                  2.7.0
Werkzeug                 3.1.8

BottleneckCSP(CSP 瓶颈模块)

BottleneckCSP 是一种 CSP(Cross Stage Partial)瓶颈模块,由 WongKinYiu 在 Cross Stage Partial Networks 中提出。它通过将输入特征沿通道拆分为两路,一路经过多个 Bottleneck 块进行深层特征提取,另一路直接传递,最后将两路拼接并融合,从而在减少计算量的同时保持梯度多样性。与 C3 模块相比,它使用了更传统的 CSP 结构(包含独立的 BN 和激活),并采用了 nn.Conv2d 直接定义部分卷积层(无 BN 和激活),提供了更高的灵活性。


代码实现

import cv2
import math
import torch
import numpy as np
import matplotlib.pyplot as plt
from torch import 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 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): Number of input channels.
            c2 (int): Number of output channels.
            k (int): Kernel size.
            s (int): Stride.
            p (int, optional): Padding.
            g (int): Groups.
            d (int): Dilation.
            act (bool | nn.Module): Activation function.
        """
        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))

class Bottleneck(nn.Module):
    """Standard bottleneck."""

    def __init__(
        self, c1: int, c2: int, shortcut: bool = True, g: int = 1, k: tuple[int, int] = (3, 3), e: float = 0.5
    ):
        """Initialize a standard bottleneck module.

        Args:
            c1 (int): Input channels.
            c2 (int): Output channels.
            shortcut (bool): Whether to use shortcut connection.
            g (int): Groups for convolutions.
            k (tuple): Kernel sizes for convolutions.
            e (float): Expansion ratio.
        """
        super().__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, k[0], 1)
        self.cv2 = Conv(c_, c2, k[1], 1, g=g)
        self.add = shortcut and c1 == c2

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        """Apply bottleneck with optional shortcut connection."""
        return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))

class BottleneckCSP(nn.Module):
    """CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks."""

    def __init__(self, c1: int, c2: int, n: int = 1, shortcut: bool = True, g: int = 1, e: float = 0.5):
        """Initialize CSP Bottleneck.

        Args:
            c1 (int): Input channels.
            c2 (int): Output channels.
            n (int): Number of Bottleneck blocks.
            shortcut (bool): Whether to use shortcut connections.
            g (int): Groups for convolutions.
            e (float): Expansion ratio.
        """
        super().__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False)
        self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False)
        self.cv4 = Conv(2 * c_, c2, 1, 1)
        self.bn = nn.BatchNorm2d(2 * c_)  # applied to cat(cv2, cv3)
        self.act = nn.SiLU()
        self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        """Apply CSP bottleneck with 4 convolutions."""
        y1 = self.cv3(self.m(self.cv1(x)))
        y2 = self.cv2(x)
        return self.cv4(self.act(self.bn(torch.cat((y1, y2), 1))))

功能

  • 通道拆分与特征融合:输入经过两个并行路径:
    • 主路径cv1(1×1 卷积 + BN + SiLU)将通道从 c1 映射到 c_,然后通过 nBottleneck 块进行深层特征提取,最后通过 cv3(1×1 卷积,无 BN)调整通道。
    • 短路径cv2(1×1 卷积,无 BN)直接将输入映射到 c_ 通道。
  • 特征拼接与归一化:两条路径的输出在通道维拼接(得到 2*c_ 通道),随后经过 bn(BatchNorm2d)和 act(SiLU)进行归一化与激活。
  • 输出压缩:最后通过 cv4(1×1 卷积,含 BN 和 SiLU)将通道压缩到 c2

初始化参数

参数类型说明
c1int输入通道数
c2int输出通道数
nintBottleneck 块的数量(默认 1)
shortcutboolBottleneck 内部是否使用残差连接(默认 True)
gint分组卷积的组数(作用于 Bottleneck 中的第二个卷积)
efloat扩展比,c_ = int(c2 * e),控制隐藏通道数(默认 0.5)

前向方法

  • forward(x):输入 x[B, c1, H, W]),输出 [B, c2, H, W]

计算流程

  1. a = self.cv1(x) → 主路径特征,[B, c_, H, W]
  2. a = self.m(a) → 通过 nBottleneck 块,输出仍为 [B, c_, H, W]
  3. a = self.cv3(a) → 1×1 卷积,[B, c_, H, W]
  4. b = self.cv2(x) → 短路径特征,[B, c_, H, W]
  5. cat = torch.cat((a, b), dim=1)[B, 2*c_, H, W]
  6. cat = self.bn(cat) → 批量归一化。
  7. cat = self.act(cat) → SiLU 激活。
  8. return self.cv4(cat) → 1×1 卷积,压缩到 c2

使用示例

在这里插入图片描述

if __name__ == '__main__':
    # 1. 随机输入
    x = torch.randn(1, 32, 64, 64)

    # 2. 创建 BottleneckCSP(输入32,输出64,n=2)
    bcsp = BottleneckCSP(c1=32, c2=64, n=2, shortcut=True, g=1, e=0.5)

    # 3. 前向传播
    with torch.no_grad():
        out = bcsp(x)
    print("输入形状:", x.shape)   # [1, 32, 64, 64]
    print("输出形状:", out.shape) # [1, 64, 64, 64]

    # 4. 使用真实图像演示(扩展为多通道)
    img_path = "cat_640x640.png"
    img_bgr = cv2.imread(img_path)
    if img_bgr is not None:
        img_gray = cv2.cvtColor(cv2.resize(img_bgr, (64, 64)), cv2.COLOR_BGR2GRAY)
        img_tensor = torch.from_numpy(img_gray).float().unsqueeze(0).unsqueeze(0)  # [1,1,64,64]
        # 扩展通道至32
        x_img = img_tensor.repeat(1, 32, 1, 1)

        bcsp_img = BottleneckCSP(c1=32, c2=32, n=1, shortcut=True, e=0.5)
        with torch.no_grad():
            out_img = bcsp_img(x_img)

        inp_ch0 = x_img[0, 0].cpu().numpy()
        out_ch0 = out_img[0, 0].cpu().numpy()

        def norm(arr):
            return (arr - arr.min()) / (arr.max() - arr.min() + 1e-8)

        plt.figure(figsize=(12, 5), constrained_layout=True)
        plt.subplot(1, 3, 1)
        plt.imshow(img_gray, cmap='gray')
        plt.title("Original Gray")
        plt.axis("off")
        plt.subplot(1, 3, 2)
        plt.imshow(norm(inp_ch0), cmap='gray')
        plt.title("Input Ch0")
        plt.axis("off")
        plt.subplot(1, 3, 3)
        plt.imshow(norm(out_ch0), cmap='gray')
        plt.title("BottleneckCSP Output Ch0")
        plt.axis("off")
        plt.savefig("bottleneckcsp_demo.png", dpi=150)
        print("可视化已保存为 bottleneckcsp_demo.png")

在这里插入图片描述
输出示例

输入形状: torch.Size([1, 32, 64, 64])
输出形状: torch.Size([1, 64, 64, 64])
可视化已保存为 bottleneckcsp_demo.png

流程示意图

输入 x (B, c1, H, W)

cv1: Conv 1x1, c1→c_ (含BN+SiLU)

cv2: Conv 1x1, c1→c_ (无BN, 无激活)

m: n 个 Bottleneck 块

cv3: Conv 1x1, c_→c_ (无BN, 无激活)

a (B, c_, H, W)

b (B, c_, H, W)

拼接 cat(a, b) → (B, 2*c_, H, W)

bn: BatchNorm2d(2*c_)

act: SiLU

cv4: Conv 1x1, 2*c_→c2 (含BN+SiLU)

输出 (B, c2, H, W)


代码解读

  • __init__
    • c_ = int(c2 * e):隐藏通道数。
    • self.cv1:标准 Conv(包含 BN + SiLU),用于主路径的初始通道变换。
    • self.cv2:普通 nn.Conv2d(无 BN,无激活),用于短路径的直接映射。
    • self.cv3:普通 nn.Conv2d(无 BN,无激活),用于主路径最后的通道调整。
    • self.cv4:标准 Conv,用于将拼接后的特征压缩到输出通道 c2
    • self.bn:BatchNorm2d 作用于拼接后的特征(2*c_ 通道)。
    • self.act:SiLU 激活函数,在 BN 后应用。
    • self.m:由 nBottleneck 组成的序列,每个 Bottleneck 的输入/输出通道均为 c_shortcutg 参数传递,e=1.0(不压缩通道)。
  • forward
    • 主路径:cv1mcv3
    • 短路径:cv2
    • 拼接后经 BN、激活,再通过 cv4 输出。

注意事项

  1. 通道数要求c1c2 可为任意,但 c_ = int(c2 * e) 必须为正整数,否则会因维度错误而报错。
  2. BN 与激活位置:与 C3 不同,BottleneckCSP 在拼接后统一进行 BN 和激活,而 C3cv3 内部已包含 BN 和激活。这种设计可能对训练稳定性有影响。
  3. cv2cv3 无偏置:使用 bias=False,因为后续有 BN 层,可节省参数量。
  4. 空间尺寸不变:所有卷积步长均为 1,填充自动 same,因此输入输出空间尺寸一致。
  5. C3 的差异C3 使用 cv1cv2 两个并行的 1×1 卷积(均含 BN+激活),而 BottleneckCSPcv2cv3 无 BN 和激活,且拼接后统一归一化。此外,BottleneckCSPBottleneckk=((1,1),(3,3))C3 相同。

优缺点

优点
  1. 结构清晰:遵循标准 CSP 设计,易于理解和扩展。
  2. 灵活性:通过 neshortcut 等参数可调节深度和宽度。
  3. 梯度流动优化:短路径提供直接梯度通道,有助于训练深层网络。
  4. 较少的 BN 层:拼接后才进行 BN,相比每个卷积后都加 BN 可能更高效。
缺点
  1. 重复计算cv2cv3 为普通卷积,缺少 BN 和激活可能影响特征分布。
  2. C3 相比,性能可能略有差异:BN 位置的变化可能改变训练动态。
  3. 无残差直通:短路径(cv2)不包含 BN 和激活,可能引入不匹配的特征统计量。

在 YOLOv4 等网络中,BottleneckCSP 被用作骨干网络的基础模块。实际使用时,可参考官方配置调整参数,并注意与后续层的兼容性。若需要更现代的实现,可考虑 C3C2f 等模块。

参考文献

[1] https://docs.ultralytics.com/
[2] https://github.com/ultralytics/ultralytics.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FriendshipT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值