https:///qq_41542989/article/details/123846530
https://mp.weixin.qq.com/s?__biz=MzI4MDYzNzg4Mw==&mid=2247552643&idx=6&sn=2d0a77aa998f25c28542802b9e6aa7a4&chksm=ebb73657dcc0bf41a3b34fa6218e8c5da3a9ed988bd0a7d26d691c053cb14720921e8fc6b823&scene=27
上图是IOU损失函数的计算方法:首先绿色的框表示真实目标的位置,蓝色框代表预测框的位置。IOU的计算方法很简单,用两个方框相交的面积/两个方框合并的面积,将得到的值取以e为底对数,前面添上负号就得到了IOU损失函数。
import numpy as np
def Iou(box1, box2, wh=False):
if wh == False:
xmin1, ymin1, xmax1, ymax1 = box1
xmin2, ymin2, xmax2, ymax2 = box2
else:
xmin1, ymin1 = int(box1[0]-box1[2]/2.0), int(box1[1]-box1[3]/2.0)
xmax1, ymax1 = int(box1[0]+box1[2]/2.0), int(box1[1]+box1[3]/2.0)
xmin2, ymin2 = int(box2[0]-box2[2]/2.0), int(box2[1]-box2[3]/2.0)
xmax2, ymax2 = int(box2[0]+box2[2]/2.0), int(box2[1]+box2[3]/2.0)
# 获取矩形框交集对应的左上角和右下角的坐标(intersection)
xx1 = np.max([xmin1, xmin2])
yy1 = np.max([ymin1, ymin2])
xx2 = np.min([xmax1, xmax2])
yy2 = np.min([ymax1, ymax2])
# 计算两个矩形框面积
area1 = (xmax1-xmin1) * (ymax1-ymin1)
area2 = (xmax2-xmin2) * (ymax2-ymin2)
inter_area = (np.max([0, xx2-xx1])) * (np.max([0, yy2-yy1])) #计算交集面积
iou = inter_area / (area1+area2-inter_area+1e-6) #计算交并比
return iou
优点
IoU就是我们所说的交并比,是目标检测中最常用的指标,在anchor-based的方法中,他的作用不仅用来确定正样本和负样本,还可以用来评价输出框(predict box)和ground-truth的距离。
作为损失函数会出现的问题(缺点)
如果两个框没有相交,根据定义,IoU=0,不能反映两者的距离大小(重合度)。同时因为loss=0,没有梯度回传,无法进行学习训练。
IoU无法精确的反映两者的重合度大小。如下图所示,三种情况IoU都相等,但看得出来他们的重合度是不一样的,左边的图回归的效果最好,右边的最差。
如图:绿色是真实目标边界框,红色是预测目标边界框,最外面的蓝色边框是将红绿矩形用最小矩形框起来的边界,Ac是蓝色矩形框的面积,u对应红绿矩形的并集面积。
如果当红绿矩形完美重合,那么IOU =1, Ac = u = 预测目标边界框面积,GIOU = 1 - 0 = 1。如果两个目标分开很远,Ac趋向于很大的数值,u趋于0,IOU也趋于0,GIOU = 0 - 1 = -1。因此GIOU取值的区间是[-1, 1]。
GIOU损失函数的最终表达形式是L(GIOU) = 1 - GIOU
def Giou(rec1,rec2):
#分别是第一个矩形左右上下的坐标
x1,x2,y1,y2 = rec1
x3,x4,y3,y4 = rec2
iou = Iou(rec1,rec2)
area_C = (max(x1,x2,x3,x4)-min(x1,x2,x3,x4))*(max(y1,y2,y3,y4)-min(y1,y2,y3,y4))
area_1 = (x2-x1)*(y1-y2)
area_2 = (x4-x3)*(y3-y4)
sum_area = area_1 + area_2
w1 = x2 - x1 #第一个矩形的宽
w2 = x4 - x3 #第二个矩形的宽
h1 = y1 - y2
h2 = y3 - y4
W = min(x1,x2,x3,x4)+w1+w2-max(x1,x2,x3,x4) #交叉部分的宽
H = min(y1,y2,y3,y4)+h1+h2-max(y1,y2,y3,y4) #交叉部分的高
Area = W*H #交叉的面积
add_area = sum_area - Area #两矩形并集的面积
end_area = (area_C - add_area)/area_C #闭包区域中不属于两个框的区域占闭包区域的比重
giou = iou - end_area
return giou
特性
与IoU相似,GIoU也是一种距离度量,作为损失函数的话如下,满足损失函数的基本要求
GIoU对scale不敏感
GIoU是IoU的下界,在两个框无限重合的情况下,IoU=GIoU=1
IoU取值[0,1],但GIoU有对称区间,取值范围[-1,1]。在两者重合的时候取最大值1,在两者无交集且无限远的时候取最小值-1,因此GIoU是一个非常好的距离度量指标。
与IoU只关注重叠区域不同,GIoU不仅关注重叠区域,还关注其他的非重合区域,能更好的反映两者的重合度。
存在的问题
在预测框和真实框没有很好地对齐时,会导致最小外接框的面积增大,从而使GIOU的值变小,而两个矩形框不重合时,也可以计算GIOU。GIOU Loss虽然解决了IOU的上述两个问题,但是当两个框属于包含关系时,借用下图来说:GIOU会退化成IOU,无法区分其相对位置关系。
由于GIOU仍然严重依赖IOU,因此在两个垂直方向,误差很大,基本很难收敛,这就是GIoU不稳定的原因。借用下图来说:红框内部分:C为两个框的最小外接矩形,此部分表征除去两个框的其余面积,预测框和真实框在相同距离的情况下,水平垂直方向时,此部分面积最小,对loss的贡献也就越小,从而导致在垂直水平方向上回归效果较差。也就是几乎退化为IoU意思
在介绍DIOU之前,先来介绍采用DIOU的效果:如图,黑色代表anchor box, 蓝色红色代表default box,绿色代表真实目标存在的框GT box的位置,期望红蓝框与绿框尽可能重合。第一行是使用GIOU训练网络,让预测边界框尽可能回归到真实目标边界框中,迭代到400次后才勉强重合。第二行使用DIOU训练网络,到达120步时,发现与目标边界框已经完全重合。可以看出,相对于GIOU,DIOU的不仅收敛速度更快,准确率也更高。
图中给出了3组目标边界框与目标边界框的重合关系,显然他们的重合位置不相同的,我们期望第三种重合(两个box中心位置尽可能重合。这三组计算的IOU loss和GIoU loss是一模一样的,因此这两种损失不能很好表达边界框重合关系)。但是DIOU计算出的三种情况的损失是不一样的,显然DIOU更加合理。
ρ代表b和b(gt)之间的欧氏距离
结合这张图理解一下公式:b代表预测中心坐标的参数,也就是黑框的中心点,bgt代表真实目标边界框中心的参数,即绿框中心点。ρ^2就是两个中心点距离的平方,即图中的d(红线)的平方,c代表两个矩形的最小外接矩形对角线(蓝线)长度。如果两个框完美重叠,d=0 ,IOU = 1,DIOU = 1 - 0 = 1 。如果两个框相距很远,d2/c2 趋近于1,IOU = 0, DIOU = 0 - 1 = -1 。因此,DIOU的取值范围也是[-1,1]。
DIOU最终损失函数为:L(DIoU) = 1 - DIOU
优点
def Diou(bboxes1, bboxes2):
rows = bboxes1.shape[0]
cols = bboxes2.shape[0]
dious = torch.zeros((rows, cols))
if rows * cols == 0:#
return dious
exchange = False
if bboxes1.shape[0] > bboxes2.shape[0]:
bboxes1, bboxes2 = bboxes2, bboxes1
dious = torch.zeros((cols, rows))
exchange = True
# #xmin,ymin,xmax,ymax->[:,0],[:,1],[:,2],[:,3]
w1 = bboxes1[:, 2] - bboxes1[:, 0]
h1 = bboxes1[:, 3] - bboxes1[:, 1]
w2 = bboxes2[:, 2] - bboxes2[:, 0]
h2 = bboxes2[:, 3] - bboxes2[:, 1]
area1 = w1 * h1
area2 = w2 * h2
center_x1 = (bboxes1[:, 2] + bboxes1[:, 0]) / 2
center_y1 = (bboxes1[:, 3] + bboxes1[:, 1]) / 2
center_x2 = (bboxes2[:, 2] + bboxes2[:, 0]) / 2
center_y2 = (bboxes2[:, 3] + bboxes2[:, 1]) / 2
inter_max_xy = torch.min(bboxes1[:, 2:],bboxes2[:, 2:])
inter_min_xy = torch.max(bboxes1[:, :2],bboxes2[:, :2])
out_max_xy = torch.max(bboxes1[:, 2:],bboxes2[:, 2:])
out_min_xy = torch.min(bboxes1[:, :2],bboxes2[:, :2])
inter = torch.clamp((inter_max_xy - inter_min_xy), min=0)
inter_area = inter[:, 0] * inter[:, 1]
inter_diag = (center_x2 - center_x1)**2 + (center_y2 - center_y1)**2
outer = torch.clamp((out_max_xy - out_min_xy), min=0)
outer_diag = (outer[:, 0] ** 2) + (outer[:, 1] ** 2)
union = area1+area2-inter_area
dious = inter_area / union - (inter_diag) / outer_diag
dious = torch.clamp(dious,min=-1.0,max = 1.0)
if exchange:
dious = dious.T
return dious
存在的问题
虽然DIOU能够直接最小化预测框和真实框的中心点距离加速收敛,但是Bounding box的回归还有一个重要的因素纵横比暂未考虑。
论文中,作者表示一个优秀的回归定位损失应该考虑三种几何参数:重叠面积、中心点距离、长宽比。CIoU就是在DIoU的基础上增加了检测框尺度的loss,增加了长和宽的loss,这样预测框就会更加的符合真实框。
因此CIOU的三项恰好对应IOU,中心点距离,长宽比的计算。CIOU loss = 1 - CIoU。α和v为长宽比,计算公式如上图所示:w、h和w(gt)、h(gt)分别代表预测框的高宽和真实框的高宽。
def bbox_overlaps_ciou(bboxes1, bboxes2):
rows = bboxes1.shape[0]
cols = bboxes2.shape[0]
cious = torch.zeros((rows, cols))
if rows * cols == 0:
return cious
exchange = False
if bboxes1.shape[0] > bboxes2.shape[0]:
bboxes1, bboxes2 = bboxes2, bboxes1
cious = torch.zeros((cols, rows))
exchange = True
w1 = bboxes1[:, 2] - bboxes1[:, 0]
h1 = bboxes1[:, 3] - bboxes1[:, 1]
w2 = bboxes2[:, 2] - bboxes2[:, 0]
h2 = bboxes2[:, 3] - bboxes2[:, 1]
area1 = w1 * h1
area2 = w2 * h2
center_x1 = (bboxes1[:, 2] + bboxes1[:, 0]) / 2
center_y1 = (bboxes1[:, 3] + bboxes1[:, 1]) / 2
center_x2 = (bboxes2[:, 2] + bboxes2[:, 0]) / 2
center_y2 = (bboxes2[:, 3] + bboxes2[:, 1]) / 2
inter_max_xy = torch.min(bboxes1[:, 2:],bboxes2[:, 2:])
inter_min_xy = torch.max(bboxes1[:, :2],bboxes2[:, :2])
out_max_xy = torch.max(bboxes1[:, 2:],bboxes2[:, 2:])
out_min_xy = torch.min(bboxes1[:, :2],bboxes2[:, :2])
inter = torch.clamp((inter_max_xy - inter_min_xy), min=0)
inter_area = inter[:, 0] * inter[:, 1]
inter_diag = (center_x2 - center_x1)**2 + (center_y2 - center_y1)**2
outer = torch.clamp((out_max_xy - out_min_xy), min=0)
outer_diag = (outer[:, 0] ** 2) + (outer[:, 1] ** 2)
union = area1+area2-inter_area
u = (inter_diag) / outer_diag
iou = inter_area / union
with torch.no_grad():
arctan = torch.atan(w2 / h2) - torch.atan(w1 / h1)
v = (4 / (math.pi ** 2)) * torch.pow((torch.atan(w2 / h2) - torch.atan(w1 / h1)), 2)
S = 1 - iou
alpha = v / (S + v)
w_temp = 2 * w1
ar = (8 / (math.pi ** 2)) * arctan * ((w1 - w_temp) * h1)
cious = iou - (u + alpha * ar)
cious = torch.clamp(cious,min=-1.0,max = 1.0)
if exchange:
cious = cious.T
return cious
CIOU Loss虽然考虑了边界框回归的重叠面积、中心点距离、纵横比。但是通过其公式中的v反映的纵横比的差异,而不是宽高分别与其置信度的真实差异,所以有时会阻碍模型有效的优化相似性。针对这一问题,有学者在CIOU的基础上将纵横比拆开,提出了EIOU Loss,并且加入Focal聚焦优质的锚框,该方法出自于2021年的一篇文章《Focal and Efficient IOU Loss for Accurate Bounding Box Regression》
特性
EIOU的惩罚项是在CIOU的惩罚项基础上将纵横比的影响因子拆开分别计算目标框和锚框的长和宽,该损失函数包含三个部分:重叠损失,中心距离损失,宽高损失,前两部分延续CIOU中的方法,但是宽高损失直接使目标盒与锚盒的宽度和高度之差最小,使得收敛速度更快。惩罚项公式如下:
其中 Cw 和 Ch 是覆盖两个Box的最小外接框的宽度和高度。
考虑到BBox的回归中也存在训练样本不平衡的问题,即在一张图像中回归误差小的高质量锚框的数量远少于误差大的低质量样本,质量较差的样本会产生过大的梯度影响训练过程。作者在EIOU的基础上结合Focal Loss提出一种Focal EIOU Loss,梯度的角度出发,把高质量的锚框和低质量的锚框分开,惩罚项公式如下:
其中IOU = |A∩B|/|A∪B|, γ为控制异常值抑制程度的参数。该损失中的Focal与传统的Focal Loss有一定的区别,传统的Focal Loss针对越困难的样本损失越大,起到的是困难样本挖掘的作用;而根据上述公式:IOU越高的损失越大,相当于加权作用,给越好的回归目标一个越大的损失,有助于提高回归精度。
存在的问题
本文针对边界框回归任务,在之前基于CIOU损失的基础上提出了两个优化方法:
Focal loss是基于二分类交叉熵CE的。它是一个动态缩放的交叉熵损失,通过一个动态缩放因子,可以动态降低训练过程中易区分样本的权重,从而将重心快速聚焦在那些难区分的样本(有可能是正样本,也有可能是负样本,但都是对训练网络有帮助的样本)。
接下来我将从以下顺序详细说明:Cross Entropy Loss (CE) -> Balanced Cross Entropy (BCE) -> Focal Loss (FL)。
1、Cross Entropy Loss:基于二分类的交叉熵损失,它的形式如下:
上式中,y的取值为1和-1,分别代表前景和背景。p的取值范围为0~1,是模型预测属于前景的概率。接下来定义一个关于P的函数:
结合上式,可得到简化公式:
注意:公式中的log函数就是ln函数:
**2、Balanced Cross Entropy:**常见的解决类不平衡方法。引入了一个权重因子α ∈ [ 0 , 1 ] ,当为正样本时,权重因子就是α,当为负样本时,权重因子为1-α。所以,损失函数也可以改写为:
这里给出一张图:
可以看出当权重因子为0.75时,效果最好。
**3、Focal Loss:**虽然BCE解决了正负样本不平衡问题,但并没有区分简单还是难分样本。当易区分负样本超级多时,整个训练过程将会围绕着易区分负样本进行,进而淹没正样本,造成大损失。所以这里引入了一个调制因子 ,用来聚焦难分样本,公式如下:
γ为一个参数,范围在 [0,5], 当 γ为0时,就变为了最开始的CE损失函数。
可以减低易分样本的损失贡献,从而增加难分样本的损失比例,**解释如下:**当Pt趋向于1,即说明该样本是易区分样本,此时调制因子是趋向于0,说明对损失的贡献较小,即减低了易区分样本的损失比例。当pt很小,也就是假如某个样本被分到正样本,但是该样本为前景的概率特别小,即被错分到正样本了,此时 调制因子是趋向于1,对loss也没有太大的影响。
对于 γ的不同取值,得到的loss效果如图所示:
可以看出,当pt越大,即易区分的样本分配的非常好,其所对于的loss就越小。
通过以上针对正负样本以及难易样本平衡,可以得到应该最终的Focal loss形式:
即通过αt 可以抑制正负样本的数量失衡,通过 γ 可以控制简单/难区分样本数量失衡。
Focal Loss理论知识总结:
①调制因子是用来减低易分样本的损失贡献 ,无论是前景类还是背景类,pt越大,就说明该样本越容易被区分,调制因子也就越小。
②αt用于调节正负样本损失之间的比例,前景类别使用 αt 时,对应的背景类别使用 1 − αt 。
③γ 和 αt 都有相应的取值范围,他们的取值相互间也是有影响的,在实际使用过程中应组合使用。
Mean absolute loss(MAE)平均绝对误差,也被称为L1损失,是以绝对误差作为距离,其公式如下:
其中,
y——样本x属于某一个类别的真实概率;
f(x)——样本属于某一类别的预测概率。
其图像为:
优点:梯度为常数,在异常点(损失值较大)的输入值处不会造成梯度爆炸。
缺点:在0处不可导,且在较小的损失值处也有着较大的梯度,不利于收敛。
Python实现:
#L1损失
#设置真实值和预测值
y_true=[[0.],[0.]]
y_pre=[[1.],[0.]]
#实例化二分类交叉熵损失
mae=tf.keras.losses.MeanAbsoluteError()
#计算损失结果
mae(y_true,y_pre)
#预测值越接近真实值,损失函数越小
Mean Squared Loss(MSE)均方误差损失,也被称为L2损失,以误差的平方和作为距离,公式为:
其中,
y——样本x属于某一个类别的真实概率;
f(x)——样本属于某一类别的预测概率。
其图像为:
优点:处处可导,随着误差值的减小,梯度也减小,利于收敛。
缺点:当预测值与目标值相差很大时,梯度容易爆炸。
Python实现:
#L2损失
#设置真实值和预测值
y_true=[[0.],[0.]]
y_pre=[[1.],[0.]]
#实例化二分类交叉熵损失
mae=tf.keras.losses.MeanSquaredError()
#计算损失结果
mae(y_true,y_pre)
#预测值越接近真实值,损失函数越小
Smooth L1损失是L1与L2损失的结合。L1损失在0点处不可导,L2损失在预测值与目标值相差很大时,梯度容易爆炸。smooth L1 损失改进了两者的缺点,分段函数1是L2损失,分段函数2 是L1损失。
其中x=f(x)-y,为真实值和预测值的差值。
L1、L2和smooth L1三者的图像为:
从图中可以看出,该函数是一个分段函数,在[-1,1]之间是L2损失,其他区间就是L1损失,这样即解决了L1损失在0处不可导的问题,也解决了L2损失在异常点处梯度爆炸的问题。
Python实现:
#smooth L1损失
#设置真实值和预测值
y_true=[[0.],[1.]]
y_pre=[[0.2],[0.8]]
#实例化二分类交叉熵损失
mae=tf.keras.losses.Huber()
#计算损失结果
mae(y_true,y_pre)
#预测值越接近真实值,损失函数越小
triplet loss 是深度学习的一种损失函数,主要是用于训练差异性小的样本,比如人脸等;其次在训练目标是得到样本的embedding任务中,triplet loss 也经常使用,比如文本、图片的embedding。
损失函数公式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wA7YDacj-1681977968148)(/home/zjx/.config/Typora/typora-user-images/image-20230412124537162.png)]
输入是一个三元组,包括锚(Anchor)示例、正(Positive)示例、负(Negative)示例,通过优化锚示例与正示例的距离小于锚示例与负示例的距离,实现样本之间的相似性计算。
a:anchor,锚示例;p:positive,与a是同一类别的样本;n:negative,与a是不同类别的样本;margin是一个大于0的常数。最终的优化目标是拉近a和p的距离,拉远a和n的距离。
其中样本可以分为三类:
即 L=0,这种情况不需要优化,天然a和p的距离很近,a和n的距离很远,如下图
即 L>margin,a和n的距离近,a和p的距离远,这种情况损失最大,需要优化,如下图
L<margin,即a和p的距离比a和n的距离近,但是近的不够多,不满足margin,这种情况存在损失,但损失比hard triplets要小,也需要优化,如下图
为什么要设置margin?
,那么只要 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tg7nRc88-1681977968149)(/home/zjx/.config/Typora/typora-user-images/image-20230412124928806.png)]就可以满足上式,也就是锚点a和正例p与锚点a和负例n的距离一样即可,这样模型很难正确区分正例和负例。
triplets loss该如何构造训练集?
首先能看到,对于triplet loss的损失公式,要有3个输入,即锚点a,正例p和负例n。对于样本来讲,有3种,即easy triplets、hard triplets和semi-hard triplets。
理论上讲,使用hard triplets训练模型最好,因为这样模型能够有很好的学习能力,但由于margin的存在,这类样本可能模型没法很好的拟合,训练比较困难;其次是使用semi-hard triplet,这类样本是实际使用中最优选择,因为这类样本损失不为0,而且损失不大,模型既可以学习到样本之间的差异,又较容易收敛;至于easy triplet,损失为0,不用拿来训练。
import torch
from torch import nn
def normalize(x, axis=-1):
"""Normalizing to unit length along the specified dimension.
Args:
x: pytorch Variable
Returns:
x: pytorch Variable, same shape as input
"""
x = 1. * x / (torch.norm(x, 2, axis, keepdim=True).expand_as(x) + 1e-12)
return x
def euclidean_dist(x, y):
"""
Args:
x: pytorch Variable, with shape [m, d]
y: pytorch Variable, with shape [n, d]
Returns:
dist: pytorch Variable, with shape [m, n]
"""
m, n = x.size(0), y.size(0)
xx = torch.pow(x, 2).sum(1, keepdim=True).expand(m, n)
yy = torch.pow(y, 2).sum(1, keepdim=True).expand(n, m).t()
dist = xx + yy
dist = dist - 2 * torch.matmul(x, y.t())
# dist.addmm_(1, -2, x, y.t())
dist = dist.clamp(min=1e-12).sqrt() # for numerical stability
return dist
def cosine_dist(x, y):
"""
Args:
x: pytorch Variable, with shape [m, d]
y: pytorch Variable, with shape [n, d]
Returns:
dist: pytorch Variable, with shape [m, n]
"""
m, n = x.size(0), y.size(0)
x_norm = torch.pow(x, 2).sum(1, keepdim=True).sqrt().expand(m, n)
y_norm = torch.pow(y, 2).sum(1, keepdim=True).sqrt().expand(n, m).t()
xy_intersection = torch.mm(x, y.t())
dist = xy_intersection/(x_norm * y_norm)
dist = (1. - dist) / 2
return dist
def hard_example_mining(dist_mat, labels, return_inds=False):
"""For each anchor, find the hardest positive and negative sample.
Args:
dist_mat: pytorch Variable, pair wise distance between samples, shape [N, N]
labels: pytorch LongTensor, with shape [N]
return_inds: whether to return the indices. Save time if `False`(?)
Returns:
dist_ap: pytorch Variable, distance(anchor, positive); shape [N]
dist_an: pytorch Variable, distance(anchor, negative); shape [N]
p_inds: pytorch LongTensor, with shape [N];
indices of selected hard positive samples; 0 <= p_inds[i] <= N - 1
n_inds: pytorch LongTensor, with shape [N];
indices of selected hard negative samples; 0 <= n_inds[i] <= N - 1
NOTE: Only consider the case in which all labels have same num of samples,
thus we can cope with all anchors in parallel.
"""
assert len(dist_mat.size()) == 2
assert dist_mat.size(0) == dist_mat.size(1)
N = dist_mat.size(0)
# shape [N, N]
is_pos = labels.expand(N, N).eq(labels.expand(N, N).t())
is_neg = labels.expand(N, N).ne(labels.expand(N, N).t())
# `dist_ap` means distance(anchor, positive)
# both `dist_ap` and `relative_p_inds` with shape [N, 1]
dist_ap, relative_p_inds = torch.max(
dist_mat[is_pos].contiguous().view(N, -1), 1, keepdim=True)
print('ppppppppppppppp',dist_mat[is_pos].shape,dist_mat[is_neg].shape)
# `dist_an` means distance(anchor, negative)
# both `dist_an` and `relative_n_inds` with shape [N, 1]
dist_an, relative_n_inds = torch.min(
dist_mat[is_neg].contiguous().view(N, -1), 1, keepdim=True)
# shape [N]
dist_ap = dist_ap.squeeze(1)
dist_an = dist_an.squeeze(1)
if return_inds:
# shape [N, N]
ind = (labels.new().resize_as_(labels)
.copy_(torch.arange(0, N).long())
.unsqueeze(0).expand(N, N))
# shape [N, 1]
p_inds = torch.gather(
ind[is_pos].contiguous().view(N, -1), 1, relative_p_inds.data)
n_inds = torch.gather(
ind[is_neg].contiguous().view(N, -1), 1, relative_n_inds.data)
# shape [N]
p_inds = p_inds.squeeze(1)
n_inds = n_inds.squeeze(1)
return dist_ap, dist_an, p_inds, n_inds
return dist_ap, dist_an
class TripletLoss(object):
"""
Triplet loss using HARDER example mining,
modified based on original triplet loss using hard example mining
"""
def __init__(self, margin=None, hard_factor=0.0):
self.margin = margin
self.hard_factor = hard_factor
if margin is not None:
self.ranking_loss = nn.MarginRankingLoss(margin=margin)
else:
self.ranking_loss = nn.SoftMarginLoss()
def __call__(self, global_feat, labels, normalize_feature=False):
if normalize_feature:
global_feat = normalize(global_feat, axis=-1)
dist_mat = euclidean_dist(global_feat, global_feat)
dist_ap, dist_an = hard_example_mining(dist_mat, labels)
dist_ap *= (1.0 + self.hard_factor)
dist_an *= (1.0 - self.hard_factor)
y = dist_an.new().resize_as_(dist_an).fill_(1)
if self.margin is not None:
loss = self.ranking_loss(dist_an, dist_ap, y)
else:
loss = self.ranking_loss(dist_an - dist_ap, y)
return loss, dist_ap, dist_an
triplet = TripletLoss()
def loss_func(score, feat, target, target_cam):
if cfg.MODEL.METRIC_LOSS_TYPE == 'triplet':
if cfg.MODEL.IF_LABELSMOOTH == 'on':
if isinstance(score, list):
ID_LOSS = [xent(scor, target) for scor in score[1:]]
ID_LOSS = sum(ID_LOSS) / len(ID_LOSS)
ID_LOSS = 0.5 * ID_LOSS + 0.5 * xent(score[0], target)
else:
ID_LOSS = xent(score, target)
if isinstance(feat, list):
TRI_LOSS = [triplet(feats, target)[0] for feats in feat[1:]]
TRI_LOSS = sum(TRI_LOSS) / len(TRI_LOSS)
TRI_LOSS = 0.5 * TRI_LOSS + 0.5 * triplet(feat[0], target)[0]
else:
TRI_LOSS = triplet(feat, target)[0]
return cfg.MODEL.ID_LOSS_WEIGHT * ID_LOSS + \
cfg.MODEL.TRIPLET_LOSS_WEIGHT * TRI_LOSS
Center Loss是通过将特征和特征中心的距离和softmax loss一同作为损失函数,使得类内距离更小,有点L1,L2正则化的意思。最关键是在训练时要使用2个Loss函数:Softmax Loss + lamda * Center Loss:
和metric learning(度量学习)的想法一致,希望同类样本之间紧凑,不同类样本之间分散。现有的CNN最常用的softmax损失函数来训练网络,得到的深度特征通常具有比较强的区分性,也就是比较强的类间判别力。关于softmax的类内判别力,直接看图:
上面给的是mnist的最后一层特征在二维空间的一个分布情况,可以看到类间是可分的,但类内存在的差距还是比较大的,在某种程度上类内间距大于类间的间距。对于像人脸这种复杂分布的数据,我们通常不仅希望数据在特征空间不仅是类间可分,更重要的是类内紧凑(分类任务中类别较多时均存在这个问题)。因为同一个人的类内变化很可能会大于类间的变化,只有保持类内紧凑,我们才能对那些类内大变化的样本有一个更加鲁棒的判定结果。也就是学习一种discriminative的特征。
下图就是我们希望达到的一种效果:
考虑保持softmax loss的类间判别力,提出center loss,center loss就是为了约束****类内紧凑****的条件。相比于传统的CNN,仅改变了原有的损失函数,易于训练和优化网络。
下面公式中log函数的输入就是softmax的结果(是概率),而Ls表示的是softmax loss的结果(是损失)。*wx+b是全连接层的输出,因此log的输入就表示xi属于类别yi的概率。*
Center Loss
先看看center loss的公式LC。cyi表示第yi个类别的特征中心,xi表示全连接层之前的特征。实际使用的时候,m表示mini-batch的大小。因此这个公式就是希望一个batch中的每个样本的feature离feature 的中心的距离的平方和要越小越好,也就是类内距离要越小越好。
关于LC的梯度和cyi的更新公式如下:
这个公式里面有个条件表达式如下式,这里当condition满足的时候,下面这个式子等于1,当不满足的时候,下面这个式子等于0。
因此上面关于cyi的更新的公式中,当yi(表示yi类别)和cj的类别j不一样的时候,cj是不需要更新的,只有当yi和j一样才需要更新。
NNCC:当我们在更新yi类的特征中心cyi时,如果类别yi和该特征中心对应的类别不一样时不更新,即某类别的特征只负责更新它对应的类别中心cyi。
完整的loss function:
作者文中用的损失L的包含softmax loss和center loss,用参数lamda控制二者的比重,如下式所示。这里的m表示mini-batch的包含的样本数量,n表示类别数。
具体的算法实现:
总结:
Center Loss损失函数对分类数不多的工程项目并没有明显的改善,如果你的分类任务中分类数大于1000,例如人脸识别,使用它效果会有明显提升。
Center Loss使得类内距离缩小,类间距离扩大,有效的将各个类别区分开。
使用了centerloss之后,每个类的样本更加聚合,因此类间的距离也随着增大。随着loss_weight的值越来越大,类间距越来越大。达到了增大类间距,缩小类内距离的效果。
https:///weixin_45657478/article/details/125811825
https://zhuanlan.zhihu.com/p/367290573
对比学习常用的损失函数为InfoNCE。
一、Robust Contrastive Learning against Noisy Views(RINCE)
原文链接:https://arxiv.org/pdf/2201.04309.pdf
本文提出RINCE,一种对噪声(如图像的过度增广、视频的过度配音、视频和标题未对准等)有鲁棒性的损失,且不需要显式地估计噪声。
文章提到:RINCE是一个用Wasserstein dependency measure表示的互信息的对比下界;而InfoNCE是KL散度表示的互信息的下界。
设数据分布为**,噪声数据集为,其中标签正确即的概率为**。则目标为最小化
其中****为二元交叉熵损失。
对称的损失函数对噪声有鲁棒性。损失函数对称即满足
(1)
(**为常数),其中为产生的预测分数(对**的梯度也应具有对称性)。
对称的对比损失有如下形式:
(2)
其中和分别为正/负样本对的分数。权重****的大小反映正负样本的相对重要程度。
InfoNCE不满足对梯度中的对称条件。
RINCE损失如下:
其中**和均在(0,1]范围内(实验表明对**的值不敏感)。
时,RINCE完全满足(2)式的对称性(此时,满足(1)式且):
此时该损失对噪声有鲁棒性。
****趋于0时,RINCE渐近趋于InfoNCE。
不论****为何值,正样本分数越高,负样本分数越低,损失越小。
在梯度计算时:
时,RINCE更重视easy-positive的样本(分数高的正样本);
InfoNCE()更重视hard-positive的样本(分数低的正样本);
两者均重视hard-negative的样本(分数高的负样本)。
因此InfoNCE在无噪声时收敛更快,而的RINCE对噪声更有鲁棒性。实际中在的范围内取****。
因篇幅问题不能全部显示,请点此查看更多更全内容