![d46e34fdb25046ee419e06e635f477cb.png](https://i-blog.csdnimg.cn/blog_migrate/058b07b2fc1eef71da8dc4a6167689d1.jpeg)
Focal loss
今天的大餐就是 Focal loss,也就是 retinanet 的巧妙之处。
![bbc6403a37de9cc9650ae297e0100c21.png](https://i-blog.csdnimg.cn/blog_migrate/d4c93604b4c74bc77465be8aeb06a439.jpeg)
甜点
最近看了 Face++ 的目标检测效果,现在目标检测在算法上效果还是不错,大部分都能实时检测并可以明确标注出来。但是如果看细节还是存在一些问题,特别是小物体检测是有些跳动,也就是时而检测到,时而不能检测到。还是存在一些误检出问题,就是把 A 物体检测为 B 物体,所以还是存在优化空间,所以这也是我们今天努力的动力。其实这里想说目标检测是计算机视觉应用基础,可以为其他模型提供数据,所以目标检测是非常重要,并值得我们花费力气去研究。
![eedbecd4b1fb65e1d2b87b75478a2807.png](https://i-blog.csdnimg.cn/blog_migrate/0b65e9383a1eddfa4804ec0c2e15d552.jpeg)
开胃菜
新增的开胃菜环节,这个环节我们一起共同复习一些本次分享用到基础知识或概念,帮助我们消化大餐的内容。
熵
如果想要确定一个有 n 种等可能(也就是每种情况发生概率都是相等的)的事件,需要有 log2 n 的信息量。对于一个发生概率是 1/n 的随机事件,我们需要 log2 n的信息量来消除不确定性,即熵为 log2 n
我们知道信息熵是和事件概率成反比的,如果收到一条太阳从东边升起的信息。那么这条信息熵就几乎为 0 ,因为我们都知道太阳从东方升起。
接下来我们对于概率不同事件进行说明,如果晴天和雨天的概率分别是 75% 和 25% ,所以要确定他们所提供的信息量也就不同,具体的计算可以看下表
![a0dafefe08e4fa7c0befe069981eab96.png](https://i-blog.csdnimg.cn/blog_migrate/2318df5d95591786744b63865227f620.png)
从而可以推导出信息熵公式,
![2c643f7e50e368f777fb5f157fa319e0.png](https://i-blog.csdnimg.cn/blog_migrate/4578b821c84f26226e3230dc2352c94f.png)
交叉熵
交叉熵通常用于分类问题的成本函数。其实所谓损失函数就衡量真实值和预测值之间差距。如果我们目标是服从一定分布的随机变量的话,我们的真实值是描述随机变量的概率分布p(x)而q(x)是我们根据已知数据预测出的概率,交叉熵作为一种尺度可以衡量出这两个概率分布之间差距。
![25bc56759f76298e5dad365e077f5ea6.png](https://i-blog.csdnimg.cn/blog_migrate/ced8e73b4a7a405637af5d04a252e8d7.png)
那么结论也就是交叉熵越小,两个分布就越相近,有了这些特征所以交叉熵可以用做分类问题的损失函数。
为什么需要 Focal loss
在 one-stage 目标检测模型中,通常是将图片切分为许多大小网格,然后每个网格又产生多个先验框,通过训练找出有目标的先验框,随后使用回归对这些有物体的先验框的大小进行调整。由于是从大量先验框中仅找到几个有目标物体存在先验框,所以如果将不包含物体先验框视为负样本,包含物体的先验框视为正样本话,就存在正负样本不均衡的问题。不平衡的正负样本并不利于训练,为了优化这个问题才提出了 Focal loss 概念。
在 Focal loss 出现之前我们是通过调整正负样本比例来缓解问题。这是 Focal loss 出现的背景
Focal loss 特点
- 控制正负样本的权重
- 控制容易分类和难分类样本的权重
控制正负样本的权重
focal Loss 是在交叉熵损失函数基础上进行改进。所以我们先列出交叉熵公式
![2fa16357fffea9f579fce1a99819565c.png](https://i-blog.csdnimg.cn/blog_migrate/0bbeb68f2579d4f1e69249ec5c0c30e6.png)
![41d9cd3be2139081c232e5964ec8ae62.png](https://i-blog.csdnimg.cn/blog_migrate/cd1d406c20fc65e872625eedfafbf628.png)
也可以根据 y 不同情况,对上面对交叉熵分类换一种写法
![0c92c517465b77deb051cadc089d2916.png](https://i-blog.csdnimg.cn/blog_migrate/8b2476061d733ec3a25fd55ebaf26620.png)
可以用 $p_t$ 来简化交叉熵 loss
![39a828cebc366e7a457dcbd1b83933f5.png](https://i-blog.csdnimg.cn/blog_migrate/a73654556ee1ac03c4417666cc30abd1.png)
通过对损失函数前添加一个系数 alpha 来调节正负样本对损失函数的影响。这里系数 alpha 有点类似之间定义 Pt。
![5bb4f1fedcc416148504f25e73879650.png](https://i-blog.csdnimg.cn/blog_migrate/7c77d245e016110f26501ddff8e822ad.png)
根据真实值不同情况,取不同 alpha 值,也就是真实值是 1 表示整样本时损失函数值乘以 alpha,对于负样本也就是 y = 0 时,损失函数乘以 (1 - alpha) 这样如果 alpha 取之在 0 到 0.5 之间,就会降低正样本的对损失函数影响。
![6226ae4623ba8e6a5076b31200c80ddf.png](https://i-blog.csdnimg.cn/blog_migrate/fa2043e66b8896609a14b34ca129f2c7.png)
所以就是通过调整 alpha 的值来改变正负样本损失的权重。
控制容易分类和难分类样本的权重
在 retinanet 中,也是通过系数控制不同(难易情况)情况下,样本对对于损失值影响程度。从而达到控制容易分类和难分类样本的权重。也就是让分类器将更多精力花费在那些难于分类的样本上。
![358449395243a93fb06ecf36736aeed7.png](https://i-blog.csdnimg.cn/blog_migrate/f9fcbccd1b464386d7a4336390248f25.png)
当输入样本属于正样本时, Pt 就有 Pt = p,这时如果 p 接近 1 就表示分类器越容易对该样本进行正确分类,那么也就是说我们分类器已经可以很好对该样本进行分类了,而这个样本对于分类器来说属于容易分类样本,所以权重就越小1 - p 就越小,反之亦然。还是说一下吧,也就是 p 越小 1 - p 就越大说明该样本对于分类器属于不容易分类样本,所以该样本就获得较大权重,对损失值给出较大影响。
![f49143a41b6b3faa41049cdea86188ee.png](https://i-blog.csdnimg.cn/blog_migrate/fff5291960e72b529a45948b8a7473d4.png)
作为 developer 我们可能更喜欢 code 而非理论和公式,将他实现才是我们的任务和我们的喜欢表达形式。
# coding=utf-8
import numpy as np
alpha = 0.25
y_true = np.array([1,0,1,0])
y_pred = np.array([0.95,0.05,0.5,0.5])
# alpha
alpha_weight = [alpha if y==1 else 1-alpha for y in y_true]
print(alpha_weight)
pt = np.zeros(4)
index_1 = np.argwhere(y_true==1)
index_0 = np.argwhere(y_true==0)
pt[index_1] = 1 - y_pred[index_1]
pt[index_0] = 1 - y_pred[index_0]
weights = pt*alpha_weight
print(weights)
这里不想多解释了,结合上面公式一看大家都应明白,其实这些代码就是对上面实现
[0.25, 0.75, 0.25, 0.75]
[0.0125 0.7125 0.125 0.375 ]
运行看结果吧,从 alpha 值来看根据样本是正样本还是负样本而不同,而 weights 输出来看对那些分对样本权重较小,而对于那些分错样本则权重较大
源码解析
def focal(alpha=0.25, gamma=2.0):
这里就不解释了,大家自己看注解吧,唯一需要注意就是最后正则化。
def _focal(y_true, y_pred):
""" Compute the focal loss given the target tensor and the predicted tensor.
As defined in https://arxiv.org/abs/1708.02002
Args
y_true: Tensor of target data from the generator with shape (B, N, num_classes).
y_pred: Tensor of predicted data from the network with shape (B, N, num_classes).
Returns
The focal loss of y_pred w.r.t. y_true.
"""
labels = y_true
classification = y_pred
# filter out "ignore" anchors
anchor_state = keras.backend.max(labels, axis=2) # -1 for ignore, 0 for background, 1 for object
indices = backend.where(keras.backend.not_equal(anchor_state, -1))
labels = backend.gather_nd(labels, indices)
classification = backend.gather_nd(classification, indices)
# compute the focal loss
# 创建一个形状和 labels 一样全部为 1 的 tensor,然后 * alpha
alpha_factor = keras.backend.ones_like(labels) * alpha
# 然后根据真实值为 1 或其他不同情况对 alpha 取不同的值
alpha_factor = backend.where(keras.backend.equal(labels, 1), alpha_factor, 1 - alpha_factor)
# 定义正负样本难易程度的样本系数
focal_weight = backend.where(keras.backend.equal(labels, 1), 1 - classification, classification)
#
focal_weight = alpha_factor * focal_weight ** gamma
cls_loss = focal_weight * keras.backend.binary_crossentropy(labels, classification)
# 进行标准化,所谓标准化就是用损失值除以正样本的数量,因为所有样本数量过大,fc 的值主要是由正样本提供,在 fc 负样本个贡献较小,所以是除以
# 这样本数量而不是除以全部样本数量。
# compute the normalizer: the number of positive anchors
normalizer = backend.where(keras.backend.equal(anchor_state, 1))
normalizer = keras.backend.cast(keras.backend.shape(normalizer)[0], keras.backend.floatx())
normalizer = keras.backend.maximum(1.0, normalizer)
return keras.backend.sum(cls_loss) / normalizer
return _focal