C3与C2f模块介绍与代码
微信公众号:幼儿园的学霸
目录
文章目录
- C3与C2f模块介绍与代码
- 目录
- 简介
- CSP/C3模块概述
- C2f模块概述
- C3与C2f结构对比
- 参考资料
简介
顺序:CSPNet->C3->C2f
C2
module refers to the CSP (Cross Stage Partial) Bottleneck with 2 convolutions
C2f
module is a faster implementation of the C2 module. It improves the execution speed of the model while maintaining similar performance. This optimization is achieved by making certain modifications to the original C2 module
CSP/C3模块概述
CSP
(Cross Stage Partial-connections,跨阶段部分连接) 模块是一种跨阶段部分连接的模块,它能够有效地整合不同阶段的特征表示,并使模型在训练过程中更加关注重要的部分.特点:降低计算量的同时保证精度
看着这跨阶段三个字,肯定又是Skip操作,目的为了解决梯度消失问题,同时丰富多尺度特征,提高检测等任务的效果。
CSP结构通过将输入特征分为两部分,然后在这两个部分之间进行交叉连接的方法来提高神经网络的性能。CSP结构能有效的提高模型的特征表示能力,从而提高模型的准确性和泛化能力。
CSP 模块主要由两个部分组成:cross connection 和 partial connection。在 cross connection 部分,输入的 feature maps 会被分为两部分,分别进行不同的预测和处理,然后再将其合并起来;在 partial connection 部分,输入的 feature maps 会经过一个卷积层和一个残差连接,来提取更高层次的特征信息。
在CSPNet中,Partial Connection通常与Cross Connection相结合来实现。具体而言,基础层的特征图被分割成两部分,其中一部分直接绕过某些层(实现Cross Connection),而另一部分则进入这些层进行处理(实现Partial Connection)。通过这种方式,网络既能够保持较强的表征能力,又能够降低计算复杂度和内存使用。
通过 cross connection 和 partial connection 的结合,CSP 模块可以在保证模型深度的同时,提高了网络的计算效率和特征表示能力,从而在目标检测、图像分类等任务中达到更好的表现。具体来说,CSP 模块可以帮助网络有效地利用不同尺度的特征信息,增强模型对于输入图像的感知能力,同时减少了由于深度网络引入的梯度消失和过拟合等问题。
具体来说,BottleneckCSP 模块会首先使用一个 1x1 的卷积层来减少 feature maps 的通道数,然后再通过一个 bottleneck 层进一步压缩 feature maps 的深度。接下来,feature maps 会被分为两个部分并进行 shuffle 操作,然后再进行 concat、BN、ReLU 等操作得到新的 feature maps。这样处理后,BottleneckCSP 模块可以有效地利用空间信息和通道信息,加深网络的层数,从而提高检测精度。同时,由于使用了 bottleneck 和 shuffle 操作,BottleneckCSP 模块的计算复杂度较低,网络也较为轻量级。
C3
模块:YOLOv5网络结构的核心就是CSPBlock模块,用YOLOv5的的语言来说,就是"C3"模块,相关代码如下所示
#!/usr/bin/env python3
# coding=utf-8
# ============================#
# Program:test.py
# C3模块展示及网络结构查看
# Date:24-7-21
# Author:liheng
# Version:V1.0
# ============================#
import torch
import torch.nn as nn
# from torchsummary import summary
# 定义 Conv 类和 Bottleneck 类
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 with args(ch_in, ch_out, kernel, stride, padding, groups, dilation, activation)."""
default_act = nn.SiLU() # default activation
# default_act = nn.ReLU() # default activation
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
"""Initialize Conv layer with given arguments including activation."""
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."""
return self.act(self.bn(self.conv(x)))
def forward_fuse(self, x):
"""Perform transposed convolution of 2D data."""
return self.act(self.conv(x))
class Bottleneck(nn.Module):
# Standard bottleneck
def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c_, c2, 3, 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):
# CSP Bottleneck with 3 convolutions
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
# ch_in, ch_out, number, shortcut, groups, expansion
super().__init__()
c_ = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, c_, 1, 1)
self.cv2 = Conv(c1, c_, 1, 1)
self.cv3 = Conv(2 * c_, c2, 1) # optional act=FReLU(c2)
self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, 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))
# 创建一个示例模型
c1 = 64 # 输入通道数
c2 = 256 # 输出通道数
model = C3(c1, c2,3)
# 创建一个示例输入张量(假设输入大小为 [batch_size, c1, height, width])
batch_size = 3
height, width = 224, 224
input_tensor = torch.randn(batch_size, c1, height, width)
# 进行前向传播
output = model(input_tensor)
# 打印输出张量的形状,以确认模型运行正常
print("Output shape:", output.shape)
# print(model)
# 使用 torchsummary 打印模型摘要
# summary(model, input_size=(c1, height, width))
# # 查看模型结构
# import netron
# import torch.onnx
# modelData='./c3.onnx'
# # 将 pytorch 模型以 onnx 格式导出并保存
# torch.onnx.export(model, input_tensor, modelData)
# # 输出网络结构
# netron.start(modelData)
C2f模块概述
C2
module refers to the CSP (Cross Stage Partial) Bottleneck with 2 convolutions
C2f块:首先由一个卷积块(Conv)组成,该卷积块接收输入特征图并生成中间特征图
特征图拆分:生成的中间特征图被拆分成两部分,一部分直接传递到最终的Concat块,另一部分传递到多个Botleneck块进行进一步处理。
Bottleneck块:输入到这些Botleneck块的特征图通过一系列的卷积、归一化和激活操作进行处理,最后生成的特征图会与直接传递的那部分特征图在Concat块进行拼接(Concat)。
模型深度控制:在C2f模块中,Botleneck模块的数量由模型的depth muliple参数定义,这意味着可以根据需求灵活调整模块的深度和计算复杂度。
最终卷积块:拼接后的特征图会输入到一个最终的卷积块进行进一步处理,生成最终的输出特征图。
yolov8使用的C2f结构同样分为两种,一种在bottleneck中有残差结构,一种没有残差结构.
C2f模块默认不使用shortcut连接,C3模块默认使用shortcut连接,C2f相比于C3模块梯度流更丰富.
新的"C2f"模块在一定程度上是受到了YOLOv7的ELAN模块的启发,加入更多的分支,丰富梯度回传时的支流。
C2f模块代码如下:
#!/usr/bin/env python3
# coding=utf-8
# ============================#
# Program:C2f.py
# C2f模块展示及网络结构查看
# Date:24-7-21
# Author:liheng
# Version:V1.0
# ============================#
import torch
import torch.nn as nn
# from torchsummary import summary
# 定义 Conv 类和 Bottleneck 类
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 with args(ch_in, ch_out, kernel, stride, padding, groups, dilation, activation)."""
default_act = nn.SiLU() # default activation
# default_act = nn.ReLU() # default activation
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
"""Initialize Conv layer with given arguments including activation."""
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."""
return self.act(self.bn(self.conv(x)))
def forward_fuse(self, x):
"""Perform transposed convolution of 2D data."""
return self.act(self.conv(x))
class Bottleneck(nn.Module):
# Standard bottleneck
# 残差连接瓶颈层, Residual block
def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5):
'''
:param c1: 输入通道
:param c2: 输出通道
:param shortcut: 为True时采用残差连接
:param g: groups 在输出通道上分组, c2 // g 分组后不同组之间的卷积核参数不同
:param e: 中间层的通道数
'''
# ch_in, ch_out, shortcut, groups, kernels, expand
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):
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
class C2f(nn.Module):
# CSP Bottleneck with 2 convolutions
def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion
super().__init__()
self.c = int(c2 * e) # hidden channels
self.cv1 = Conv(c1, 2 * self.c, 1, 1)
self.cv2 = Conv((2 + n) * self.c, c2, 1) # optional act=FReLU(c2)
self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n))
def forward(self, x):
y = list(self.cv1(x).split((self.c, self.c), 1))
y.extend(m(y[-1]) for m in self.m)
return self.cv2(torch.cat(y, 1))
# 创建一个示例模型
c1 = 64 # 输入通道数
c2 = 256 # 输出通道数
shortcut = False
model = C2f(c1, c2,3,shortcut=shortcut)
# 创建一个示例输入张量(假设输入大小为 [batch_size, c1, height, width])
batch_size = 3
height, width = 224, 224
input_tensor = torch.randn(batch_size, c1, height, width)
# 进行前向传播
output = model(input_tensor)
# 打印输出张量的形状,以确认模型运行正常
print("Output shape:", output.shape)
# print(model)
# 使用 torchsummary 打印模型摘要
# summary(model, input_size=(c1, height, width))
# 查看模型结构
import netron
import torch.onnx
modelData='./c2f.onnx'
# 将 pytorch 模型以 onnx 格式导出并保存
torch.onnx.export(model, input_tensor, modelData)
# 输出网络结构
netron.start(modelData)
C3与C2f结构对比
基于以上代码,可以绘制两者的模型结构如下:
C3模块模型结构如下:
C2f模块模型结构如下:
C2f模块默认不使用shortcut连接,C3模块默认使用shortcut连接,C2f相比于C3模块梯度流更丰富.
参考资料
1.Why the C2f module in yaml file called ‘C2f’? What does it means
2.《目标检测大杂烩》-第13章-浅析YOLOv8
3.yolov5进阶
4.YOLOv8中的C2f模块