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的简化版