YOLOv5改进 | 损失函数 | EIoU、SIoU、WIoU、DIoU、FocuSIoU等多种损失函数

kay_545 2024-10-04 15:01:11 阅读 61

秋招面试专栏推荐 :深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转   


💡💡💡本专栏所有程序均经过测试,可成功执行💡💡💡


专栏目录: 《YOLOv5入门 + 改进涨点》专栏介绍 & 专栏目录 |目前已有40+篇内容,内含各种Head检测头、损失函数Loss、Backbone、Neck、NMS等创新点改进


在目标检测领域内,尽管YOLO系列的算法傲视群雄,但在某些方面仍然存在改进的空间。在YOLOv5的损失函数中,默认是使用的CIoU,但是CIoU仍然存在一定的问题。例如CIOU的计算方式相对复杂需要对边界框的坐标进行更多的处理和计算。本文给大家带来的教程是将原来的CIoU替换为EIoU、SIoU、WIoU、DIoU、FocusIoU。文章在介绍主要的原理后,将手把手教学如何进行模块的代码添加和修改,并将修改后的完整代码放在文章的最后,方便大家一键运行,小白也可轻松上手实践。以帮助您更好地学习深度学习目标检测YOLO系列的挑战。

 专栏地址:YOLOv5改进+入门——持续更新各种有效涨点方法——点击即可跳转

目录

1. ✒️CIoU

1.1 CIoU原理

1.2 CIoU计算

1.3 📌CIoU代码实现

2. ✒️WIOU(Efficient-IoU)

2.1 WIoU原理

2.2 代码实现 

3. 将EIoU、SIoU、WIoU、DIoU、FocusIoU添加到YOLOv5中

3.1 添加代码

3.2 回调函数

4.完整代码分享

5. 进阶

6. 总结


1. ✒️CIoU

1.1 CIoU原理

img

论文地址:Distance-IoU Loss: Faster and Better Learning for Bounding Box Regression——点击即可跳转

论⽂考虑到bbox回归三要素中的⻓宽⽐还没被考虑到计算中,为此,进⼀步在DIoU的基础上提出了CIoU,同时考虑两个矩形的长宽比,也就是形状的相似性。所以CIOU在DIOU的基础上添加了长宽比的惩罚项。

其中, 是权重函数, 而用来度量长宽比的相似性。计算公式为:

☀️优点

更准确的相似性度量:CIOU考虑了边界框的中心点距离和对角线距离,因此可以更准确地衡量两个边界框之间的相似性,尤其是在目标形状和大小不规则的情况下。 鲁棒性更强:相比传统的IoU,CIOU对于目标形状和大小的变化更具有鲁棒性,能够更好地适应各种尺寸和形状的目标检测任务。

⚡️缺点

计算复杂度增加:CIOU引入了额外的中心点距离和对角线距离的计算,因此相比传统的IoU,计算复杂度有所增加,可能会增加一定的计算成本。 实现难度较高:CIOU的计算方式相对复杂,需要对边界框的坐标进行更多的处理和计算,因此在实现上可能会相对困难一些,需要更多的技术和经验支持。

1.2 CIoU计算

中心点 b、中心点 bgt的坐标分别为:(3,4)、(6,6),由此CIoU计算公式如下:

1.3 📌CIoU代码实现

<code>import numpy as np

import IoU

import DIoU

# box : [左上角x坐标,左上角y坐标,右下角x坐标,右下角y坐标]

box1 = [0, 0, 6, 8]

box2 = [3, 2, 9, 10]

# CIoU

def CIoU(box1, box2):

x1, y1, x2, y2 = box1

x3, y3, x4, y4 = box2

# box1的宽:box1_w,box1的高:box1_h,

box1_w = x2 - x1

box1_h = y2 - y1

# box2的宽:box2_w,box2的高:box2_h,

box2_w = x4 - x3

box2_h = y4 - y3

iou = IoU(box1, box2)

diou = DIoU(box1, box2)

# v用来度量长宽比的相似性

v = (4 / (np.pi) ** 2) * (np.arctan(int(box2_w / box2_h)) - np.arctan(int(box1_w / box1_h)))

# α是权重函数

a = v / ((1 + iou) + v)

ciou = diou - a * v

return ciou

print(CIoU(box1, box2))

2. ✒️WIOU(Efficient-IoU)

2.1 WIoU原理

WIoU的主要原理

论文地址: Wise-IoU: Bounding Box Regression Loss with Dynamic Focusing Mechanism

WIoU(Wise-IoU)是为了改进边界框回归(Bounding Box Regression, BBR)损失而提出的一种新的损失函数。WIoU引入了一种动态的非单调聚焦机制(Focusing Mechanism, FM),用于解决高质量和低质量样本对模型训练的不利影响。其主要原理如下:

动态非单调聚焦机制

WIoU使用非单调聚焦机制,通过评估anchor box(锚框)的离群度(outlier degree)来衡量其质量。

离群度的计算公式是β = LIoU / LIoU,即将IoU损失标准化。

聚焦机制会根据离群度分配梯度增益。对高质量的anchor box分配较小的梯度增益,对低质量的anchor box分配较小的梯度增益,从而减少低质量样本对模型训练的负面影响。

梯度增益分配策略

WIoU引入了一个“明智的”梯度增益分配策略,使得模型能够关注普通质量的anchor box。

这种策略减少了高质量anchor box的竞争性,同时也减少了低质量样本产生的有害梯度,从而提高了模型的整体性能。

应用场景

WIoU被应用于最先进的实时检测器YOLOv7上,在MS-COCO数据集上的AP75从53.03%提高到54.50%。

具体实现

在具体实现上,WIoU通过以下步骤来优化BBR损失:

生成离群度:计算每个anchor box的离群度,作为衡量其质量的指标。

分配梯度增益:根据离群度动态调整梯度增益。对高质量和低质量的anchor box赋予较小的梯度增益,而对普通质量的anchor box赋予较大的梯度增益。

损失计算:结合IoU损失和聚焦机制计算最终的WIoU损失。

论文中的具体实现细节

根据论文内容,WIoU的公式和实现细节如下:

IoU损失公式

LIoU = 1 - \frac{Wi \cdot Hi}{Su}

其中,Wi 和 Hi 分别是重叠区域的宽度和高度, Su 是联合区域的面积。

离群度计算

\beta = \frac{LIoU}{LIoU}

梯度增益分配: 动态非单调聚焦机制根据离群度分配梯度增益,减少高质量和低质量样本对模型的不利影响。

WIoU的主要贡献在于引入了动态的非单调聚焦机制,使得模型能够更加有效地处理普通质量的anchor box,从而提高了边界框回归的性能。 

2.2 代码实现 

<code>class WIoU_Scale:

''' monotonous: {

None: origin v1

True: monotonic FM v2

False: non-monotonic FM v3

}

momentum: The momentum of running mean'''

iou_mean = 1.

monotonous = False

_momentum = 1 - 0.5 ** (1 / 7000)

_is_train = True

def __init__(self, iou):

self.iou = iou

self._update(self)

@classmethod

def _update(cls, self):

if cls._is_train: cls.iou_mean = (1 - cls._momentum) * cls.iou_mean + \

cls._momentum * self.iou.detach().mean().item()

@classmethod

def _scaled_loss(cls, self, gamma=1.9, delta=3):

if isinstance(self.monotonous, bool):

if self.monotonous:

return (self.iou.detach() / self.iou_mean).sqrt()

else:

beta = self.iou.detach() / self.iou_mean

alpha = delta * torch.pow(gamma, beta - delta)

return beta / alpha

return 1

3. 将EIoU、SIoU、WIoU、DIoU、FocusIoU添加到YOLOv5中

3.1 添加代码

关键步骤一: 在utils/metrics.py中,找到bbox_iou函数,可以把原有的注释掉,换成下面的代码:

class WIoU_Scale:

''' monotonous: {

None: origin v1

True: monotonic FM v2

False: non-monotonic FM v3

}

momentum: The momentum of running mean'''

iou_mean = 1.

monotonous = False

_momentum = 1 - 0.5 ** (1 / 7000)

_is_train = True

def __init__(self, iou):

self.iou = iou

self._update(self)

@classmethod

def _update(cls, self):

if cls._is_train: cls.iou_mean = (1 - cls._momentum) * cls.iou_mean + \

cls._momentum * self.iou.detach().mean().item()

@classmethod

def _scaled_loss(cls, self, gamma=1.9, delta=3):

if isinstance(self.monotonous, bool):

if self.monotonous:

return (self.iou.detach() / self.iou_mean).sqrt()

else:

beta = self.iou.detach() / self.iou_mean

alpha = delta * torch.pow(gamma, beta - delta)

return beta / alpha

return 1

def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, SIoU=False, EIoU=False, WIoU=False, Focal=False,

alpha=1, gamma=0.5, scale=False, eps=1e-7):

# Returns Intersection over Union (IoU) of box1(1,4) to box2(n,4)

# Get the coordinates of bounding boxes

if xywh: # transform from xywh to xyxy

(x1, y1, w1, h1), (x2, y2, w2, h2) = box1.chunk(4, -1), box2.chunk(4, -1)

w1_, h1_, w2_, h2_ = w1 / 2, h1 / 2, w2 / 2, h2 / 2

b1_x1, b1_x2, b1_y1, b1_y2 = x1 - w1_, x1 + w1_, y1 - h1_, y1 + h1_

b2_x1, b2_x2, b2_y1, b2_y2 = x2 - w2_, x2 + w2_, y2 - h2_, y2 + h2_

else: # x1, y1, x2, y2 = box1

b1_x1, b1_y1, b1_x2, b1_y2 = box1.chunk(4, -1)

b2_x1, b2_y1, b2_x2, b2_y2 = box2.chunk(4, -1)

w1, h1 = b1_x2 - b1_x1, (b1_y2 - b1_y1).clamp(eps)

w2, h2 = b2_x2 - b2_x1, (b2_y2 - b2_y1).clamp(eps)

# Intersection area

inter = (b1_x2.minimum(b2_x2) - b1_x1.maximum(b2_x1)).clamp(0) * \

(b1_y2.minimum(b2_y2) - b1_y1.maximum(b2_y1)).clamp(0)

# Union Area

union = w1 * h1 + w2 * h2 - inter + eps

if scale:

self = WIoU_Scale(1 - (inter / union))

# IoU

# iou = inter / union # ori iou

iou = torch.pow(inter / (union + eps), alpha) # alpha iou

if CIoU or DIoU or GIoU or EIoU or SIoU or WIoU:

cw = b1_x2.maximum(b2_x2) - b1_x1.minimum(b2_x1) # convex (smallest enclosing box) width

ch = b1_y2.maximum(b2_y2) - b1_y1.minimum(b2_y1) # convex height

if CIoU or DIoU or EIoU or SIoU or WIoU: # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1

c2 = (cw ** 2 + ch ** 2) ** alpha + eps # convex diagonal squared

rho2 = (((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 + (

b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4) ** alpha # center dist ** 2

if CIoU: # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47

v = (4 / math.pi ** 2) * (torch.atan(w2 / h2) - torch.atan(w1 / h1)).pow(2)

with torch.no_grad():

alpha_ciou = v / (v - iou + (1 + eps))

if Focal:

return iou - (rho2 / c2 + torch.pow(v * alpha_ciou + eps, alpha)), torch.pow(inter / (union + eps),

gamma) # Focal_CIoU

else:

return iou - (rho2 / c2 + torch.pow(v * alpha_ciou + eps, alpha)) # CIoU

elif EIoU:

rho_w2 = ((b2_x2 - b2_x1) - (b1_x2 - b1_x1)) ** 2

rho_h2 = ((b2_y2 - b2_y1) - (b1_y2 - b1_y1)) ** 2

cw2 = torch.pow(cw ** 2 + eps, alpha)

ch2 = torch.pow(ch ** 2 + eps, alpha)

if Focal:

return iou - (rho2 / c2 + rho_w2 / cw2 + rho_h2 / ch2), torch.pow(inter / (union + eps),

gamma) # Focal_EIou

else:

return iou - (rho2 / c2 + rho_w2 / cw2 + rho_h2 / ch2) # EIou

elif SIoU:

# SIoU Loss https://arxiv.org/pdf/2205.12740.pdf

s_cw = (b2_x1 + b2_x2 - b1_x1 - b1_x2) * 0.5 + eps

s_ch = (b2_y1 + b2_y2 - b1_y1 - b1_y2) * 0.5 + eps

sigma = torch.pow(s_cw ** 2 + s_ch ** 2, 0.5)

sin_alpha_1 = torch.abs(s_cw) / sigma

sin_alpha_2 = torch.abs(s_ch) / sigma

threshold = pow(2, 0.5) / 2

sin_alpha = torch.where(sin_alpha_1 > threshold, sin_alpha_2, sin_alpha_1)

angle_cost = torch.cos(torch.arcsin(sin_alpha) * 2 - math.pi / 2)

rho_x = (s_cw / cw) ** 2

rho_y = (s_ch / ch) ** 2

gamma = angle_cost - 2

distance_cost = 2 - torch.exp(gamma * rho_x) - torch.exp(gamma * rho_y)

omiga_w = torch.abs(w1 - w2) / torch.max(w1, w2)

omiga_h = torch.abs(h1 - h2) / torch.max(h1, h2)

shape_cost = torch.pow(1 - torch.exp(-1 * omiga_w), 4) + torch.pow(1 - torch.exp(-1 * omiga_h), 4)

if Focal:

return iou - torch.pow(0.5 * (distance_cost + shape_cost) + eps, alpha), torch.pow(

inter / (union + eps), gamma) # Focal_SIou

else:

return iou - torch.pow(0.5 * (distance_cost + shape_cost) + eps, alpha) # SIou

elif WIoU:

if Focal:

raise RuntimeError("WIoU do not support Focal.")

elif scale:

return getattr(WIoU_Scale, '_scaled_loss')(self), (1 - iou) * torch.exp(

(rho2 / c2)), iou # WIoU https://arxiv.org/abs/2301.10051

else:

return iou, torch.exp((rho2 / c2)) # WIoU v1

if Focal:

return iou - rho2 / c2, torch.pow(inter / (union + eps), gamma) # Focal_DIoU

else:

return iou - rho2 / c2 # DIoU

c_area = cw * ch + eps # convex area

if Focal:

return iou - torch.pow((c_area - union) / c_area + eps, alpha), torch.pow(inter / (union + eps),

gamma) # Focal_GIoU https://arxiv.org/pdf/1902.09630.pdf

else:

return iou - torch.pow((c_area - union) / c_area + eps, alpha) # GIoU https://arxiv.org/pdf/1902.09630.pdf

if Focal:

return iou, torch.pow(inter / (union + eps), gamma) # Focal_IoU

else:

return iou # IoU

3.2 回调函数

关键步骤二:utils/loss.py中,找到ComputeLoss类中的__call__()函数,把Regression loss中计算iou的代码,换成下面这句:

<code> iou = bbox_iou(pbox, tbox[i], WIoU=True)

if type(iou) is tuple:

if len(iou) == 2:

lbox += (iou[1].detach().squeeze() * (1 - iou[0].squeeze())).mean()

iou = iou[0].squeeze()

else:

lbox += (iou[0] * iou[1]).mean()

iou = iou[2].squeeze()

else:

lbox += (1.0 - iou.squeeze()).mean() # iou loss

iou = iou.squeeze()

4.完整代码分享

https://pan.baidu.com/s/16E9eva_mm6rHPaTOAyZ5uA?pwd=i3ba

提取码:i3ba  

5. 进阶

可以融合其他的注意力机制,修改backbone以及neck,多个模块进行改进。

6. 总结

WIoU(Wise-IoU)通过引入动态非单调聚焦机制来优化边界框回归损失。具体而言,它根据锚框的离群度(outlier degree)来分配梯度增益,离群度越高的锚框被认为质量越差,因此分配较小的梯度增益,而离群度较低的高质量锚框也分配较小的梯度增益。这种机制使得模型能够专注于普通质量的锚框,减少高质量和低质量样本对模型训练的负面影响,从而提高目标检测中的边界框定位精度和整体性能。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。