YOLOv1-yolov5三个方面来说:优缺点、网络结构、改进点
基本原理:这里以yolov1为例。首先明确一点YOLO是将目标检测问题转为回归问题。图像输入到模型后,首先,将原始图像分为7*7个网格如图1所示,每个网格有两个任务。任务1:每个网格生成两个候选框并负责找到该网格内的物体如图2所示。任务2:每个网格预测一个类别,并预测物体概率值。其次,每个网格会有30维的列向量。前十维分别是网格中的两个候选框会:1个候选框共有五维列向量。分别是x、y:表示某一候选框左上角相对于整个图片左上角左移和下移的偏移量。w、h:候选框的宽度和高度。置信度表示候选框得分情况。后20维向量分别是2017Pascal数据集中20个类别数量如图3所示。最后经过NMS筛选和IOU判断后生成若干个包含20个类别之内的边界框和该边界框对应的置信度、类别标签。狗和车会被框起来见图四所示。
图一 图二
图三
图四
经典框架:TensorFlow、PyTorch、PaddlePaddle
1998年首次提出经典分类网络LeNet,开创了计算机视觉领域
AlexNet、VggNet验证了网络越深提取的特征越好
中国人的骄傲---------ResNet由何凯明团队提出,在计算机视觉领域贡献了一种设计思想。所有复杂大模型里面都有ResNet的影子。
集大成者YOLO提出者约瑟夫·雷多蒙,外号:快乐的小野马对标何凯明团队二阶段算法。
yolov1雷多蒙,一直到yolov3,在平台上宣布退出视觉领域:YOLO算法很快很准,考虑到智能设备如无人机+YOLO在军事及个人隐私上的影响退出CV视觉领域。
整合所有传统网络模型的优势及设计思想,有五个级别:m、n、l、s、x
杀死物体检测这个赛道,当前及未来的一段时间仍会跟着Ultralytics 公司解决问题
YOLO可以做什么:图像分类、目标检测、实体分割、追踪、姿态识别
应用:
1、优缺点
开山之作v1: 2015年
优势
速度快:可以达到实时检测
背景误检率低:是基于整个图像进行预测,背景误检率相对较低。
通用性强:可以学到物体的泛化特征,对于新物体的检测也有一定效果。
劣势:
定位不准确:对于小目标或相邻目标,定位精度不高,容易漏检、误检。
召回率低:相对于其他算法,YOLOv1的召回率较低,可能漏掉一些目标。
亮点:
端到端训练:YOLOv1是第一个实现端到端训练的目标检测算法,无需复杂的预处理和后处理步骤。
实时性:在保证一定准确率的同时,实现了实时检测。
v2: 2016年
优势:
检测精度提升:YOLOv2在检测精度上有显著提升,尤其是小目标和相邻目标的检测。
可识别更多类别:通过联合训练目标检测和分类数据集,可以识别多达9000种物体类别。
劣势:
计算量增加:为了提升性能,YOLOv2的网络结构更加复杂,计算量也相应增加。
训练难度增大:联合训练多个数据集需要更复杂的训练策略和技巧。
亮点:
多尺度训练:通过在不同尺寸的输入图像上进行训练,增强了模型对不同尺寸目标的检测能力。
v3: 2018年
优势:
检测精度进一步提升:通过改进网络结构和损失函数,在检测精度上有了进一步的提升。
多尺度预测:采用了多尺度特征融合的方式,提高了对不同大小目标的检测性能。
更好的小目标检测效果:通过引入特征金字塔网络(FPN)结构,提升了小目标的检测效果。
劣势:
计算量较大:相对于v2有所优化,计算量仍然较大,需要较高计算资源。
对遮挡和重叠目标的检测效果不佳:当目标之间存在遮挡或重叠时,检测效果会受到影响。
亮点:
更深的网络结构:采用了更深的网络结构Darknet-53,提高了特征的表达能力。
多标签分类:支持多标签分类任务,使得模型可以更加灵活地应对复杂场景。
v4: 2020年
优势:
检测速度和精度又一次提升:引入各种优化策略和网络结构改进,YOLOv4在检测速度和精度上都取得了显著提升。
泛化能力更强:通过引入数据增强和正则化技术,提高了模型的泛化能力。
劣势:
计算资源需求高:为了实现高性能,需要较高的计算资源和内存。
训练时间长:由于网络结构复杂和优化策略众多,训练时间相对较长。
亮点:
CSPDarknet53网络结构:采用了CSPDarknet53作为主干网络,提高特征的提取和计算效率。
Mosaic数据增强:通过Mosaic数据增强技术,增加了模型的多样性和泛化能力。
v5: 2020年
优势:
检测速度更快:通过优化网络结构和采用轻量级模块,YOLOv5实现了更快的检测速度。
检测精度更高:通过引入更先进的训练策略和技巧,YOLOv5在保持速度优势的同时提高了检测精度。
劣势:
对于特定场景的适应性不足:针对不同场景可能需要进行一定模型调整和优化。
训练和调整难度较大:为了实现高性能,YOLOv5的训练和调整过程可能相对复杂。
亮点:
自适应锚点框机制:自适应锚点框机制提高了边界框回归的准确性和稳定性。
轻量级网络结构:采用了轻量级网络结构和模块设计,降低了计算资源和内存需求。
v7: 2022年
优势:速度快、精度高,精度在56.8%的情况下,速度可达到30FPS以上。
劣势:
亮点:
正样本分配策略
卷积和批处理化做了一个合并,常规都是3*3卷积核是因为英伟达做硬件优化时认为3*3效果最好。
AUX辅助输出
2、网络结构
开山之作v1:
卷积神经网络结构进行特征提取
v2:神
darkNet-19作为特征提取网
将YOLOv1网络的FC层和最后一个Pooling层去掉
然后缩减网络,用416×416大小的输入代替原来YOLOv1的448×448
引入Anchor Boxes
v3:
darknet-53网络作为特征提取网络;每个卷积层之后包含一个批量归一化层和一个Leaky ReLU
网络结构图:
利用特征金字塔网络结构实现了多尺度检测
分类方法使用逻辑回归代替了softmax
检测精度与速度兼具,可以选择darknet-53作为backbone;如果你希望达到更快的检测速度,精度方面可以妥协,那么tiny-darknet是你很好的选择。
v4:
YOLOv4 = CSPDarknet53(主干) + SPP附加模块(颈) + PANet路径聚合(颈) + YOLOv3(头部)
总结了各种Tricks,选择合适的Tricks来提高自己的检测器性能。
在不增加推理成本的前提下获得更好的精度,而只改变训练策略或只增加训练成本的方法,作着称之为 “免费包”。
只增加少量推理成本但能显著提高目标检测精度的插件模块和后处理方法,称之为“特价包”。
v5:
总共五个版本,n、s、l、m、x便于用户选择精度和速度
详细介绍:
首先yolov5整个模型框架可以分为主干网络backbone用于特征提取、neck用于特征融合和head用于输出边界框坐标、类别和置信度。backbone中有focus结构、c3模块、sppf模块。在输入图像进入模型提取特征之前首先通过focus结构对图像进行切片操作,实现一张输入图像被裁剪为4张图像,每张图像相当于对输入图像进行2倍下采样来得到的,然后在通道维度上进行拼接操作,从而继续后续的卷积操作。提高特征表示能力、减少计算复杂度、特征融合。将原始图像640*640*3通道输入到Focus结构中,通过切片操作变成320*320*12的特征图。因此将原始图像输入到模型中进行特征提取经过一个focus结构和4个conv层进行5次下采样得到20*20*1024的特征图。因为有1024个卷积核所以有1024个通道数。
主干网络通过五次卷积和池化操作逐步将特征图尺寸从640x640缩小到20x20,并在SPPF模块中进一步处理,保持尺寸为20x20x512。颈部模块通过上采样将20x20x512特征图上采样到40x40x256,并与主干网络中的40x40x256特征图拼接,形成40x40x512特征图。通过C3模块进一步处理拼接后的40x40x512特征图,保持尺寸为40x40x512。检测头在40x40x512特征图上进行目标检测,生成中等大小目标的检测结果。
主干网络通过五次卷积和池化操作逐步将特征图尺寸从640x640缩小到20x20,并在SPPF模块中进一步处理,保持尺寸为20x20x512。颈部模块通过上采样将20x20x512特征图上采样到40x40x256,并与主干网络中的40x40x256特征图拼接,形成40x40x512特征图。颈部模块通过进一步上采样将40x40x512特征图上采样到80x80x256,并与主干网络中的80x80x128特征图拼接,形成80x80x256特征图。通过C3模块进一步处理拼接后的80x80x256特征图,保持尺寸为80x80x256。检测头在80x80x256特征图上进行目标检测,生成较小目标的检测结果。
改进
自适应锚框计算、自适应灰度填充、损失函数:分类用交叉熵损失函数(BEC Loss),边界框回归用 CIoU Loss
v7
主要由三部分组成,BackBone、FPN和Yolo Head组成。
改进:
v8:
输入图像640*640*3,经过第一个卷积层P1(卷积核大小、批量归一化、激活函数)根据计算公式图像由640变成320,注:步长为2可以缩小上一级图像。
经过第二个卷积层p2(卷积核大小、批量归一化、激活函数)因为步长为2所以图像继续缩小一半根据计算公式。图像变成160。
c2f层1是将卷积层堆叠到一起,继续往下走
经过第三个卷积层p3(卷积核大小、批量归一化、激活函数)因为步长为2所以图像继续缩小一半根据计算公式。图像变成80。
从第三层p3开始之后就会有分支了c2f层2是将卷积层堆叠到一起,有两个分支1、2
分支1:经过第四个卷积层p4(卷积核大小、批量归一化、激活函数)因为步长为2所以图像继续缩小一半根据计算公式。图像变成40。
沿着分支1继续走,c2f层3是将卷积层堆叠到一起,继续往下走,又有两个分支,分支3分支4
分支3:经过第五个卷积层p5(卷积核大小、批量归一化、激活函数)因为步长为2所以图像继续缩小一半根据计算公式。图像变成20。
沿着分支3继续走,c2f层5是将卷积层堆叠到一起,继续往下走,
经过SPPF层1
分支4:把backbone的c2f层4送到neck部分进行第一次多尺度特征融合。
分支2:把backbone的c2f层2送到neck部分进行第一次多尺度特征融合。
把SPPF层1通过上采样层1把特征图变为40*40,然后使用concat层1与c2f层4进行特征图做拼接操作,最后往下一层传递。
传递到c2f层6(含有多个卷积层)。
操作类似,进行上采样,进行多尺度特征图拼接。
v8总结:
backbone:输入图像经过一个比较深的卷积神经网络,它会让图像不断的变小,通道数不断的增加,划分了三个分支用于多尺度特征融合
YOLOv8配置文件详解:
1、各个模块放置在nn文件夹下modules文件夹下
2、task.py就相当于yolo.py,对模型结构进行解析,解析函数:parse_model
def parse_model(d, ch, verbose=True): # model_dict, input_channels(3)
"""Parse a YOLO model.yaml dictionary into a PyTorch model."""
import ast
#d是一个字典,打印出来的是yolov8.yaml中所有内容,以字典形式出现。
#ch是图像输入通道
#从形参1字典中提取模型配置,类别、各个权重的宽高大小及网络层数、backbone、neck、head部分
# Args
max_channels = float("inf")
nc, act, scales = (d.get(x) for x in ("nc", "activation", "scales"))
depth, width, kpt_shape = (d.get(x, 1.0) for x in ("depth_multiple", "width_multiple", "kpt_shape"))
#判断权重的scales是否存在默认为yolov8n.yaml,
if scales:
scale = d.get("scale")
if not scale:
scale = tuple(scales.keys())[0]
LOGGER.warning(f"WARNING ⚠️ no model scale passed. Assuming scale='{scale}'.")
depth, width, max_channels = scales[scale]
#使用哪种模型和获得该模型的参数
if act:
Conv.default_act = eval(act) # redefine default activation, i.e. Conv.default_act = nn.SiLU()
if verbose:
LOGGER.info(f"{colorstr('activation:')} {act}") # print
if verbose:
LOGGER.info(f"\n{'':>3}{'from':>20}{'n':>3}{'params':>10} {'module':<45}{'arguments':<30}")
ch = [ch]
#layers是你要添加到模型的层数、c2是输出通道数
layers, save, c2 = [], [], ch[-1] # layers, savelist, ch out
#循环遍历yolov8.yaml文件夹下模型配置中字典d中backbone、head
#把通过函数获取到的模型给到了m,判断m中是否有有nn,有则从torch.nn模块中获取对应的类和属性
#如果没有则获取全局空间中找与m对应的值。
for i, (f, n, m, args) in enumerate(d["backbone"] + d["head"]): # from, number, module, args
#args
m = getattr(torch.nn, m[3:]) if "nn." in m else globals()[m] # get module
for j, a in enumerate(args):
if isinstance(a, str):
with contextlib.suppress(ValueError):
args[j] = locals()[a] if a in locals() else ast.literal_eval(a)
n = n_ = max(round(n * depth), 1) if n > 1 else n #模块backbone重复次数repeat
if m in {
Classify,
Conv,
ConvTranspose,
GhostConv,
Bottleneck,
GhostBottleneck,
SPP,
SPPF,
DWConv,
Focus,
BottleneckCSP,
C1,
C2,
C2f,
RepNCSPELAN4,
ADown,
SPPELAN,
C2fAttn,
C3,
C3TR,
C3Ghost,
nn.ConvTranspose2d,
DWConvTranspose2d,
C3x,
RepC3,
}:
c1, c2 = ch[f], args[0]
if c2 != nc: # if c2 not equal to number of classes (i.e. for Classify() output)
c2 = make_divisible(min(c2, max_channels) * width, 8)
if m is C2fAttn:
args[1] = make_divisible(min(args[1], max_channels // 2) * width, 8) # embed channels
args[2] = int(
max(round(min(args[2], max_channels // 2 // 32)) * width, 1) if args[2] > 1 else args[2]
) # num heads
args = [c1, c2, *args[1:]]
if m in {BottleneckCSP, C1, C2, C2f, C2fAttn, C3, C3TR, C3Ghost, C3x, RepC3}:
args.insert(2, n) # number of repeats
n = 1
elif m is AIFI:
args = [ch[f], *args]
elif m in {HGStem, HGBlock}:
c1, cm, c2 = ch[f], args[0], args[1]
args = [c1, cm, c2, *args[2:]]
if m is HGBlock:
args.insert(4, n) # number of repeats
n = 1
elif m is ResNetLayer:
c2 = args[1] if args[3] else args[1] * 4
elif m is nn.BatchNorm2d:
args = [ch[f]]
elif m is Concat:
c2 = sum(ch[x] for x in f)
elif m in {Detect, WorldDetect, Segment, Pose, OBB, ImagePoolingAttn}:
args.append([ch[x] for x in f])
if m is Segment:
args[2] = make_divisible(min(args[2], max_channels) * width, 8)
elif m is RTDETRDecoder: # special case, channels arg must be passed in index 1
args.insert(1, [ch[x] for x in f])
elif m is CBLinear:
c2 = args[0]
c1 = ch[f]
args = [c1, c2, *args[1:]]
elif m is CBFuse:
c2 = ch[f[-1]]
else:
c2 = ch[f]
m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args) # module
t = str(m)[8:-2].replace("__main__.", "") # module type
m.np = sum(x.numel() for x in m_.parameters()) # number params
m_.i, m_.f, m_.type = i, f, t # attach index, 'from' index, type
if verbose:
LOGGER.info(f"{i:>3}{str(f):>20}{n_:>3}{m.np:10.0f} {t:<45}{str(args):<30}") # print
save.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1) # append to savelist
layers.append(m_)
if i == 0:
ch = []
ch.append(c2)
return nn.Sequential(*layers), sorted(save)
3、yolov8的模型结构主要是在yolov8.yaml上
# Ultralytics YOLO 🚀, AGPL-3.0 license
# YOLOv8 object detection model with P3-P5 outputs. For Usage examples see https://docs.ultralytics.com/tasks/detect
# Parameters
nc: 80 # number of classes
scales: # model compound scaling constants, i.e. 'model=yolov8n.yaml' will call yolov8.yaml with scale 'n'
# [depth, width, max_channels]
n: [0.33, 0.25, 1024] # YOLOv8n summary: 225 layers, 3157200 parameters, 3157184 gradients, 8.9 GFLOPs
s: [0.33, 0.50, 1024] # YOLOv8s summary: 225 layers, 11166560 parameters, 11166544 gradients, 28.8 GFLOPs
m: [0.67, 0.75, 768] # YOLOv8m summary: 295 layers, 25902640 parameters, 25902624 gradients, 79.3 GFLOPs
l: [1.00, 1.00, 512] # YOLOv8l summary: 365 layers, 43691520 parameters, 43691504 gradients, 165.7 GFLOPs
x: [1.00, 1.25, 512] # YOLOv8x summary: 365 layers, 68229648 parameters, 68229632 gradients, 258.5 GFLOPs
# YOLOv8.0n backbone
#-1代表从前一层获得的输入,repeats表示backbone模块下某一操作重复的次数
#module表示网络模块的名称,args表示[输出、卷积核、步长、填充、groups]
backbone:
# [from, repeats, module, args]
- [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
- [-1, 1, Conv, [128, 3, 2]] # 1-P2/4
- [-1, 3, C2f, [128, True]]
- [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
- [-1, 6, C2f, [256, True]]
- [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
- [-1, 6, C2f, [512, True]]
- [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
- [-1, 3, C2f, [1024, True]]
- [-1, 1, SPPF, [1024, 5]] # 9
# YOLOv8.0n head
head:
- [-1, 1, nn.Upsample, [None, 2, "nearest"]]
- [[-1, 6], 1, Concat, [1]] # cat backbone P4
- [-1, 3, C2f, [512]] # 12
- [-1, 1, nn.Upsample, [None, 2, "nearest"]]
- [[-1, 4], 1, Concat, [1]] # cat backbone P3
- [-1, 3, C2f, [256]] # 15 (P3/8-small)
- [-1, 1, Conv, [256, 3, 2]]
- [[-1, 12], 1, Concat, [1]] # cat head P4
- [-1, 3, C2f, [512]] # 18 (P4/16-medium)
- [-1, 1, Conv, [512, 3, 2]]
- [[-1, 9], 1, Concat, [1]] # cat head P5
- [-1, 3, C2f, [1024]] # 21 (P5/32-large)
- [[15, 18, 21], 1, Detect, [nc]] # Detect(P3, P4, P5)
二、基于YOLO补充知识点
1、数据处理方面
数据增强:增加样本数量、提升模型的泛化性和鲁棒性以及缓解过拟合问题。
随机缩放、
翻转、旋转
图像扰动、加噪声、遮挡
改变亮度、对比对、饱和度、色调
随机裁剪(random crop)
随机擦除(random erase)
Cutout
MixUp
CutMix
正则化:控制模型复杂度,防止过拟合,提高泛化稳定性
DropOut
DropConnect
DropBlock
回归损失改进 调整模型参数更好拟合训练数据
GIOU
DIOU
CIoU
额外实验创新点:
增大感受野:更好的捕捉图像的全局特征
SPP
ASPP
RFB
Squeeze-and-Excitation (SE)
Spatial Attention Module (SAM)
特征融合集成: 提取更丰富更具表达力的特征
FPN
SFAM
ASFF
BiFPN (出自于大名鼎鼎的EfficientDet)
更好的激活函数:引出非线性能更好处理多领域的信息
ReLU
LReLU
PReLU
ReLU6
SELU
Swish
hard-Swish
后处理非极大值抑制算法:减少冗余,保留最具代表性的目标框或边缘
soft-NMS
DIoU NMS
不同尺度、深层浅层、分辨率、语义信息进一步理解。
这些不同层级的特征图具有不同的空间分辨率和语义信息。较浅层的特征图通常具有较高的空间分辨率,对于检测小尺寸目标有较好的效果,而较深层的特征图具有较高的语义信息,对于检测大尺寸目标和具有更高级别特征的目标更为适用。
目标检测算法通常会采取多尺度的策略。这意味着算法会在不同的尺度上对输入图像进行处理和分析,以确保能够检测到各种大小的目标。
在多尺度目标检测中,通常会使用多个尺度的特征图或图像金字塔。通过对输入图像进行缩放或通过不同的卷积层获取不同分辨率的特征图,算法可以在不同的尺度上进行目标检测。这样可以处理不同大小的目标,并提高算法对尺度变化的适应能力。
2、项目部署如图
目录了解:
data:主要是存放一些超参数的配置文件(yaml文件)是用来配置训练集和测试集还有验证集的路径的,其中还包括目标检测的种类数和种类的名称);还有一些官方提供测试的图片。如果是训练自己的数据集的话,那么就需要修改其中的yaml文件。但是自己的数据集不建议放在这个路径下面,而是建议把数据集放到yolov5项目的同级目录下面。
models:里面主要是一些网络构建的配置文件和函数,其中包含了yolo的四个不同的版本,分别为是s、m、l、x。从名字就可以看出,这几个版本的大小。他们的检测速度分别都是从快到慢,但是精确度分别是从低到高。如果训练自己的数据集的话,就需要修改这里面相对应的yaml文件来训练自己模型。
weights:放置训练好的权重参数。
detect.py:利用训练好的权重参数进行目标检测,可以进行图像、视频和摄像头的检测。
requirements.txt:这是一个文本文件,里面写着使用yolov5项目的环境依赖包的一些版本,可以利用该文本导入相应版本的包。
训练集、测试集、验证集的基本概念:
训练集用于训练模型的参数和权重,验证集用于选择和调整模型的超参数,测试集用于最终评估模型的性能。
不同数据集中参数修改:
修改yolov5s.yaml中nc的值
复制coco.yaml并重命名之后修改nc的数量、类别名称和路径
修改train.py下的参数,如优化器、训练轮次、处理样本批次、置信度、IOU等具体问题具体分析
验证、推理:
model=YOLO('runs/detect/train/weights/best.pt')
model.val(**{'data':'dataset/data/yaml'})
model=YOLO('runs/detect/train/weights/best.pt')
model=val(**{'data':'dataset/data.yaml'})
3、模型性能评价指标及提高模型准确率策略
混淆矩阵:
P:预测为正样本、N预测为负样本。
TP:预测为正样本是正确的
TN:预测为负样本是正确的
FP:预测为正样本时错误的
FN:预测为负样本是错误的
精准率:模型检测之后,所有预测为正样本的数量为分母,预测为正样本是正确的为分子即检测之后所有正样本里面有多少是正确的。
公式:TP/(TP+FP)
召回率:模型检测之前,所有正样本数量为分母,真实的正样本数量为分子即相对于检测之前的正样本我检测出了多少个正样本。
公式:TP/(TP+FN)
准确率,在模型中预测所有样本正确的比例
公式:(TP+FN)/(TP+TN+FP+FN)
平均精确度AP,计算出不同类别的平均精确度,衡量单个类别中物体检测的性能
平均精确度mAP衡量多个类别中物体检测的性能,即计算每一个类别下的AP
mAP50:表示IOU阙值(预测边界框与真实框重叠的程度)在0.5一下的多类别物体平均精度
mAP50-95:表示IOU阙值在0.5-0.95范围内的平均精度。
F1值:
Precision和Recall的调和平均。
4、YOLOv5改进策略
原则:必须要看足够多的结构
改进的角度
主干网络改进、轻量化网络、注意力机制、空间金字塔池化、损失函数及NMS、检测头部改进、
(1)数据集的正负样本问题
解决方案:
思路:尝试为困难样本分配更高的权重。
操作:将参数μ将样本分为正样本和负样本。然后,通过加权函数 Slide
对边界处的样本进行强调
import math
class SlideLoss(nn.Module):
def __init__(self, loss_fcn):
super(SlideLoss, self).__init__()
self.loss_fcn = loss_fcn
self.reduction = loss_fcn.reduction
self.loss_fcn.reduction = 'none' # required to apply SL to each element
def forward(self, pred, true, auto_iou=0.5):
loss = self.loss_fcn(pred, true)
if auto_iou < 0.2:
auto_iou = 0.2
b1 = true <= auto_iou - 0.1
a1 = 1.0
b2 = (true > (auto_iou - 0.1)) & (true < auto_iou)
a2 = math.exp(1.0 - auto_iou)
b3 = true >= auto_iou
a3 = torch.exp(-(true - 1.0))
modulating_weight = a1 * b1 + a2 * b2 + a3 * b3
loss *= modulating_weight
if self.reduction == 'mean':
return loss.mean()
elif self.reduction == 'sum':
return loss.sum()
else: # 'none'
return loss
loss.py源码
# Loss functions
import torch
import torch.nn as nn
from utils.general import bbox_iou, Wasserstein
from utils.torch_utils import is_parallel
from utils.RepulsionLoss import repulsion_loss_torch
import math
def smooth_BCE(eps=0.1): # https://github.com/ultralytics/yolov3/issues/238#issuecomment-598028441
# return positive, negative label smoothing BCE targets
return 1.0 - 0.5 * eps, 0.5 * eps
class SlideLoss(nn.Module):
def __init__(self, loss_fcn):
super(SlideLoss, self).__init__()
self.loss_fcn = loss_fcn
self.reduction = loss_fcn.reduction
self.loss_fcn.reduction = 'none' # required to apply SL to each element
def forward(self, pred, true, auto_iou=0.5):
loss = self.loss_fcn(pred, true)
if auto_iou < 0.2:
auto_iou = 0.2
b1 = true <= auto_iou - 0.1
a1 = 1.0
b2 = (true > (auto_iou - 0.1)) & (true < auto_iou)
a2 = math.exp(1.0 - auto_iou)
b3 = true >= auto_iou
a3 = torch.exp(-(true - 1.0))
modulating_weight = a1 * b1 + a2 * b2 + a3 * b3
loss *= modulating_weight
if self.reduction == 'mean':
return loss.mean()
elif self.reduction == 'sum':
return loss.sum()
else: # 'none'
return loss
class BCEBlurWithLogitsLoss(nn.Module):
# BCEwithLogitLoss() with reduced missing label effects.
def __init__(self, alpha=0.05):
super(BCEBlurWithLogitsLoss, self).__init__()
self.loss_fcn = nn.BCEWithLogitsLoss(reduction='none') # must be nn.BCEWithLogitsLoss()
self.alpha = alpha
def forward(self, pred, true):
loss = self.loss_fcn(pred, true)
pred = torch.sigmoid(pred) # prob from logits
dx = pred - true # reduce only missing label effects
# dx = (pred - true).abs() # reduce missing label and false label effects
alpha_factor = 1 - torch.exp((dx - 1) / (self.alpha + 1e-4))
loss *= alpha_factor
return loss.mean()
class FocalLoss(nn.Module):
# Wraps focal loss around existing loss_fcn(), i.e. criteria = FocalLoss(nn.BCEWithLogitsLoss(), gamma=1.5)
def __init__(self, loss_fcn, gamma=1.5, alpha=0.25):
super(FocalLoss, self).__init__()
self.loss_fcn = loss_fcn # must be nn.BCEWithLogitsLoss()
self.gamma = gamma
self.alpha = alpha
self.reduction = loss_fcn.reduction
self.loss_fcn.reduction = 'none' # required to apply FL to each element
def forward(self, pred, true):
loss = self.loss_fcn(pred, true)
# p_t = torch.exp(-loss)
# loss *= self.alpha * (1.000001 - p_t) ** self.gamma # non-zero power for gradient stability
# TF implementation https://github.com/tensorflow/addons/blob/v0.7.1/tensorflow_addons/losses/focal_loss.py
pred_prob = torch.sigmoid(pred) # prob from logits
p_t = true * pred_prob + (1 - true) * (1 - pred_prob)
alpha_factor = true * self.alpha + (1 - true) * (1 - self.alpha)
modulating_factor = (1.0 - p_t) ** self.gamma
loss *= alpha_factor * modulating_factor
if self.reduction == 'mean':
return loss.mean()
elif self.reduction == 'sum':
return loss.sum()
else: # 'none'
return loss
class QFocalLoss(nn.Module):
# Wraps Quality focal loss around existing loss_fcn(), i.e. criteria = FocalLoss(nn.BCEWithLogitsLoss(), gamma=1.5)
def __init__(self, loss_fcn, gamma=1.5, alpha=0.25):
super(QFocalLoss, self).__init__()
self.loss_fcn = loss_fcn # must be nn.BCEWithLogitsLoss()
self.gamma = gamma
self.alpha = alpha
self.reduction = loss_fcn.reduction
self.loss_fcn.reduction = 'none' # required to apply FL to each element
def forward(self, pred, true):
loss = self.loss_fcn(pred, true)
pred_prob = torch.sigmoid(pred) # prob from logits
alpha_factor = true * self.alpha + (1 - true) * (1 - self.alpha)
modulating_factor = torch.abs(true - pred_prob) ** self.gamma
loss *= alpha_factor * modulating_factor
if self.reduction == 'mean':
return loss.mean()
elif self.reduction == 'sum':
return loss.sum()
else: # 'none'
return loss
class ComputeLoss:
# Compute losses
def __init__(self, model, autobalance=False):
super(ComputeLoss, self).__init__()
device = next(model.parameters()).device # get model device
h = model.hyp # hyperparameters
# Define criteria
BCEcls = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([h['cls_pw']], device=device))
BCEobj = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([h['obj_pw']], device=device))
# Class label smoothing https://arxiv.org/pdf/1902.04103.pdf eqn 3
self.cp, self.cn = smooth_BCE(eps=h.get('label_smoothing', 0.0)) # positive, negative BCE targets
self.C = h['c']
# reswan loss
self.u = h['u']
if self.u > 0:
BCEcls, BCEobj = SlideLoss(BCEcls), SlideLoss(BCEobj)
# Focal loss
g = h['fl_gamma'] # focal loss gamma
if g > 0:
BCEcls, BCEobj = FocalLoss(BCEcls, g), FocalLoss(BCEobj, g)
det = model.module.model[-1] if is_parallel(model) else model.model[-1] # Detect() module
self.balance = {3: [4.0, 1.0, 0.4]}.get(det.nl, [4.0, 1.0, 0.25, 0.06, .02]) # P3-P7
self.ssi = list(det.stride).index(16) if autobalance else 0 # stride 16 index
self.BCEcls, self.BCEobj, self.gr, self.hyp, self.autobalance = BCEcls, BCEobj, model.gr, h, autobalance
for k in 'na', 'nc', 'nl', 'anchors':
setattr(self, k, getattr(det, k))
def __call__(self, p, targets): # predictions, targets, model
device = targets.device
lcls, lbox, lobj = torch.zeros(1, device=device), torch.zeros(1, device=device), torch.zeros(1, device=device)
lrepBox, lrepGT = torch.zeros(1, device=device), torch.zeros(1, device=device)
tcls, tbox, indices, anchors = self.build_targets(p, targets) # targets
# Losses
for i, pi in enumerate(p): # layer index, layer predictions
b, a, gj, gi = indices[i] # image, anchor, gridy, gridx
tobj = torch.zeros_like(pi[..., 0], device=device) # target obj
n = b.shape[0] # number of targets
if n:
ps = pi[b, a, gj, gi] # prediction subset corresponding to targets
# Regression
pxy = ps[:, :2].sigmoid() * 2. - 0.5
pwh = (ps[:, 2:4].sigmoid() * 2) ** 2 * anchors[i]
pbox = torch.cat((pxy, pwh), 1) # predicted box
iou = bbox_iou(pbox.T, tbox[i], x1y1x2y2=False, CIoU=True) # iou(prediction, target)
nwd = torch.exp(-torch.pow(Wasserstein(pbox.T, tbox[i], x1y1x2y2=False), 1 / 2) / self.C)
auto_iou = iou.mean()
# lbox += (1.0 - iou).mean() # iou loss
lbox += 0.8 * (1.0 - iou).mean() + 0.2 * (1.0 - nwd).mean()
# Objectness
tobj[b, a, gj, gi] = (1.0 - self.gr) + self.gr * iou.detach().clamp(0).type(tobj.dtype) # iou ratio
# Classification
if self.nc > 1: # cls loss (only if multiple classes)
t = torch.full_like(ps[:, 5:], self.cn, device=device) # targets
t[range(n), tcls[i]] = self.cp
if self.u > 0:
lcls += self.BCEcls(ps[:, 5:], t, auto_iou) # BCE
else:
lcls += self.BCEcls(ps[:, 5:], t) # BCE
# Repulsion Loss
dic = {0: [], 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [], 8: [], 9: [], 10: [], 11: [], 12: [],
13: [], 14: [], 15: [], 16: [], 17: [], 18: [], 19: [], 20: [], 21: [], 22: [], 23: [], 24: [],
25: [], 26: [], 27: [], 28: [], 29: [], 30: [], 31: [], 32: [], 33: [], 34: [], 35: [], 36: [],
37: [], 38: [], 39: [], 40: [], 41: [], 42: [], 43: [], 44: [], 45: [], 46: [], 47: [], 48: [],
49: [], 50: [], 51: [], 52: [], 53: [], 54: [], 55: [], 56: [], 57: [], 58: [], 59: [], 60: [],
61: [], 62: [], 63: [], 64: [], 65: [], 66: [], 67: [], 68: [], 69: [], 70: [], 71: [], 72: [],
73: [], 74: [], 75: [], 76: [], 77: [], 78: [], 79: [], 80: [], 81: [], 82: [], 83: [], 84: [],
85: [], 86: [], 87: [], 88: [], 89: [], 90: [], 91: [], 92: [], 93: [], 94: [], 95: [], 96: [],
97: [], 98: [], 99: [], 100: [], 101: [], 102: [], 103: [], 104: [], 105: [], 106: [], 107: [],
108: [], 109: [], 110: [], 111: [], 112: [], 113: [], 114: [], 115: [], 116: [], 117: [], 118: [],
119: [], 120: [], 121: [], 122: [], 123: [], 124: [], 125: [], 126: [], 127: [], 128: [], 129: [],
130: [], 131: [], 132: [], 133: [], 134: [], 135: [], 136: [], 137: [], 138: [], 139: [], 140: [],
141: [], 142: [], 143: [], 144: [], 145: [], 146: [], 147: [], 148: [], 149: []}
for indexs, value in enumerate(b):
# print(indexs, value)
dic[int(value)].append(indexs)
# print('dic', dic)
bts = 0
deta = self.hyp['deta']
Rp_nms = self.hyp['Rp_nms']
_lrepGT = 0.0
_lrepBox = 0.0
for id, indexs in dic.items(): # id = batch_name indexs = target_id
if indexs:
lrepgt, lrepbox = repulsion_loss_torch(pbox[indexs], tbox[i][indexs], deta=deta, pnms=Rp_nms, gtnms=Rp_nms)
_lrepGT += lrepgt
_lrepBox += lrepbox
bts += 1
if bts > 0:
_lrepGT /= bts
_lrepBox /= bts
lrepGT += _lrepGT
lrepBox += _lrepBox
# Append targets to text file
# with open('targets.txt', 'a') as file:
# [file.write('%11.5g ' * 4 % tuple(x) + '\n') for x in torch.cat((txy[i], twh[i]), 1)]
if self.u > 0 and n:
obji = self.BCEobj(pi[..., 4], tobj, auto_iou)
else:
obji = self.BCEobj(pi[..., 4], tobj)
lobj += obji * self.balance[i] # obj loss
if self.autobalance:
self.balance[i] = self.balance[i] * 0.9999 + 0.0001 / obji.detach().item()
if self.autobalance:
self.balance = [x / self.balance[self.ssi] for x in self.balance]
lbox *= self.hyp['box']
lobj *= self.hyp['obj']
lcls *= self.hyp['cls']
lrep = self.hyp['alpha'] * lrepGT / 3.0 + self.hyp['beta'] * lrepBox / 3.0
bs = tobj.shape[0] # batch size
loss = lbox + lobj + lcls + lrep
return loss * bs, torch.cat((lbox, lobj, lcls, lrep, loss)).detach()
def build_targets(self, p, targets):
# Build targets for compute_loss(), input targets(image,class,x,y,w,h)
na, nt = self.na, targets.shape[0] # number of anchors, targets
tcls, tbox, indices, anch = [], [], [], []
gain = torch.ones(7, device=targets.device) # normalized to gridspace gain
ai = torch.arange(na, device=targets.device).float().view(na, 1).repeat(1, nt) # same as .repeat_interleave(nt)
targets = torch.cat((targets.repeat(na, 1, 1), ai[:, :, None]), 2) # append anchor indices
g = 0.5 # bias
off = torch.tensor([[0, 0],
[1, 0], [0, 1], [-1, 0], [0, -1], # j,k,l,m
# [1, 1], [1, -1], [-1, 1], [-1, -1], # jk,jm,lk,lm
], device=targets.device).float() * g # offsets
for i in range(self.nl):
anchors = self.anchors[i]
gain[2:6] = torch.tensor(p[i].shape)[[3, 2, 3, 2]] # xyxy gain
# Match targets to anchors
t = targets * gain
if nt:
# Matches
r = t[:, :, 4:6] / anchors[:, None] # wh ratio
j = torch.max(r, 1. / r).max(2)[0] < self.hyp['anchor_t'] # compare
# j = wh_iou(anchors, t[:, 4:6]) > model.hyp['iou_t'] # iou(3,n)=wh_iou(anchors(3,2), gwh(n,2))
t = t[j] # filter
# Offsets
gxy = t[:, 2:4] # grid xy
gxi = gain[[2, 3]] - gxy # inverse
j, k = ((gxy % 1. < g) & (gxy > 1.)).T
l, m = ((gxi % 1. < g) & (gxi > 1.)).T
j = torch.stack((torch.ones_like(j), j, k, l, m))
t = t.repeat((5, 1, 1))[j]
offsets = (torch.zeros_like(gxy)[None] + off[:, None])[j]
else:
t = targets[0]
offsets = 0
# Define
b, c = t[:, :2].long().T # image, class
gxy = t[:, 2:4] # grid xy
gwh = t[:, 4:6] # grid wh
gij = (gxy - offsets).long()
gi, gj = gij.T # grid xy indices
# Append
a = t[:, 6].long() # anchor indices
indices.append((b, a, gj.clamp_(0, gain[3] - 1), gi.clamp_(0, gain[2] - 1))) # image, anchor, grid indices
tbox.append(torch.cat((gxy - gij, gwh), 1)) # box
anch.append(anchors[a]) # anchors
tcls.append(c) # class
return tcls, tbox, indices, anch
先导包再把损失函数加入到utils目录下的loss.py中
import math
#加入slide损失函数创新点
class SlideLoss(nn.Module):
def __init__(self, loss_fcn):
super(SlideLoss, self).__init__()
self.loss_fcn = loss_fcn
self.reduction = loss_fcn.reduction
self.loss_fcn.reduction = 'none' # required to apply SL to each element
def forward(self, pred, true, auto_iou=0.5):
loss = self.loss_fcn(pred, true)
if auto_iou < 0.2:
auto_iou = 0.2
b1 = true <= auto_iou - 0.1
a1 = 1.0
b2 = (true > (auto_iou - 0.1)) & (true < auto_iou)
a2 = math.exp(1.0 - auto_iou)
b3 = true >= auto_iou
a3 = torch.exp(-(true - 1.0))
modulating_weight = a1 * b1 + a2 * b2 + a3 * b3
loss *= modulating_weight
if self.reduction == 'mean':
return loss.mean()
elif self.reduction == 'sum':
return loss.sum()
else: # 'none'
return loss
然后添加一行代码关于slide的,在最后一行
又添加一行关于iou在最后一行
补充了auto_iou在最后一行
损失函数是由类别损失函数、置信度损失函数、定位损失函数组成,当框内没有目标会计算置信度损失,有目标会计算三种损失函数。
(2)注意力机制
基本介绍:
大体上有13种著名的注意力机制,笔者在这里分为两种,一种不需要接收通道数一种需要接收通道数注意力机制。也可以分为内部注意力机制和外部注意力机制。
解决的问题:在处理图像时算法会更加关注重要的特征,忽略不重要的信息如背景、遮挡物等。加注意力机制能够帮助 YOLO 更准确地检测出图像中的物体,这些权重指示了不同部分的重要程度。
如何判断:对输入进行卷积等变换就是不需要接收通道数注意力机制。
首先把SimAM.py(没有输入通道数的注意力机制)代码放入models目录下,下面是SimAM的代码
import torch
import torch.nn as nn
class SimAM(torch.nn.Module):
def __init__(self, e_lambda=1e-4):
super(SimAM, self).__init__()
self.activaton = nn.Sigmoid()
self.e_lambda = e_lambda
def __repr__(self):
s = self.__class__.__name__ + '('
s += ('lambda=%f)' % self.e_lambda)
return s
@staticmethod
def get_module_name():
return "simam"
def forward(self, x):
b, c, h, w = x.size()
n = w * h - 1
x_minus_mu_square = (x - x.mean(dim=[2, 3], keepdim=True)).pow(2)
y = x_minus_mu_square / (4 * (x_minus_mu_square.sum(dim=[2, 3], keepdim=True) / n + self.e_lambda)) + 0.5
return x * self.activaton(y)
if __name__ == '__main__':
input = torch.randn(3, 64, 7, 7)
model = SimAM()
outputs = model(input)
print(outputs.shape)
然后在yolo.py中导入包
from models.SimAM import SimAM
第二步,在yolov5s.yaml或n、m中添加注意力机制,这里笔者在yolov5s.yaml中添加,这里在head模块中加入了一个注意力机制simAM,代码如图所示。加完之后,后面每层的层数也要修改尤其是最后一行要注意。
head: [
[-1, 1, Conv, [512, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, "nearest"]],
[[-1, 6], 1, Concat, [1]], # cat backbone P4
[-1,1,SimAM,[1e-4]], #加入注意力机制simAM,层数一定要调整因为多了一层
[-1, 3, C3, [512, False]], # 14
[-1, 1, Conv, [256, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, "nearest"]],
[[-1, 4], 1, Concat, [1]], # cat backbone P3
[-1, 3, C3, [256, False]], # 18 (P3/8-small)
[-1, 1, Conv, [256, 3, 2]],
[[-1, 15], 1, Concat, [1]], # cat head P4
[-1, 3, C3, [512, False]], # 21 (P4/16-medium)
[-1, 1, Conv, [512, 3, 2]],
[[-1, 10], 1, Concat, [1]], # cat head P5
[-1, 3, C3, [1024, False]], # 24 (P5/32-large)
[[18, 21, 24], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
]
第二种有输入通道数的注意力机制SE
首先将SE.py放入models目录下,在yolo.py中导包
#注意力机制导入包
from models.SimAM import SimAM
from models.SE import SEAttention
然后在yolov5s.yaml中的head模块加入注意力机制,16是要看SE.py中初始化的类,reduction=16
head: [
[-1, 1, Conv, [512, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, "nearest"]],
[[-1, 6], 1, Concat, [1]], # cat backbone P4
[-1,1,SEAttention,[16]], #加入注意力机制simAM,层数一定要调整因为多了一层
[-1, 3, C3, [512, False]], # 14
最后再yolo.py中加东西
崩了,还不如不加呢。
(3)卷积操作:
基本介绍:不同与标准卷积和可变型卷积,核心是为卷积核提供任意数量的参数和任意采样的形状,能使任意数量的参数来提取特征。适应于轻量化模型
效果如图
标准卷积和可变型卷积感受野
实战演练:
首先上传ops_cnv3文件放入models目录下,切换到ops_cnv3文件下执行sh make.sh
会生成一下文件,如果没有成功想办法解决。
第一步,把可变型卷积中部分代码如下图放到models目录下的common.py中
from models.ops_dcnv3.modules import DCNv3
class DCNV3_YoLo(nn.Module):
def __init__(self, inc, ouc, k=1, s=1, p=None, g=1, d=1, act=True):
super().__init__()
self.conv = Conv(inc, ouc, k=1)
self.dcnv3 = DCNv3(ouc, kernel_size=k, stride=s, group=g, dilation=d)
self.bn = nn.BatchNorm2d(ouc)
self.act = Conv.default_act
def forward(self, x):
x = self.conv(x)
x = x.permute(0, 2, 3, 1)
x = self.dcnv3(x)
x = x.permute(0, 3, 1, 2)
x = self.act(self.bn(x))
return x
class Bottleneck_DCNV3(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 = DCNV3_YoLo(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_DCNV3(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_DCNV3(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))
如图所示:
第二步,把C3_DCNV3、C3_DCNV3_YoLo放入yolo.py代码中。注:这样操作只替换后head中的两个卷积层,故前两个卷积层依然正产使用,后两个卷积层使用的是可变型卷积。
n = n_ = max(round(n * gd), 1) if n > 1 else n # depth gain
if m in {
Conv,
GhostConv,
Bottleneck,
GhostBottleneck,
SPP,
SPPF,
DWConv,
MixConv2d,
Focus,
CrossConv,
BottleneckCSP,
C3,
C3TR,
C3SPP,
C3Ghost,
nn.ConvTranspose2d,
DWConvTranspose2d,
C3x,
C3_DCNV3,
C3_DCNV3_YoLo,
}:
在上面那段代码中的C3x下面加C3_DCNV3,C3_DCNV3_YoLo
在下面这段代码中的if m in 代码段中C3Ghost后面加C3x,C3_DCNV3
c1, c2 = ch[f], args[0]
if c2 != no: # if not output
c2 = make_divisible(c2 * gw, ch_mul)
args = [c1, c2, *args[1:]]
if m in {BottleneckCSP, C3, C3TR, C3Ghost, C3x,C3_DCNV3}:
第三步:把下面的代码放到yolo.py中DetectionModel(BaseModel)类中,替换这个类中的m.stride
m = self.model[-1] # Detect()
if isinstance(m, (Detect, Segment)):
s = 256 # 2x min stride
m.inplace = self.inplace
self.model.to(torch.device('cuda'))
forward = lambda x: self.forward(x)[0] if isinstance(m, Segment) else self.forward(x)
# m.stride = torch.tensor([s / x.shape[-2] for x in forward(torch.zeros(1, ch, s, s))]) # forward
#可变型卷积创新点
m.stride = torch.tensor([s / x.shape[-2] for x in forward(torch.zeros(1, ch, s, s).to(torch.device('cuda')))]).cpu() # forward
self.model.cpu()
第四步,修改yolos或者n、m、l系列也可以,这里修改yolov5s.yaml的head模块下的C3
先把yolov5s.yaml下backbone的后三个C3模块给替换成C3_DCNV3。
运行时出现报错问题:
显示C3_DCNV3,C3_DCNV3_YoLo没有被定义。最后通过大神,师兄的帮助解决了这个问题
第一个是缓存的问题第二个是命名的问题,具体跑完再说,看有没有提升。
代码解决:在yolo.py中,刚开始的导入模块加入第一行代码就OK了。虽然没有提高成功率但也算跑出来了。只不过跑的更低了。
from models.common import C3_DCNV3, DCNV3_YoLo
from models.common import (
C3,
C3SPP,
C3TR,
SPP,
SPPF,
Bottleneck,
BottleneckCSP,
C3Ghost,
C3x,
Classify,
Concat,
Contract,
Conv,
CrossConv,
DetectMultiBackend,
DWConv,
DWConvTranspose2d,
Expand,
Focus,
GhostBottleneck,
GhostConv,
Proto,
)
提示:train.py中有个早时间停止,patience=10意味着连续十轮性能没有提升将会停止运行。
(4)在骨干网络上做一些改进
先下载timm包,pip install timm。
将yolo.py第一段代码替换掉models目录下的yolo.py的代码
def parse_model(d, ch): # model_dict, input_channels(3)
# Parse a YOLOv5 model.yaml dictionary
LOGGER.info(f"\n{'':>3}{'from':>18}{'n':>3}{'params':>10} {'module':<40}{'arguments':<30}")
anchors, nc, gd, gw, act = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple'], d.get('activation')
if act:
Conv.default_act = eval(act) # redefine default activation, i.e. Conv.default_act = nn.SiLU()
LOGGER.info(f"{colorstr('activation:')} {act}") # print
na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors # number of anchors
no = na * (nc + 5) # number of outputs = anchors * (classes + 5)
is_backbone = False
layers, save, c2 = [], [], ch[-1] # layers, savelist, ch out
for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']): # from, number, module, args
try:
t = m
m = eval(m) if isinstance(m, str) else m # eval strings
except:
pass
for j, a in enumerate(args):
with contextlib.suppress(NameError):
try:
args[j] = eval(a) if isinstance(a, str) else a # eval strings
except:
args[j] = a
n = n_ = max(round(n * gd), 1) if n > 1 else n # depth gain
if m in {
Conv, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, MixConv2d, Focus, CrossConv,
BottleneckCSP, C3, C3TR, C3SPP, C3Ghost, nn.ConvTranspose2d, DWConvTranspose2d, C3x}:
c1, c2 = ch[f], args[0]
if c2 != no: # if not output
c2 = make_divisible(c2 * gw, 8)
args = [c1, c2, *args[1:]]
if m in {BottleneckCSP, C3, C3TR, C3Ghost, C3x}:
args.insert(2, n) # number of repeats
n = 1
elif m is nn.BatchNorm2d:
args = [ch[f]]
elif m is Concat:
c2 = sum(ch[x] for x in f)
# TODO: channel, gw, gd
elif m in {Detect, Segment}:
args.append([ch[x] for x in f])
if isinstance(args[1], int): # number of anchors
args[1] = [list(range(args[1] * 2))] * len(f)
if m is Segment:
args[3] = make_divisible(args[3] * gw, 8)
elif m is Contract:
c2 = ch[f] * args[0] ** 2
elif m is Expand:
c2 = ch[f] // args[0] ** 2
elif isinstance(m, str):
t = m
m = timm.create_model(m, pretrained=args[0], features_only=True)
c2 = m.feature_info.channels()
# elif m in {}:
# m = m(*args)
# c2 = m.channel
else:
c2 = ch[f]
if isinstance(c2, list):
is_backbone = True
m_ = m
m_.backbone = True
else:
m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args) # module
t = str(m)[8:-2].replace('__main__.', '') # module type
np = sum(x.numel() for x in m_.parameters()) # number params
m_.i, m_.f, m_.type, m_.np = i + 4 if is_backbone else i, f, t, np # attach index, 'from' index, type, number params
LOGGER.info(f'{i:>3}{str(f):>18}{n_:>3}{np:10.0f} {t:<40}{str(args):<30}') # print
save.extend(x % (i + 4 if is_backbone else i) for x in ([f] if isinstance(f, int) else f) if x != -1) # append to savelist
layers.append(m_)
if i == 0:
ch = []
if isinstance(c2, list):
ch.extend(c2)
for _ in range(5 - len(ch)):
ch.insert(0, 0)
else:
ch.append(c2)
return nn.Sequential(*layers), sorted(save)
BaseModel下的forward_once也被替换掉了
导入包
import timm
from models.fasternet import *
以上修改均在models下的yolo.py中
新建文件夹命名为yolov5_custom.yaml,下面是代码
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
# Parameters
nc: 80 # number of classes
depth_multiple: 0.33 # model depth multiple
width_multiple: 0.25 # layer channel multiple
anchors:
- [10,13, 16,30, 33,23] # P3/8
- [30,61, 62,45, 59,119] # P4/16
- [116,90, 156,198, 373,326] # P5/32
# 0-P1/2
# 1-P2/4
# 2-P3/8
# 3-P4/16
# 4-P5/32
# YOLOv5 v6.0 backbone
backbone:
# [from, number, module, args]
[[-1, 1, vovnet39a, [False]], # 4
[-1, 1, SPPF, [1024, 5]], # 5
]
# YOLOv5 v6.0 head
head:
[[-1, 1, Conv, [512, 1, 1]], # 6
[-1, 1, nn.Upsample, [None, 2, 'nearest']], # 7
[[-1, 3], 1, Concat, [1]], # cat backbone P4 8
[-1, 3, C3, [512, False]], # 9
[-1, 1, Conv, [256, 1, 1]], # 10
[-1, 1, nn.Upsample, [None, 2, 'nearest']], # 11
[[-1, 2], 1, Concat, [1]], # cat backbone P3 12
[-1, 3, C3, [256, False]], # 13 (P3/8-small)
[-1, 1, Conv, [256, 3, 2]], # 14
[[-1, 10], 1, Concat, [1]], # cat head P4 15
[-1, 3, C3, [512, False]], # 16 (P4/16-medium)
[-1, 1, Conv, [512, 3, 2]], # 17
[[-1, 5], 1, Concat, [1]], # cat head P5 18
[-1, 3, C3, [1024, False]], # 19 (P5/32-large)
[[13, 16, 19], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
]
先注释掉再加东西如图
在yolo_custome.yaml中的backbone模块换掉一些东西
backbone:
# [from, number, module, args]
[[-1, 1, fasternet_t1, []], # 4
[-1, 1, SPPF, [1024, 5]], # 5
]
并且把yolov5s.yaml中的nc及深度和宽度都改成自己的
nc: 5 # number of classes
depth_multiple: 0.33 # model depth multiple
width_multiple: 0.50 # layer channel multiple
执行结果
不加任何改进
受不了,今天的改进机制很多种,但提不了点是为啥呢?再见
(6)改进Trick
使用不同版本,yolov5-p6、yolov5-p7
yolov5的主干网络只做到32倍下采样,yolov5-p6做到64倍下采样、yolov5-p7128倍下采样,因为输入原始图像尺寸稍大一点,下采样倍数增加的大一点提取的特征图会变小,意味着信息压缩的更多。
基于视频的行为识别SlowFast
步骤:
获取低高频数据图像:低通道和双通道
特征提取与融合:3D卷积
分类:SVM
SlowFast核心是使用双通道处理视频。
Slow相较于Fast具有相对较低的帧率,但是具有更多的通道数,Slow用于捕捉空间中的语义信息,即Slow捕捉到了视频中相对静态的信息。
Fast具有更高的帧率,但是具有更少的通道数,这使得Fast的计算量大大减小,但同时也使得Fast对空间信息的建模能力减弱,而更多关注与时间维度上变化明显的信息。low和Fast并非独立存在,二者之间的信息融合为单向信息融合,二者通过多次的横向连接实现信息融合,横向连接的方向为从Fast到Slow,这意味着Fast不会接收到有关Slow的任何信息,但是Slow则可以包含Fast中的信息。
Slow Fast具有两条路径,(i)一个缓慢的路径,在低帧率下运行,以捕获空间语义,即获得环境信息(ii)一个快速的路径,在高帧率下运行,以捕获精细时间分辨率的运动。即动作信息,快速路径可以通过减少其通道容量而变得非常轻,通道数为slow 路径的倍,但是fast路径具有高帧率(),且在时间维度不进行降采样。
slowfast自动标注目标,然后再手动打标签
step1:
先进入detectron2-main目录,然后在该目录下创建Img文件,并上传一张照片,完毕后执行下段代码。
python demo.py --config-file ../configs/COCO-Detection/faster_rcnn_R_50_FPN_3x.yaml \
step2:
python /root/detectron2-main/demo/myvia.py --config-file configs/COCO-Detection/faster_rcnn_R_50_FPN_3x.yaml --opts MODEL.WEIGHTS detectron2://COCO-Detection/faster_rcnn_R_50_FPN_3x/137849458/model_final_280758.pkl
在myvia.py这块遇到点问题解决一天了都太难了,再试试使用yolo实现半自动化标注吧.
终于解决了,总是出现想解决他解决不掉,不想解决他一下就解决了。
想要via.py标注什么目标类别,根据yolo中的coco.yaml文件的类别直接输入就行了,明天再传代码吧
slowfast运行步骤:
我的运行环境,在linux服务器上,用服务器的cuda跑视频会报错显示cuda兼容问题,所以自己另外下载了cuda,最后成功运行出来。
nvcc -V
cuda版本: cuda_11.7.r11.7
安装cuda也有两种方式,第一种是直接在官网上找到cuda,然后选择版本后会有命令,直接照着命令安装就行。我用的是第二种
wget https://developer.download.nvidia.com/compute/cuda/12.0.0/local_installers/cuda_12.0.0_525.60.13_linux.run
sudo sh cuda_12.0.0_525.60.13_linux.run
输入accept
然后还有修改配置环境这一步,需要进到vim编辑器
vi ~/.bashrc #打开bashrc配置文件
具体增加或修改哪部分我忘了,可以去csdn上找反正是有这一步呢。
修改完要输入wq,保存并退出。:
表示进入命令模式,w
表示保存,q
表示退出(quit),!
表示强制执行。
source ~/.bashrc #最后配置一下文件
如有遗漏步骤请参考其他博客!
安装完毕后,需要下载并修改配置文件
先下载后两个,一个权重文件一个yaml文件,最后一个yaml文件如下:
TRAIN:
ENABLE: False
DATASET: ava
BATCH_SIZE: 16
EVAL_PERIOD: 1
CHECKPOINT_PERIOD: 1
AUTO_RESUME: True
CHECKPOINT_FILE_PATH: /root/SlowFast-main/demo/AVA/SLOWFAST_32x2_R101_50_50.pkl #path to pretrain model
CHECKPOINT_TYPE: pytorch
DATA:
NUM_FRAMES: 32
SAMPLING_RATE: 2
TRAIN_JITTER_SCALES: [256, 320]
TRAIN_CROP_SIZE: 224
TEST_CROP_SIZE: 256
INPUT_CHANNEL_NUM: [3, 3]
DETECTION:
ENABLE: True
ALIGNED: False
AVA:
BGR: False
DETECTION_SCORE_THRESH: 0.8
TEST_PREDICT_BOX_LISTS: ["person_box_67091280_iou90/ava_detection_val_boxes_and_labels.csv"]
SLOWFAST:
ALPHA: 4
BETA_INV: 8
FUSION_CONV_CHANNEL_RATIO: 2
FUSION_KERNEL_SZ: 5
RESNET:
ZERO_INIT_FINAL_BN: True
WIDTH_PER_GROUP: 64
NUM_GROUPS: 1
DEPTH: 101
TRANS_FUNC: bottleneck_transform
STRIDE_1X1: False
NUM_BLOCK_TEMP_KERNEL: [[3, 3], [4, 4], [6, 6], [3, 3]]
SPATIAL_DILATIONS: [[1, 1], [1, 1], [1, 1], [2, 2]]
SPATIAL_STRIDES: [[1, 1], [2, 2], [2, 2], [1, 1]]
NONLOCAL:
LOCATION: [[[], []], [[], []], [[6, 13, 20], []], [[], []]]
GROUP: [[1, 1], [1, 1], [1, 1], [1, 1]]
INSTANTIATION: dot_product
POOL: [[[2, 2, 2], [2, 2, 2]], [[2, 2, 2], [2, 2, 2]], [[2, 2, 2], [2, 2, 2]], [[2, 2, 2], [2, 2, 2]]]
BN:
USE_PRECISE_STATS: False
NUM_BATCHES_PRECISE: 200
SOLVER:
MOMENTUM: 0.9
WEIGHT_DECAY: 1e-7
OPTIMIZING_METHOD: sgd
MODEL:
NUM_CLASSES: 80
ARCH: slowfast
MODEL_NAME: SlowFast
LOSS_FUNC: bce
DROPOUT_RATE: 0.5
HEAD_ACT: sigmoid
TEST:
ENABLE: False
DATASET: ava
BATCH_SIZE: 8
DATA_LOADER:
NUM_WORKERS: 2
PIN_MEMORY: True
NUM_GPUS: 1
NUM_SHARDS: 1
RNG_SEED: 0
OUTPUT_DIR: .
# TENSORBOARD:
# MODEL_VIS:
# TOPK: 2
DEMO:
ENABLE: True
LABEL_FILE_PATH: /root/SlowFast-main/demo/AVA/ava.json
#WEBCAM: 0
INPUT_VIDEO: "/root/SlowFast-main/demo/input/cow01.mp4"
OUTPUT_FILE: "/root/SlowFast-main/demo/output/cow01_1.mp4"
最后面修改一下路径,最上面修改一下权重文件路径。
最后执行代码运行:
cd /root/SlowFast-main
python /root/SlowFast-main/tools/run_net.py --cfg /root/SlowFast-main/demo/AVA/SLOWFAST_32x2_R101_50_50.yaml
干饭,五一劳动节!挺好