当前位置:   article > 正文

YOLOv9改进策略 | 损失函数篇 | 利用真实边界框损失之MPDIoU助力YOLOv9精度更上一层楼_pred_bboxes_pos = torch.masked_select

pred_bboxes_pos = torch.masked_select

一、本文介绍

Hello,大家好我是Snu77,这里给大家带来的是YOLOv9系列的第一篇改进策略,本文的内容是用了YOLOv9官方集成的MPDIoU损失来给大家进行改进 | MPDIoU需要图片的真实大小,但是官方并没有传递这个参数,在训练的阶段还好因为YOLO系列是固定图片大小训练,但是在训练时的验证确是不固定的所以我们需要将图片的真实大小传递进去但是官方并没有做这个功能,但是它预留了两个参数位可以看出我们这么做是没有任何问题的。

PS:最近群里很多读者都在催更YOLOv9系列(不下于二十人),这里再次讲一下没有更新的原因,一是因为YOLOv9的论文还没有收录,二是目前官方的项目中Bug众多,其次最重要的就是YOLOv9并没有放出其轻量化的版本(这对于众多算力有限的读者是一个很严重的问题),在一个以前的许多机制放在YOLOv9上的效果可能并不好因为V9的各方面比较完美。所以我们的改进策略专栏会从损失函数切入,随着今年更多的定会机制推出我们会将更多真实有效的改进放在YOLOv9上,助力大家发表论文。

专栏地址YOLOv9有效涨点专栏-持续复现各种顶会内容-有效涨点-全网改进最全的专栏  

目录

一、本文介绍

二、MPDIoU介绍

2.1 问题提出

2.2 MPDIoU的提出

2.3 实验验证

三、手把手教你添加MPDIoU

3.1 步骤1

3.2 步骤2 

四、GELAN的MPDIoU教程 

五、全文总结 


二、MPDIoU介绍

2.1 问题提出

问题提出文章指出,在目标检测和实例分割的过程中,传统的边界框回归(BBR)损失函数难以优化预测框和真实框在宽高比相同但具体尺寸不同时的情况,下面是描述现有的边界框回归的方法的计算因素总结(包括GIoU、DIoU、CIoU和EIoU)的计算因素。这些度量方法是用于评估和优化边界框回归模型性能的关键工具。虽然文章没有直接展示下图的内容,但它们包括以下几个方面:

  • GIoU(Generalized IoU):除了传统的IoU(交并比)之外,GIoU还考虑了边界框之间的包含关系和空间分布。

  • DIoU(Distance IoU):在IoU的基础上,DIoU还考虑了边界框中心点之间的距离,以改进对齐和尺度不一致的情况。

  • CIoU(Complete IoU):结合了DIoU的特点,并加入了宽高比的考虑,进一步提高了对边界框的精确度。

  • EIoU(Expected IoU):这是一种更高级的度量方法,考虑了预测边界框与真实边界框之间的预期相似度。

文章提出的MPDIoU是在这些现有度量方法的基础上发展起来的,旨在通过直接最小化预测框和真实框之间的关键点距离,提供一种易于实现的解决方案,用于计算两个轴对齐矩形之间的MPDIoU​


2.2 MPDIoU的提出

MPDIoU的提出为了克服这一挑战,文章提出了一种新的边界框相似度度量方法——MPDIoU(Minimum Point Distance Intersection over Union)。MPDIoU是基于水平矩形的最小点距离来计算的,能够综合考虑重叠区域、中心点距离以及宽度和高度的偏差。

下图展示了两种不同的边界框回归结果情况。其中,绿色框代表真实的边界框而红色框代表预测的边界框。在这两种情况下,传统的损失函数(如GIoU、DIoU、CIoU和EIoU)计算出的损失值是相同的,但是使用MPDIoU方法计算出的损失值却有所不同。这说明传统方法在某些特定情况下可能无法区分不同的预测结果,而MPDIoU能更准确地反映预测框和真实框之间的差异。

这个发现突显了MPDIoU在处理边界框回归问题上的优势,尤其是在区分具有相同宽高比但不同尺寸或位置的边界框时。MPDIoU通过直接计算预测框和真实框之间的关键点距离,提供了更精确的损失度量方法。

这些因素包括如何在训练阶段通过最小化损失函数来使模型预测的边界框接近其真实边界框。具体来说,每个预测的边界框

B_{prd} = \left[ \begin{array}{c} x_{prd} \\ y_{prd} \\ w_{prd} \\ h_{prd} \end{array} \right]

通过最小化以下损失函数来逼近其真实边界框:

B_{gt} = [x_{gt}, y_{gt}, w_{gt}, h_{gt}]^T

L = \min_{\Theta} L(B_{gt}, B_{prd} | \Theta)

其中,B_{gt} 是真实边界框的集合,而 \Theta 是回归深度模型的参数。文章中提出的LMPDIoU损失函数公式为: 

LMPDIoU=1-MPDIoU


2.3 实验验证

实验验证通过在多个数据集(如PASCAL VOC、MS COCO和IIIT5k)上对YOLACT和YOLOv7等模型的训练和测试,文章验证了MPDIoU和LMPDIoU在实际应用中的有效性。实验结果显示,这种新的损失函数在多个方面优于传统的损失函数,尤其是在处理具有相似宽高比但不同尺寸的边界框时。

下面是一些检测效果对比图 

总结:文章通过引入MPDIoU,提供了一种新的视角来优化目标检测中的边界框回归问题,同时通过实验验证了其在提高检测模型准确性方面的有效性。

PS:损失函数都是各种公式,数学推导这方面我能力确实不太行,大家有兴趣的可以自行推到看看。

 


三、手把手教你添加MPDIoU

这里还有一个问题,我们这里改进的是以YOLOv9的代码DualDetect版本进行修改,如果你用的是GELAN版本的那么修改教程都是一样的。

3.1 步骤1

我们第一步需要找到一个文件'utils/loss_tal_dual.py',我们找到这个文件之后,按照找到如下图所示的代码。

下面的样子是修改之后的,大家用我下面给的代码替换红框内的代码即可。

  1. # bbox loss
  2. if fg_mask.sum():
  3. loss[0], loss[2], iou = self.bbox_loss(pred_distri,
  4. pred_bboxes,
  5. anchor_points,
  6. target_bboxes,
  7. target_scores,
  8. target_scores_sum,
  9. fg_mask, imgsz[0], imgsz[1])
  10. loss[0] *= 0.25
  11. loss[2] *= 0.25
  12. if fg_mask2.sum():
  13. loss0_, loss2_, iou2 = self.bbox_loss2(pred_distri2,
  14. pred_bboxes2,
  15. anchor_points,
  16. target_bboxes2,
  17. target_scores2,
  18. target_scores_sum2,
  19. fg_mask2, imgsz[0], imgsz[1])

下面的图片为修改之后的样子,大家可以看好了,不要修改错了!! 

 


3.2 步骤2 

第二步我们还是这个文件'utils/loss_tal_dual.py'并没有离开这个文件请注意!我们需要网上翻页面,找到图片所示的代码,下面的代码是未作修改的!

我们将下面我给的代码替换掉整个红框内的代码即可! 

  1. def forward(self, pred_dist, pred_bboxes, anchor_points, target_bboxes, target_scores, target_scores_sum, fg_mask, h, w):
  2. # iou loss
  3. bbox_mask = fg_mask.unsqueeze(-1).repeat([1, 1, 4]) # (b, h*w, 4)
  4. pred_bboxes_pos = torch.masked_select(pred_bboxes, bbox_mask).view(-1, 4)
  5. target_bboxes_pos = torch.masked_select(target_bboxes, bbox_mask).view(-1, 4)
  6. bbox_weight = torch.masked_select(target_scores.sum(-1), fg_mask).unsqueeze(-1)
  7. iou = bbox_iou(pred_bboxes_pos, target_bboxes_pos, xywh=False, CIoU=False, MDPIoU=True, feat_h=h, feat_w=w)
  8. loss_iou = 1.0 - iou
  9. loss_iou *= bbox_weight
  10. loss_iou = loss_iou.sum() / target_scores_sum
  11. # dfl loss
  12. if self.use_dfl:
  13. dist_mask = fg_mask.unsqueeze(-1).repeat([1, 1, (self.reg_max + 1) * 4])
  14. pred_dist_pos = torch.masked_select(pred_dist, dist_mask).view(-1, 4, self.reg_max + 1)
  15. target_ltrb = bbox2dist(anchor_points, target_bboxes, self.reg_max)
  16. target_ltrb_pos = torch.masked_select(target_ltrb, bbox_mask).view(-1, 4)
  17. loss_dfl = self._df_loss(pred_dist_pos, target_ltrb_pos) * bbox_weight
  18. loss_dfl = loss_dfl.sum() / target_scores_sum
  19. else:
  20. loss_dfl = torch.tensor(0.0).to(pred_dist.device)
  21. return loss_iou, loss_dfl, iou

下面的图片未修改之后的样子,图片中我也标记了我们修改了何处的地方! 

 到此就修改完成了,我们将CioU设置为False,将MPDIoU设置为True,将真是边界框大小传入给损失函数即可(官方自带集成了MPDIoU所以我们不需要修改损失函数的内部)我们之后运行自己的yaml文件运行的就是MPDIoU了!

 


四、GELAN的MPDIoU教程 

针对于运行GELAN.yaml文件的同学,修改教程其实和上面一样,只是步骤一的时候大家是修改一处即可,这里我提供‘utils/loss_tal.py’文件的修改完成版本给该群体的用于大家完整替换即可!

此处请注意,此处是'utils/loss_tal.py'的损失函数修改代码,和上面的教程不是一个!! 

我们将下面的代码完全替换 utils/loss_tal.py文件的代码之后运行的就是MPDIoU了!

  1. import os
  2. import torch
  3. import torch.nn as nn
  4. import torch.nn.functional as F
  5. from utils.general import xywh2xyxy
  6. from utils.metrics import bbox_iou
  7. from utils.tal.anchor_generator import dist2bbox, make_anchors, bbox2dist
  8. from utils.tal.assigner import TaskAlignedAssigner
  9. from utils.torch_utils import de_parallel
  10. def smooth_BCE(eps=0.1): # https://github.com/ultralytics/yolov3/issues/238#issuecomment-598028441
  11. # return positive, negative label smoothing BCE targets
  12. return 1.0 - 0.5 * eps, 0.5 * eps
  13. class VarifocalLoss(nn.Module):
  14. # Varifocal loss by Zhang et al. https://arxiv.org/abs/2008.13367
  15. def __init__(self):
  16. super().__init__()
  17. def forward(self, pred_score, gt_score, label, alpha=0.75, gamma=2.0):
  18. weight = alpha * pred_score.sigmoid().pow(gamma) * (1 - label) + gt_score * label
  19. with torch.cuda.amp.autocast(enabled=False):
  20. loss = (F.binary_cross_entropy_with_logits(pred_score.float(), gt_score.float(),
  21. reduction="none") * weight).sum()
  22. return loss
  23. class FocalLoss(nn.Module):
  24. # Wraps focal loss around existing loss_fcn(), i.e. criteria = FocalLoss(nn.BCEWithLogitsLoss(), gamma=1.5)
  25. def __init__(self, loss_fcn, gamma=1.5, alpha=0.25):
  26. super().__init__()
  27. self.loss_fcn = loss_fcn # must be nn.BCEWithLogitsLoss()
  28. self.gamma = gamma
  29. self.alpha = alpha
  30. self.reduction = loss_fcn.reduction
  31. self.loss_fcn.reduction = "none" # required to apply FL to each element
  32. def forward(self, pred, true):
  33. loss = self.loss_fcn(pred, true)
  34. # p_t = torch.exp(-loss)
  35. # loss *= self.alpha * (1.000001 - p_t) ** self.gamma # non-zero power for gradient stability
  36. # TF implementation https://github.com/tensorflow/addons/blob/v0.7.1/tensorflow_addons/losses/focal_loss.py
  37. pred_prob = torch.sigmoid(pred) # prob from logits
  38. p_t = true * pred_prob + (1 - true) * (1 - pred_prob)
  39. alpha_factor = true * self.alpha + (1 - true) * (1 - self.alpha)
  40. modulating_factor = (1.0 - p_t) ** self.gamma
  41. loss *= alpha_factor * modulating_factor
  42. if self.reduction == "mean":
  43. return loss.mean()
  44. elif self.reduction == "sum":
  45. return loss.sum()
  46. else: # 'none'
  47. return loss
  48. class BboxLoss(nn.Module):
  49. def __init__(self, reg_max, use_dfl=False):
  50. super().__init__()
  51. self.reg_max = reg_max
  52. self.use_dfl = use_dfl
  53. def forward(self, pred_dist, pred_bboxes, anchor_points, target_bboxes, target_scores, target_scores_sum, fg_mask, h, w):
  54. # iou loss
  55. bbox_mask = fg_mask.unsqueeze(-1).repeat([1, 1, 4]) # (b, h*w, 4)
  56. pred_bboxes_pos = torch.masked_select(pred_bboxes, bbox_mask).view(-1, 4)
  57. target_bboxes_pos = torch.masked_select(target_bboxes, bbox_mask).view(-1, 4)
  58. bbox_weight = torch.masked_select(target_scores.sum(-1), fg_mask).unsqueeze(-1)
  59. iou = bbox_iou(pred_bboxes_pos, target_bboxes_pos, xywh=False, CIoU=False, MDPIoU=True, feat_h=h, feat_w=w)
  60. loss_iou = 1.0 - iou
  61. loss_iou *= bbox_weight
  62. loss_iou = loss_iou.sum() / target_scores_sum
  63. # dfl loss
  64. if self.use_dfl:
  65. dist_mask = fg_mask.unsqueeze(-1).repeat([1, 1, (self.reg_max + 1) * 4])
  66. pred_dist_pos = torch.masked_select(pred_dist, dist_mask).view(-1, 4, self.reg_max + 1)
  67. target_ltrb = bbox2dist(anchor_points, target_bboxes, self.reg_max)
  68. target_ltrb_pos = torch.masked_select(target_ltrb, bbox_mask).view(-1, 4)
  69. loss_dfl = self._df_loss(pred_dist_pos, target_ltrb_pos) * bbox_weight
  70. loss_dfl = loss_dfl.sum() / target_scores_sum
  71. else:
  72. loss_dfl = torch.tensor(0.0).to(pred_dist.device)
  73. return loss_iou, loss_dfl, iou
  74. def _df_loss(self, pred_dist, target):
  75. target_left = target.to(torch.long)
  76. target_right = target_left + 1
  77. weight_left = target_right.to(torch.float) - target
  78. weight_right = 1 - weight_left
  79. loss_left = F.cross_entropy(pred_dist.view(-1, self.reg_max + 1), target_left.view(-1), reduction="none").view(
  80. target_left.shape) * weight_left
  81. loss_right = F.cross_entropy(pred_dist.view(-1, self.reg_max + 1), target_right.view(-1),
  82. reduction="none").view(target_left.shape) * weight_right
  83. return (loss_left + loss_right).mean(-1, keepdim=True)
  84. class ComputeLoss:
  85. # Compute losses
  86. def __init__(self, model, use_dfl=True):
  87. device = next(model.parameters()).device # get model device
  88. h = model.hyp # hyperparameters
  89. # Define criteria
  90. BCEcls = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([h["cls_pw"]], device=device), reduction='none')
  91. # Class label smoothing https://arxiv.org/pdf/1902.04103.pdf eqn 3
  92. self.cp, self.cn = smooth_BCE(eps=h.get("label_smoothing", 0.0)) # positive, negative BCE targets
  93. # Focal loss
  94. g = h["fl_gamma"] # focal loss gamma
  95. if g > 0:
  96. BCEcls = FocalLoss(BCEcls, g)
  97. m = de_parallel(model).model[-1] # Detect() module
  98. self.balance = {3: [4.0, 1.0, 0.4]}.get(m.nl, [4.0, 1.0, 0.25, 0.06, 0.02]) # P3-P7
  99. self.BCEcls = BCEcls
  100. self.hyp = h
  101. self.stride = m.stride # model strides
  102. self.nc = m.nc # number of classes
  103. self.nl = m.nl # number of layers
  104. self.no = m.no
  105. self.reg_max = m.reg_max
  106. self.device = device
  107. self.assigner = TaskAlignedAssigner(topk=int(os.getenv('YOLOM', 10)),
  108. num_classes=self.nc,
  109. alpha=float(os.getenv('YOLOA', 0.5)),
  110. beta=float(os.getenv('YOLOB', 6.0)))
  111. self.bbox_loss = BboxLoss(m.reg_max - 1, use_dfl=use_dfl).to(device)
  112. self.proj = torch.arange(m.reg_max).float().to(device) # / 120.0
  113. self.use_dfl = use_dfl
  114. def preprocess(self, targets, batch_size, scale_tensor):
  115. if targets.shape[0] == 0:
  116. out = torch.zeros(batch_size, 0, 5, device=self.device)
  117. else:
  118. i = targets[:, 0] # image index
  119. _, counts = i.unique(return_counts=True)
  120. out = torch.zeros(batch_size, counts.max(), 5, device=self.device)
  121. for j in range(batch_size):
  122. matches = i == j
  123. n = matches.sum()
  124. if n:
  125. out[j, :n] = targets[matches, 1:]
  126. out[..., 1:5] = xywh2xyxy(out[..., 1:5].mul_(scale_tensor))
  127. return out
  128. def bbox_decode(self, anchor_points, pred_dist):
  129. if self.use_dfl:
  130. b, a, c = pred_dist.shape # batch, anchors, channels
  131. pred_dist = pred_dist.view(b, a, 4, c // 4).softmax(3).matmul(self.proj.type(pred_dist.dtype))
  132. # pred_dist = pred_dist.view(b, a, c // 4, 4).transpose(2,3).softmax(3).matmul(self.proj.type(pred_dist.dtype))
  133. # pred_dist = (pred_dist.view(b, a, c // 4, 4).softmax(2) * self.proj.type(pred_dist.dtype).view(1, 1, -1, 1)).sum(2)
  134. return dist2bbox(pred_dist, anchor_points, xywh=False)
  135. def __call__(self, p, targets, img=None, epoch=0):
  136. loss = torch.zeros(3, device=self.device) # box, cls, dfl
  137. feats = p[1] if isinstance(p, tuple) else p
  138. pred_distri, pred_scores = torch.cat([xi.view(feats[0].shape[0], self.no, -1) for xi in feats], 2).split(
  139. (self.reg_max * 4, self.nc), 1)
  140. pred_scores = pred_scores.permute(0, 2, 1).contiguous()
  141. pred_distri = pred_distri.permute(0, 2, 1).contiguous()
  142. dtype = pred_scores.dtype
  143. batch_size, grid_size = pred_scores.shape[:2]
  144. imgsz = torch.tensor(feats[0].shape[2:], device=self.device, dtype=dtype) * self.stride[0] # image size (h,w)
  145. anchor_points, stride_tensor = make_anchors(feats, self.stride, 0.5)
  146. # targets
  147. targets = self.preprocess(targets, batch_size, scale_tensor=imgsz[[1, 0, 1, 0]])
  148. gt_labels, gt_bboxes = targets.split((1, 4), 2) # cls, xyxy
  149. mask_gt = gt_bboxes.sum(2, keepdim=True).gt_(0)
  150. # pboxes
  151. pred_bboxes = self.bbox_decode(anchor_points, pred_distri) # xyxy, (b, h*w, 4)
  152. target_labels, target_bboxes, target_scores, fg_mask = self.assigner(
  153. pred_scores.detach().sigmoid(),
  154. (pred_bboxes.detach() * stride_tensor).type(gt_bboxes.dtype),
  155. anchor_points * stride_tensor,
  156. gt_labels,
  157. gt_bboxes,
  158. mask_gt)
  159. target_bboxes /= stride_tensor
  160. target_scores_sum = max(target_scores.sum(), 1)
  161. # cls loss
  162. # loss[1] = self.varifocal_loss(pred_scores, target_scores, target_labels) / target_scores_sum # VFL way
  163. loss[1] = self.BCEcls(pred_scores, target_scores.to(dtype)).sum() / target_scores_sum # BCE
  164. # bbox loss
  165. if fg_mask.sum():
  166. loss[0], loss[2], iou = self.bbox_loss(pred_distri,
  167. pred_bboxes,
  168. anchor_points,
  169. target_bboxes,
  170. target_scores,
  171. target_scores_sum,
  172. fg_mask, imgsz[0], imgsz[1])
  173. loss[0] *= 7.5 # box gain
  174. loss[1] *= 0.5 # cls gain
  175. loss[2] *= 1.5 # dfl gain
  176. return loss.sum() * batch_size, loss.detach() # loss(box, cls, dfl)

 


五、全文总结 

到此本文的正式分享内容就结束了,在这里给大家推荐我的YOLOv9改进有效涨点专栏,本专栏目前为新开的,后期我会根据各种最新的前沿顶会进行论文复现,也会对一些老的改进机制进行补充,如果大家觉得本文帮助到你了,订阅本专栏(目前免费订阅,后期不迷路),关注后续更多的更新~

 专栏地址YOLOv9有效涨点专栏-持续复现各种顶会内容-有效涨点-全网改进最全的专栏  

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/493184
推荐阅读
相关标签
  

闽ICP备14008679号