文章目录
前言
本是想寻找针对yolov5的训练技巧来提升模型的效果,但发现yolov5本身就包含了很多训练技巧,反正也要学习yolov5的原理,就一块儿学了,以此篇记录下!
训练技巧
1.训练预热 Warmup
1.1 什么是训练预热 Warmup?
运动员在比赛之前都会进行热身活动,不然一下子用力过猛容易发生拉伤、扭伤。
那对于我们的模型呢?众所周知学习率是一个非常重要的超参数,直接影响着网络训练的速度核收敛情况。通常情况下,网络开始训练之前,我们会随机初始化权重,但设置学习率过大会导致模型振荡严重,类似于运动员比赛之前不热身,“用力过猛”,而学习率过小,又会导致网络收敛太慢。
那该怎么做呢?
相信你会想到,可以训练开始初期将学习率设置小一点,到后面时,再将学习率设置得大一点。对了,这,就是最简单的Warmup。
为什么叫做训练预热?
刚开始设置小学习率的那段时间,就像是在“预热”,在预热的小学习率下,模型可以慢慢趋于稳定,等模型相对稳定后在选择预先设置的学习率进行训练
训练预热的效果?
可以使模型的收敛速度变得更快,模型效果更佳。
为了更好的理解训练预热,这里引用别人的一段解释:
我们可以把Warmup的过程想成,模型最开始是一个小孩,学习率太大容易认识事物太绝对了,这个时候需要小的学习率,摸着石头过河,小心翼翼地学习,当他对事物有一定了解和积累,认知有了一定地水平,这个时候步子再迈大一点就没问题了。
来源:yolov5——训练策略_yolov5 冻结训练-CSDN博客
1.2 常见的训练预热类型
(1)Constant Warmup
学习率从一个非常小的数值线性增加到预设值之后保持不变,如下图:
(2)Linner Warmup
学习率从一个非常小的数值线性增加到预设值之后,然后再线性减小,如:
(3) Cosine Warmup
学习率先从很小的数值线性增加到预设学习率,然后按照余弦函数值进行衰减,如:
1.3 yolov5里面的warmup
yolov5里面的采用Gradual Warmup,这是一种改进的Warmup方法,它从最初的小学习率开始,每个step逐渐增大,直到达到最初设置的比较大的学习率时,采用最初设置的学习率进行训练。
nb = len(train_loader) # number of batches | 一个epoch拥有的batch数量
nw = max(round(hyp["warmup_epochs"] * nb), 100) # number of warmup | 热身的总迭代次数
pbar = enumerate(train_loader) # 遍历train_loader
# 记录日志
LOGGER.info(("\n" + "%11s" * 7) % ("Epoch", "GPU_mem", "box_loss", "obj_loss", "cls_loss", "Instances", "Size"))
# 如果在主线程中,那么给enumberate加上tqdm进度条
if RANK in {-1, 0}:
pbar = tqdm(pbar, total=nb, bar_format=TQDM_BAR_FORMAT) # progress bar
# 开始遍历train_loader
for i, (imgs, targets, paths, _) in pbar: # batch
# imgs: 一个batch的图片
# targets: 一个batch的标签
# paths: 一个batch的路径
callbacks.run("on_train_batch_start") # 记录此时正在干什么
# 计算当前的迭代次数
ni = i + nb * epoch # number integrated batches (since train start)
imgs = imgs.to(device, non_blocking=True).float() / 255 # uint8 to float32, 0-255 to 0.0-1.0
# Warmup
if ni <= nw: # 如果当前的迭代次数小于需要热身的迭代次数,则开始热身
xi = [0, nw] # x interp
# accumulate变量的作用是动态地控制累积的 Batch 数,以便在训练开始时逐渐增加累积的 Batch 数,
# 从而实现从较小的累积 Batch 数到较大的累积 Batch 数的平滑过渡
# 这有助于模型在训练初期稳定地学习
accumulate = max(1, np.interp(ni, xi, [1, nbs / batch_size]).round())
for j, x in enumerate(optimizer.param_groups):
# bias lr falls from 0.1 to lr0, all other lrs rise from 0.0 to lr0
x["lr"] = np.interp(ni, xi, [hyp["warmup_bias_lr"] if j == 0 else 0.0, x["initial_lr"] * lf(epoch)])
if "momentum" in x:
x["momentum"] = np.interp(ni, xi, [hyp["warmup_momentum"], hyp["momentum"]])
2.余弦退火调整学习率 CosineAnnealingLR
2.1 解释
余弦学习率(Cosine Annealng)是一种周期性的学习率调整方式。它将学习率设置在一个余弦函数上,从一个初始值开始,然后逐渐下降
到最小值,接着又返回到最大值,这个过程像一个完整的余弦波形。这有助干避免是停问题,并目能保持较长的学习阶段,使得模型能够
探索更多的解决方案空间
余弦退火(Cosine Annealing Warm Restarts)则是对余弦学习率的一种扩展,它结合了周期性重启的概念。在每个完整周期结束后,会
以最低点开始新的余弦周期,而不是首接回到初始值。这种方法可以在长期内维持良好的性能,并通过定期“重启"来外理可能出现的局部
最优。
Yolov5中使用余弦退火(Cosine Annealing)作为学习率衰减策略是一种常见的做法。余弦退火可以使模型在训练过程中逐渐降低学习率,从而更好地收敛到最优解。
公式:
η
t
=
η
min
+
1
2
(
η
max
−
η
min
)
(
1
+
cos
(
T
c
u
r
T
m
a
x
π
)
)
\eta_t = \eta_{\min} + \frac{1}{2} (\eta_{\max} - \eta_{\min}) \left(1 + \cos\left(\frac{T_{cur}}{T_{max}} \pi\right)\right)
ηt=ηmin+21(ηmax−ηmin)(1+cos(TmaxTcurπ))
其中:
-
η t \eta_t ηt是当前第 (t) 次迭代的学习率。
-
η min \eta_{\min} ηmin是学习率的下界,即最小学习率。
-
η max \eta_{\max} ηmax是学习率的上界,即初始学习率。
-
T c u r T_{cur} Tcur是当前的迭代次数。
-
T m a x T_{max} Tmax是一个周期内的总迭代次数。
具体实现步骤如下:
- 首先,确定一个初始学习率(通常是比较大的值),以及总的训练迭代次数(epochs)。
- 在每个迭代中,计算当前的训练迭代次数(current epoch)。
- 根据余弦函数计算一个介于0和1之间的衰减因子(decay factor): decay, factor=0.5*(1+ cos(current epoch/epochs)*pi) 这里使用了余弦函数来控制衰减因子的变化。
- 最后,将初始学习率乘以衰减因子,得到当前迭代的学习率
2.2 yolov5中的余弦退火
#train.py
# Scheduler
if opt.cos_lr:
lf = one_cycle(1, hyp['lrf'], epochs) # cosine 1->hyp['lrf']
else:
lf = lambda x: (1 - x / epochs) * (1.0 - hyp['lrf']) + hyp['lrf'] # linear
scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf) # plot_lr_scheduler(optimizer, scheduler, epochs)
参数opt.cos_lr
来自于训练时的传参:
parser.add_argument('--cos-lr', action='store_true', help='cosine LR scheduler')
可以看到这里有个One Cycle学习率和线性学习率,one_cycle表示余弦退火学习率,而线性学习率表示学习率成线性递减的方式
3.自适应锚框 Autoanchor
3.1 什么是anchor?
我们都知道,目标检测是识别出图像中物体的类别/名字和检测出物体的位置,简而言之就是 检测=识别(what)+定位(where),目标检测呈现的结果就是在图像中框出物体,并标示出类别,如下图:
那YOLOv5算法是如何能够识别出物体名字和位置的呢?
这就涉及到anchor了,在目标检测算法YOLOv5中,Anchor(锚框)是一系列预设的边界框(bounding boxes),YOLOv5使用这些锚框作为预测的参考框架,以检测图像中的物体,通过算法,哪个anchor的得分高,哪个就能代表物体及其位置。
锚框的大小和比例是根据训练数据集中的目标框(ground truth boxes)通过聚类算法(如K-means)确定的,目的是使模型能够更准确地预测物体的位置和尺寸。所以预置的anchor包含在数据集中存在的对象大小的组合,这自然包括数据中存在的不同长宽比和比例。通常在图像中的每一个位置预置4-10个anchor。
为了提高检测性能,YOLOv5提出了自适应锚框的思想,它可以根据目标在特征图上的分布情况自适应地调整锚框的大小和尺度。
AutoAnchor 的步骤:
- 分析数据集:分析数据集中的边界框,了解对象的大小和形状分布。 Anchor聚类:使用聚类算法(如
- K-means)对边界框进行聚类,以确定最佳的 Anchor 数量和大小。
- 更新配置:根据聚类结果更新 Anchor配置,以便在训练期间使用这些新 Anchor。
- 重新训练:使用新的 Anchor 配置重新开始训练过程。
3.1 yolov5中的默认锚框
yolov5中预先设定了一下锚定框,这些锚框是针对coco数据集的,其他目标检测也适用,可以在models/yolov5.yaml文件中查看,例如如图所示,这些框针对的图片大小是640x640。这是默认的anchor大小。
代码中anchors
的三行分别代表大特征图、中特征图、小特征图上的锚框。因为注意的是,在目标检测任务中,一般使用大特征图上去检测小目标,因为大特征图含有更多小目标信息,因此大特征图上的anchor数值通常设置为小数值,小特征图检测大目标,因此小特征图上anchor数值设置较大。
3.3 yolov5中的自适应锚框
在yolov5 中自动锚定框选项,训练开始前,会自动计算数据集标注信息针对默认锚定框的最佳召回率,当最佳召回率大于等于0.98时,则不需要更新锚定框;如果最佳召回率小于0.98,则需要重新计算符合此数据集的锚定框。
在train.py
的parse_opt中设置了默认自动计算锚框选项,如果不想自动计算,可以设置这个,建议不要改动。
这里参数是noautoanchor
,默认为False,也就是不会不autoanchor,就是默认设置了autoanchor.
在train.py中设置检查锚框是否符合要求,主要使用的函数是check_anchors。
check_anchor函数的流程大概是:先判断锚框是否符合要求(判断条件bpr / aat,大于0.98就不会更新),然后利用k-mean聚类更新锚框。它存在于utils/autoanchor.py
中。
4.超参数进化 Hyperparameter evolution
超参数进化(Hyperparameter Evolution)是一种模型优化技术,它涉及在训练过程中动态地调整模型的超参数(hyperparameters),以找到在特定数据集上性能最佳的参数设置。这些超参数是模型设计中的高级设置,它们控制模型的学习过程,但不直接作为模型输入的一部分。常见的超参数包括学习率、批量大小、迭代次数、正则化参数、Anchor 大小等。
超参数进化的目标是减少超参数调整的试错过程,提高模型训练的效率。传统的超参数调整方法通常需要手动调整超参数或使用网格搜索(Grid Search)等方法进行大量的实验来找到最佳设置。这些方法既耗时又可能无法找到最优解。
YOLOv5的超参数进化训练策略是一种利用 ** 遗传算法(Genetic Algorithm, GA)** 进行超参数优化的方法。
超参数进化的基本步骤:
1.初始化超参数: 首先,需要定义一组初始的超参数集合,这些超参数可以是基于经验的默认值,也可以是从先前研究中获得的值。
2.定义适应度函数: 适应度函数是评价超参数集合好坏的标准,通常与模型的性能指标相关,如准确率(mAP)等。
3.进化过程: 遗传算法通过选择、交叉和变异等操作来迭代更新超参数集合。选择操作基于适应度函数,交叉操作将两个超参数集合的“基因”组合成新的集合,变异操作则引入随机变化以增加搜索空间的多样性。
4.评估和选择: 新生成的超参数集合会被用于训练模型,并根据其性能评估适应度。适应度高的集合更有可能被选中用于产生下一代超参数。
5.迭代: 这个过程会重复进行多代,直到满足停止条件,如达到预设的代数或者适应度不再显著提高。
6.结果分析: 进化过程结束后,会选择适应度最高的超参数集合,这些超参数可以用于训练最终的模型。
在YOLOv5中,超参数进化可以通过在训练命令中添加–evolve参数来启动。默认的进化设置会运行基础场景多次,例如300代的进化。遗传操作中,变异操作使用前几代中最好的父代,以一定的概率和变异率生成新的后代。进化的结果会被记录,并可以用于指导后续的模型训练。
4.1 初始化超参数
YOLOv5 有大约 30 个超参数,用于不同的训练设置。这些参数在 *.yaml 文件中的 /data/hyps 目录下:
# hyp.Objects365.yaml
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
# Hyperparameters for Objects365 training
# python train.py --weights yolov5m.pt --data Objects365.yaml --evolve
# See Hyperparameter Evolution tutorial for details https://github.com/ultralytics/yolov5#tutorials
lr0: 0.00258
lrf: 0.17
momentum: 0.779
weight_decay: 0.00058
warmup_epochs: 1.33
warmup_momentum: 0.86
warmup_bias_lr: 0.0711
box: 0.0539
cls: 0.299
cls_pw: 0.825
obj: 0.632
obj_pw: 1.0
iou_t: 0.2
anchor_t: 3.44
anchors: 3.2
fl_gamma: 0.0
hsv_h: 0.0188
hsv_s: 0.704
hsv_v: 0.36
degrees: 0.0
translate: 0.0902
scale: 0.491
shear: 0.0
perspective: 0.0
flipud: 0.0
fliplr: 0.5
mosaic: 1.0
mixup: 0.0
copy_paste: 0.0
4.2 YOLOv5中的应用
在YOLOv5中,超参数进化可以通过在训练命令中添加–evolve参数来启动。默认的进化设置会运行基础场景多次,例如300代的进化。遗传操作中,变异操作使用前几代中最好的父代,以一定的概率和变异率生成新的后代。进化的结果会被记录,并可以用于指导后续的模型训练
5.自动混合精度训练 Automatic mixed precision (AMP) training
5.1 什么是AMP训练?
自动混合精度(Automatic Mixed Precision, AMP)训练是一种深度学习训练技术,它可以在训练过程中动态地选择使用浮点数的精度。
自动混合精度训练的基本思想是,根据计算的需求和成本,自动地在单精度(FP32)和半精度(FP16)之间切换。具体来说,AMP 训练会识别那些对精度要求不高的计算(例如,权重矩阵的乘法),并将这些计算转换为半精度(FP16)计算,以减少梯度计算中的数值误差。而对于那些对精度要求较高的计算(例如,激活函数的计算),AMP 训练仍然使用单精度(FP32)计算,以保持模型的准确性和响应性。
5.2 AMP训练的优点
- 提高训练速度:使用双精度进行某些计算可以减少浮点运算的次数,从而提高训练速度。
- 减少内存使用:双精度通常需要比单精度更多的内存,但只在必要时使用双精度,可以减少总体内存使用。
- 提高数值稳定性:在一些情况下,使用双精度可以减少梯度更新的数值误差,提高模型的训练稳定性。
5.3 yolov5中的应用
混合精度预示着有不止一种精度的Tensor,在PyTorch的AMP模块里有2种:torch.FloatTensor
和torch.HalfTensor
,它们分别对应于单精度(FP32)和半精度(FP16)浮点数。
- torch.FloatTensor: 这是 PyTorch 中的单精度浮点张量。它使用 32 位(4 字节)来存储每个浮点数,提供了较高的数值精度和较大的数值范围。这是大多数深度学习任务中默认使用的浮点类型。
- torch.HalfTensor: 这是 PyTorch 中的半精度浮点张量。它使用 16 位(2 字节)来存储每个浮点数,数值范围和精度都比单精度浮点数低。
对于torch.HalfTensor来说:
- 优点:就是存储小、计算快、更好的利用CUDA设备的Tensor Core。因此训练的时候可以减少显存的占用(可以增加batchsize了),同时训练速度更快;
- 缺点:数值范围小(更容易Overflow / Underflow)、舍入误差(Rounding Error,导致一些微小的梯度信息达不到16bit精度的最低分辨率,从而丢失)。
自动混合精度训练预示着Tensor的dtype类型会自动变化,也就是框架按需自动调整tensor的dtype(其实不是完全自动,有些地方还是需要手工干预)
如何在PyTorch中使用自动混合精度?
使用autocast + GradScaler。
1. autocast
使用torch.cuda.amp模块中的autocast 类。当进入autocast的上下文后,可支持AMP的CUDA ops 会把tensor的dtype转换为半精度浮点型,从而在不损失训练精度的情况下加快运算。刚进入autocast的上下文时,tensor可以是任何类型,不需要在model或者input上手工调用.half() ,框架会自动做,这也是自动混合精度中“自动”一词的由来。另外一点就是,autocast上下文应该只包含网络的前向过程(包括loss的计算),而不要包含反向传播,因为BP的op会使用和前向op相同的类型。
2. GradScaler
使用torch.cuda.amp.GradScaler,需要在训练最开始之前实例化一个GradScaler对象。通过放大loss的值来防止梯度的underflow(这只是BP的时候传递梯度信息使用,真正更新权重的时候还是要把放大的梯度再unscale回去)。
6.断点续训
断点续训(Resume Training)是机器学习训练过程中的一个功能,它允许模型训练在之前停止的地方继续进行。这对于处理大型数据集或需要长时间训练的模型尤为重要,因为在训练过程中可能会由于各种原因(如硬件故障、电力中断等)导致训练过程意外停止。
断点续训不仅能够帮助节省时间,避免从头开始训练,还能够确保模型训练的连贯性和最终效果。在实际应用中,这是一个非常实用的功能,可以提高模型训练的效率。
🎯用法:
# 用法1: 直接使用 last.pt 进行断点续训
python train.py --resume
# 用法2: 使用指定的权重进行断点续训
python train.py --resume runs/exp/weights/example_weights.pt
7.多GPU训练 Multi-GPU Training
YOLOv5支持多GPU训练,这是一种通过利用多个图形处理单元(GPU)来加速模型训练过程的技术。在YOLOv5中,多GPU训练可以显著减少训练时间,尤其是在处理大型数据集时。
多GPU训练的配置和启动
在YOLOv5中,多GPU训练可以通过在命令行中使用--nproc_per_node
参数来指定每个节点上使用的GPU数量。例如,如果想在一个节点上使用两个GPU进行训练,可以使用以下命令:
python -m torch.distributed.launch --nproc_per_node 2 train.py
数据并行和分布式训练:
YOLOv5的多GPU训练通常采用数据并行(Data Parallelism)和分布式训练的方式。数据并行是指将输入数据分割成多个批次,每个GPU处理一个子集,然后在一个优化步骤中将梯度汇总起来更新模型参数。分布式训练则是在多个节点上进行数据并行训练,这在使用多台机器进行大规模训练时非常有用
拓展补充知识:
💡 1. DP (Data Parallel,数据并行)
class torch.nn.DataParallel(module,
device_ids=None,
output_device=None,
dim=0)
可以看到,DP 可以在 module 级别实现数据并行。具体来说,这个容器(DP)通过在 Batch 维度上分块,将输入分割到指定的设备上,从而实现给定 module 的并行应用(其他对象将每个设备复制一次)。
💡 2.DDP(Distributed Data Parallel,分布式数据并行)
Distributed Data Parallel (DDP) 是 PyTorch 中用于分布式训练的高级原语,它允许模型在多个节点上进行训练,每个节点可以有多个 GPU。DDP 可以显著提高训练速度,尤其是在使用大量数据和复杂模型时。
DDP 背后的主要思想是将模型复制到每个节点,并在每个节点上独立地处理一部分数据。在每个训练步骤中,每个节点上的模型副本会计算梯度,然后这些梯度会在所有节点之间进行平均。通过这种方式,DDP 可以确保每个节点上的模型参数保持同步。
DDP 相对于其他并行方法(如 DataParallel)的主要优势在于其高效的通信机制。DDP 使用 Ring Allreduce 算法来减少梯度同步时的通信瓶颈,这使得 DDP 特别适合于大型模型和大规模训练工作。
💡3.DP VS DDP
对比维度 | DP | DDP(DistributedData Parallel) |
---|---|---|
适用场景 | 单机多卡 | 单机多卡和多机多卡 |
并行模式 | 单进程多线程,每个线程运行在不同的GPU上,模型拷贝到每个GPU,最后汇总 | 多进程,每个GPU对应一个进程,每个进程有自己的模型副本 |
一次训练模型个数 | 只能一个 | 可以多个 |
YOLOv5 开启 GPU 训练
✔️〔推荐〕单 GPU 训练
python train.py \
--weights weights/yolov5s.pt \
--data data/coco128.yaml \
--hyp data/hyps/hyp.scratch-low.yaml \
--epochs 150 \
--batch-size 64 \
--imgsz 640 \
--project runs/train \
--name exp
--device 0
⚠️〔不推荐〕DP 训练
python train.py \
--weights weights/yolov5s.pt \
--data data/coco128.yaml \
--hyp data/hyps/hyp.scratch-low.yaml \
--epochs 150 \
--batch-size 64 \
--imgsz 640 \
--project runs/train \
--name exp \
--device 0,1
✔️〔推荐〕DDP 训练
python -m torch.distributed.run \
--nproc_per_node 4 \ # 每个节点的 GPU 数量
train.py \
--weights weights/yolov5s.pt \
--data data/coco128.yaml \
--hyp data/hyps/hyp.scratch-low.yaml \
--epochs 150 \
--batch-size 64 \
--imgsz 640 \
--project runs/train \
--name exp \
--device 0,1,2,3