【yolov5】loss.py源码理解

本文详细介绍了YoloV5中loss.py的build_targets函数,该函数通过两步操作扩充正样本,缓解正负样本不均衡问题。首先,针对每个目标匹配最合适的anchor,然后根据目标框的偏移量选取邻域的2个网格作为额外正样本。作者通过自己的代码实现了这一过程,加深了对YoloV5目标检测算法的理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

yolov5的loss.py中的build_targets函数中有两处扩充正样本的地方:

  1. 因为anchor有3个,所以将targets扩充成3份,每一份共享一个anchor;假设一共有20个targets目标框,则将目标数扩充至[3, 20],共60个目标;第一份的20个目标与第一个anchor匹配,第二份的20个目标与第二个anchor匹配,第三份的20个目标与第三个anchor匹配,那么会有一部分目标没有匹配上(目标框与anchor的宽比或高比超出阈值),则60个targets里可能只有30个targets匹配成功,剩余的targets过滤掉;
    因此,可以看到原先的20个正样本被扩充到30个,起到了扩充正样本的作用;当然如果阈值(anchor_t)卡的太严,也可能会有大量的目标框被过滤掉;
  2. yolov5考虑到下采样的过程中可能导致中心点偏移误差,因此根据targets的偏移量选择邻域的2个网格(4邻域中选2个)也作为正样本,这个操作可将正样本扩充到原来的3倍,即第一步的30个目标又被扩充至90个;

可以看到,经过两处操作,最初的20个目标被扩充至90个,缓解了正负样本不均衡的问题;

yolov5的代码晦涩难懂,看了好久才了解其中思路,用自己的代码复现了一遍:

    def build_targets(self, preds, targets):
        '''
        :param preds:       list(Tensor[b, 3, h, w, 85],...)
        :param targets:     Tensor[N, 6]  img_indx, cls, x, y, w, h
        :return:
            tcls            list(Tensor[N1], Tensor[N2], Tensor[N3])   对应三个输出层,每层的targets的类别
            tbox            list(Tensor[N1, 4],Tensor[N2, 4],Tensor [N3, 4])		三个输出层,每层的targets目标框的尺寸(x, y, w, h)
            indices         list(tuple(Tensor[N1], Tensor[N1],Tensor [N1],Tensor [N1]), 
            							tuple(Tensor[N2], Tensor[N2],Tensor [N2],Tensor [N2]),
            							tuple(Tensor[N3], Tensor[N3],Tensor [N3],Tensor [N3]))		三个输出层,每层的targets目标框的信息(b, a, gj, gi)
            anch            list(Tensor[N1, 2],Tensor [N2, 2], Tensor[N3, 2])		三个输出层,每层的targets目标框对应的anchor
        '''
        nt, na = targets.shape[0], self.anchors.shape[1]
        tcls, tbox, indices, anch = [], [], [], []
        device = targets.device
        targets = targets.repeat(3, 1, 1)   # [3, N, 6]
        anchor_idx = torch.arange(3, device=device).view(3, -1).repeat(1, nt)      # [3, N]
        targets = torch.cat((targets, anchor_idx[..., None]), 2)    # [3, N, 7]
        gain = torch.ones(7, device=device)
        for i, pred in enumerate(preds):
            h, w = pred.shape[2], pred.shape[3]
            anchor = self.anchors[i]        # [3, 2]
            if nt:
                '''为每个target匹配合适的anchor'''
                gain[2:6] = torch.tensor([w, h, w, h], device=device)   # [7]
                t_pixel = targets * gain    # targets的pixel坐标[3, N, 7]
                ratio = t_pixel[..., 4:6] / anchor[:, None]   # [3, N, 2]/[3, 1, 2] = [3, N, 2]
                j = torch.max(ratio, 1/ratio).max(2)[0] < self.hyp['anchor_t']        # [3, N]
                t_pixel = t_pixel[j]    # [N1, 6]

                '''为每个target扩增正样本'''
                g = 0.5
                gxy = t_pixel[..., 2:4]      # [N1, 2]
                gxy_t = torch.tensor([w, h], device=device) - gxy     # [N1, 2]
                i, j = ((gxy % 1 < g) & (gxy > 1)).T
                l, k = ((gxy_t % 1 < g) & (gxy_t > 1)).T

                t = torch.cat((t_pixel, t_pixel[i], t_pixel[j], t_pixel[l], t_pixel[k]), dim=0)               # [3*N1, 6]

                t_left = t_pixel[i][..., 2:4] + torch.tensor([-1, 0], device=device)    # [n1, 2]
                t_right = t_pixel[l][..., 2:4] + torch.tensor([1, 0], device=device)    # [n2, 2]
                t_up = t_pixel[j][..., 2:4] + torch.tensor([0, -1], device=device)      # [N1-n1, 2]
                t_down = t_pixel[k][..., 2:4] + torch.tensor([0, 1], device=device)     # [N1-n2, 2]

                tij = torch.cat((gxy, t_left, t_up, t_right, t_down), dim=0).long()     # [3*N1, 2]
            else:
                t = targets[0]
                tij = t[:, 2:4].long()

            ai = t[:, 6].long()            # [3×N1]
            gwh = t[:, 4:6]
            gxy_offset = t[:, 2:4] - tij

            tcls.append(t[:, 1].long())
            tbox.append(torch.cat((gxy_offset, gwh), 1))
            anch.append(anchor[ai])
            indices.append((t[:, 0].long(), t[:, 6].long(), tij[:, 1].long().clamp(0, h-1), tij[:, 0].long().clamp(0, w-1)))
        return tcls, tbox, indices, anch
### YOLOv5 `train.py` 源码解析 #### 初始化与导入模块 在YOLOv5的`train.py`文件中,首先会引入必要的库和自定义模块。这些模块不仅包含了PyTorch等深度学习框架的基础组件,还涵盖了项目特有的工具函数。 ```python import argparse, math, os, random, time from pathlib import Path import torch.distributed as dist from utils.general import increment_path, check_git_status, colorstr ``` 这部分代码负责设置环境变量、加载配置文件以及初始化分布式训练所需的参数[^1]。 #### 参数获取与验证 通过命令行接收用户输入的各种超参选项,并对其进行合法性检查。这一步骤确保了后续操作能够基于有效的设定展开: ```python parser = argparse.ArgumentParser() parser.add_argument('--weights', type=str, default='yolov5s.pt') # 更多参数... opt = parser.parse_args() check_requirements(exclude=('tensorboard', 'thop')) ``` 此阶段还会处理恢复训练逻辑,即当存在未完成的任务时可以从断点继续执行而不是重新开始整个过程[^2]。 #### 数据集准备 构建数据管道对于任何机器学习任务来说都是至关重要的环节之一,在这里主要涉及到了如何读取标注信息并将其转换成适合网络消耗的形式。具体实现上采用了Dataset类来封装图像及其对应的标签路径列表;而DataLoader则用来批量采样样本供迭代器遍历使用。 ```python nc = int(data_dict['nc']) # number of classes names = data_dict['names'] # class names assert len(names) == nc, f'The length of the names {len(names)} is not equal to the number of classes {nc}' ``` 上述片段展示了从配置字典中提取类别数量及名称的过程,同时进行了简单的错误检测以防止不一致的情况发生。 #### 训练循环主体 进入核心部分——实际的优化流程。在此期间,程序会不断调整权重直至满足收敛条件为止。为了提升泛化能力,除了常规前向传播外还包括了一些额外机制比如EMA(Exponential Moving Average),它通过对历史版本加权平均的方式平滑掉了偶然波动带来的影响从而达到稳定效果的目的。 ```python ema.updates = epoch * nbs / total_batch_size # update EMA parameter count for i, (imgs, targets, paths, _) in enumerate(dataloader): ni = i + nb * epoch # number integrated batches (since train start) if multi_scale: sz = random.randrange(imgsz * 0.5, imgsz * 1.5 + gs) // gs * gs # resize sf = sz / max(imgs.shape[2:]) # scale factor if sf != 1: ns = [math.ceil(x * sf / gs) * gs for x in imgs.shape[2:]] # new shape (stretched to 32x multiple) imgs = F.interpolate(imgs, size=ns, mode='bilinear', align_corners=False) loss, loss_items = compute_loss(pred, targets.to(device)) ``` 以上摘录体现了动态调整输入尺寸策略的应用场景,同时也揭示了损失计算的具体方式。 #### 结果保存与评估 每当一轮epoch结束之后都会触发一次全面评测动作,此时不仅要记录下当前最佳成绩还要定期导出checkpoint以便日后查阅或部署应用。此外,可视化图表也是不可或缺的一部分,它们有助于直观展示性能变化趋势给开发者参考。 ```python if main_process and opt.save_period > 0 and (epoch % opt.save_period == 0 or epoch == epochs - 1): ckpt = { "model": model.float().state_dict(), "optimizer": optimizer.state_dict(), "best_fitness": best_fitness, "training_results": results_file.read_text(), "epoch": epoch} save_dir.mkdir(parents=True, exist_ok=True) # make dir weights_path = str(save_dir / ('last' if epoch < epochs - 1 else 'best')) + '.pt' torch.save(ckpt, weights_path) ``` 这段代码说明了模型状态持久化的细节,特别是针对最终版和其他周期性快照的区别对待措施。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值