Focal Loss for Dense Object Detection
首先,需要了解交叉熵是怎么工作的: https://blog.csdn.net/tsyccnh/article/details/79163834
本文的核心公式如下:
借用上面博客中的概率表:
* 猫 青蛙 老鼠
Label 0 1 0
Pred 0.3 0.6 0.1
本文将该论文应用在多分类任务中的类别不平衡问题上.
其中alpha是控制类别不平衡问题的超参数,每个类别对应相应的alpha值,样本多的对应的alpha小,样本少的对应alpha大.
gamma是控制难易样本的超参数,当这个是难样本时,预测的pt会很低,(1-pt)值就会变高,使得这个损失加大,反之,样本为简单样本时,pt会很大,而(1-pt)会很小,使得这个损失变得更小.这样就能使得模型更加关注与难样本.由于类别不平衡时,少量样本的类别预测结果更差,因此也能获得更多的关注.这样也能起到控制类别不平衡的效果.
例如上面计算结果为-0.4log(0.6)*alpha.
当青蛙的样本为难样本时,假设预测概率为0.2.
则计算结果就为-0.8*log(0.2)*alpha
很明显结果比普通的交叉熵大.
该篇论文推荐的alpha为0.25, gamma为2
代码如下,input为预测结果,如果不是softmax的预测结果则使用第二个,否则使用第一行代码.
target为标签
logpt = torch.log(input)
#logpt = F.log_softmax(input)
#pt = nn.Softmax()(input) # N*H*W,C
pt = pt.gather(1, target).view(-1)
logpt = logpt.gather(1, target)
logpt = logpt * alpha
loss = -1 * (1 - pt)**self.gamma * logpt
---------------------
作者:一条不爱读书的咸鱼
来源:CSDN
原文:https://blog.csdn.net/a362682954/article/details/85207937
版权声明:本文为博主原创文章,转载请附上博文链接!
https://www.aiuai.cn/aifarm636.html
题目: Focal Loss for Dense Object Detection - ICCV2017
作者: Tsung-Yi, Lin, Priya Goyal, Ross Girshick, Kaiming He, Piotr Dollar
团队: FAIR
精度最高的目标检测器往往基于 RCNN 的 two-stage 方法,对候选目标位置再采用分类器处理. 而,one-stage 目标检测器是对所有可能的目标位置进行规则的(regular)、密集采样,更快速简单,但是精度还在追赶 two-stage 检测器. <论文所关注的问题于此.>
论文发现,密集检测器训练过程中,所遇到的极端前景背景类别不均衡(extreme foreground-background class imbalance)是核心原因.
对此,提出了 Focal Loss,通过修改标准的交叉熵损失函数,降低对能够很好分类样本的权重(down-weights the loss assigned to well-classified examples),解决类别不均衡问题.
Focal Loss 关注于在 hard samples 的稀疏子集进行训练,并避免在训练过程中大量的简单负样本淹没检测器. <RetinaNet>
Focal Loss 是动态缩放的交叉熵损失函数,随着对正确分类的置信增加,缩放因子(scaling factor) 衰退到 0. 如图:
Focal Loss 的缩放因子能够动态的调整训练过程中简单样本的权重,并让模型快速关注于困难样本(hard samples).
基于 Focal Loss 的 RetinaNet 的目标检测器表现.
1. Focal Loss
Focal Loss 旨在解决 one-stage 目标检测器在训练过程中出现的极端前景背景类不均衡的问题(如,前景:背景 = 1:1000).
首先基于二值分类的交叉熵(cross entropy, CE) 引入 Focal Loss:
其中,
为 groundtruth 类别; 是模型对于类别
所得到的预测概率.
符号简介起见,定义
:
则,
.
CE Loss 如图 Figure 1 中的上面的蓝色曲线所示. 其一个显著特点是,对于简单易分的样本(
),其 loss 也是一致对待. 当累加了大量简单样本的 loss 后,具有很小 loss 值的可能淹没稀少的类(rare class).
1.1 均衡交叉熵 Blanced CE
解决类别不均衡的一种常用方法是,对类别 +1 引入权重因子
,对于类别 -1 引入权重
.
符号简介起见,定义
:
则,
-balanced CE loss 为:
1.2 Focal Loss 定义
虽然
能够平衡 positive/negative 样本的重要性,但不能区分 easy/had 样本.
对此,Focal Loss 提出将损失函数降低 easy 样本的权重,并关注于对 hard negatives 样本的训练.
添加调制因子(modulating factor)
到 CE loss,其中
为可调的 focusing 参数.
Focal Loss 定义为:
如图 Figure 1,给出了
中几个值的可视化.
Focal Loss 的两个属性:
- [1] - 当样本被误分,且
值很小时,调制因子接近于 1,loss 不受影响. 随着
- ,则调制因子接近于 0,则容易分类的样本的损失函数被降低权重.
- [2] - focusing 参数
- 平滑地调整哪些 easy 样本会被降低权重的比率(rate). 当 ,FL=CE;随着 增加,调制因子的影响也会随之增加(实验中发现
- 效果最佳.)
直观上,调制因子能够减少 easy 样本对于损失函数的贡献,并延伸了loss 值比较地的样本范围.
例如,
时,被分类为 的样本,与 CE 相比,会减少 100x 倍;而且,被分类为 的样本,与 CE 相比,会有少于 1000x 倍的 loss 值. 这就自然增加了将难分类样本的重要性(如 且时,难分类样本的 loss 值会增加 4x 倍.)
实际上,论文采用了 Focal Loss 的
-balanced 变形:
1.3. Focal Loss 例示
Focal Loss 并不局限于具体的形式. 这里给出另一种例示.
假设
,
定义
为(类似于前面对于的定义):
定义:
,其中,是 groundtruth 类别.
则:
当
时,样本被正确分类,此时.
有:
对于交叉熵损失函数
,由,
对于 Focal Loss
,其中为常数.
再者,假设
,则 ,其中为常数.
则,
包含两个参数 和,控制着 loss 曲线的陡度(steepness) 和移动(shift). 如 Figure 5.
1.4. Focal Loss 求导
的求导:
的求导:
的求导:
如图 Figure 6. 三种 loss 函数,对于high-confidence 的预测结果,其导数都趋近于 -1 或 0.
但,与
不同的是, 和 的有效设置时,只要,二者的导数都是很小的.
2. SoftmaxFocalLoss 求导
Focal Loss 损失函数:
其中:
Softmax 函数:
其中,
为类别数, 是网络全连接层等的输出向量, 是向量的第个元素值.
则
关于求导:
而,
Softmax 函数关于 x 的求导为:
当
时,
当
时,
Softmax 的函数求导即为:
故:
3. Pytorch 实现
import torch import torch.nn as nn import torch.nn.functional as F class FocalLoss(nn.Module): def __init__(self, alpha=0.25, gamma=2, size_average=True): super(FocalLoss, self).__init__() self.alpha = alpha self.gamma = torch.Tensor([gamma]) self.size_average = size_average if isinstance(alpha, (float, int, long)): if self.alpha > 1: raise ValueError('Not supported value, alpha should be small than 1.0') else: self.alpha = torch.Tensor([alpha, 1.0 - alpha]) if isinstance(alpha, list): self.alpha = torch.Tensor(alpha) self.alpha /= torch.sum(self.alpha) 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] ([N,C,D,H,W]->[N,C,D*H*W]) # target # [N,1,D,H,W] ->[N*D*H*W,1] if self.alpha.device != input.device: self.alpha = torch.tensor(self.alpha, device=input.device) target = target.view(-1, 1) logpt = torch.log(input + 1e-10) logpt = logpt.gather(1, target) logpt = logpt.view(-1, 1) pt = torch.exp(logpt) alpha = self.alpha.gather(0, target.view(-1)) gamma = self.gamma if not self.gamma.device == input.device: gamma = torch.tensor(self.gamma, device=input.device) loss = -1 * alpha * torch.pow((1 - pt), gamma) * logpt if self.size_average: loss = loss.mean() else: loss = loss.sum() return loss
(原)SphereFace及其pytorch代码
转载请注明出处:
http://www.cnblogs.com/darkknightzh/p/8524937.html
论文:
SphereFace: Deep Hypersphere Embedding for Face Recognition
https://arxiv.org/abs/1704.08063
http://wyliu.com/papers/LiuCVPR17v3.pdf
官方代码:
https://github.com/wy1iu/sphereface
pytorch代码:
https://github.com/clcarwin/sphereface_pytorch
说明:没用过mxnet,下面的代码注释只是纯粹从代码的角度来分析并进行注释,如有错误之处,敬请谅解,并欢迎指出。
传统的交叉熵公式如下:
Li=−logeWTyixi+byi∑jeWTjxi+bj=−loge‖‖Wyi‖‖‖‖xi‖‖cos(θyi,i)+byi∑je‖‖Wj‖‖‖‖xi‖‖cos(θj,i)+bj
将W归一化到1,且不考虑偏置项,即bj=0
,则上式变成:
Lmodified=1N∑i−log(e‖‖xi‖‖cos(θyi,i)∑je‖‖xi‖‖cos(θj,i))
其中θ为w和x的夹角。
为了进一步限制夹角的范围,使用mθ,上式变成
Lang=1N∑i−log(e‖‖xi‖‖cos(mθyi,i)e‖‖xi‖‖cos(mθyi,i)+∑j≠yie‖‖xi‖‖cos(θj,i))
其中θ范围为[0,πm]
。
为了使得上式单调,引入ψ(θyi,i)
:
Lang=1N∑i−log(e‖‖xi‖‖ψ(θyi,i)e‖‖xi‖‖ψ(θyi,i)+∑j≠yie‖‖xi‖‖cos(θj,i))
其中
ψ(θyi,i)=(−1)kcos(mθyi,i)−2k
,θyi,i∈[kπm,(k+1)πm],k∈[0,m−1],m≥1
代码中引入了超参数λ,为
λ=max(λmin,λmax1+0.1×iterator)
其中,λmin=5
,λmax=1500
为程序中预先设定的值。
实际的ψ(θ)
为
ψ(θyi)=(−1)kcos(mθyi)−2k+λcos(θyi)1+λ
对应下面代码为:
output = cos_theta * 1.0
output[index] -= cos_theta[index]*(1.0+0)/(1+self.lamb)
output[index] += phi_theta[index]*(1.0+0)/(1+self.lamb)
对于yi处的计算,
output(yi)=cos(θyi)−cos(θyi)1+λ+ψ(θyi)1+λ=ψ(θyi)+λcos(θyi)1+λ=(−1)kcos(mθyi)−2k+λcos(θyi)1+λ
和上面的公式对应。
具体的代码如下(完整的代码见参考网址):
1 class AngleLinear(nn.Module):
2 def __init__(self, in_features, out_features, m = 4, phiflag=True):
3 super(AngleLinear, self).__init__()
4 self.in_features = in_features
5 self.out_features = out_features
6 self.weight = Parameter(torch.Tensor(in_features,out_features))
7 self.weight.data.uniform_(-1, 1).renorm_(2,1,1e-5).mul_(1e5)
8 self.phiflag = phiflag
9 self.m = m
10 self.mlambda = [
11 lambda x: x**0, # cos(0*theta)=1
12 lambda x: x**1, # cos(1*theta)=cos(theta)
13 lambda x: 2*x**2-1, # cos(2*theta)=2*cos(theta)**2-1
14 lambda x: 4*x**3-3*x,
15 lambda x: 8*x**4-8*x**2+1,
16 lambda x: 16*x**5-20*x**3+5*x
17 ]
18
19 def forward(self, input): # input为输入的特征,(B, C),B为batchsize,C为图像的类别总数
20 x = input # size=(B,F),F为特征长度,如512
21 w = self.weight # size=(F,C)
22
23 ww = w.renorm(2,1,1e-5).mul(1e5) #对w进行归一化,renorm使用L2范数对第1维度进行归一化,将大于1e-5的截断,乘以1e5,使得最终归一化到1.如果1e-5设置的过大,裁剪时某些很小的值最终可能小于1。注意,第0维度只对每一行进行归一化(每行平方和为1),第1维度指对每一列进行归一化。由于w的每一列为x的权重,因而此处需要对每一列进行归一化。如果要对x归一化,需要对每一行进行归一化,此时第二个参数应为0
24 xlen = x.pow(2).sum(1).pow(0.5) # 对输入x求平方,而后对不同列求和,再开方,得到每行的模,最终大小为第0维的,即B(由于对x不归一化,但是计算余弦时需要归一化,因而可以先计算模。但是对于w,不太懂为何不直接使用这种方式,而是使用renorm函数?)
25 wlen = ww.pow(2).sum(0).pow(0.5) # 对权重w求平方,而后对不同行求和,再开方,得到每列的模(理论上之前已经归一化,此处应该是1,但第一次运行到此处时,并不是1,不太懂),最终大小为第1维的,即C
26
27 cos_theta = x.mm(ww) # 矩阵相乘(B,F)*(F,C)=(B,C),得到cos值,由于此处只是乘加,故未归一化
28 cos_theta = cos_theta / xlen.view(-1,1) / wlen.view(1,-1) # 对每个cos值均除以B和C,得到归一化后的cos值
29 cos_theta = cos_theta.clamp(-1,1) #将cos值截断到[-1,1]之间,理论上不截断应该也没有问题,毕竟w和x都归一化后,cos值不可能超出该范围
30
31 if self.phiflag:
32 cos_m_theta = self.mlambda[self.m](cos_theta) # 通过cos_theta计算cos_m_theta,mlambda为cos_m_theta展开的结果
33 theta = Variable(cos_theta.data.acos()) # 通过反余弦,计算角度theta,(B,C)
34 k = (self.m*theta/3.14159265).floor() # 通过公式,计算k,(B,C)。此处为了保证theta大于k*pi/m,转换过来就是m*theta/pi,再向上取整
35 n_one = k*0.0 - 1 # 通过k的大小,得到同样大小的-1矩阵,(B,C)
36 phi_theta = (n_one**k) * cos_m_theta - 2*k # 通过论文中公式,得到phi_theta。(B,C)
37 else:
38 theta = cos_theta.acos() # 得到角度theta,(B, C),每一行为当前特征和w的每一列的夹角
39 phi_theta = myphi(theta,self.m) #
40 phi_theta = phi_theta.clamp(-1*self.m,1)
41
42 cos_theta = cos_theta * xlen.view(-1,1) # 由于实际上不对x进行归一化,此处cos_theta需要乘以B。(B,C)
43 phi_theta = phi_theta * xlen.view(-1,1) # 由于实际上不对x进行归一化,此处phi_theta需要乘以B。(B,C)
44 output = (cos_theta,phi_theta)
45 return output # size=(B,C,2)
46
47
48 class AngleLoss(nn.Module):
49 def __init__(self, gamma=0):
50 super(AngleLoss, self).__init__()
51 self.gamma = gamma
52 self.it = 0
53 self.LambdaMin = 5.0
54 self.LambdaMax = 1500.0
55 self.lamb = 1500.0
56
57 def forward(self, input, target):
58 self.it += 1
59 cos_theta,phi_theta = input # cos_theta,(B,C)。 phi_theta,(B,C)
60 target = target.view(-1,1) #size=(B,1)
61
62 index = cos_theta.data * 0.0 #得到和cos_theta相同大小的全0矩阵。(B,C)
63 index.scatter_(1,target.data.view(-1,1),1) # 得到一个one-hot矩阵,第i行只有target[i]的值为1,其他均为0
64 index = index.byte() # index为float的,转换成byte类型
65 index = Variable(index)
66
67 self.lamb = max(self.LambdaMin,self.LambdaMax/(1+0.1*self.it)) # 得到lamb
68 output = cos_theta * 1.0 #size=(B,C) # 如果直接使用output=cos_theta,可能不收敛(未测试,但其他程序中碰到过直接对输入使用[index]无法收敛,加上*1.0可以收敛的情况)
69 output[index] -= cos_theta[index]*(1.0+0)/(1+self.lamb) # 此行及下一行将target[i]的值通过公式得到最终输出
70 output[index] += phi_theta[index]*(1.0+0)/(1+self.lamb)
71
72 logpt = F.log_softmax(output) # 得到概率
73 logpt = logpt.gather(1,target) # 下面为交叉熵的计算(和focal loss的计算有点类似,当gamma为0时,为交叉熵)。
74 logpt = logpt.view(-1)
75 pt = Variable(logpt.data.exp())
76
77 loss = -1 * (1-pt)**self.gamma * logpt
78 loss = loss.mean()
79
80 # target = target.view(-1) # 若要简化,理论上可直接使用这两行计算交叉熵(此处未测试,在其他程序中使用后可以正常训练)
81 # loss = F.cross_entropy(cos_theta, target)
82
83 return loss
Focal Loss 的前向与后向公式推导
之前在知乎上发表了基于pytorch的Focal Loss 以及在YOLOv2上的实验结果(点这)。由于实现Pytorch的一个自定义操作并不需要我们直接写实现梯度后传计算。但是我们本着严谨的方式,还是把Focal Loss的前向和后向进行数学化描述。本文的公式可能数学公式比较多。本文尽量采用分解的方式一步一步的推倒。达到能易懂的目的。
Focal Loss 前向
其中 是输入的数据 是输入的标签。
其中
后向梯度计算
为了计算前向公式(3)的梯度我们,首先计算单元 的导数。
计算计算 导数:
有了(4)和(5)我们就来对(3)进行推倒。
在(6)中把(4)(5)带入并合并整理就得到(7)
(7)就是Focal loss的后向的最后结果。要是在Caffe中实现Focal Loss 即可采用(7)实现backward。