YOLO v5-C3模块实现

本文介绍了YOLOv5中C3模块的实现,包括自动padding、基本Conv模块、Bottleneck模块、C3模块和其他如SPP、SPPF模块的构建。通过这些模块,作者实现了对数据集的分类任务,讨论了如何确定第一个线性层的神经元个数,并提供了数据集处理和网络构建的步骤。
摘要由CSDN通过智能技术生成

YOLO v5-C3模块实现


前言


总结

  • 在本周的学习中自己学会YOLO v5中的C3模块的搭建, 同时根据网络结构图,实现SPP, SPPF 等模块, 并用几个简单的模块,对之前的数据集实现分类
  • 自己在学习过程是,知道class一些实现方法,写网络要找到共同的地方,定义基本模块,实现模块的复用。
  • 如何确定第一个linear层的神经元个数

一、定义自动padding

  • 为了保持图像大小卷积前后一致,就需要用到自动padding
def autopad(k, p=None):                        # kernel  padding 根据卷积核大小k自动计算卷积核padding数(0填充)
    """
    :param k: 卷积核的 kernel_size
    :param p: 卷积的padding  一般是None
    :return:  自动计算的需要pad值(0填充)
    """
    if p is None:
        # k 是 int 整数则除以2, 若干的整数值则循环整除
        p = k // 2 if isinstance(k, int) else [x // 2 for x in k]
    return p

二、基本Conv模块

  • C3, SPP, SPPF模块中都有基本的Conv模块,需要编写基本的Conv模块,方面复用,提高代码的复用性
    在这里插入图片描述
class Conv(nn.Module):
    def __init__(self, c1, c2, k=1, s=1, p=None, act=True, g=1):
        """
        :param c1: 输入的channel值
        :param c2: 输出的channel值
        :param k: 卷积的kernel_size
        :param s: 卷积的stride
        :param p: 卷积的padding  一般是None
        :param act: 激活函数类型   True就是SiLU(), False就是不使用激活函数
        :param g: 卷积的groups数  =1就是普通的卷积  >1就是深度可分离卷积
        """
        super(Conv, self).__init__()

        self.conv_1 = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=True)
        self.bn = nn.BatchNorm2d(c2)

        self.act = nn.SiLU() if act else nn.Identity()     # 若act=True, 则激活,  act=False, 不激活

    def forward(self, x):

        return self.act(self.bn(self.conv_1(x)))

三、Bottleneck模块

  • Bottleneck模块中包含一个残差连接结构(左),和不包含的残差结构(右),就需要传入参数,来判断是否需要使用残差结构
    在这里插入图片描述
class Bottleneck(nn.Module):
    def __init__(self, c1, c2, e=0.5, shortcut=True, g=1):
        """
        :param c1: 整个Bottleneck的输入channel
        :param c2: 整个Bottleneck的输出channel
        :param e: expansion ratio  c2*e 就是第一个卷积的输出channel=第二个卷积的输入channel
        :param shortcut: bool Bottleneck中是否有shortcut,默认True
        :param g: Bottleneck中的3x3卷积类型  =1普通卷积  >1深度可分离卷积
        """
        super(Bottleneck, self).__init__()

        c_ = int(c2*e)                            # 使通道减半, c_具体多少取决于e
        self.conv_1 = Conv(c1, c_, 1, 1)
        self.conv_2 = Conv(c_, c2, 3, 1, g=g)
        self.add = shortcut and c1 == c2

    def forward(self, x):
        return x + self.conv_2(self.conv_1(x)) if self.add else self.conv_2(self.conv_1(x))

四、C3模块

  • 在这里插入图片描述
class C3(nn.Module):
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):
        """
        :param c1: 整个 C3 的输入channel
        :param c2: 整个 C3 的输出channel
        :param n: 有n个Bottleneck
        :param shortcut: bool Bottleneck中是否有shortcut,默认True
        :param g: C3中的3x3卷积类型  =1普通卷积  >1深度可分离卷积
        :param e: expansion ratio
        """
        super(C3, self).__init__()
        c_ = int(c2 * e)
        self.cv_1 = Conv(c1, c_, 1, 1)
        self.cv_2 = Conv(c1, c_, 1, 1)
        # *操作符可以把一个list拆开成一个个独立的元素,然后再送入Sequential来构造m,相当于m用了n次Bottleneck的操作
        self.m = nn.Sequential(*[Bottleneck(c_, c_, e=1, shortcut=True, g=1) for _ in range(n)])
        self.cv_3 = Conv(2*c_, c2, 1, 1)

    def forward(self, x):
        return self.cv_3(torch.cat((self.m(self.cv_1(x)), self.cv_2(x)), dim=1))

五、其他模块

class BottleneckCSP(nn.Module):
    def __init__(self, c1, c2, e=0.5, n=1):
        """
        :param c1: 整个BottleneckCSP的输入channel
        :param c2: 整个BottleneckCSP的输出channel
        :param e: expansion ratio c2*e=中间其他所有层的卷积核个数/中间所有层的输入输出channel数
        :param n: 有 n 个Bottleneck
        """
        super(BottleneckCSP, self).__init__()
        c_ = int(c2*e)
        self.conv_1 = Conv(c1, c_, 1, 1)
        self.m = nn.Sequential(*[Bottleneck(c_, c_, e=1, shortcut=True, g=1) for _ in range(n)])
        self.conv_3 = Conv(c_, c_, 1, 1)
        self.conv_2 = Conv(c1, c_, 1, 1)
        self.bn = nn.BatchNorm2d(2*c_)
        self.LeakyRelu = nn.LeakyReLU()
        self.conv_4 = Conv(2*c_, c2, 1, 1)

    def forward(self, x):
        x_1 = self.conv_3(self.m(self.conv_1(x)))
        x_2 = self.conv_2(x)
        x_3 = torch.cat([x_1, x_2], dim=1)
        x_4 = self.LeakyRelu(self.bn(x_3))
        x = self.conv_4(x_4)
        return x

class SPP(nn.Module):
    def __init__(self, c1, c2, e=0.5, k1=5, k2=9, k3=13):
        """
        :param c1: SPP模块的输入channel
        :param c2: SPP模块的输出channel
        :param e: expansion ratio
        :param k1: Maxpool 的卷积核大小
        :param k2: Maxpool 的卷积核大小
        :param k3: Maxpool 的卷积核大小
        """
        super(SPP, self).__init__()
        c_ = int(c2*e)
        self.cv_1 = Conv(c1, c_, 1, 1)
        self.pool_1 = nn.MaxPool2d(kernel_size=k1, stride=1, padding=k1 // 2)
        self.pool_2 = nn.MaxPool2d(kernel_size=k2, stride=1, padding=k2 // 2)
        self.pool_3 = nn.MaxPool2d(kernel_size=k3, stride=1, padding=k3 // 2)
        self.cv_2 = Conv(4*c_, c2, 1, 1)

    def forward(self, x):
        return self.cv_2(torch.cat((self.pool_1(self.cv_1(x)), self.pool_2(self.cv_1(x)), self.pool_3(self.cv_1(x)), self.cv_1(x)), dim=1))

六、编写模块构建网络实现分类

6.1 数据集操作

total_dir = './weather_photos/'

transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(std=[0.5, 0.5, 0.5], mean=[0.5, 0.5, 0.5])
])

total_data = torchvision.datasets.ImageFolder(total_dir, transform)
print(total_data)
print(total_data.class_to_idx)

idx_to_class = dict((v, k) for k,v in total_data.class_to_idx.items())
print(idx_to_class)

train_size = int(len(total_data) * 0.8)
test_size = int(len(total_data)) - train_size

train_dataset, test_dataset = torch.utils.data.random_split(total_data, [train_size, test_size])

train_dataloader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_dataloader = DataLoader(test_dataset, batch_size=32, shuffle=True)

6.1 构建网络

  • 在编写上述模块时,发现最主要的参数就是输入通道,和输入通道,只需要保证调用模块时,上次一层的输入通道与下一层输出通道保持一致就行。

6.2 linear神经元个数判定

  • 在构建网络模块时不知道第一个全连接神经元个数,可用如下方法进行判断,构建好初全连接以外的层,在forward进行传播,并在最后一个层前向传播完进行打印size。
class model(nn.Module):
	def __init__(self):
        super(model, self).__init__()

        self.conv = Conv(3, 32, 3, 2)                   # 3:输入通道  32:输出通道  3: kernel  2:stride
        self.spp = SPP(32, 64)
        self.c3 = C3(64, 128, n=1, shortcut=True, g=1, e=0.5)
        # 接下来就是 linear 层

    def forward(self, x):
        x = self.conv(x)
        x = self.spp(x)
        x = self.c3(x)
        print(x.size())
        return x


device = 'cuda' if torch.cuda.is_available() else 'cpu'
model = model().to(device)

for x, y in train_dataloader:
    x, y = x.to(device), y.to(device)
    y_pre = model(x)
    break
  • 打印输出 torch.Size([32, 128, 112, 112]), 32为batch-size,全连接神经元个数就是128112112,
  • 重新编写网络结构函数
class model(nn.Module):
    def __init__(self):
        super(model, self).__init__()

        self.conv = Conv(3, 32, 3, 2)                   # 3:输入通道  32:输出通道  3: kernel  2:stride
        self.spp = SPP(32, 64)
        self.c3 = C3(64, 128, n=1, shortcut=True, g=1, e=0.5)
        self.linear = nn.Sequential(
            nn.Linear(128*112*112, 1000),
            nn.ReLU(),

            nn.Linear(1000, 4)
        )

    def forward(self, x):
        x = self.conv(x)
        x = self.spp(x)
        x = self.c3(x) 
        x = x.view(-1, 128*112*112)
        x = self.linear(x)
        return x

  • 并运行代码: Process finished with exit code 0
  • 按照之前的代码进行训练评估即可

### YOLO v5 工作原理与架构解析 #### 输入端 YOLO v5 接收图像作为输入,支持多种尺寸的输入图片。为了提高模型性能,在预处理阶段引入了 Mosaic 数据增强技术[^3]。该技术通过拼接四张随机选取的小批量样本形成一张大图来扩充数据集多样性。 #### 骨干网络 (Backbone) 骨干网负责从原始图像中抽取基础特征。YOLO v5 使用 CSPDarknet53 作为其主要骨架结构[^2]。此设计借鉴了 CSPNet 的思想,即跨阶段部分连接机制,有效减少了计算成本的同时增强了梯度传播效果。具体而言: - **CSPBlock**:由卷积层、批标准化(Batch Normalization)、激活函数 ReLU 组成的基础单元; - **Residual Pathway**:类似于 ResNet 中残差路径的设计思路,有助于缓解深层神经网络中的退化问题; - **Spatial Pyramid Pooling Module (SPP)**:用于捕捉多尺度上下文信息,进一步提升了检测器对于目标大小变化鲁棒性。 ```python class C3(nn.Module): # CSP Bottleneck with 3 convolutions def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): 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) self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)]) ``` #### 颈部模块 (Neck) 颈部的作用在于加强空间维度上的语义表达力,并促进低层次细节特征向高层次抽象表示传递。YOLO v5 实现了一个名为 PANet 的双向特征金字塔网络(PAN)[^2]。它不仅继承了 FPN 自顶向下构建高分辨率特征映射的能力,还额外加入了自底向上路径聚合节点间的信息流,从而更好地平衡全局视野和局部纹理之间的关系。 ```python def build_neck(cfg, in_channels_list): neck_type = cfg.MODEL.NECK.TYPE if neck_type == 'PAN': neck_module = PPAN(in_channels=in_channels_list, out_channels=[int(i*cfg.MODEL.CSPNET.WIDTH_MULTIPLE) for i in [256, 512, 1024]], num_blocks=3) elif ... ``` #### 头部输出 (Head) 最终预测头完成边界框回归及类别分类任务。YOLO v5 支持多个不同规模版本(small/middle/large/xlarge),这些变体可通过调整 `depth_multiple` 和 `width_multiple` 参数灵活控制网络深度与宽度。此外,针对每个网格位置生成三个先验框(anchor boxes),利用 K-means 聚类算法自动学习最优形状配置。 ```python class DetectMultiBackend(nn.Module): stride = None # strides computed during build onnx_dynamic = False # ONNX export parameter def __init__(self, weights='yolov5s.pt', device=torch.device('cpu'), dnn=False, data=None, fp16=False): super().__init__() w = str(weights[0] if isinstance(weights, list) else weights) suffixes = ['.pt'] pt = any([suffix in w.lower() for suffix in suffixes]) model = attempt_load(w, map_location=device) # load FP32 model .stride = model.stride.max().item() ... ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小啊磊_Vv

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

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

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

打赏作者

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

抵扣说明:

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

余额充值