从验证训练角度解读YOLOV5的源码:程序是如何得到最后输出的maps,正确率等信息的?

前言

前两篇文章合在一起完成的是在一个batch的图片一起训练一次时计算机内部发生的变化。链接如下:从反向传播角度解读YOLOV5源码:如何从改变优化器,损失函数计算方式等角度提升模型的性能?_vindicater的博客-CSDN博客

从前向传播角度解读YOLOV5的源码:如何修改网络结构或增添多余的层?_vindicater的博客-CSDN博客 但是事实上,我们需要在训练中观察训练的效果以知晓是否需要继续进行训练,训练是否已经达到了峰值。且我们需要总体的loss或者是判断的准确率这样的一个量化的标准以评价训练的效果。这就是本文的目的,对于计算上述这种有助于我们做出判断的指标的代码进行分析。由于这个部分也没有太多作为调试修改的用户能提升的,这里依旧采用整体介绍梳理网络结构和运行过程的方式进行阐释。

模型是如何计算指标值的?(代码详细分析)

直接代码:

if not noval or final_epoch:  # Calculate mAP
    results, maps, _ = validate.run(data_dict,
                        batch_size=batch_size // WORLD_SIZE * 2,
                        imgsz=imgsz,
                        half=amp,
                        model=ema.ema,
                        single_cls=single_cls,
                        dataloader=val_loader,
                        save_dir=save_dir,
                        plots=False,
                        callbacks=callbacks,
                        compute_loss=compute_loss)

当不是最后一个epoch的时候,调用validate中的run函数得到mAp值等结果。

于是本节将主要分析validate.run()

Step1:准备工作

由于是从training传入进来了模型modeldataloader,所以准备工作只要加载进来即可

参数内容:

Names:从model中传入的种类的名称,以dict的形式保存

以下评价标准全部定为0

tp, fp, p, r, f1, ap, ap_class

预处理:

(0.1)读入图片:
pbar = tqdm(dataloader, desc=s, bar_format=TQDM_BAR_FORMAT)
for batch_i, (im, targets, paths, shapes) in enumerate(pbar):

类似于之前读入训练集的时候,val_loader的内容通过tqdm的方式读入pbar之中,然后用enumerate的方式进行一一遍历。

(0.2)对于输入图像进行处理
with dt[0]:
    if cuda:
        im = im.to(device, non_blocking=True)
        targets = targets.to(device)
    im = im.half() if half else im.float()  # uint8 to fp16/32
    im /= 255  # 0 - 255 to 0.0 - 1.0
    nb, _, height, width = im.shape

1.把读入的32张图片挂上cuda加速

2.选择是否调整成半精度

3.图片对应的矩阵归一化

4.读取一批图片的数量,图片的长宽

(1)利用model进行训练
pred = model(imgs)  # forward

forward部分的代码

preds, train_out = model(im) if compute_loss else (model(im, augment=augment), None)

val.py中的代码

两者为何不同?
差别定位:

区别出现在了yolo.py中的Detect类的forward函数之中,而forward层也是模型运算中的最后一层。 

两者相同的内容:
z = []  # inference output
for i in range(self.nl):
    x[i] = self.m[i](x[i])  # conv
    bs, _, ny, nx = x[i].shape  # x(bs,255,20,20) to x(bs,3,20,20,85)
    x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()  # 按照上面的说法进行顺序的交换

 

detect层的具体结构

Detect层的目的是把前面不同的卷积输出结果变成255层,也就是3*4+1+80),那么在以上的同样的部分之中,完成的是把255层拆成85*3形成三个anchor框和85个概率。

维度变化:

32,255,64,64)--》(32,3,85,64,64)

在val.py中多余的内容:        
if not self.training:
    else:
        xy, wh, conf = x[i].sigmoid().split((2, 2, self.nc + 1), 4)
        xy = (xy * 2 + self.grid[i]) * self.stride[i]  # xy
        wh = (wh * 2) ** 2 * self.anchor_grid[i]  # wh
        y = torch.cat((xy, wh, conf), 4)
    z.append(y.view(bs, self.na * nx * ny, self.no))
不同点的目的:

Step1:x[i]中分离出来xy,wh,conf参数,其维度分别是:2,2,self.nc+1

Step2:xy,wh做出一些调整:

为何要调整?

矩阵中得到的结果是相对值,调整后变成绝对位置:

如何调整?

对于xy来说:将得到的xy的偏差值乘2之后加上左上角的坐标位置得到一个新的xy数组

对于wh来说:把得到的wh*2之后进行平方操作,然后把得到的结果乘上anchor框的size得到具体预测框的size

最后把得到的两个和置信度和种类拼接在一起形成y

Step3:输出调整维度后的y数组:

(32,3,85,64,64)--》(32,3*64*64,85

最终输出结果:

二维数组:

 (torch.cat(z, 1), x)
返回内容:

preds:经过处理后的预测框xyhw置信度和种类的概率

Train_out:原样保存模型处理的结果

2.1)利用train_out得到loss
with dt[1]:
    preds, train_out = model(im) if compute_loss

类似于之前在backward模块中的操作,计算得出损失值

注意到这里的输入内容是train_out,和之前内容中的preds都是没有处理过的内容。所以客观上和之前的所有行为都是一模一样的这里不在赘述

2.2)对于preds进行NMS非极大值抑制
with dt[2]:
    preds = non_max_suppression(preds,
                                conf_thres,
                                iou_thres,
                                labels=lb,
                                multi_label=True,
                                agnostic=single_cls,
                                max_det=max_det)

Step2:根据预处理和预先准备的内容做出判断

(1.1):预测集的准备

for si, pred in enumerate(preds):
    labels = targets[targets[:, 0] == si, 1:]
    nl, npr = labels.shape[0], pred.shape[0]  # number of labels, predictions
    path, shape = Path(paths[si]), shapes[si][0]
    correct = torch.zeros(npr, niou, dtype=torch.bool, device=device)  # init
    seen += 1

1.Pred:通过enumerate的方式读取每一张图片的预测框

2.Labels:从targets中获取该图片对应的标注框信息

3.nl,npr:上面两个的计数

4.Correct:初始化的0矩阵:行数是预测框的总数,列数是在每一个IOU加以判断的阈值,内容物是bool值,用来存放在某个IOU下这个预测是否正确的判断结果

5.通过scale_boxes函数得到在原图上的预测框信息predn

(1.2):label集合的准备

tbox = xywh2xyxy(labels[:, 1:5])  # target boxes label中的是中心点
scale_boxes(im[si].shape[1:], tbox, shape, shapes[si][1])  # native-space labels
labelsn = torch.cat((labels[:, 0:1], tbox), 1)  # native-space labels

1.使用xywh2xyxy的方式将标注框的信息转化成tbox:现在有的是两个对角的信息

2.利用scale_boxes函数把tbox的信息处理到原图上去

3.label中的batch中图片的信息和处理后得到的tbox拼接起来产生labelsn,目的是为了和predn形式相同

(2):比较得出correct矩阵且存入stats之中

correct = process_batch(predn, labelsn, iouv)

利用函数:process_batch(),位置:val.py

stats.append((correct, pred[:, 4], pred[:, 5], labels[:, 0]))

分别存入的元素是:

Correct:判断矩阵

Pred[:,4]:置信度

Pred[:,5]:预测出来的class

Labels[:,0]:标注框对应的class

Step3:保存成果

1.输出标注框图和预测框图

对于前三张图来说,绘制出含有标注框的图和绘制出含有预测框的图

plot_images(im, targets, paths, save_dir / f'val_batch{batch_i}_labels.jpg', names)
plot_images(im, output_to_target(preds), paths, save_dir / f'val_batch{batch_i}_pred.jpg', names)  # pred

2.计算出具体的计算结果:

stats = [torch.cat(x, 0).cpu().numpy() for x in zip(*stats)]  # to numpy
if len(stats) and stats[0].any():
    tp, fp, p, r, f1, ap, ap_class = ap_per_class(*stats, plot=plots, save_dir=save_dir, names=names)
    ap50, ap = ap[:, 0], ap.mean(1)  # AP@0.5, AP@0.5:0.95
    mp, mr, map50, map = p.mean(), r.mean(), ap50.mean(), ap.mean()
nt = np.bincount(stats[3].astype(int), minlength=nc)  # number of targets per class

1.stats的所有内容从一共5000个元素的list转换成二维数组:数组的行数是所有预测框的数量,列数是IOU分成的段,如0.5-0.9510个段

2.通过函数:ap_per_class()来获取所需要输出的各个参数

3.打印输出结果并返回

maps = np.zeros(nc) + map
for i, c in enumerate(ap_class):
    maps[c] = ap[i]
return (mp, mr, map50, map, *(loss.cpu() / len(dataloader)).tolist()), maps, t

返回了上述需要的参数

至此:validate.run()部分结束,输出内容:评价指标

Step4:收尾,保存信息

Section1:更新最好的mAP

fitness函数处理过之后和已经存着的best_fitness进行比较,如果更大就加以覆盖。最后使用callback函数将结果写进results.csv文件之中

fi = fitness(np.array(results).reshape(1, -1))  # weighted combination of [P, R, mAP@.5, mAP@.5-.95]
stop = stopper(epoch=epoch, fitness=fi)  # early stop check
if fi > best_fitness:
    best_fitness = fi
log_vals = list(mloss) + list(results) + lr

Section2:保存模型

1.保存到last.pt之中

2.如果是最好的,保存到best.pt之中

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')
del ckpt
callbacks.run('on_model_save', last, epoch, final_epoch, best_fitness, fi)

至此,一整个epoch训练完毕

整个train.py究竟得到了一些什么?(训练结束后的总返回值)

results, _, _ = validate.run(
    data_dict,
    batch_size=batch_size // WORLD_SIZE * 2,
    imgsz=imgsz,
    model=attempt_load(f, device).half(),
    iou_thres=0.65 if is_coco else 0.60,  # best pycocotools at iou 0.65
    single_cls=single_cls,
    dataloader=val_loader,
    save_dir=save_dir,
    save_json=is_coco,
    verbose=True,
    plots=plots,
    callbacks=callbacks,
    compute_loss=compute_loss)  # val best model with plots

利用best.pt的模型来重新跑一遍测试集得到最好的结果设为results,将results返回

获得的最主要成果:Last.pt、train.pt

总结:

这一篇文章分析研究的是一个epoch结束之后直到第二个epoch开始之前的过程中进行评价的过程中程序发生的变化。在此过程中我们得到了一些评判标准的数据:比如说mAp的值和总损失值。并且在模型的最后我们保存了训练下来最好的模型对应的参数,给出了最好条件下的损失值精确度之类的信息。至此整个train.py已经被切分成了以上的几篇文章以帮助大家理解这个最庞大也是最核心的源代码文件。如果觉得本系列对于您的学习起到了一些帮助,请点赞关注吧ovo

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
yolov5源码是一个用于目标检测的项目,以下是对源码的一些解读: 1. 项目目录结构:源码包含了data、models、utils、train、test等文件夹。其中,data文件夹包含了用于配置数据集的yaml文件和下载数据集的shell命令;models文件夹包含了模型的定义和相关操作;utils文件夹包含了一些辅助函数和工具;train文件夹包含了训练相关的代码;test文件夹包含了测试相关的代码。\[1\] 2. 数据集配置文件:在data文件夹中,可以找到yaml文件,用于配置不同的数据集,如coco、coco128、pascalvoc等。这些配置文件定义了数据集的路径、类别信息、图像大小等。\[1\] 3. 超参数微调配置文件:在data文件夹中,还有一个hyps文件夹,其中的yaml文件用于微调超参数,以优化模型的性能。\[1\] 4. 脚本文件:在scripts文件夹中,存放着下载数据集和权重的shell脚本,可以通过运行这些脚本来获取所需的数据集和权重文件。\[1\] 5. 项目解读:对于项目的解读,可以从项目目录结构开始,了解每个文件的作用和功能。可以先从最基础的文件开始,逐步深入理解代码。同时,可以参考作者提供的英文文档进行解读,也可以参考其他相关资料和教程。\[2\] 总之,yolov5源码是一个用于目标检测的项目,包含了数据集配置、模型定义、训练和测试等功能。通过对源码解读,可以深入理解该项目的实现原理和使用方法。\[1\]\[2\] #### 引用[.reference_title] - *1* [YOLOV5源码的详细解读](https://blog.csdn.net/BGMcat/article/details/120930016)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [YOLOv5源码逐行超详细注释与解读(1)——项目目录结构解析](https://blog.csdn.net/weixin_43334693/article/details/129356033)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值