yolov5里面的骨干组件14:C3模块 (CSP Bottleneck with 3 Convolutions)

C3模块 (CSP Bottleneck with 3 Convolutions)

!abstract 论文信息

  • 论文: YOLOv5开源项目 (无正式论文)
  • 作者: Ultralytics团队
  • 年份: 2020
  • 来源: YOLOv5开源项目
  • 核心贡献: 简化的CSP结构,更适合YOLO的高效模块

引言

在读本篇文章之前,需要先补一下前文的基础,C3模块本质上是CSP模块的官方改进。C3 用更简单的双分支结构实现 CSP:一部分特征经过 Bottleneck 深加工,另一部分走短路径保留,最后拼接融合,从而减少重复计算并兼顾精度

一、核心思想

复制代码
C3的核心: 简化CSP结构,使用3个卷积实现高效特征提取

CSP vs C3:
┌─────────────────────────────────────┐
│  CSP (原始):                         │
│  - 使用Dense Block                  │
│  - 计算量较大                        │
│  - 内存占用高                        │
├─────────────────────────────────────┤
│  C3 (YOLOv5):                       │
│  - 使用Bottleneck堆叠               │
│  - 更轻量化                          │
│  - 推理速度更快                      │
└─────────────────────────────────────┘

直觉理解:
- 保持CSP的跨阶段部分连接思想
- 用更简单的Bottleneck替代Dense Block
- 平衡精度和速度,适合实时检测

二、模块结构

复制代码
C3模块结构:

输入特征图 (C×H×W)
    │
    ▼
┌─────────────────────────────────────┐
│  CV1: 1×1卷积 (通道压缩)             │
│  C×H×W → C/2×H×W                  │
│  (Part2的输入)                       │
└─────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────┐
│  Split (分割)                        │
│  直接从输入分出Part1                  │
│  C×H×W → C/2×H×W                  │
│  (旁路,直接传递)                     │
└─────────────────────────────────────┘
    │              │
    ▼              ▼
┌──────────┐  ┌──────────────────────┐
│  Part1   │  │  Part2               │
│  直接传递 │  │  N个Bottleneck堆叠    │
│          │  │  CV1 → Bottleneck×N  │
└──────────┘  └──────────────────────┘
    │              │
    ▼              ▼
┌─────────────────────────────────────┐
│  Concat (拼接)                       │
│  C/2×H×W + C/2×H×W → C×H×W        │
└─────────────────────────────────────┘
    │
    ▼
┌─────────────────────────────────────┐
│  CV3: 1×1卷积 (通道恢复)             │
│  C×H×W → C×H×W                    │
└─────────────────────────────────────┘
    │
    ▼
输出特征图 (C×H×W)

三、数学公式

复制代码
C3模块的数学表达:

1. 特征提取 (Part2):
   y₁ = CV1(x)  // 1×1卷积压缩通道
   y₂ = Bottleneck_N(...Bottleneck_2(Bottleneck_1(y₁)))
   
   其中:
   - Bottleneck_i: 第i个瓶颈块
   - N: 瓶颈块数量

2. 旁路 (Part1):
   y₃ = x[:, :C/2, :, :]  // 直接取前半通道
   或
   y₃ = CV2(x)  // 用1×1卷积提取

3. Concat (拼接):
   y = Concat(y₃, y₂)

4. 输出 (CV3):
   z = CV3(y)
   
   其中:
   - CV1: 1×1卷积, 压缩通道
   - CV2: 1×1卷积, 提取旁路特征 (可选)
   - CV3: 1×1卷积, 恢复通道

5. Bottleneck内部:
   out = x + Conv2(Conv1(x))  // shortcut连接
   或
   out = Conv2(Conv1(x))       // 无shortcut

四、代码实现

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

def autopad(k, p=None):
    if p is None:
        p = k // 2 if isinstance(k, int) else [x // 2 for x in k]
    return p

class Conv(nn.Module):
    """基础卷积: Conv + BN + 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, autopad(k, p), 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):
    """瓶颈块"""
    def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5):
        super().__init__()
        c_ = int(c2 * e)  # 隐藏通道数
        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):
        return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))

class C3(nn.Module):
    """C3模块: CSP Bottleneck with 3 Convolutions"""
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
        """
        Args:
            c1: 输入通道数
            c2: 输出通道数
            n: Bottleneck数量
            shortcut: 是否使用残差连接
            g: 分组卷积组数
            e: 通道扩展系数
        """
        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)  # 恢复通道
        
        # Bottleneck堆叠
        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):
        # Part2: 经过Bottleneck处理
        y1 = self.m(self.cv1(x))
        
        # Part1: 旁路直接传递
        y2 = self.cv2(x)
        
        # Concat + CV3
        return self.cv3(torch.cat([y1, y2], 1))

class C3NoShortcut(nn.Module):
    """无残差连接的C3模块"""
    def __init__(self, c1, c2, n=1, g=1, e=0.5):
        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)
        self.m = nn.Sequential(
            *[Bottleneck(c_, c_, shortcut=False, g=g, k=((1, 1), (3, 3)), e=1.0) for _ in range(n)]
        )
    
    def forward(self, x):
        return self.cv3(torch.cat([self.m(self.cv1(x)), self.cv2(x)], 1))

# 使用示例
if __name__ == "__main__":
    # 创建C3模块
    model = C3(c1=64, c2=64, n=3, shortcut=True)
    x = torch.randn(1, 64, 32, 32)
    
    # 前向传播
    out = model(x)
    
    print(f"输入形状: {x.shape}")
    print(f"输出形状: {out.shape}")
    
    # 计算参数量
    total_params = sum(p.numel() for p in model.parameters())
    print(f"参数量: {total_params:,}")

五、在YOLO中的应用

复制代码
C3模块在YOLOv5中的应用:

YOLOv5 Backbone:
┌─────────────────────────────────────┐
│  Focus → Conv → C3×1 → Conv → C3×3 │
│  → Conv → C3×3 → Conv → C3×1       │
│  → SPPF → Conv                      │
└─────────────────────────────────────┘

YOLOv5 Neck (PAN):
┌─────────────────────────────────────┐
│  上采样 → Concat → C3               │
│  上采样 → Concat → C3               │
│  Conv → Concat → C3                 │
│  Conv → Concat → C3                 │
└─────────────────────────────────────┘

C3模块配置:
┌─────────────────────────────────────┐
│  backbone:                          │
│    - [-1, 1, Conv, [64, 6, 2, 2]]  │
│    - [-1, 1, C3, [64, 3]]          │
│    - [-1, 1, Conv, [128, 3, 2]]    │
│    - [-1, 3, C3, [128]]            │
│    - [-1, 1, Conv, [256, 3, 2]]    │
│    - [-1, 3, C3, [256]]            │
│    ...                              │
└─────────────────────────────────────┘

C3 vs 其他模块对比:
┌────────┬──────────┬──────────┬──────────┐
│ 模块   │ 参数量   │ FLOPs    │ mAP      │
├────────┼──────────┼──────────┼──────────┤
│ C3     │ 基准     │ 基准     │ 基准     │
│ C3x    │ -10%     │ -8%      │ -0.3%   │
│ C3TR   │ +5%      │ +3%      │ +0.5%   │
└────────┴──────────┴──────────┴──────────┘

六、优缺点

优点

复制代码
✅ 结构简单: 三个卷积实现,易于理解和实现
✅ 计算高效: 比原始CSP更轻量
✅ 灵活配置: 可调整Bottleneck数量和通道数
✅ 梯度流畅: CSP结构保证梯度传播
✅ 即插即用: 可替换任何backbone模块

缺点

复制代码
❌ 固定结构: 分割比例固定为1:1
❌ 表达能力: 比DenseNet的特征复用少
❌ 设计空间: Bottleneck类型有限
❌ 缺乏创新: 本质上是CSP的简化版

参考

相关推荐
雪的季节4 小时前
不安装YOLO只安装 PyTorch,加载已有yolo数据,从无到有创建模型训练数据并加载使用(重要)
人工智能·pytorch·yolo
guo_xiao_xiao_5 小时前
YOLO[室内与室外多场景混凝土块目标检测]目标检测数据集
yolo·目标检测·目标跟踪
江畔柳前堤5 小时前
YOLO 目标检测全流程深度剖析
人工智能·yolo·目标检测·计算机视觉·unity·面试·vllm
guo_xiao_xiao_6 小时前
YOLO[野外溪流与人工水域][鱼]目标检测数据集
yolo·目标检测·目标跟踪
智购科技智能售货柜8 小时前
自动售货机商品识别YOLO模型训练实战:从6万张图片到98%识别率的完整复盘~YH
运维·服务器·数据库·人工智能·redis·物联网·yolo
CoderIsArt9 小时前
SAHI with YOLOv5 for Sliced Inference
人工智能·yolo
jay神19 小时前
深度学习确定baseline之后怎么做改进?
人工智能·深度学习·yolo·计算机视觉·分类
jay神1 天前
基于深度学习的车辆识别收费管理系统(全套源码+数据集)
人工智能·深度学习·yolo·计算机视觉·分类
Unknown To Known1 天前
血液细胞检测数据集 8类 血小板 淋巴细胞 免疫球蛋白 YOLO格式
yolo