本博客还有多个超详细综述,感兴趣的朋友可以移步:
卷积神经网络:卷积神经网络超详细介绍
目标检测:目标检测超详细介绍
语义分割:语义分割超详细介绍
NMS:让你一文看懂且看全 NMS 及其变体
数据增强:一文看懂计算机视觉中的数据增强
损失函数:分类检测分割中的损失函数和评价指标
Transformer:A Survey of Visual Transformers
机器学习实战系列:决策树
YOLO 系列:v1、v2、v3、v4、scaled-v4、v5、v6、v7、yolof、yolox、yolos、yolop
损失函数是用来衡量真实值和预测值不一致程度的,越小越好。
信息量:用来衡量一个事件所包含的信息大小,一个事件发生的概率越大,则不确定性越小,信息量越小。公式如下,从公式中可以看出,信息量是概率的负对数:
h ( x ) = − l o g 2 p ( x ) h(x) = -log_2p(x) h(x)=−log2p(x)
熵:用来衡量一个系统的混乱程度,代表系统中信息量的总和,是信息量的期望,信息量总和越大,表明系统不确定性就越大。
E n t r o p y = − ∑ p l o g ( p ) Entropy = -\sum p log(p) Entropy=−∑plog(p)
交叉熵:用来衡量实际输出和期望输出的接近程度的量,交叉熵越小,两个概率分布就越接近。
L = ∑ c = 1 M y c l o g ( p c ) L = \sum_{c=1}^My_clog(p_c) L=c=1∑Myclog(pc)
其中,M表示类别数, y c y_c yc是label,即one-hot向量, p c p_c pc是预测概率,当M=2时,就是二元交叉熵损失
pytorch中的交叉熵:
Pytorch中CrossEntropyLoss()函数的主要是将softmax-log-NLLLoss合并到一块得到的结果。
交叉熵loss可以用到大多数语义分割场景中,但他有一个明显的缺点,对于只用分割前景和背景时,前景数量远远小于背景像素的数量,损失函数中y=0的成分就会占据主导,使得模型严重偏向背景,导致效果不好。
语义分割 cross entropy loss计算方式:
输入 model_output=[2, 19, 128, 256] # 19 代表输出类别
标签 target=[2, 512,1024]
1、将model_output的维度上采样到 [512, 1024]
2、softmax处理:这个函数的输入是网络的输出预测图像,输出是在dim=1(19)上计算概率。输出维度不变,dim=1维度上的值变成了属于每个类别的概率值。
x_softmax = F.softmax(x,dim=1)
3、log处理:
log_softmax_output = torch.log(x_softmax)
4、nll_loss 函数(negative log likelihood loss):这个函数目的就是把标签图像的元素值,作为索引值,在temp3中选择相应的值,并求平均。
output = nn.NLLLoss(log_softmax_output, target)
如果第一个点真值对应的3,则在预测结果中第3个通道去找该点的预测值,假设该点预测结果为-0.62,因为是loss前右负号,则最终结果变为0.62。其余结果依次类推,最后求均值即时最终损失结果。
L = ∑ c = 1 M w c y c l o g ( p c ) L = \sum_{c=1}^Mw_cy_clog(p_c) L=c=1∑Mwcyclog(pc)
这个参数 w c w_c wc计算公式为 w c = N − N c N w_c = \frac{N-N_c}{N} wc=NN−Nc,其中N表示总的像素格式, N c N_c Nc 表示GT类别为c的像素个数,相比原来的loss,在样本数量不均衡的情况下可以获得更好的效果。
在one-stage检测算法中,目标检测器通常会产生10k数量级的框,但只有极少数是正样本,会导致正负样本数量不平衡以及难易样本数量不平衡的情况,为了解决这一问题提出了focal loss。
Focal Loss是为one-stage的检测器的分类分支服务的,它支持0或者1这样的离散类别label。
目的是解决样本数量不平衡的情况:
一般分类时候通常使用交叉熵损失:
C r o s s E n t r o p y ( p , y ) = { − l o g ( p ) , y = 1 − l o g ( 1 − p ) , y = 0 CrossEntropy(p,y)= \begin{cases} -log(p), & y=1 \\ -log(1-p), & y=0 \end{cases} CrossEntropy(p,y)={−log(p),−log(1−p),y=1y=0
为了解决正负样本数量不平衡的问题,我们经常在二元交叉熵损失前面加一个参数 α \alpha α。负样本出现的频次多,那么就降低负样本的权重,正样本数量少,就相对提高正样本的权重。因此可以通过设定 α \alpha α的值来控制正负样本对总的loss的共享权重。 α \alpha α取比较小的值来降低负样本(多的那类样本)的权重。即:
C r o s s E n t r o p y ( p , y ) = { − α l o g ( p ) , y = 1 − ( 1 − α ) l o g ( 1 − p ) , y = 0 CrossEntropy(p,y)= \begin{cases} -\alpha log(p), & y=1 \\ -(1-\alpha) log(1-p), & y=0 \end{cases} CrossEntropy(p,y)={−αlog(p),−(1−α)log(1−p),y=1y=0
虽然平衡了正负样本的数量,但实际上,目标检测中大量的候选目标都是易分样本。这些样本的损失很低,但是由于数量极不平衡,易分样本的数量相对来讲太多,最终主导了总的损失。
因此,Focal loss 论文中认为易分样本(即,置信度高的样本)对模型的提升效果非常小,模型应该主要关注与那些难分样本 。一个简单的想法就是只要我们将高置信度样本的损失降低一些, 也即是下面的公式:
F o c a l l o s s = { − ( 1 − p ) γ l o g ( p ) , y = 1 − p γ l o g ( 1 − p ) , y = 0 Focalloss = \begin{cases} -(1-p)^ \gamma log(p), & y=1 \\ -p^\gamma log(1-p), & y=0 \end{cases} Focalloss={−(1−p)γlog(p),−pγlog(1−p),y=1y=0
当 γ = 0 \gamma=0 γ=0 时,即为交叉熵损失函数,当其增加时,调整因子的影响也在增加,实验发现为2时效果最优。
假设取 γ = 2 \gamma=2 γ=2,如果某个目标置信得分p=0.9,即该样本学的非常好,那么这个样本的权重为 ( 1 − 0.9 ) 2 = 0.001 (1-0.9)^2=0.001 (1−0.9)2=0.001,损失贡献降低了1000倍。
为了同时平衡正负样本问题,Focal loss还结合了加权的交叉熵loss,所以两者结合后得到了最终的Focal loss:
F o c a l l o s s = { − α ( 1 − p ) γ l o g ( p ) , y = 1 − ( 1 − α ) p γ l o g ( 1 − p ) , y = 0 Focal loss = \begin{cases} -\alpha (1-p)^\gamma log(p), & y=1 \\ -(1-\alpha) p^\gamma log(1-p), & y=0 \end{cases} Focalloss={−α(1−p)γlog(p),−(1−α)pγlog(1−p),y=1y=0
取 α = 0.25 \alpha=0.25 α=0.25 在文中,即正样本要比负样本占比小,这是因为负样本易分。
单单考虑alpha的话,alpha=0.75时是最优的。但是将gamma考虑进来后,因为已经降低了简单负样本的权重,gamma越大,越小的alpha结果越好。最后取的是alpha=0.25,gamma=2.0
https://zhuanlan.zhihu.com/p/49981234
class FocalLoss(nn.Module):
def __init__(self, gamma=0, alpha=None, size_average=True):
super(FocalLoss, self).__init__()
self.gamma = gamma
self.alpha = alpha
if isinstance(alpha,(float,int,long)): self.alpha = torch.Tensor([alpha,1-alpha])
if isinstance(alpha,list): self.alpha = torch.Tensor(alpha)
self.size_average = size_average
def forward(self, input, target):
if input.dim()>2:
input = input.view(input.size(0),input.size(1),-1) # N,C,H,W => N,C,H*W
input = input.transpose(1,2) # N,C,H*W => N,H*W,C
input = input.contiguous().view(-1,input.size(2)) # N,H*W,C => N*H*W,C
target = target.view(-1,1)
logpt = F.log_softmax(input)
logpt = logpt.gather(1,target)
logpt = logpt.view(-1)
pt = Variable(logpt.data.exp())
if self.alpha is not None:
if self.alpha.type()!=input.data.type():
self.alpha = self.alpha.type_as(input.data)
at = self.alpha.gather(0,target.data.view(-1))
logpt = logpt * Variable(at)
loss = -1 * (1-pt)**self.gamma * logpt
if self.size_average: return loss.mean()
else: return loss.sum()
Varifocal 论文中提出了一个概念 IACS:分类得分向量中的一个标量元素
为了学习 IACS 得分( localization-aware 或 IoU-aware 的 classification score),作者设计了一个 Varifocal Loss,灵感得益于 Focal Loss。
Focal Loss:
Varifocal Loss:
Focal loss 对正负样本的处理是对等的(也就是使用相同的参数来加权),而 Varifocal loss 是使用不对称参数来对正负样本进行加权。
Varifocal loss 是如何不对等的处理前景和背景对 loss 的贡献的呢:VFL 只对负样本进行了的衰减,这是由于正样本太少了,希望更充分利用正样本的监督信号
检测中有两个任务,分类+回归
画图如下:
x = np.linspace(-2, 2, 300)
y = abs(x)
y2 = x**2
y3 = [0.5*(xx**2) if abs(xx)<1 else (abs(xx)-0.5) for xx in x]
plt.plot(x, y, label="L1")
plt.plot(x, y2, label="L2")
plt.plot(x, y3, label="Smooth L1")
plt.legend(loc=1)
plt.show()
L 1 ( x ) = ∣ x ∣ L_1(x)=|x| L1(x)=∣x∣
L1损失的梯度:
Δ L 1 ( e ) Δ ( e ) = { 1 , x > = 0 − 1 , o t h e r w i s e \frac{\Delta L_1(e)}{\Delta(e)}= \begin{cases} 1, & x>=0 \\ -1, & otherwise \end{cases} Δ(e)ΔL1(e)={1,−1,x>=0otherwise
L 2 ( x ) = x 2 L_2(x)=x^2 L2(x)=x2
L2损失的梯度:
Δ L 2 ( e ) Δ ( e ) = 2 e \frac{\Delta L_2(e)}{\Delta(e)}=2e Δ(e)ΔL2(e)=2e
L2 loss 的缺点:
L1 loss 和 L2 loss 的对比:
公式如下:
s m o o t h L 1 ( x ) = { 0.5 x 2 , i f ∣ x ∣ < 1 ∣ x ∣ − 0.5 , o t h e r w i s e smooth_{L1}(x) = \begin{cases} 0.5x^2, & if |x|<1 \\ |x|-0.5, & otherwise \end{cases} smoothL1(x)={0.5x2,∣x∣−0.5,if∣x∣<1otherwise
Smooth L1 Loss特点:
smooth L1 loss 相对于 L2 loss的优点:
上面三种 Loss 用于计算目标检测的bounding-box loss时,独立的求出4个点的loss,然后进行相加得到最终的bounding-box loss。
论文:UnitBox: An Advanced Object Detection Network
出处:ACM MM 2016
IoU loss:L1 和 L2 loss是将bbox四个点分别求loss然后相加,并没有考虑靠坐标之间的相关性,而实际评价指标 IoU 是具备相关性。
有一些情况的 L1 loss一样,但实际上两个框的 IoU 有很大的不同,所以难以很好的衡量 anchor 的质量。
IoU loss 常用版本:
L I o U = 1 − I o U L_{IoU} = 1-IoU LIoU=1−IoU
上图展示了L2 Loss和 IoU Loss 的求法,图中红点表示目标检测网络结构中Head部分上的点 ( i , j ) (i,j) (i,j),绿色的框表示GT框, 蓝色的框表示Prediction的框。
IoU loss的定义如上,先求出2个框的IoU,然后再求个 − l n ( I o U ) -ln(IoU) −ln(IoU),在实际使用中,实际很多IoU常常被定义为 I o U L o s s = 1 − I o U IoU Loss = 1-IoU IoULoss=1−IoU。
其中IoU是真实框和预测框的交集和并集之比,当它们完全重合时,IoU就是1,那么对于Loss来说,Loss是越小越好,说明他们重合度高,所以IoU Loss就可以简单表示为 1- IoU。
IoU Loss 的优点:
IoU Loss 的缺点:
由于 IoU loss 无法反应两框如何相交,且无法优化未重叠框,则 GIoU Loss 被提出。
方法:
在 IoU 基础上加了一个惩罚项,当bbox的距离越大时,惩罚项将越大:
GIoU 的优点:
GIoU存在的问题:
论文:Distance-IoU Loss: Faster and Better Learning for Bounding Box Regression
代码:https://github.com/Zzh-tju/DIoU
出处:AAAI2020
DIoU 损失:
Distance-IoU 和 Complete-IoU 来自同一篇文章。
由于 GIoU 存在收敛较慢等问题,作者提出了新的损失函数。提出该损失函数的前提是,作者认为一个好的 loss 函数,需要同时考虑三个方面:
所以,Distance-IoU 在 IoU 的基础上添加了对两个框中心点距离的约束,从公式可以看出,D-IoU 能够同时最小化外接矩形的面积和两框中心点的距离,这会使得网络更倾向于移动 bbox 的位置来减少 loss:
DIoU loss 的优点:
DIoU 被用于 NMS 中,得到 DIoU-NMS,使用 DIoU 作为 NMS 中框排序的依据,同时考虑了重合率和中心点距离作为衡量指标。
对于有高得分的框 M,DIoU-NMS 被定义如下:
CIoU 损失:
当两个框中心点重合的情况下,DIoU loss 无法继续优化,c 和 d 的值都不变,所以引入了框的宽高比。
Complete-IoU 在 IoU 的基础上同时考虑了对中心点距离和纵横比的约束,公式如下:
其中:
Loss 函数定义如下:
α \alpha α 定义如下,
CIoU 的优化和 DIoU 一致,除过 v v v 要对 w w w 和 h h h 分别求导:
CIoU 的优点(在 DIoU 优点的基础上):
CIoU 的缺点:
虽然 CIoU 同时考虑了交并比、中心点距离和纵横比,但纵横比是对一个比例的优化,而非对单独参数的优化,导致 w 和 h 无法同大同小,必须一个变大,一个变小,这会限制模型的优化效果。所以 EIoU 将纵横比的优化改为了对 w 和 h 的单独优化,分别求两个框的 w 和 h 的差,用于模型优化。
EIoU Loss 定义如下:
Focal-EIoU 要实现的目标:IoU 越高的高质量样本,损失越大,也就是回归越好的目标,给的损失的权重越大,相当于加权,让网络更关注那些回归好的目标,毕竟后面还要做 NMS,低质量的框可以给更少的关注。
Focal-EIoU loss:
在框回归问题中,高质量的 anchor 总是比低质量的 anchor 少很多,这也对训练过程有害无利。所以,需要研究如何让高质量的 anchor 起到更大的作用。这里的高质量就是指的回归误差小的框。
下图中,经过实验, β = 0.8 \beta=0.8 β=0.8 效果最优,
所以 Focal-EIoU loss 公式如下, γ = 0.5 \gamma=0.5 γ=0.5 最优(图 9c):
L F o c a l − E I o U = I o U γ L E I o U L_{Focal-EIoU}=IoU^{\gamma} L_{EIoU} LFocal−EIoU=IoUγLEIoU
这里以 IoU loss 的形式来分析一下 Focal 形式的曲线(EIoU 曲线不太好画),蓝线为 ( 1 − I o U ) (1-IoU) (1−IoU) 曲线,橘色线为 I o U 0.5 ( 1 − I o U ) IoU^{0.5}(1-IoU) IoU0.5(1−IoU),所以在 IoU 小的时候(0-0.8), I o U 0.5 ( 1 − I o U ) IoU^{0.5}(1-IoU) IoU0.5(1−IoU) 会被拉低,在 IoU 大的时候(0.8-1), I o U 0.5 ( 1 − I o U ) IoU^{0.5}(1-IoU) IoU0.5(1−IoU) 基本保持不变。
从这个曲线可以看出,Focal-EIoU loss 能够通过降低难样本的 loss 来让网络更关注简单样本。
作者认为,在检测任务中,IoU 经常被用来选择预选框,但这种直接的做法也忽略了样本分布的不均衡的特点,这会影响定位 loss 的梯度,从而影响最终的效果。
所以作者思考,能不能在训练的过程中不断 rectify 所有样本的梯度,来提升效果。于是就提出了 Rectified IoU (RIoU) loss。
该损失函数和 Focal-EIoU 类似,都是为了提高 high IoU 样本的权重,来让网络更关注简单样本,提升训练的稳定性,并且让不同难易程度的样本更平衡。
Rectified IoU (RIoU) loss 的目的:增大 high IoU 样本的梯度,抑制 low IoU 样本的梯度
Rectified IoU (RIoU) loss 的特点:在使用 RIoU 训练时,大量简单样本(IoU 大)的梯度会被增大,让网络更关注这些样本,而少量的难样本(IoU 小)的梯度会被抑制。这样会使得每种类型的样本的贡献更均衡,训练过程更高效和稳定。
为什么抑制难样本的梯度后,每类样本的贡献会更均衡呢?
因为 IoU 低的样本占大多数,所以对应的 loss 更大,所以作者通过提高简单样本对 loss 的贡献,来让难易样本的贡献更均衡。
L I o U L_{IoU} LIoU 的形式如下:
L I o U = 1 − I o U L_{IoU}=1-IoU LIoU=1−IoU
L I o U L_{IoU} LIoU 的偏导如下:
∣ g r a d i e n t s ( I o U ) ∣ = ∣ ∂ L I o U ∂ I o U ∣ = 1 |gradients(IoU)|=|\frac{\partial L_{IoU}}{\partial_{IoU}}|=1 ∣gradients(IoU)∣=∣∂IoU∂LIoU∣=1
由此可看出, ∣ g r a d i e n t s ( I o U ) ∣ |gradients(IoU)| ∣gradients(IoU)∣ 是一个常数,对难易样本无差别对待,但不同 IoU 样本的分布是很不均衡的。low IoU 样本的数量大于 high IoU 样本的数量,也就是说,在训练过程中,low IoU 样本掌握着梯度,让网络更偏向于难样本的回归。
Rectified IoU (RIoU) loss 的目的:增大 high IoU 样本的梯度,抑制 low IoU 样本的梯度
如何修正呢:
如果梯度总是随着 IoU 的增大而增大,则会面临一个问题,即当某个框回归的非常好时( I o U → 1 IoU \to 1 IoU→1),其梯度会最大,这是不合适的。
所以作者提出了如下的方式,a, b, c, k 可以控制梯度曲线的形状。上升后,在 I o U = β IoU =\beta IoU=β 处快速下降,本文 β = 0.95 \beta=0.95 β=0.95。
当 β = 0.95 \beta=0.95 β=0.95 时的 Loss 曲线对应如下, I o U < β IoU<\beta IoU<β 时是凸型, I o U > β IoU>\beta IoU>β 时是凹型:
前面了 Focal-EIoU 和 R-IoU loss 都是通过提高 high IoU object 对 loss 的贡献,来平稳训练过程,提升训练效果,这两个方法主要是提高 high IoU object 的梯度,但这两者都不够简洁,并且没有进行全面的实验对比。
所以有研究者提出了一个对 IoU loss 的统一形式,通过 power transformation 来生成 IoU 和其对应惩罚项的统一形式,来动态调整不同 IoU 目标的 loss 和 gradient。
α \alpha α-IoU 的形式是怎样的:
首先,在 IoU loss L I o U = 1 − I o U L_{IoU} = 1-IoU LIoU=1−IoU 上使用 Box-Cox 变换,得到 α \alpha α-IoU:
L α − I o U = ( 1 − I o U α ) α , α > 0 L_{\alpha-IoU}=\frac{(1-IoU^{\alpha})}{\alpha}, \alpha>0 Lα−IoU=α(1−IoUα),α>0
对 α \alpha α 的分析如下,可以得到很多形式:
对 α \alpha α-IoU 简化得到:
图 1 左侧展示了 IoU 和 L I o U L_{IoU} LIoU 的关系,图 1 右侧展示了 IoU 和梯度模值的关系。
α > 1 \alpha>1 α>1,能够让网络更加关注 high IoU 的样本,且能够在训练后期提升训练效果,本文作者发现 α = 3 \alpha=3 α=3 对大多数情况表现都较好,所以选择了 α = 3 \alpha=3 α=3。
L α − I o U L_{\alpha-IoU} Lα−IoU 是如何动态调整的:
一般来说mAP针对整个数据集而言的;AP针对数据集中某一个类别而言的;而percision和recall针对单张图片某一类别的。
precision = TP/( TP+FP)
recall/TPR = TP/(TP+FN)
FPR = FP/(FP+TN)
F1 = 2TP/(2TP+FP+FN)
PR 曲线:Precision 纵轴,Recall 横轴
如果模型的精度越高,召回率越高,那么模型的性能越好。也就是说PR曲线下面的面积越大,模型的性能越好。绘制的时候也是设定不同的分类阈值来获得对应的坐标,从而画出曲线。
PR 曲线特点:
AP 是以每个数据集计算一个值,也定义为 PR 曲线下的面积:
A P = ∫ 0 1 p ( r ) d r AP =\int_0^1p(r) dr AP=∫01p(r)dr
不同数据集某个类别的AP计算方法大同小异,主要有三种:
VOC2010之前,选取当 recall>=0, 0.1, 0.2, 1.0 这11个点时的 precision 最大值,然后将其平均就是 AP, mAP 就是所有类别 AP 的平均,也就是 AP 计算之前,会对 PR 曲线进行平滑,平滑方法为对每一个 Precision值,使用其右边最大的precision值替代,如下图所示:
平滑之后就变成了红色的虚线,实际计算的时候对这11个点(0.1间隔)求平均即可。
VOC2010之后,需要针对每一个不同的recall值(包括0和1),选取其>=这些recall值的precision最大值,然后计算PR曲线下的面积作为AP值。
由于上述11点法插值点数过少,容易导致结果不准,一个解决方法就是内插所有点,也就是对上述平滑之后的曲线计算曲线下的面积。
COCO数据集,设定多个IoU阈值(0.5~0.95,step为0.005),在每个IoU阈值下的AP平均,就是所求的最终的某类别的AP值。
COCO 数据集来说,其实也是内插 AP 的计算方式,但与 VOC2008 不同的是,COCO 在 PR 曲线上采样了 100 个点进行计算,IoU 的阈值从固定的 0.5 调整为 0.5~0.95(间隔 0.05)区间上每隔 0.05 计算一次 AP 的值,取所有结果的平均值作为最终结果。而且在 COCO 语境下,AP 就是 mAP。
A P 50 AP_{50} AP50:IoU阈值为0.5时的AP测量值
A P 75 AP_{75} AP75:IoU阈值为0.75时的测量值
A P S AP_{S} APS : 像素面积小于 3 2 2 32^2 322 的目标框的AP测量值
A P M AP_{M} APM : 像素面积在 3 2 2 32^2 322 - 9 6 2 96^2 962 之间目标框的测量值
A P L AP_{L} APL : 像素面积大于 9 6 2 96^2 962 的目标框的AP测量值
计算某个类别的 AP:
我们知道 AP 是 PR 曲线下的面积,所以首先需要绘制这个类别的 PR 曲线。
第一步:计算 Precision 和 Recall
第二步:统计 TP/FP/FN
mAP 是数据集中所有类别AP的平均值
ROC 曲线:FPR 横轴,TPR 纵轴
特点:
ROC曲线特点:
优点:当测试集中的正负样本的分布变化的时候,ROC曲线能够保持不变。因为TPR聚焦于正例,FPR聚焦于与负例,使其成为一个比较均衡的评估方法。
在实际的数据集中经常会出现类不平衡(class imbalance)现象,即负样本比正样本多很多(或者相反),而且测试数据中的正负样本的分布也可能随着时间变化。
缺点:提到ROC曲线的优点是不会随着类别分布的改变而改变,这在某种程度上也是其缺点。因为负例N增加了很多,而曲线却没变,这等于产生了大量FP。像信息检索中如果主要关心正例的预测准确性的话,这就不对了。在类别不平衡的背景下,负例的数目众多致使FPR的增长不明显,导致ROC曲线呈现一个过分乐观的效果估计。ROC曲线的横轴采用FPR,根据FPR ,当负例N的数量远超正例P时,FP的大幅增长只能换来FPR的微小改变。结果是虽然大量负例被错判成正例,在ROC曲线上却无法直观地看出来。(也可以只分析ROC曲线左边一小段)。
PR曲线:
PR曲线使用了Precision,因此PR曲线的两个指标都聚焦于正例。类别不平衡问题中由于主要关心正例,所以在此情况下PR曲线被广泛认为优于ROC曲线。
使用场景:
1、ROC曲线由于兼顾正例与负例,所以适用于评估分类器的整体性能,相比而言PR曲线完全聚焦于正例。
2、如果有多份数据且存在不同的类别分布,比如信用卡欺诈问题中每个月正例和负例的比例可能都不相同,这时候如果只想单纯地比较分类器的性能且剔除类别分布改变的影响,则ROC曲线比较适合,因为类别分布改变可能使得PR曲线发生变化时好时坏,这种时候难以进行模型比较;反之,如果想测试不同类别分布下对分类器的性能的影响,则PR曲线比较适合。
3、如果想要评估在相同的类别分布下正例的预测情况,则宜选PR曲线。
4、类别不平衡问题中,ROC曲线通常会给出一个乐观的效果估计,所以大部分时候还是PR曲线更好。
5、最后可以根据具体的应用,在曲线上找到最优的点,得到相对应的precision,recall,f1 score等指标,去调整模型的阈值,从而得到一个符合具体应用的模型。
非极大值抑制:在所有预测结果中先保留得分最高的框,然后剔除掉和该框iou大于设定阈值的框
测试的时候需要做 NMS,上述的 mAP 等指标也是在做了 NMS 之后才计算的,因为训练的时候需要大量的正负样本来学习。
计算步骤:
分割其实是对每个像素点进行分类,一般也就是使用分类的损失,如交叉熵等。
Dice Loss:
D i c e L o s s = 1 − D i c e C o e f f i c i e n t Dice Loss = 1-Dice Coefficient DiceLoss=1−DiceCoefficient
Dice 系数:
根据 Lee Raymond Dice命名,是一种集合相似度度量函数,通常用于计算两个样本的相似度(值范围为 [0, 1]),公式如下,分子有2是因为分母加了两次TP:
D i c e C o e f f i c i e n t = 2 ∣ X ∩ Y ∣ ∣ X ∣ + ∣ Y ∣ DiceCoefficient = \frac{2|X \cap Y|}{|X|+|Y|} DiceCoefficient=∣X∣+∣Y∣2∣X∩Y∣
其中, ∣ X ∣ |X| ∣X∣和 ∣ Y ∣ |Y| ∣Y∣分别表示集合的元素个数,分割任务中,两者分别表示GT和预测。
所以 Dice Loss 公式如下:
D i c e L o s s = 1 − 2 ∣ X ∩ Y ∣ ∣ X ∣ + ∣ Y ∣ DiceLoss = 1-\frac{2|X \cap Y|}{|X|+|Y|} DiceLoss=1−∣X∣+∣Y∣2∣X∩Y∣
IoU的算式:
I o U = T P T P + F P + F N IoU = \frac{TP}{TP+FP+FN} IoU=TP+FP+FNTP
简单的说就是,重叠的越多,IoU越接近1,预测效果越好。
Dice 系数:
D i c e C o e f f i c i e n t = 2 ∣ X ∩ Y ∣ ∣ X ∣ + ∣ Y ∣ DiceCoefficient = \frac{2|X \cap Y|}{|X|+|Y|} DiceCoefficient=∣X∣+∣Y∣2∣X∩Y∣
所以:
D i c e C o e f f i c i e n t = 2 × T P T P + F N + T P + F P DiceCoefficient = \frac{2 \times TP}{TP+FN+TP+FP} DiceCoefficient=TP+FN+TP+FP2×TP
所以我们可以得到Dice和IoU之间的关系了,这里的之后的Dice默认表示Dice Coefficient:
I o U = D i c e 2 − D i c e IoU=\frac{Dice}{2-Dice} IoU=2−DiceDice
这个函数图像如下图,我们只关注0~1这个区间就好了,可以发现:
需要注意的是Dice Loss存在两个问题:
(1)训练误差曲线非常混乱,很难看出关于收敛的信息。尽管可以检查在验证集上的误差来避开此问题。
(2)Dice Loss比较适用于样本极度不均的情况,一般的情况下,使用 Dice Loss 会对反向传播造成不利的影响,容易使训练变得不稳定。
所以在一般情况下,还是使用交叉熵损失函数。
I o U = T P T P + F P + F N IoU = \frac{TP}{TP+FP+FN} IoU=TP+FP+FNTP
语义分割中,IoU是用mask来评价的:
GT:
Prediction:
Intersection:
Union:
mIoU:各个类别的IoU的均值
mIoU 的计算:
混淆矩阵:
真实情况 | 预测结果 | 预测结果 |
---|---|---|
真实情况 | 正例 | 反例 |
正例 | TP | FN |
反例 | FP | TN |
混淆矩阵的对角线上的值表示预测正确的值,IoU是只求正例的IoU,如何找出和正例有关的混淆矩阵元素呢,可以通过划线法来得到,
假设有N个类别,则混淆矩阵就是一个NxN的矩阵,对角表示TP,横看真实,竖看预测。
假设一个3类的预测输出的混淆矩阵如下所示:
真实 | 预测 | 预测 | 预测 |
---|---|---|---|
真实 | 0 | 1 | 2 |
0 | 3 | 0 | 0 |
1 | 0 | 2 | 1 |
2 | 0 | 1 | 2 |
真实 | 预测 | 预测 | 预测 |
---|---|---|---|
真实 | 0 | 1 | 2 |
0 | a | b | c |
1 | d | e | f |
2 | g | h | i |
精确率:
p r e c i s i o n 0 = a / ( a + d + g ) precision_0 = a/(a+d+g) precision0=a/(a+d+g)
p r e c i s i o n 1 = e / ( b + e + h ) precision_1 = e/(b+e+h) precision1=e/(b+e+h)
p r e c i s i o n 2 = i / ( c + f + i ) precision_2 = i/(c+f+i) precision2=i/(c+f+i)
p r e c i s i o n 0 = 3 / ( 3 + 0 + 0 ) = 1 precision_0 = 3/(3+0+0)=1 precision0=3/(3+0+0)=1
p r e c i s i o n 1 = 2 / ( 2 + 1 + 0 ) = 2 / 3 precision_1 = 2/(2+1+0)=2/3 precision1=2/(2+1+0)=2/3
p r e c i s i o n 2 = 2 / ( 2 + 1 + 0 ) = 2 / 3 precision_2 = 2/(2+1+0)=2/3 precision2=2/(2+1+0)=2/3
召回率:
r e c a l l 0 = a / ( a + b + c ) recall_0 = a/(a+b+c) recall0=a/(a+b+c)
r e c a l l 1 = e / ( d + e + f ) recall_1 = e/(d+e+f) recall1=e/(d+e+f)
r e c a l l 2 = i / ( g + h + i ) recall_2 = i/(g+h+i) recall2=i/(g+h+i)
r e c a l l 0 = 3 / ( 3 + 0 + 0 ) recall_0 = 3/(3+0+0) recall0=3/(3+0+0)
r e c a l l 1 = 2 / ( 2 + 1 + 0 ) = 2 / 3 recall_1 = 2/(2+1+0)=2/3 recall1=2/(2+1+0)=2/3
r e c a l l 2 = 2 / ( 2 + 1 + 0 ) = 2 / 3 recall_2 = 2/(2+1+0)=2/3 recall2=2/(2+1+0)=2/3
PA:Pixel Accuracy(像素精度)→标记正确的像素占总像素的比例
P A = 对角线元素之和 / 矩阵所有元素之和 PA = 对角线元素之和/矩阵所有元素之和 PA=对角线元素之和/矩阵所有元素之和
P A = 3 + 2 + 2 3 + 2 + 2 + 0 + 0 + 0 + 0 + 1 + 1 = 0.78 PA = \frac{3+2+2}{3+2+2+0+0+0+0+1+1}=0.78 PA=3+2+2+0+0+0+0+1+13+2+2=0.78
CPA:Class Pixel Accuracy(每类的像素精度)→按类别计算正确的像素占总像素的比例
P i = 对角线值 / 对应列的像素总数 P_i = 对角线值/对应列的像素总数 Pi=对角线值/对应列的像素总数
p 0 = 3 / ( 3 + 0 + 0 ) = 1 p_0 = 3/(3+0+0) = 1 p0=3/(3+0+0)=1
p 1 = 2 / ( 0 + 2 + 1 ) = 0.67 p_1 = 2/(0+2+1) = 0.67 p1=2/(0+2+1)=0.67
p 2 = 2 / ( 0 + 1 + 2 ) = 0.67 p_2 = 2/(0+1+2) = 0.67 p2=2/(0+1+2)=0.67
MPA:Mean Pixel Accuracy→每类正确分类像素比例的平均
M P A = s u m ( p i ) / 类别数 = ( 1 + 0.67 + 0.67 ) / 3 = 0.78 MPA = sum(p_i)/类别数 = (1+0.67+0.67)/3=0.78 MPA=sum(pi)/类别数=(1+0.67+0.67)/3=0.78
IoU:
划线法:
I o U 0 = 3 / ( 3 + 0 + 0 + 0 + 0 ) = 1 IoU_0 = 3/(3+0+0+0+0) = 1 IoU0=3/(3+0+0+0+0)=1
I o U 1 = 2 / ( 0 + 2 + 1 + 1 + 1 ) = 0.5 IoU_1 = 2/(0+2+1+1+1) = 0.5 IoU1=2/(0+2+1+1+1)=0.5
I o U 2 = 2 / ( 0 + 1 + 2 + 2 + 1 ) = 0.5 IoU_2 = 2/(0+1+2+2+1) = 0.5 IoU2=2/(0+1+2+2+1)=0.5
代码方法: S A ∪ B = S A + S B − S A ∩ B S_{A\cup B} = S_A+S_B-S_{A\cap B} SA∪B=SA+SB−SA∩B
I o U 0 = 3 / [ ( 3 + 0 + 0 ) + ( 3 + 0 + 0 ) − 3 ] = 1 IoU_0 = 3/[(3+0+0)+(3+0+0) - 3] = 1 IoU0=3/[(3+0+0)+(3+0+0)−3]=1
I o U 1 = 2 / [ ( 0 + 2 + 1 ) + ( 1 + 2 + 1 ) − 2 ] = 0.5 IoU_1 = 2/[(0+2+1)+(1+2+1) - 2]= 0.5 IoU1=2/[(0+2+1)+(1+2+1)−2]=0.5
I o U 2 = 2 / [ ( 0 + 1 + 2 ) + ( 0 + 2 + 1 ) − 2 ] = 0.5 IoU_2 = 2/[(0+1+2)+(0+2+1) - 2] = 0.5 IoU2=2/[(0+1+2)+(0+2+1)−2]=0.5
mIoU:
m I o U = s u m ( I o U i ) / c l a s s mIoU = sum(IoU_i)/class mIoU=sum(IoUi)/class
代码:
# gt 为 label,predict 为预测的结果,num_category 是分割的类别
# 输出为混淆矩阵
def gen_confusion_matrix(predict, gt, num_category):
mask = (gt >= 0) & (gt < num_category)
label = num_category * gt[mask] + predict[mask]
count = np.bincount(label, minlength=num_category**2)
confusion_matrix = count.reshape(num_category, num_category)
return confusion_matrix
# 依据混淆矩阵计算 IoU,这里 IoU 是一个列表,每个值表示对应的类别的 IoU
def cal_iou(confusion_matrix):
intersection = np.diag(confusion_matrix)
union = np.sum(confusion_matrix, axis=1) + np.sum(confusion_matrix, axis=0) - np.diag(confusion_matrix)
union = [np.nan if x == 0 else x for x in union]
IoU = intersection / union
return IoU
# 求 mIoU
for seg_result, label in zip(seg_results, labels):
confusion_matrix += gen_confusion_matrix(seg_result, label, num_category)
IoU = cal_iou(confusion_matrix)
IoU_per_category = dict(zip(classes_names, IoU))
mIoU = np.nanmean(IoU)
metric_output = {
"mIoU": mIoU,
"IoU": IoU_per_category,
}
print("metric_output:", metric_output)
import numpy as np
class IOUMetric:
"""
Class to calculate mean-iou using fast_hist method
"""
def __init__(self, num_classes):
self.num_classes = num_classes
self.hist = np.zeros((num_classes, num_classes))
def _fast_hist(self, label_pred, label_true):
# 找出标签中需要计算的类别,去掉了背景
mask = (label_true >= 0) & (label_true < self.num_classes)
# np.bincount计算了从0到n**2-1这n**2个数中每个数出现的次数,返回值形状(n, n)
hist = np.bincount(
self.num_classes * label_true[mask].astype(int) +
label_pred[mask], minlength=self.num_classes ** 2).reshape(self.num_classes, self.num_classes)
return hist
# 输入:预测值和真实值
# 语义分割的任务是为每个像素点分配一个label
def evaluate(self, predictions, gts):
for lp, lt in zip(predictions, gts):
self.hist += self._fast_hist(lp.flatten(), lt.flatten())
#miou
iou = np.diag(self.hist) / (self.hist.sum(axis=1) + self.hist.sum(axis=0) - np.diag(self.hist))
miou = np.nanmean(iu)
#其他性能指标
acc = np.diag(self.hist).sum() / self.hist.sum()
acc_cls = np.nanmean(np.diag(self.hist) / self.hist.sum(axis=1))
freq = self.hist.sum(axis=1) / self.hist.sum()
fwavacc = (freq[freq > 0] * iu[freq > 0]).sum()
return acc, acc_cls, iou, miou, fwavacc