- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊
博主简介:努力学习的22级本科生一枚 🌟;探索AI算法,C++,go语言的世界;在迷茫中寻找光芒🌸
博客主页:羊小猪~~-CSDN博客
内容简介:common.py详解,common.py文件讲述的是yolo网络具体实现 🙏 🙏 🙏
往期–>yolov5网络结构讲解:深度学习基础–yolov5网络结构简介,C3模块构建_yolov5的c3模块结构-CSDN博客
文章目录
- 1、导入库
- 2、基本组件
- autopad
- Conv
- Focus
- Bottlenck
- BottlenckCSP
- C3
- SPP
- ConCat
- Contract、Expand
📖 前言:
common.py
文件是实现YOLO算法中各个模块的地方,如果我们需要修改某一模块(例如C3),那么就需要修改这个文件中对应模块的的定义。*.yaml
文件是yolo
网络结构的搭建,而common.py
是每个网络模块具体实现的地方。
YOLOv5网络结构:
1、导入库
这部分是导入需要用到的包和库。
import ast
import contextlib
import json
import math
import platform
import warnings
import zipfile
from collections import OrderedDict, namedtuple
from copy import copy
from pathlib import Path
from urllib.parse import urlparseimport cv2
import numpy as np
import pandas as pd
import requests
import torch
import torch.nn as nn
from PIL import Image
from torch.cuda import amp
2、基本组件
autopad
这个模块是自动计算padding值的。
def autopad(k, p=None, d=1):"""Pads kernel to 'same' output shape, adjusting for optional dilation; returns padding size.`k`: kernel, `p`: padding, `d`: dilation."""if d > 1:k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k] # actual kernel-sizeif p is None:p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-padreturn p
Conv
这个模块是整个网络中最基础的组件,由卷积层 + BN层 + SiLU激活函数组成。
class Conv(nn.Module):# Standard convolution with args(ch_in, ch_out, kernel, stride, padding, groups, dilation, activation)default_act = nn.SiLU() # default activationdef __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):"""Initializes a standard convolution layer with optional batch normalization and activation."""'''c1:输入的channel值c2:输出的channel值k:卷积的kernel sizeparamss:卷积的strideparams p:卷积的padding一般是None 可以通过autopad自行计算需要pad的paddingparams g:卷积的groups数=1就是普通的卷积 >1就是深度可分离卷积params act:激活函数类型True就是SiLU()/Swish,False就是不使用激活函数类型是nn.Module就使用传进来的激活函数类型'''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):"""Applies a convolution followed by batch normalization and an activation function to the input tensor `x`."""return self.act(self.bn(self.conv(x)))def forward_fuse(self, x):"""Applies a fused convolution and activation function to the input tensor `x`."""return self.act(self.conv(x))
Focus
这个模块是作者设计出来的,目的是为了减少浮点数和提高精度。
📘 本质:将图片进行切片,将图片的宽、高进行切分,然后聚合到通道中,即实现了图片缩放、也提高了特征提前取。
实现:分组卷积。
class Focus(nn.Module):# Focus wh information into c-spacedef __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):"""Initializes Focus module to concentrate width-height info into channel space with configurable convolutionparameters."""''' 在yolo.py的parse_model函数中被调用理论:从高分辨率图像中,周期性的抽出像素点重构到低分辨率图像中,即将图像相邻的四个位置进行堆叠,聚焦wh维度信息到c通道,提高每个点感受野,并减少原始信息的丢失,该模块的设计主要是减少计算量加快速度。Focus wh information into c-space 把宽度w和高度h的信息整合到c空间中先做4个slice 再concat 最后再做Convslice后 (b,c1,w,h) -> 分成4个slice 每个slice(b,c1,w/2,h/2)concat(dim=1)后 4个slice(b,c1,w/2,h/2)) -> (b,4c1,w/2,h/2)conv后 (b,4c1,w/2,h/2) -> (b,c2,w/2,h/2):params c1: slice后的channel:params c2: Focus最终输出的channel:params k: 最后卷积的kernel:params s: 最后卷积的stride:params p: 最后卷积的padding:params g: 最后卷积的分组情况 =1普通卷积 >1深度可分离卷积:params act: bool激活函数类型 默认True:SiLU()/Swish False:不用激活函数'''super().__init__()self.conv = Conv(c1 * 4, c2, k, s, p, g, act=act)# self.contract = Contract(gain=2)def forward(self, x):"""Processes input through Focus mechanism, reshaping (b,c,w,h) to (b,4c,w/2,h/2) then applies convolution."""return self.conv(torch.cat((x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]), 1))# return self.conv(self.contract(x))
Bottlenck
模型结构:
作用:采用残差连接,解决梯度消失问题。
class Bottleneck(nn.Module):# Standard bottleneckdef __init__(self, c1, c2, shortcut=True, g=1, e=0.5):"""在BottleneckCSP和yolo.py的parse_model中调用Standard bottleneck Conv+Conv+shortcut:params c1: 第一个卷积的输入channel:params c2: 第二个卷积的输出channel:params shortcut: bool 是否有shortcut连接 默认是True:params g: 卷积分组的个数 =1就是普通卷积 >1就是深度可分离卷积:params e: expansion ratio e*c2就是第一个卷积的输出channel=第二个卷积的输入channel"""super().__init__()c_ = int(c2 * e) # hidden channelsself.cv1 = Conv(c1, c_, 1, 1)self.cv2 = Conv(c_, c2, 3, 1, g=g)self.add = shortcut and c1 == c2def forward(self, x):"""Processes input through two convolutions, optionally adds shortcut if channel dimensions match; input is atensor."""return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
BottlenckCSP
这个模块有Bottleneck模块和CSP结构组成。这个模块和C3等效,但是一般YOLO用的是C3结构。
网络结构:
class BottleneckCSP(nn.Module):# CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworksdef __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):"""在C3模块和yolo.py的parse_model模块调用CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks:params c1: 整个BottleneckCSP的输入channel:params c2: 整个BottleneckCSP的输出channel:params n: 有n个Bottleneck:params shortcut: bool Bottleneck中是否有shortcut,默认True:params g: Bottleneck中的3x3卷积类型 =1普通卷积 >1深度可分离卷积:params e: expansion ratio c2xe=中间其他所有层的卷积核个数/中间所有层的输入输出channel数"""super().__init__()c_ = int(c2 * e) # hidden channelsself.cv1 = Conv(c1, c_, 1, 1)self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False)self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False)self.cv4 = Conv(2 * c_, c2, 1, 1)self.bn = nn.BatchNorm2d(2 * c_) # applied to cat(cv2, cv3)self.act = nn.SiLU()self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))def forward(self, x):"""Performs forward pass by applying layers, activation, and concatenation on input x, returning feature-enhanced output."""y1 = self.cv3(self.m(self.cv1(x)))y2 = self.cv2(x)return self.cv4(self.act(self.bn(torch.cat((y1, y2), 1))))
C3
这个是BottlenckCSP的简化版,常用,作用是缓解梯度消失、提取特征。
👓 网络结构:
class C3(nn.Module):# CSP Bottleneck with 3 convolutionsdef __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):"""在C3TR模块和yolo.py的parse_model模块调用CSP Bottleneck with 3 convolutions:params c1: 整个BottleneckCSP的输入channel:params c2: 整个BottleneckCSP的输出channel:params n: 有n个Bottleneck:params shortcut: bool Bottleneck中是否有shortcut,默认True:params g: Bottleneck中的3x3卷积类型 =1普通卷积 >1深度可分离卷积:params e: expansion ratio c2xe=中间其他所有层的卷积核个数/中间所有层的输入输出channel数"""super().__init__()c_ = int(c2 * e) # hidden channelsself.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):"""Performs forward propagation using concatenated outputs from two convolutions and a Bottleneck sequence."""return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), 1))
SPP
空间金字塔池化(Spatial Pyramid Pooling,SPP)是目标检测算法中对高层特征进行多尺度池化以增加感受野的重要措施之一。
经典的空间金字塔池化模块首先将输入的卷积特征分成不同的尺寸,然后每个尺寸提取固定维度的特征,最后将这些特征拼接成个固定的维度
如图所示:
- 输入的卷积特征图的大小为(w,h),第一层空间金字塔采用 4x4 的刻度对特征图进行划分,其将输入的特征图分成了16个块,每块的大小为(w/4,h/4);
- 第二层空间金字塔采用2x2刻度对特征图进行划分,其将特征图分为4个快每块大小为(w/2,h/2);
- 第三层空间金字塔将整张特征图作为一块,进行特征提取操作,最终的特征向量为 21=16+4+1 维
SPP模块:
class SPP(nn.Module):# Spatial Pyramid Pooling (SPP) layer https://arxiv.org/abs/1406.4729def __init__(self, c1, c2, k=(5, 9, 13)):"""Initializes SPP layer with Spatial Pyramid Pooling, ref: https://arxiv.org/abs/1406.4729, args: c1 (input channels), c2 (output channels), k (kernel sizes)."""super().__init__()c_ = c1 // 2 # hidden channelsself.cv1 = Conv(c1, c_, 1, 1)self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1)self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])def forward(self, x):"""Applies convolution and max pooling layers to the input tensor `x`, concatenates results, and returns outputtensor."""x = self.cv1(x)with warnings.catch_warnings():warnings.simplefilter("ignore") # suppress torch 1.9.0 max_pool2d() warningreturn self.cv2(torch.cat([x] + [m(x) for m in self.m], 1))
ConCat
这个模块作用是:对两个维度进行合并,合并成一个新的特征图(一般是通道进行合并).
class Concat(nn.Module):# Concatenate a list of tensors along dimensiondef __init__(self, dimension=1):"""在yolo.py的parse_model模块调用Concatenate a list of tensors along dimension:params dimension: 沿着哪个维度进行concat"""super().__init__()self.d = dimensiondef forward(self, x):"""Concatenates a list of tensors along a specified dimension; `x` is a list of tensors, `dimension` is anint."""return torch.cat(x, self.d)
Contract、Expand
这两个函数用于改变 feature map 维度。
-
Contract 函数改变输入特征的shape,将feature map 的 w和h 维度(缩小)的数据收缩到 channel 维度上(放大)。如:x(1,64,80,80)
to
x(1,256,40,40) -
Expand 函数也是改变输入特征的 shape,不过与 Contract 的相反,是将 channel 维度(变小)的数据扩展到W和H维度(变大)。如:x(1,64,80,80)
to
x(1,16,160,160)
class Contract(nn.Module):# Contract width-height into channels, i.e. x(1,64,80,80) to x(1,256,40,40)def __init__(self, gain=2):"""Initializes a layer to contract spatial dimensions (width-height) into channels, e.g., input shape(1,64,80,80) to (1,256,40,40)."""super().__init__()self.gain = gaindef forward(self, x):"""Processes input tensor to expand channel dimensions by contracting spatial dimensions, yielding output shape`(b, c*s*s, h//s, w//s)`."""b, c, h, w = x.size() # assert (h / s == 0) and (W / s == 0), 'Indivisible gain's = self.gainx = x.view(b, c, h // s, s, w // s, s) # x(1,64,40,2,40,2)x = x.permute(0, 3, 5, 1, 2, 4).contiguous() # x(1,2,2,64,40,40)return x.view(b, c * s * s, h // s, w // s) # x(1,256,40,40)class Expand(nn.Module):# Expand channels into width-height, i.e. x(1,64,80,80) to x(1,16,160,160)def __init__(self, gain=2):"""Initializes the Expand module to increase spatial dimensions by redistributing channels, with an optional gainfactor.Example: x(1,64,80,80) to x(1,16,160,160)."""super().__init__()self.gain = gaindef forward(self, x):"""Processes input tensor x to expand spatial dimensions by redistributing channels, requiring C / gain^2 ==0."""b, c, h, w = x.size() # assert C / s ** 2 == 0, 'Indivisible gain's = self.gainx = x.view(b, s, s, c // s**2, h, w) # x(1,2,2,16,80,80)x = x.permute(0, 3, 4, 1, 5, 2).contiguous() # x(1,16,80,2,80,2)return x.view(b, c // s**2, h * s, w * s) # x(1,16,160,160)