1、前言
YOLOv5项目地址:ultralytics/yolov5
项目自发布以来,直到现在仍然在不断改进模型、项目。作者的更新频率很大,很多问题都能够及时解决,当然问题也很多!到写稿此时,项目的device参数仍然无法正常工作,查看源码,作者的代码写的GPU设备控制比较复杂,修改源码也没有解决,可能我里解决就差一步了吧!在项目提交bug后,得到作者的及时回应,但是最后仍然没有解决。难道使用GPU必须从GPU:0开始吗?查看bug请点击。
-
ONNX (Open Neural Network Exchange)
开发式神经网络交换格式!
它是深度学习模型的一种标准,可使模型在不同框架间转移
-
TensorRT
英伟达官方提供的GPU版深度学习模型加速平台,可以和Tensor Serving一起部署模型,实现模型的快速运算,其特点是性能高、资源占用少。
1.1 模型训练
$ python train.py --batch-size 64 --data coco.yaml --weights yolov5l.pt --device 0,1
使用 --device 0,1指定多个GPU是没有用的!会衰减计算速度。推荐多GPU使用数据并行的方法。
$ python -m torch.distributed.launch --nproc_per_node 2 train.py --batch-size 64 --data coco.yaml --weights yolov5x.pt
-
--nproc_per_node: 指定你的GPU节点数;
-
--batch-size:这里的批量是总的批量,实际每个GPU的的batch为 64/32.
这里的处理方式和keras是一样的,采用数据并行可以提高我们的计算速度。
$ python -m torch.distributed.launch --nproc_per_node 2 train.py --batch-size 64 --data coco.yaml --cfg yolov5x.yaml --weights '' --device 2,3
数据并行运算的时候可以指定要使用的GPU: --device 2,3
$ python -m torch.distributed.launch --nproc_per_node 2 train.py --batch-size 64 --data coco.yaml --cfg yolov5x.yaml --weights '' --sync-bn
SyncBatchNorm多个GPU之间进行批量标准化!
下面介绍将模型转为ONNX模型,使用TensorRT部署……
2、yolov5模型转ONNX模型
2.1 环境准备
$ git clone https://github.com/ultralytics/yolov5 # clone repo
$ cd yolov5
$ pip install -r requirements.txt # base requirements
$ pip install onnx>=1.7.0 # for ONNX export
$ pip install coremltools==4.0 # for CoreML export
2.2 输出已训练的模型为ONNX模型
$ python models/export.py --weights yolov5x.pt --img 640 --batch 1 # export at 640x640 with batch size 1
export.py
输出了三个模型,这里我们主要查看onnx模型!
使用https://netron.app/可以查看我们生成的ONNX模型结构。
文件中代码:
print('\nStarting ONNX export with onnx %s...' % onnx.__version__)
f = opt.weights.replace('.pt', '.onnx') # filename
torch.onnx.export(model, img, f, verbose=False, opset_version=12, input_names=['images'],
output_names=['classes', 'boxes'] if y is None else ['output'])
# Checks
onnx_model = onnx.load(f) # load onnx model
onnx.checker.check_model(onnx_model) # check onnx model
# print(onnx.helper.printable_graph(onnx_model.graph)) # print a human readable model
print('ONNX export success, saved as %s' % f)
except Exception as e:
print('ONNX export failure: %s' % e)
torch.onnx.export()模型转换关键函数
关于torch.onnx的介绍见:torch.onnx — PyTorch 1.9.1 documentation
3、Test Time Augmentation (TTA)
添加命令行参数--augment
实现测试、预测时的性能增强!
3.1 Test Normally
$ python test.py --weights yolov5x.pt --data coco.yaml --img 640
3.2 Test with TTA
$ python test.py --weights yolov5x.pt --data coco.yaml --img 832 --augment
3.3 Inference with TTA
$ python detect.py --weights yolov5s.pt --img 832 --source ./inference/images/ --augment
4、Model Ensembling模型嵌入
还记得决策树吗?而现在我们一般使用随机森林、LightGBM等模型。Ensembling的思想在深度学习模型中同样适应,这里我们对yolov5的不同模型进行融合,结果发现,预测显著性明显提升。
4.1 Test Normally
$ python test.py --weights yolov5x.pt --data coco.yaml --img 640
4.2 Ensemble Test
$ python test.py --weights yolov5x.pt yolov5l.pt --data coco.yaml --img 640
4.3 Ensemble Inference
$ python detect.py --weights yolov5x.pt yolov5l.pt --img 640 --source ./inference/images/
这里测试结果为:齐达内是人的概率从0.8上涨到0.9!
5、Pruning/Sparsity Tutorial 剪枝/稀疏教程
5.1 Test Normally
$ python test.py --weights yolov5x.pt --data coco.yaml --img 640
Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.492 < -------- baseline mAP
Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.676
Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.534
Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.318
Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.541
Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.633
Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.376
Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.616
Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.670 < -------- baseline mAR
Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.493
Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.723
Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.812
5.2 Test with 30% Pruned Model
我们现在用一个修剪过的模型重复上面的测试,设置修剪30%,这里我们需要修改test.py文件的torch_utils.prune(model, 0.3)
$ python test.py --weights yolov5x.pt --data coco.yaml --img 640
Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.462 < -------- lower mAP
Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.649
Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.505
Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.293
Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.507
Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.602
Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.361
Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.590
Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.643 < -------- lower mAR
Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.465
Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.691
Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = 0.786
在结果中我们可以观察到,我们的模型在剪枝后达到了30%的稀疏性,这意味着30%的模型权重参数nn.Conv2d层等于0。测试时间(在P100 GPU上)保持不变,而模型的AP和AR分数略有降低。
6、 Hyperparameter Evolution超参数调优
超参数对模型的影响是很大的,但是具体是如何影响的,通过严格的数学推导我们是做不到的。科学家在如何获取最优参数的道路上做了很多尝试,遗传算法(GA)就是比较出名的存在。
6.1 初始化超参数
YOLOv5有大约25个用于各种训练设置的超参数。这些是在/data目录的yaml文件中定义的。更好的初始猜测将产生更好的最终结果,因此在演化之前正确初始化这些值是很重要的。如果有疑问,只需使用默认值,这是优化YOLOv5在COCO数据集的配置参数。
6.2 定义适应性函数
适应性是我们追求最大化的价值。在YOLOv5中,我们将默认适应度函数定义为度量的加权组合:mp@0.5贡献了10%、mp@0.5:0.95占其余90%。您可以根据需要调整这些值,也可以使用默认的适应度定义。
def fitness(x):
# Returns fitness (for use with results.txt or evolve.txt)
w = [0.0, 0.0, 0.1, 0.9] # weights for [P, R, mAP@0.5, mAP@0.5:0.95]
return (x[:, :4] * w).sum(1)
6.3 遗传算法演变
evolution是在一个我们寻求改进的基本场景下进行的。本例中的基本场景是使用预训练的yolov5对COCO128进行10个时期的微调。基本场景训练命令是:
$ python train.py --epochs 10 --data coco128.yaml --weights yolov5s.pt --cache
为了进化特定于这个场景的超参数,从我们在第6.1节中定义的初始值开始,并最大化第6.2节中定义的适应度,增加--evolve
# Single-GPU
python train.py --epochs 10 --data coco128.yaml --weights yolov5s.pt --cache --evolve
# Multi-GPU
for i in 0 1 2 3; do
nohup python train.py --epochs 10 --data coco128.yaml --weights yolov5s.pt --cache --evolve --device $i > evolve_gpu_$i.log &
done
# Multi-GPU bash while (not recommended)
for i in 0 1 2 3; do
nohup "$(while true; do python train.py ... --evolve --device $i; done)" > evolve_gpu_$i.log &
done
主要的遗传算子是交叉和变异。在这项工作中,使用了变异,以90%的概率和0.04的方差,根据所有前几代最好的父母的组合来创造新的后代。结果记录在yolov5中/evolve.txt,每一代都保存着最适合的后代于yolov5/runs/evolve/hyp_evolved.yaml中。
我们建议至少300代进化才能获得最佳结果。请注意,进化通常是昂贵和耗时的,因为基本场景需要数百次训练,可能需要数百或数千个小时的GPU训练。
7、迁移学习冻结yolov5层
本指南解释了如何在迁移学习时冻结YOLOv5层。迁移学习是一种有用的方法,可以快速地在新数据上重新训练模型,而不必重新训练整个网络。相反,一部分初始权重被冻结在原位,其余权重用于计算损失,并由优化器更新。这需要比正常训练更少的资源,并且允许更快的训练时间,尽管它也可能导致最终训练精度的降低。
Transfer Learning with Frozen Layers
7.1 冻结骨干网络
# Freeze
freeze = [] # parameter names to freeze (full or partial)
for k, v in model.named_parameters():
v.requires_grad = True # train all layers
if any(x in k for x in freeze):
print('freezing %s' % k)
v.requires_grad = False
查看模型的参数名:
for k, v in model.named_parameters():
print(k)
# Output
model.0.conv.conv.weight
model.0.conv.bn.weight
model.0.conv.bn.bias
model.1.conv.weight
model.1.bn.weight
model.1.bn.bias
model.2.cv1.conv.weight
model.2.cv1.bn.weight
...
model.23.m.0.cv2.bn.weight
model.23.m.0.cv2.bn.bias
model.24.m.0.weight
model.24.m.0.bias
model.24.m.1.weight
model.24.m.1.bias
model.24.m.2.weight
model.24.m.2.bias
发现模型的backbone是0~9层,所以在freeze中添加这几层的name。
freeze = ['model.%s.' % x for x in range(10)] # parameter names to freeze (full or partial)
7.2 冻结全部yolov5的layers
这个简单,你可以将name全部放入freeze中,也可以设置所以层v.requires_grad = False
7.3 总结
冻结yolov5的backbone之后,GPU的占用率下降了20%,显存占用下降了40%+。训练其他数据即的性能指标下降了0.0021%,这种性能损失还是可以接受的。
既然训练非backbone部分训练时间更快,GPU占用、显存占用更少,是不是可以先训练非backbone部分,再一起训练呢?
8、TensorRT模型加速
yolov5里面涉及的一些结构在TensorRT里面还没有支持,这些结构要加速就只能自己用C++实现。使用C++编程对很多人来说都是非常麻烦的事情,这里借鉴TensorRTx项目进入这个领域。
TensorRTx旨在通过TensorRT网络定义API实现流行的深度学习网络。我们知道,TensorRT有内置的解析器,包括caffeparser、uffparser、onnxparser等,但当我们使用这些解析器时,常常会遇到一些“不支持的操作或层”问题,特别是一些最新的模型正在使用新型的层。
我们为什么不跳过所有的解析器呢?我们只需要使用TensorRT网络定义API来构建整个网络,并不复杂。
这个项目是为了熟悉TensorRT API,也为了分享和向社区学习。
所有模型首先在pytorch/mxnet/tensorflow中实现,并导出一个权重文件xxx.wts然后使用TensorRT进行权值加载、网络定义和推理。一些pytorch实现可以在我的repo Pytorchx中找到,其余的来自热门的开源实现。