yolov5——训练策略

前言

yolov5的训练策略big big丰富,这也是yolov5涨分厉害的reason,目前yolov5的使用量也是非常大的,官网的star已经23.5k了,无论是在迁移学习还是实际场景的应用都是非常广泛的。之前参加比赛,发现好几页的选手都在使用yolov5,确实有必要梳理一下,yolov5的训练策略。感觉这些策略对以后自己实验帮助会很大,所以要细嚼慢咽,好了,不说了,走起开吃!!!!!!!!!
在这里插入图片描述

1. 训练预热——Warmup

1.1 what是Warmup

众所周知学习率是一个非常重要的超参数,直接影响着网络训练的速度核收敛情况。通常情况下,网络开始训练之前,我们会随机初始化权重,设置学习率过大会导致模型振荡严重,学习率过小,网络收敛太慢。那这个时候该怎么做呢?是不是有人会说,我前面几十个或者几百个epoch学习率设置小一点,后面正常后,设置大一点呢,没错这就是最简单的Warmup。

1.2 why用Warmup

我们可以把Warmup的过程想成,模型最开始是一个小孩,学习率太大容易认识事物太绝对了,这个时候需要小的学习率,摸着石头过河,小心翼翼地学习,当他对事物有一定了解和积累,认知有了一定地水平,这个时候步子再迈大一点就没问题了。

1.3 常见Warmup类型

1. Constant Warmup
在前面100epoch里,学习率线性增加,大于100epoch以后保持不变,整个过程如下如所示:
在这里插入图片描述
2. Linner Warmup
在前面100epoch里,学习率线性增加,大于100epoch以后保持线性下降,整个过程如下如所示:
在这里插入图片描述
2. Cosine Warmup
在前面100epoch里,学习率线性增加,大于100epoch以后保持x余弦方式下降,整个过程如下如所示:
在这里插入图片描述
通常来说第三种Cosine Warmup使用地频率较多一点。

1.4 yolov5中的Warmup

1. 超参数设置
在yolov5中data/hyps/hyp.scratch-*.yaml三个文件中,都存在着warmup_epoch代表训练预热轮次,以这
hyp.scratch.scratch-med.yaml为例,如图超参数列表
在这里插入图片描述
2. 训练转化
nb表示训练的类别数,例如coco数据集80类,在超参数列表中warmup_epochs=3,则nw = 3 * 80 = 240,所以热身训练240epoch, 这里要注意的是最少热身训练100次,所以设施epoch的时候最好大于100epoch,要不然热身都还没有做完,运动就结束了。
在这里插入图片描述
3.预热训练开始
yolov5的预测训练从这里开始,超参数的初始值和变化范围data/hyps/hyp.scratch-*.yaml给出来了,计算过程就在这里。
在这里插入图片描述

2. 自动调整锚定框——Autoanchor

2.1 what是anchor

anchor是指预定义的框集合,其宽度和高度与数据集中对象的宽度和高度相匹配。预置的anchor包含在数据集中存在的对象大小的组合,这自然包括数据中存在的不同长宽比和比例。通常在图像中的每一个位置预置4-10个anchor。
在这里插入图片描述

训练目标检测网络的典型任务包括:生成anchor,搜索潜在anchor,将生成的anchor与可能的ground truth配对,将其余anchor分配给背景类别,然后进行sampling和训练。
推理过程就是对anchor的分类和回归,score大于阈值的anchor进一步做回归,小于阈值的作为背景舍弃,这样就得到了目标检测的结果。

2.2 why用anchor

目标检测可以理解为回归+分类,怎么样最好的完成这个任务呢,是不是想到了用锚框,首先预设一组不同尺度不同位置的固定参考框,覆盖几乎所有位置和尺度,每个参考框负责检测与其交并比大于阈值 (训练预设值,常用0.5或0.7) 的目标,anchor技术将问题转换为"这个固定参考框中有没有认识的目标,目标框偏离参考框多远",不再需要像传统的目标检测那样,挨个挨个不同大小的滑动,费时费力。正是anchor的出现把目标检测分为了anchor free和anchor base。
想要了解更多的anchor相关的知识可以查看连接:
目标检测Anchor的What/Where/When/Why/How
目标检测中的Anchor

2.1 yolov5默认锚定框

yolov5中预先设定了一下锚定框,这些锚框是针对coco数据集的,其他目标检测也适用,可以在models/yolov5.文件中查看,例如如图所示,这些框针对的图片大小是640640。这是默认的anchor大小。需要注意的是在目标检测任务中,一般使用大特征图上去检测小目标,因为大特征图含有更多小目标信息,因此大特征图上的anchor数值通常设置为小数值,小特征图检测大目标,因此小特征图上anchor数值设置较大。
在这里插入图片描述

2.2 yolov5自动锚框

在yolov5 中自动锚定框选项,训练开始前,会自动计算数据集标注信息针对默认锚定框的最佳召回率,当最佳召回率大于等于0.98时,则不需要更新锚定框;如果最佳召回率小于0.98,则需要重新计算符合此数据集的锚定框。
在parse_opt设置了默认自动计算锚框选项,如果不想自动计算,可以设置这个,建议不要改动。
在这里插入图片描述
在train.py中设置检查锚框是否符合要求,主要使用的函数是check_anchor。
在这里插入图片描述
check_anchor函数的流程大概是:先判断锚框是否符合要求(判断条件bpr / aat,大于0.98就不会更新),然后利用k-mean聚类更新锚框。
在这里插入图片描述

3. 超参数进化——遗传算法调优(GA)

3.1 what是GA

遗传算法是利用种群搜索技术将种群作为一组问题解,通过对当前种群施加类似生物遗传环境因素的选择、交叉、变异等一系列的遗传操作来产生新一代的种群,并逐步使种群优化到包含近似最优解的状态。

3.2 why用GA

遗传算法调优能够求出优化问题的全局最优解,优化结果与初始条件无关,算法独立于求解域,具有较强的鲁棒性,适合于求解复杂的优化问题,应用较为广泛。

3.3 yolov5超参数进化

yolov5使用遗传超参数进化,提供的默认参数是通过在COCO数据集上使用超参数进化得来的。由于超参数进化会耗费大量的资源和时间,如果默认参数训练出来的结果能满足你的使用,使用默认参数是不很nice的选择。yolov5/data/hyp.scratch-*.yaml有三个文件共大家选择,这里使用hyp.scratch-low.yaml
在这里插入图片描述

train.p文件中parse_opt函数可以设置是否经行超参数优化。
在这里插入图片描述

超参数进化开始,使用fitness寻求最优化值。
在这里插入图片描述

4. 冻结训练——Freeze training

4.1 what是冻结训练

冻结训练是迁移学习常用的方法,当我们在使用数据量不足的情况下,通常我们会选择公共数据集提供权重作为预训练权重,我们知道网络的backbone主要是用来提取特征用的,一般大型数据集训练好的权重主干特征提取能力是比较强的,这个时候我们只需要冻结主干网络,fine-tune后面层就可以了,不需要从头开始训练,大大减少了实践而且还提高了性能。

4.2 how弄冻结训练

冻结训练的优势不言而喻了,这里简单的提一下冻结训练的步骤好了,通常的做法是:
1.定义一个冻结层, 冻结之前的学习率和bs可以设置大一点。
2.设置不更新权重param.requires_grad = False
整个过程如下

# 冻结阶段训练参数,learning_rate和batch_size可以设置大一点
Freeze_Epoch        = 100
Freeze_batch_size   = 32
Freeze_lr           = 1e-3
# 解冻阶段训练参数,learning_rate和batch_size设置小一点
UnFreeze_Epoch      = 100
Unfreeze_batch_size = 16
Unfreeze_lr         = 1e-4
# 可以加一个变量控制是否进行冻结训练
Freeze_Train        = True
# 冻结一部分进行训练
batch_size  = Freeze_batch_size
lr          = Freeze_lr
start_epoch = Init_Epoch
end_epoch   = Freeze_Epoch
if Freeze_Train:
	for param in model.backbone.parameters():
	param.requires_grad = False
# 解冻后训练
batch_size  = Unfreeze_batch_size
lr          = Unfreeze_lr
start_epoch = Freeze_Epoch
end_epoch   = UnFreeze_Epoch
if Freeze_Train:
	for param in model.backbone.parameters():
	param.requires_grad = True

4.3 yolov5冻结训练

yolov5的train.py文件中提供了冻结训练选项,在parse_opt函数中
在这里插入图片描述
yolov5s.yaml文件中可以查看到0-9层是backbone,因此在设置冻结层的时候注意不能超过9
在这里插入图片描述
冻结训练开始部分代码
在这里插入图片描述
这里提yolov5冻结效果查看的网站Freezing Layers in YOLOv5

5. 多尺度训练——multi-scale training

5.1 what是multi-scale training

多尺度训练在比赛中经常可以看到他身影,是被证明了有效提高性能的方式。输入图片的尺寸对检测模型的性能影响很大,在基础网络部分常常会生成比原图小数十倍的特征图,导致小物体的特征描述不容易被检测网络捕捉。通过输入更大、更多尺寸的图片进行训练,能够在一定程度上提高检测模型对物体大小的鲁棒性。
知乎这里有个讨论:目标检测中的多尺度训练/测试

多尺度训练是指设置几种不同的图片输入尺度,训练时每隔一定iterations随机选取一种尺度训练。这样训练出来的模型鲁棒性强,其可以接受任意大小的图片作为输入,使用尺度小的图片测试速度会快些。
先了解更多多尺度训练/测试的方法可以查看这篇文章目标检测中的多尺度检测方法

5.2 yolov5多尺度训练

在train.py文件中提供了多尺度训练的选项
在这里插入图片描述
在train.py文件这里是多尺度训练开始的位置
在这里插入图片描述

6. 加权图像策略

6.1 图像加权策略

图像加权策略可以解决样本不平衡的,具体操作步骤图下:
根据样本种类分布使用图像调用频率不同的方法解决。
1、读取训练样本中的GT,保存为一个列表;
2、计算训练样本列表中不同类别个数,然后给每个类别按相应目标框数的倒数赋值,数目越多的种类权重越小,形成按种类的分布直方图;
3、对于训练数据列表,训练时按照类别权重筛选出每类的图像作为训练数据。使用random.choice(population, weights=None, *, cum_weights=None, k=1)更改训练图像索引,可达到样本均衡的效果。

6.2 yolov5图像加权策略

在yolov5中的train.py文件中存在着图像加权策略选项,如函数parse_opt
在这里插入图片描述
在train.py文件训练部分,这个位置开始使用图像加权
在这里插入图片描述
获取类别权重的函数如下
在这里插入图片描述

7. 矩形推理——Rectangular Inference

在讲矩形推理之前我们需要先弄清楚方形推理,下面通过对比介绍这两个概念

7.1 方形推理 Square Inference

我们知道yolo系列的输入都是固定大小416*416,因为下采样32倍的原因,因此必须是32的倍数。但是我们输入的图片大小不一致,因此在输入网络之前需要统一做一个仿射变换,这个过程就是方形推理。具体操作代码如下:

def cv2_letterbox_image(image, expected_size):
    ih, iw = image.shape[0:2]
    ew, eh = expected_size
    scale = min(eh / ih, ew / iw)
    nh = int(ih * scale)
    nw = int(iw * scale)
    image = cv2.resize(image, (nw, nh), interpolation=cv2.INTER_CUBIC)
    top = (eh - nh) // 2
    bottom = eh - nh - top
    left = (ew - nw) // 2
    right = ew - nw - left
    new_img = cv2.copyMakeBorder(image, top, bottom, left, right, cv2.BORDER_CONSTANT)
    return new_img

效果
在这里插入图片描述
我们可以看到经过这种填充之后存在很多冗余信息,填充的部分太多,不利于我们的预测,提高了推理时间。Rectangular Training思路就是想要去掉这些冗余的部分,减少推理时间。下面先对比一下这两种推理方法的时间吧。

python3 detect.py  # 416 square inference
# 方形推理
Using CPU
image 1/2 data/samples/bus.jpg: 416x416 1 handbags, 3 persons, 1 buss, Done. (0.999s)
image 2/2 data/samples/zidane.jpg: 416x416 1 ties, 2 persons, Done. (1.008s)

python3 detect.py  # 416 rectangular inference
# 矩形推理
Using CPU
image 1/2 data/samples/bus.jpg: 416x320 1 handbags, 3 persons, 1 buss, Done. (0.767s)
image 2/2 data/samples/zidane.jpg: 256x416 1 ties, 2 persons, Done. (0.632s)

7.2 矩形推理——Rectangular Inference

可以看出矩形推理时间可以减少37%,那他具体是怎么做到的呢,其实思路很简单就是就是保证长边是416,短边是32的倍数,但是倍数可以不相同。比如场边是416/32=13,短边可以是320/32=10、256/32=8。

代码

# Rectangular Training
        if self.rect:
            # Sort by aspect ratio
            s = self.shapes  # wh
            ar = s[:, 1] / s[:, 0]  # aspect ratio
            irect = ar.argsort()
            self.im_files = [self.im_files[i] for i in irect]
            self.label_files = [self.label_files[i] for i in irect]
            self.labels = [self.labels[i] for i in irect]
            self.shapes = s[irect]  # wh
            ar = ar[irect]

            # Set training image shapes
            shapes = [[1, 1]] * nb
            for i in range(nb):
                ari = ar[bi == i]
                mini, maxi = ari.min(), ari.max()
                if maxi < 1:
                    shapes[i] = [maxi, 1]
                elif mini > 1:
                    shapes[i] = [1, 1 / mini]

            self.batch_shapes = np.ceil(np.array(shapes) * img_size / stride + pad).astype(np.int) * stride

效果
在这里插入图片描述

7.3 yolov5矩形训练

在train,py文件中有使用矩形训练的选项,默认情况是不适用矩形训练
在这里插入图片描述
开启矩形训练之后,可以在数据加载这里看到
在这里插入图片描述
在对数据预处理的文件里面,这个位置,有矩形处理的代码
在这里插入图片描述

8. 标签平滑

8.1 标签平滑——label smooth

在训练样本中,我们并不能保证所有sample都标注正确,如果某个样本标注错误,就可能产生负面印象,如果我们有办法“告诉”模型,样本的标签不一定正确,那么训练出来的模型对于少量的样本错误就会有“免疫力”采用随机化的标签作为训练数据时,损失函数有1-ε的概率与上面的式子相同,比如说告诉模型只有0.95概率是那个标签。

8.2 操作代码

在这里给大家两种标签平滑的方式

# label smoothing: 两个极端的值变得不那么极端
# 在训练数据太少, 不足表征所有样本的特征的情况下,会导致过拟合
# 用的是平滑后的label和softmax后的值做交叉熵,也就是给标签乘上一个平滑系数

import numpy as np
import tensorflow as tf

def SmoothOneHot(labels, classes, smooth=0.0):
    """
    if smoothing == 0, it's one-hot method
    if 0 < smoothing < 1, it's smooth method
    """
    assert 0 <= smooth < 1
    labels *= 1- smooth
    labels += smooth/labels.shape[1]

    return labels

if __name__ == "__main__":
    out = np.array([[4.0, 5.0, 10.0], [1.0, 5.0, 4.0], [1.0, 15.0, 4.0]])
    y   = np.array([[0.0, 0.0, 1.00], [0.0, 1.0, 0.0], [0.0, 1.00, 0.0]])

    res1 = tf.losses.softmax_cross_entropy(onehot_labels=y, logits=out, label_smoothing=0)
    print("不使用标签平滑:", tf.Session().run(res1))

    res2 = tf.losses.softmax_cross_entropy(onehot_labels=y, logits=out, label_smoothing=0.001)
    print("使用标签平滑:", tf.Session().run(res2))

    # 自定义标签平滑
    # new_onehot_labels = onehot_labels * (1 - label_smoothing) + label_smoothing / num_classes
    new_onehot_labels = y * (1 - 0.001) + 0.001 / 3
    print("原始标签:", y)
    print("平滑处理后的标签:", new_onehot_labels)
    res3 = tf.losses.softmax_cross_entropy(onehot_labels=new_onehot_labels, logits=out, label_smoothing=0)
    print(tf.Session().run(res3))

8.3 yolov5标签平滑

在train.py文件中的这个位置可以根据自己的数据集分布特点设置平滑系数。
在这里插入图片描述
------------------------最近太忙了,剩下的后面在补--------------------------------------

9. 非极大值抑制——NMS

9.1 what是NMS

在目标检测过程中,同一目标会生成多个候选框,这些候选框之间是大量重叠的,要在大量的候选框之间最适合目标的框,就需要用到非极大抑制

9.2 how是NMS

非极大值抑制的操作步骤如下:

  1. 首先置信度得分进行排序
  2. 找到最大概率的候选框
  3. 计算置信度最高的边界框与其它候选框的IoU。
  4. 删除IoU大于阈值的边界框
  5. 重复上述过程,直至边界框列表为空。

下面提供两个python代码,帮助大家理解这个过程

import cv2
import numpy as np

"""
    Non-max Suppression Algorithm
    @param list  Object candidate bounding boxes
    @param list  Confidence score of bounding boxes
    @param float IoU threshold
    @return Rest boxes after nms operation
"""
def nms(bounding_boxes, confidence_score, threshold):
    # If no bounding boxes, return empty list
    if len(bounding_boxes) == 0:
        return [], []

    # Bounding boxes
    boxes = np.array(bounding_boxes)

    # coordinates of bounding boxes
    start_x = boxes[:, 0]
    start_y = boxes[:, 1]
    end_x = boxes[:, 2]
    end_y = boxes[:, 3]

    # Confidence scores of bounding boxes
    score = np.array(confidence_score)

    # Picked bounding boxes
    picked_boxes = []
    picked_score = []

    # Compute areas of bounding boxes
    areas = (end_x - start_x + 1) * (end_y - start_y + 1)

    # Sort by confidence score of bounding boxes
    order = np.argsort(score)

    # Iterate bounding boxes
    while order.size > 0:
        # The index of largest confidence score
        index = order[-1]

        # Pick the bounding box with largest confidence score
        picked_boxes.append(bounding_boxes[index])
        picked_score.append(confidence_score[index])

        # Compute ordinates of intersection-over-union(IOU)
        x1 = np.maximum(start_x[index], start_x[order[:-1]])
        x2 = np.minimum(end_x[index], end_x[order[:-1]])
        y1 = np.maximum(start_y[index], start_y[order[:-1]])
        y2 = np.minimum(end_y[index], end_y[order[:-1]])

        # Compute areas of intersection-over-union
        w = np.maximum(0.0, x2 - x1 + 1)
        h = np.maximum(0.0, y2 - y1 + 1)
        intersection = w * h

        # Compute the ratio between intersection and union
        ratio = intersection / (areas[index] + areas[order[:-1]] - intersection)

        left = np.where(ratio < threshold)
        order = order[left]

    return picked_boxes, picked_score


# Image name
image_name = './li.jpg'

# Bounding boxes
bounding_boxes = [(187, 82, 337, 317), (150, 67, 305, 282), (246, 121, 368, 304)]
confidence_score = [0.9, 0.75, 0.8]

# Read image
image = cv2.imread(image_name)

# Copy image as original
org = image.copy()
cv2.imshow('Original', org)
# Draw parameters
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 1
thickness = 2

# IoU threshold
threshold = 0.4

# Draw bounding boxes and confidence score
for (start_x, start_y, end_x, end_y), confidence in zip(bounding_boxes, confidence_score):
    (w, h), baseline = cv2.getTextSize(str(confidence), font, font_scale, thickness)
    cv2.rectangle(org, (start_x, start_y - (2 * baseline + 5)), (start_x + w, start_y), (0, 255, 255), -1)
    cv2.rectangle(org, (start_x, start_y), (end_x, end_y), (0, 255, 255), 2)
    cv2.putText(org, str(confidence), (start_x, start_y), font, font_scale, (0, 0, 0), thickness)

# Run non-max suppression algorithm
picked_boxes, picked_score = nms(bounding_boxes, confidence_score, threshold)

# Draw bounding boxes and confidence score after non-maximum supression
for (start_x, start_y, end_x, end_y), confidence in zip(picked_boxes, picked_score):
    (w, h), baseline = cv2.getTextSize(str(confidence), font, font_scale, thickness)
    cv2.rectangle(image, (start_x, start_y - (2 * baseline + 5)), (start_x + w, start_y), (0, 255, 255), -1)
    cv2.rectangle(image, (start_x, start_y), (end_x, end_y), (0, 255, 255), 2)
    cv2.putText(image, str(confidence), (start_x, start_y), font, font_scale, (0, 0, 0), thickness)
print(org)

# Show image
cv2.imshow('Original', org)
cv2.imshow('NMS', image)
cv2.waitKey(0)

运行结果
在这里插入图片描述

代码2

import numpy as np

def NMS(dets, thresh):
    #x1、y1、x2、y2、以及score赋值
    # (x1、y1)(x2、y2)为box的左上和右下角标
    x1 = dets[:, 0]
    y1 = dets[:, 1]
    x2 = dets[:, 2]
    y2 = dets[:, 3]
    scores = dets[:, 4]

    #每一个候选框的面积
    areas = (x2 - x1 + 1) * (y2 - y1 + 1)
    #order是按照score降序排序的,得到的是排序的本来的索引,不是排完序的原数组
    order = scores.argsort()[::-1]
    # ::-1表示逆序

    temp = []
    while order.size > 0:
        i = order[0]
        temp.append(i)
        #计算当前概率最大矩形框与其他矩形框的相交框的坐标
        # 由于numpy的broadcast机制,得到的是向量
        xx1 = np.maximum(x1[i], x1[order[1:]])
        yy1 = np.minimum(y1[i], y1[order[1:]])
        xx2 = np.minimum(x2[i], x2[order[1:]])
        yy2 = np.maximum(y2[i], y2[order[1:]])

        #计算相交框的面积,注意矩形框不相交时w或h算出来会是负数,需要用0代替
        w = np.maximum(0.0, xx2 - xx1 + 1)
        h = np.maximum(0.0, yy2 - yy1 + 1)
        inter = w * h
        #计算重叠度IoU
        ovr = inter / (areas[i] + areas[order[1:]] - inter)

        #找到重叠度不高于阈值的矩形框索引
        inds = np.where(ovr <= thresh)[0]

        #将order序列更新,由于前面得到的矩形框索引要比矩形框在原order序列中的索引小1,所以要把这个1加回来
        order = order[inds + 1]
    return temp


if __name__ == "__main__":
    dets = np.array([[310, 30, 420, 5, 0.6],
                     [20, 20, 240, 210, 1],
                     [70, 50, 260, 220, 0.8],
                     [400, 280, 560, 360, 0.7]])
    print(dets[:, 0])
    # 设置阈值
    thresh = 0.4
    keep_dets = NMS(dets, thresh)
    # # 打印留下的框的索引
    print(keep_dets)
    # # 打印留下的框的信息
    # print(dets[keep_dets])

10. 早停止

11. 分布式训练

12. 跨卡同步BN

13. 断点训练

14.断点训练

猜你喜欢

转载自blog.csdn.net/CharmsLUO/article/details/123577851