文章来自公众号【机器学习炼丹术】python
焦点损失函数 Focal Loss(2017年何凯明大佬的论文)被提出用于密集物体检测任务。机器学习
固然,在目标检测中,可能待检测物体有1000个类别,然而你想要识别出来的物体,只是其中的某一个类别,这样其实就是一个样本很是不均衡的一个分类问题。函数
而Focal Loss简单的说,就是解决样本数量极度不平衡的问题的。学习
说到样本不平衡的解决方案,相比你们是知道一个混淆矩阵的f1-score的,可是这个好像不能用在训练中当成损失。而Focal loss能够在训练中,让小数量的目标类别增长权重,让分类错误的样本增长权重。spa
先来看一下简单的二值交叉熵的损失:.net
【而后看focal loss的改进】:
这个增长了一个$(1-y')^\gamma$的权重值,怎么理解呢?就是若是给出的正确类别的几率越大,那么$(1-y')^\gamma$就会越小,说明分类正确的样本的损失权重小,反之,分类错误的样本的损权重大。3d
【focal loss的进一步改进】:
这里增长了一个$\alpha$,这个alpha在论文中给出的是0.25,这个就是单纯的下降正样本或者负样本的权重,来解决样本不均衡的问题。code
二者结合起来,就是一个能够解决样本不平衡问题的损失focal loss。orm
【总结】:blog
这个GHM是为了解决Focal loss存在的一些问题。
【Focal Loss的弊端1】
让模型过多的关注特别难分类的样本是会有问题的。样本中有一些异常点、离群点(outliers)。因此模型为了拟合这些很是难拟合的离群点,就会存在过拟合的风险。
Focal Loss是从置信度p的角度入手衰减loss的。而GHM是必定范围内置信度p的样本数量来衰减loss的。
首先定义了一个变量g,叫作梯度模长(gradient norm):
能够看出这个梯度模长,其实就是模型给出的置信度$p^*$与这个样本真实的标签之间的差值(距离)。g越小,说明预测越准,说明样本越容易分类。
下图中展现了g与样本数量的关系:
【从图中能够看到】
GHM是这样想的,对于梯度模长小的易分类样本,咱们忽视他们;可是focal loss过于关注难分类样本了。关键是难分类样本其实也有不少!,若是模型一直学习难分类样本,那么可能模型的精确度就会降低。因此GHM对于难分类样本也有一个衰减。
那么,GHM对易分类样本和难分类样本都衰减,那么真正被关注的样本,就是那些不难不易的样本。而抑制的程度,能够根据样本的数量来决定。
这里定义一个GD,梯度密度:
$$GD(g)=\frac{1}{l(g)}\sum_{k=1}^N{\delta(g_k,g)}$$
总之,$GD(g)$就是梯度模长在$[g-\frac{\epsilon}{2},g+\frac{\epsilon}{2}]$内的样本总数除以$\epsilon$.
而后把每个样本的交叉熵损失除以他们对应的梯度密度就好了。
$$L_{GHM}=\sum^N_{i=1}{\frac{CE(p_i,p_i^*)}{GD(g_i)}}$$
论文中呢,是把梯度模长划分红了10个区域,由于置信度p是从0~1的,因此梯度密度的区域长度就是0.1,好比是0~0.1为一个区域。
下图是论文中给出的对比图:
【从图中能够获得】
固然能够想到的是,GHM看起来是须要整个样本的模型估计值,才能计算出梯度密度,才能进行更新。也就是说mini-batch看起来彷佛不能用GHM。
在GHM原文中也提到了这个问题,若是光使用mini-batch的话,那么极可能出现不均衡的状况。
【我我的以为的处理方法】
上面讲述的关键在于focal loss实现的功能:
在CenterNet中预测中心点位置的时候,也是使用了Focal Loss,可是稍有改动。
下面经过代码来理解:
class FocalLoss(nn.Module): def __init__(self): super().__init__() self.neg_loss = _neg_loss def forward(self, output, target, mask): output = torch.sigmoid(output) loss = self.neg_loss(output, target, mask) return loss
这里面的output能够理解为是一个1通道的特征图,每个pixel的值都是模型给出的置信度,而后经过sigmoid函数转换成0~1区间的置信度。
而target是CenterNet的热力图,这一点可能比较难理解。打个比方,一个10*10的全都是0的特征图,而后这个特征图中只有一个pixel是1,那么这个pixel的位置就是一个目标检测物体的中心点。有几个1就说明这个图中有几个要检测的目标物体。
而后,若是一个特征图上,全都是0,只有几个孤零零的1,未免显得过于稀疏了,直观上也很是的不平滑。因此CenterNet的热力图还须要对这些1为中心作一个高斯
能够看做是一种平滑:
能够看到,数字1的四周是一样的数字。这是一个以1为中心的高斯平滑。
这里咱们回到上面说到的$(1-Y)^\beta$:
对于数字1来讲,咱们计算loss天然是用第一行来计算,可是对于1附近的其余点来讲,就要考虑$(1-Y)^\beta$了。越靠近1的点的$Y$越大,那么$(1-Y)^\beta$就会越小,这样从而下降1附近的权重值。其实这里我也讲不太明白,就是根据距离1的距离下降负样本的权重值,从而能够实现样本过多的类别的权重较小。
咱们回到主题,对output进行sigmoid以后,与output一块儿放到了neg_loss中。咱们来看什么是neg_loss:
def _neg_loss(pred, gt, mask): pos_inds = gt.eq(1).float() * mask neg_inds = gt.lt(1).float() * mask neg_weights = torch.pow(1 - gt, 4) loss = 0 pos_loss = torch.log(pred) * torch.pow(1 - pred, 2) * pos_inds neg_loss = torch.log(1 - pred) * torch.pow(pred, 2) * \ neg_weights * neg_inds num_pos = pos_inds.float().sum() pos_loss = pos_loss.sum() neg_loss = neg_loss.sum() if num_pos == 0: loss = loss - neg_loss else: loss = loss - (pos_loss + neg_loss) / num_pos return loss
先说一下,这里面的mask是根据特定任务中加上的一个小功能,就是在该任务中,一张图片中有一部分是不须要计算loss的,因此先用过mask把那个部分过滤掉。这里直接忽视mask就行了。
从neg_weights = torch.pow(1 - gt, 4)
能够得知$\beta=4$,从下面的代码中也不难推出,$\alpha=2$,剩下的内容就都同样了。
把每个pixel的损失都加起来,除以目标物体的数量便可。