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模块模型结构如下:
C3模块

C2f模块模型结构如下:
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模块


图注:幼儿园的学霸

  • 12
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
YOLOv8是一种目标检测算法,它是YOLOv5的改进版本。在YOLOv8中,C3C2f是两个重要的模块C3模块的源代码可以在YOLOv5的工程代码中的models/common.py文件中找到。这个模块是一种CSP(Cross Stage Partial)瓶颈结构,它包含了3个卷积层。C3模块的输入参数包括c1(输入通道数)、c2(输出通道数)、n(重复次数)、shortcut(是否使用shortcut连接)、g(分组数)、e(扩展系数)。C3模块的前向传播函数将输入x分别经过self.cv1和self.cv2进行卷积操作,然后将这两个结果和原始输入x在通道维度上进行拼接,然后经过一系列的Bottleneck块(数量为n),最后再经过一个1x1的卷积层self.cv3,将通道数变为c2。最终输出的结果就是C3模块的输出。 C2f模块是参考了C3模块和ELAN的思想进行设计的。它的结构图可以在引用的图2-2中找到。C2f模块的设计目的是在保持轻量化的同时获得更加丰富的梯度流信息。 在YOLOv8的网络结构中,C2f模块C3模块的接口保持一致,都是(ch_in, ch_out, number, shortcut, groups, expansion)。不同的是C2f模块默认不使用shortcut连接,而C3模块默认使用shortcut连接。在网络结构中,两者的shortcut位置没有差别,都是在Backbone中使用shortcut连接。 综上所述,C3模块C2f模块是YOLOv8中的两个重要模块,它们在网络结构中起到了不同的作用,但接口保持一致,方便在YOLOv8中进行调用和使用。 参考资料: YOLOv5的完整工程代码:ultralytic/yolov5 引用处的图2-2 引用处的代码片段
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值