赞
踩
在 YOLOv5 中,warm-up(预热)是指在训练初始阶段使用较小的学习率,然后逐渐增加学习率,以帮助模型更好地适应数据集。这个过程有助于避免在初始阶段出现梯度爆炸或不稳定的情况,使模型更容易收敛。
YOLOv5 中的 warm-up 主要体现在学习率的调整上。具体而言,YOLOv5 使用线性 warm-up 策略,即在初始训练阶段,学习率从一个较小的初始值线性增加到设定的初始学习率。这有助于减缓模型的参数更新速度,防止在初始时出现过大的权重更新,从而提高训练的稳定性。
在 YOLOv5 的实现中,warm-up 阶段通常持续一定的迭代次数,这个次数是在训练开始时设定的。一旦 warm-up 阶段结束,模型将以设定的初始学习率进行正常的训练。
Warm-up 的主要优势在于可以在模型开始学习任务时更好地控制学习的速度,从而有助于模型更快地适应数据分布。这在处理复杂的目标检测任务中尤为重要,因为这些任务通常具有大量的样本和复杂的背景。
我们看一下相关的源码(train.py
):
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"]])
在 How suspend image mixing #931 中有作者关于 warm-up 的说明:
warmup 慢慢地将训练参数从它们的初始(更稳定)值调整到它们的默认训练值。例如,通常会在最初的几个 Epoch 内将学习率从 0 调整到某个初始值,以避免早期训练的不稳定、nan 等问题。
热身效果可以在 Tensorboard 的学习率曲线图中观察到,这些曲线自从最近的提交以来已经被自动跟踪。下面的例子显示了在自定义数据集上大约 30 个 Epoch 的热身,每个参数组有一个曲线图。最后一个曲线图展示了不同的热身策略(即不同的超参数设置)。
numpy.interp(x, xp, fp, left=None, right=None, period=None)
是 NumPy 中的一个函数,用于线性插值。线性插值是一种估算在两个已知值之间的未知值的方法,假设这些值之间的变化是线性的。
其中:
x
: 需要进行插值的一维数组。xp
: 已知数据点的 x 坐标(一维数组)-> x points。fp
: 已知数据点的 y 坐标(一维数组)-> function points。left
: 当 x 小于 xp 的最小值时,返回的默认值,默认为 fp[0]。right
: 当 x 大于 xp 的最大值时,返回的默认值,默认为 fp[-1]。period
: 如果提供了 period,表示 xp 是周期性的,此时插值会考虑周期性。period 是周期的长度。示例:
import numpy as np import matplotlib.pyplot as plt # 已知数据点 x_known = np.array([1, 2, 3, 4, 5]) y_known = np.array([3, 5, 7, 9, 11]) # 待插值的数据点 x_unknown = [0.0, 1.5, 3.0, 4.5, 6.0] # 使用np.interp进行插值 y_unknown = np.interp(x_unknown, x_known, y_known) print(f"{y_unknown = }") # [3, 4, 7, 10, 11] # 绘制图形 plt.figure(figsize=(10, 6), dpi=200) plt.plot(x_known, y_known, 'o', label='Known points', color='green') # 已知数据点 plt.plot(x_unknown, y_unknown, 'o', label='Unknown points', color='red') # 插值结果 plt.xlabel('x') plt.ylabel('y') plt.title(r'Example for $np.interp()$') plt.legend() plt.grid(True) plt.savefig('Example4np.interp.jpg')
看不懂没关系,我们作图看一下:
外推规则如下:
x
的值小于 xp
的最小值,则 np.interp
返回与 xp
最小值对应的 fp
值。x
的值大于 xp
的最大值,则 np.interp
返回与 xp
最大值对应的 fp
值。分析如下:
⚠️ x 和 y 取的是索引,而 xp 和 fp 这里不是取索引,而是取值
Cosine Annealing Warm Restart 是一种学习率调度策略,它是基于余弦退火周期性调整学习率的算法。这种策略在学习率调整上引入了周期性的“重启”,使得模型在训练过程中能够周期性地跳出局部最小值,从而有助于提高模型的泛化能力和性能。
具体来说,Cosine Annealing Warm Restart 策略包括以下几个关键组成部分:
Cosine Annealing Warm Restart 策略的优势在于它通过周期性重启和调整周期长度,使得模型能够在训练过程中不断探索新的参数空间,从而有可能找到更好的局部最小值或全局最小值。这种策略特别适合于那些容易陷入局部最小值的复杂模型训练,可以提高模型的最终性能和泛化能力。
在论文 Bag of Tricks for Image Classification with Convolutional Neural Networks 中有介绍到余弦退火和阶段两种学习率在 ImageNet 数据集上的表现(模型为 ResNet-50):
图 3:带有热身阶段的学习率计划的可视化。顶部:Batch size=1024 下的余弦和阶跃调度。底部:两种调度下的Top-1验证准确率曲线。
余弦退火热重启的调用如下:
import torch.optim as optim
model = ...
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 使用CosineAnnealingWarmRestarts调度器
# T_0是初始周期的大小,T_mult每个周期结束后周期大小乘以的倍数
scheduler = optim.lr_scheduler.CosineAnnealingWarmRestarts(optimizer, T_0=10, T_mult=2)
for epoch in range(num_epochs):
# 训练模型的代码
train(...)
# 在每个epoch后更新学习率
scheduler.step(epoch)
在上面的代码中,T_0
参数代表初始周期的大小,即在第一次余弦退火周期中,学习率将按照余弦调度进行调整的 Epoch 数。T_mult
参数指定了每个周期结束后周期大小将乘以的倍数。scheduler.step(epoch)
应该在每次更新参数之后、每个epoch结束时调用。
请根据我们的具体需求调整 T_0
和 T_mult
的值,以及 num_epochs
,即我们的训练周期总数。
# Scheduler
if opt.cos_lr: # 如果使用cosine学习率
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)
我们画图看一下二者的区别:
import matplotlib.pyplot as plt import math def one_cycle(y1=0.0, y2=1.0, steps=100): # lambda function for sinusoidal ramp from y1 to y2 https://arxiv.org/pdf/1812.01187.pdf return lambda x: ((1 - math.cos(x * math.pi / steps)) / 2) * (y2 - y1) + y1 # 设定训练的总epoch数 epochs = 100 # YOLOv5中的超参数 hyp = { "lr0": 0.01, # 初始学习率 "lrf": 0.1 # final OneCycleLR learning rate (lr0 * lrf) } # 创建一个numpy数组,表示epoch数 epoch_lst = range(epochs) # Cosine调度器的学习率变化 lf_cos = one_cycle(1, hyp["lrf"], epochs) lr_cos = [lf_cos(epoch) for epoch in epoch_lst] # Linear调度器的学习率变化 lf_lin = lambda x: (1 - x / epochs) * (1.0 - hyp["lrf"]) + hyp["lrf"] lr_lin = [lf_lin(epoch) for epoch in epoch_lst] # 绘制学习率变化曲线 plt.figure(figsize=(10, 6), dpi=200) plt.plot(epoch_lst, lr_cos, '-', label='Cosine Scheduler', color='skyblue') plt.plot(epoch_lst, lr_lin, '-.', label='Linear Scheduler', color='lightpink') plt.xlabel('Epochs') plt.ylabel('Learning Rate') plt.title('Comparison of Cosine and Linear Learning Rate Schedulers') plt.legend() plt.grid(True) plt.savefig('Le0v1n/results/Comparison-of-Cosine-and-Linear-Learning-Rate-Schedulers.jpg')
AutoAnchor 是 YOLOv5 中的一个功能,用于自动调整 Anchor(anchor boxes)的大小以更好地适应训练数据集中的对象形状。
Anchor 是在对象检测任务中使用的一种技术,它们代表了不同大小和宽高比的预定义边界框,用于预测真实对象的位置和大小。
在 YOLOv5 中,AutoAnchor 的主要目的是优化 Anchor 的大小,以便在训练期间提高检测精度和效率。这个功能在训练过程开始时执行,根据训练数据集中的边界框计算最佳 Anchor 配置。通过这种方式,YOLOv5 可以自动适应新的数据集,而无需手动调整 Anchor。
AutoAnchor 的优势在于它能够为特定的数据集定制 Anchor,这有助于提高检测精度,尤其是在处理具有不同对象大小和形状的多样化数据集时。通过自动调整 Anchor,YOLOv5 可以更有效地利用计算资源,减少对超参数的手动调整需求,从而简化了模型训练过程。
首先需要先计算当前的 Anchor 与数据集的适应程度。
@TryExcept(f"{PREFIX}ERROR") def check_anchors(dataset, model, thr=4.0, imgsz=640): # 函数作用:检查anchor是否适合数据,如有必要,则重新计算anchor # 从模型中获取检测层(Detect()) m = model.module.model[-1] if hasattr(model, "module") else model.model[-1] # 计算输入图片的尺寸相对于最大尺寸的比例 shapes = imgsz * dataset.shapes / dataset.shapes.max(1, keepdims=True) # 生成一个随机的比例因子,用于扩大或缩小图片尺寸 scale = np.random.uniform(0.9, 1.1, size=(shapes.shape[0], 1)) # 计算所有图片的宽高(wh) wh = torch.tensor(np.concatenate([l[:, 3:5] * s for s, l in zip(shapes * scale, dataset.labels)])) def metric(k): # 计算度量值 # 计算每个anchor与gt boxes的宽高比 r = wh[:, None] / k[None] # 计算最小比率和最大比率 x = torch.min(r, 1 / r).min(2)[0] # 找到最大比率的anchor best = x.max(1)[0] # 计算超过阈值(thr)的anchor数量占比 aat = (x > 1 / thr).float().sum(1).mean() # 计算BPR(best possible recall) bpr = (best > 1 / thr).float().mean() return bpr, aat # 获取模型的步长(stride) stride = m.stride.to(m.anchors.device).view(-1, 1, 1) # 计算当前的anchor anchors = m.anchors.clone() * stride # 计算当前anchor与gt boxes的比值,并找到最佳比值和超过阈值的anchor占比 bpr, aat = metric(anchors.cpu().view(-1, 2)) s = f"\n{PREFIX}{aat:.2f} anchors/target, {bpr:.3f} Best Possible Recall (BPR). " # 如果最佳比值召回率大于0.98,说明当前anchor适合数据集 if bpr > 0.98: LOGGER.info(f"{s}Current anchors are a good fit to dataset ✅") else: # 说明anchor不适合数据集,需要尝试改进 LOGGER.info(f"{s}Anchors are a poor fit to dataset ⚠️, attempting to improve...") # 计算anchor数量 na = m.anchors.numel() // 2 # 使用k-means聚类算法重新计算anchor anchors = kmean_anchors(dataset, n=na, img_size=imgsz, thr=thr, gen=1000, verbose=False) # 计算新anchor的最佳比值召回率 new_bpr = metric(anchors)[0] # 如果新anchor的召回率比原来的高,则替换anchor if new_bpr > bpr: anchors = torch.tensor(anchors, device=m.anchors.device).type_as(m.anchors) m.anchors[:] = anchors.clone().view_as(m.anchors) # 检查anchor顺序是否正确(必须在像素空间,不能在网格空间) check_anchor_order(m) m.anchors /= stride s = f"{PREFIX}Done
超参数进化(Hyperparameter Evolution)是一种模型优化技术,它涉及在训练过程中动态地调整模型的超参数(hyperparameters),以找到在特定数据集上性能最佳的参数设置。这些超参数是模型设计中的高级设置,它们控制模型的学习过程,但不直接作为模型输入的一部分。常见的超参数包括学习率、批量大小、迭代次数、正则化参数、Anchor 大小等。
超参数进化的目标是减少超参数调整的试错过程,提高模型训练的效率。传统的超参数调整方法通常需要手动调整超参数或使用网格搜索(Grid Search)等方法进行大量的实验来找到最佳设置。这些方法既耗时又可能无法找到最优解。
在 《超参数演变》 这一官方文档中对其进行了介绍:
超参数演化是一种使用遗传算法(GA)进行优化的超参数优化方法。
ML 中的超参数控制着训练的各个方面,而为超参数寻找最佳值是一项挑战。网格搜索等传统方法很快就会变得难以处理,原因在于:1)搜索空间维度高;2)维度之间的相关性未知;3)评估每个点的适配性成本高昂,因此 GA 是超参数搜索的合适候选方法。
GA 的流程如下:
我们看一下官方的介绍:
YOLOv5 有大约 30 个超参数,用于不同的训练设置。这些参数在 *.yaml
文件中的 /data/hyps
目录。更好的初始猜测将产生更好的最终结果,因此在演化之前正确初始化这些值非常重要。如果有疑问,只需使用默认值即可,这些值已针对 YOLOv5 COCO 从头开始的训练进行了优化。
# YOLOv5 声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/307682
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。