目录
一、课题背景和开发环境
📌第Y4周:common.py文件解读📌
- 语言:Python3、Pytorch
- 📌本周任务:将yolov5s网络模型中的C3模块按照下图方式修改,并跑通yolov5。
- 💫任务提示:仅需修改
./models/common.py
文件 - 文件位置:
./models/common.py
该文件是实现YOLO算法中各个模块的地方,如果我们需要修改某一模块(例如C3),那么就需要修改这个文件中对应模块的定义。这里我们先围绕代码,过一遍各个模块的定义,详细介绍将参考 @K同学啊|接辅导、项目定制 后续的教案内容逐步展开。由于YOLOv5版本问题,同一个模块我们可能会看到不同的版本,这些都是正常的,以官网为主即可。
开发环境
- 电脑系统:Windows 10
- 语言环境:Python 3.8.2
- 编译器:无(直接在cmd.exe内运行)
- 深度学习环境:Pytorch 1.8.1+cu111
- 显卡及显存:NVIDIA GeForce GTX 1660 Ti 12G
- CUDA版本:Release 10.2, V10.2.89(
cmd
输入nvcc -V
或nvcc --version
指令可查看) - YOLOv5开源地址:YOLOv5开源地址
- 数据:🔗水果检测
二、代码解析
0.导入需要的包和基本配置
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 # Path将str转换为Path对象,使字符串路径易于操作的模块
from urllib.parse import urlparse
import cv2
import numpy as np # numpy数组操作模块
import pandas as pd # pandas数组操作模块
import requests # Python的HTTP客户端库
import torch # pytorch深度学习框架
import torch.nn as nn # 专门为神经网络设计的模块化接口
from IPython.display import display
from PIL import Image # 图像基础操作模块
from torch.cuda import amp # 混合精度训练模块
from utils import TryExcept
from utils.dataloaders import exif_transpose, letterbox
from utils.general import (LOGGER, ROOT, Profile, check_requirements, check_suffix, check_version, colorstr,
increment_path, is_notebook, make_divisible, non_max_suppression, scale_boxes, xywh2xyxy,
xyxy2xywh, yaml_load)
from utils.plots import Annotator, colors, save_one_box
from utils.torch_utils import copy_attr, smart_inference_mode
1.基本组件
1.1 autopad
这个模块可以根据输入的卷积核计算卷积模块所需的pad值。将会用于下面会讲到的 Conv
函数和 Classify
函数中。
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
1.2 Conv
这个函数是整个网络中最基础的组件,由 卷积层 + BN层 + 激活函数 组成,具体结构如下
另外这个类中还有一个特殊函数 forward_fuse
,这是一个前向加速推理模块,在前向传播过程中,通过融合 Conv + BN 层,达到加速推理的作用,一般用于测试或验证阶段。
class Conv(nn.Module):
# Standard convolution with args(ch_in, ch_out, kernel, stride, padding, groups, dilation, activation)
default_act = nn.SiLU() # default activation
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
''' 在Focus、Bottleneck、BottleneckCSP、C3、SPP、DWConv、TransformerBlock等模块中调用
Standard convolution : conv + BN + act
:params c1: 输入的channel值
:params c2: 输出的channel值
:params k: 卷积的kernel_size
:params s: 卷积的stride
:params p: 卷积的padding,默认是None,可以通过autopad自行计算需要的padding值
:params 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):
return self.act(self.bn(self.conv(x)))
def forward_fuse(self, x):
''' 用于Model类的fuse函数
融合 Conv + BN 加速推理,一般用于测试/验证阶段
'''
return self.act(self.conv(x))
1.3 Focus
为了减少浮点数和提高速度,而不是增加featuremap的,本质就是将图像进行切片,类似于下采样取值,将原图像的宽高信息切分,聚合到channel通道中。结构如下所示:
class Focus(nn.Module):
# Focus wh information into c-space 把宽度w和高度h的信息整合到c空间中
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
''' 在yolo.py的parse_model函数中被调用
理论:从高分辨率图像中,周期性的抽出像素点重构到低分辨率图像中,即将图像相邻的四个位置进行堆叠,
聚集wh维度信息到c通道中,提高每个点的感受野,并减少原始信息的丢失,该模块的设计主要是减少计算量加快速度。
先做4个slice,再concat,最后在做Conv
slice后 (b1,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): # x(b,c,w,h) -> y(b,4c,w/2,h/2)
''' 有点像做了个下采样 '''
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))
1.4 Bottleneck
模型结构
class Bottleneck(nn.Module):
# Standard bottleneck Conv + Conv + shortcut
def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion
''' 在BottleneckCSP和yolo.py的parse_model函数中被调用
: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 channels
self.cv1 = Conv(c1, c_, 1, 1) # 1x1
self.cv2 = Conv(c_, c2, 3, 1, g=g) # 3x3
self.add = shortcut and c1 == c2 # shortcut=Ture & c1==c2 才能做shortcut
def forward(self, x):
return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
1.5 BottleneckCSP
这个模块是由Bottleneck和CSP结构组成。CSP结构来源于2019年发表的一篇论文:CSPNet: A New Backbone that can Enhance Learning Capability of CNN
这个模块和上面yolov5s中的C3模块等效,如果要用的话直接在yolov5s.yaml文件中将C3改成BottleneckCSP即可,但一般来说不用改,因为C3更好。
BottleneckCSP模块具体的结构如下所示:
class BottleneckCSP(nn.Module):
# CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks
def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion
''' 在C3模块和yolo.py的parse_model函数中被调用
: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,e*c2=中间其它所有层的卷积核个数=中间所有层的的输入输出channel
'''
super().__init__()
c_ = int(c2 * e) # hidden channels
self.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) 2*c_
self.act = nn.SiLU()
# 叠加n次Bottleneck
self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)))
def forward(self, x):
y1 = self.cv3(self.m(self.cv1(x)))
y2 = self.cv2(x)
return self.cv4(self.act(self.bn(t