🚀🚀 前言 🚀🚀
YOLO系列是一种目标检测算法,具有高效的实时性能和较高的准确性。一些常用的YOLO训练技巧往往可以帮助提高模型的性能和收敛速度。而这些技巧在YOLOv5、YOLOv7和YOLOv8几乎通用。
🔥🔥 YOLO系列实验实战篇:
📖 YOLOv5/v7/v8改进实验(一)之数据准备篇
📖 YOLOv5/v7/v8改进实验(二)之数据增强篇
📖 YOLOv5/v7/v8改进实验(三)之训练技巧篇
更新中…
目录
- 一、入门级操作(必学!!!)
- 1.1 数据预处理
- 1.2 模型选择 🔥
- 1.3 模型基线
- 1.4 超参数设置
- img-size
- batch-size
- epochs
- cache 🔥🔥🔥
- workers
- seeds 🔥🔥
- 1.5 权重文件保存 🔥
- 二、进阶级操作
- 2.1 模型测试
- 2.2 GFLOPs显示 🔥
一、入门级操作(必学!!!)
1.1 数据预处理
在训练之前,对数据进行预处理是很重要的。常见的预处理步骤包括随机裁剪、翻转、旋转等操作和归一化。详细代码请移步–YOLOv5改进实战 | YOLOv5-7.0改进实验(二)之数据增强篇。
1.2 模型选择 🔥
模型越大,训练出来的结果更理想,但对硬件的要求更高,需要更多的显存去训练,且训练时间更长。
- 对于实验来说,建议使用YOLOv5s;
- 对于移动部署,建议使用YOLOv5s/m;
- 对于云部署,建议使用YOLOv5l/x。
是否使用预训练模型
- 如果是大数据集,则建议从头开始训练(不使用预训练模型);
- 如果是小数据集,则建议使用预训练模型做迁移学习。
Transferred 342/349 items from yolov5s.pt
当然,在训练时加载预训练模型可以加速模型的收敛和提高模型的精度,因为预训练模型已经学习到了一些通用的特征,可以帮助模型更快地学习到目标检测任务所需的特征。不过,您的模型改进是有效的,那么即使不加载预训练模型,模型也应该具有更好的性能。所以,个人还是建议不使用预训练模型,从头开始训练。只是个人建议,还是取决于具体情况和实验需求,可以根据实验结果进行选择。
1.3 模型基线
训练使用默认设置,不做任何改动,得到模型性能基线。实验最开始需要完成的工作,必不可少的一个操作,因为有它你才能对比。
1.4 超参数设置
img-size
推荐使用默认的 640 ∗ 640 640*640 640∗640,无需做修改。如果数据集中有大量的小目标,可以适当增加数值,比如800、1000、1280等,有利于训练结果。但也会增加训练时间和显存需求。模型推理img-size值需与训练img-size值一致。
parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='train, val image size (pixels)')
batch-size
批量大小Batchsize训练过程中需要调整的超参数之一。合适的批量大小可以充分利用GPU资源。建议使用硬件允许的最大值,需避免Batchsize值过小产生糟糕的统计结果。
parser.add_argument('--batch-size', type=int, default=32, help='total batch size for all GPUs, -1 for autobatch')
PS:做实验的话不建议将Batchsize拉满,原因如下:
- 做对比实验需要保持Batchsize一致,但不同模型在Batchsize相同情况下往往存在所需显存不同,举个例子,YOLOv5训练时我将batchsize设置为128,这时显存刚好拉满,但是换成YOLOv7训练,如果你的batchsize设置为128,那显存必爆无疑。众所周知,YOLOv7训练所需要的显存要高得多。
- 如果你对网络进行修改,增加了很多模块,这常常会使得计算量FLOPs增加,那么就需要更多的显存来进行训练。
总结:实验需要保持Batchsize一致,不过这个值需要各位根据具体情况确定。
epochs
迭代次数是训练过程中需要调整的超参数之一。迭代次数要足够多以达到模型收敛。
parser.add_argument('--epochs', type=int, default=300, help='total training epochs')
默认300个,出现过拟合则减小该值,反之亦然。
PS:如果时从头开始训练,则可以在训练模型基线时将该值调大,然后观察训练loss大致在多少轮数收敛,那么再结合自身情况综合考虑确定轮数。
cache 🔥🔥🔥
YOLOv5的训练参数里面有一个--cache
,默认不开启,开启后默认是ram
,就是把解码后的图片保存在内存中。也可以是disk
,就会把解码后的图片保存在硬盘上,可以加快训练。
parser.add_argument('--cache', type=str, nargs='?', const='ram', help='image --cache ram/disk')
如果训练图片总量不大,或者服务器内存超高,那么完全可以把解码后的图片放内存里,直接加上--cache
参数搞定:
python train.py --cache
workers
在YOLOv5中,workers
参数用于指定用于数据加载的工作进程数量。这些工作进程负责将数据从存储设备(如硬盘)加载到内存中,并进行预处理操作,例如图像缩放、数据增强等。默认值为8,一般不做修改,Windows下建议设置为0。
parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)')
增加workers
参数可以并行化数据加载和预处理过程,从而加快数据的处理速度,提高训练速度。每个工作进程都可以独立地加载和处理数据,减少了数据加载和预处理的等待时间。因此,在选择workers
参数时,需要根据系统的硬件配置和资源限制进行权衡。
seeds 🔥🔥
随机种子(seeds)设置用于控制随机性,从而使训练过程更加可重复和可控。模型训练过程通常包含随机性,例如数据增强、权重初始化和优化器等。这些随机因素会影响模型的训练结果,使得每次训练的结果可能不同。如果需要比较不同模型或参数设置的性能,或者需要在多次训练中保持结果的一致性,就需要使用随机种子来控制随机性。
PS:随机种子的设置,能确保每次运行时的随机数生成是可重复的。不过设置种子只能保证模型在相同的种子值下生成相同的随机数序列,但不能保证模型的训练结果完全相同。(少量数据集下训练结果是完全相同的)
- YOLOv5可以通过设置
--seed
参数来指定随机种子。例如,--seed 2
表示使用随机种子2进行训练。建议在实验中使用随机种子来控制随机性,并根据实验结果进行调整。python train.py --seed 2
- YOLOv7设置随机种子,可移步YOLOv7改进实战(1) | YOLOv7训练随机种子设置
- YOLOv8则通过设置seed参数即可。
from ultralytics import YOLO
# Load a model
model = YOLO(r"ultralytics\cfg\models\v8\yolov8s.yaml") # build a new model from scratch
# Use the model
model.train(seed=2) # train the model
1.5 权重文件保存 🔥
YOLOv5官方保存pt权重文件代码在train.py
:
# Save last, best and delete
torch.save(ckpt, last)
if best_fitness == fi:
torch.save(ckpt, best)
if opt.save_period > 0 and epoch % opt.save_period == 0:
torch.save(ckpt, w / f'epoch{epoch}.pt')
训练完成后在相应的weights文件夹下只会有一个best.pt
和last.pt
文件。可发现关键在于fi
这个变量,它是P、R、mAP@0.5、mAP@0.5:0.95的加权和,具体计算公式如下:
def fitness(x):
# Model fitness as a weighted combination of metrics
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)
每次best.pt
的保存都会覆盖上一次的best.pt
权重文件,当然我们训练的时候并不知晓best.pt在测试集的效果,那么如果将这些fi
值相等的权重全部保存下来,我们甚至有机会能在众多权重中进行挑选表现最优以及最合适自己的权重,,岂不美哉!不然我们把宝全部压在那一个best.pt
上,风险太大,弄不好就是从头再来。
所以我们可以对上述代码进行修改,其实很简单,只是需要注意一个小细节添加上strip_optimizer
函数,不然你的磁盘可能会很快爆满:
# Save last, best and delete
torch.save(ckpt, last)
if best_fitness == fi:
torch.save(ckpt, best)
# 保存pt文件
if (best_fitness == fi and epoch >= 200):
torch.save(ckpt, w / f'best{epoch}.pt')
strip_optimizer(w / f'best{epoch}.pt')
if opt.save_period > 0 and epoch % opt.save_period == 0:
torch.save(ckpt, w / f'epoch{epoch}.pt')
YOLOv7作者已经设置好无需修改
YOLOv8需要在ultralytics/engine/trainer.py
(在这里不得不说一句,v8的代码是真难找啊!!!!)进行相应的修改:
# Save last, best and delete
torch.save(ckpt, self.last, pickle_module=pickle)
if self.best_fitness == self.fitness:
torch.save(ckpt, self.best, pickle_module=pickle)
# 保存权重文件
if self.best_fitness == self.fitness and self.epoch >= 200:
torch.save(ckpt, self.wdir / f'best{self.epoch}.pt', pickle_module=pickle)
strip_optimizer(self.wdir / f'best{self.epoch}.pt')
二、进阶级操作
2.1 模型测试
如果你按照1.5节的操作进行操作,那么你训练完后,weights
文件夹中必将有不少的best_xxx.pt
权重文件,训练完接着就是需要在测试集上进行模型性能测试,当然你可以一个一个指定执行,但是很耗时,这时候可以选择使用sh脚本进行自动化执行,新建一个test.sh
脚本文件,脚本如下:
#!/bin/bash
# 指定目标文件夹的路径
target_folder=$1
task=$2
data="/root/dataset/data.yaml" # 需要修改为自己数据集的yaml配置文件
log_dir="log"
parent_folder=$(dirname "$target_folder" | awk -F'/' '{print $NF}')
echo "Found folder: $parent_folder"
mkdir -p "$log_dir/$parent_folder"
log_file="$log_dir/$parent_folder/$task.log"
rm -f "$log_file"
file_list=$(find "$target_folder" -type f -name "*.pt")
exec > "$log_file" 2>&1
for file in $file_list; do
file_name=$(basename "$file" .pt)
echo "========================================"
echo "Found file: $file_name"
echo "========================================"
python test.py --weights "$file" --data "$data" --task $task
done
sh test.sh runs/train/exp/weights test
执行完会在根目录下生成log
文件夹,里面对应的log日志文件。
2.2 GFLOPs显示 🔥
不少小伙伴给YOLO做改进时,运行models/yolo.py
会遇到这样的情况:
YOLOv5s-ghostv2-backbone summary: 406 layers, 5156430 parameters, 5156430 gradients
GFLOPs指标无法显示,YOLOv5/v7可以在utils/torch_utils.py
中model_info
函数进行修改,YOLOv8可以在ultralytics/utils/torch_utils.py
中的get_flops
函数中修改
try: # FLOPs
p = next(model.parameters())
stride = max(int(model.stride.max()), 32) if hasattr(model, 'stride') else 32 # max stride
im = torch.empty((1, p.shape[1], stride, stride), device=p.device) # input image in BCHW format
flops = thop.profile(deepcopy(model), inputs=(im, ), verbose=False)[0] / 1E9 * 2 # stride GFLOPs
imgsz = imgsz if isinstance(imgsz, list) else [imgsz, imgsz] # expand if int/float
fs = f', {flops * imgsz[0] / stride * imgsz[1] / stride:.1f} GFLOPs' # 640x640 GFLOPs
except Exception as e:
# print(e)
im = torch.rand(1, 3, 640, 640).to(p.device)
flops, parms = thop.profile(model, inputs=(im,), verbose=False)
# print(f'Params: {parms}, GFLOPs: {flops * 2 / 1e9}')
# fs=''
fs = f', {flops * 2 / 1E9:.1f} GFLOPs'