论文:Focal Loss for Dense Object Detection(CVPR 2017) 速达>>
代码:fizyr/keras-retinanet
针对问题
多阶段检测器虽然有较高的精度,但是速度与单阶段检测器有相当大的差距,实用价值大打折扣。单阶段网络的最大劣势就是精度不够高
那么为什么one-stage detector的准确率不够高?
- 样本的类别不均衡导致的,负样本数量太大,占总的loss的大部分(主导Loss),不利于真正目标的学习
- 多数为简单样本(目标少,大量背景当做负样本),容易使模型的优化方向跑偏(变成预测背景而不是前景)
两阶段检测器通常可以较好地上述两个问题,如:Faster RCNN :
- 减少简单样本:根据RPN预测的正负样本得分取前N个(训练:12000)预测框,经过NMS得到大约2000个建议框
- 平衡正负样本:从根据与gt_box 的IoU 挑选出n(128,正负样本比例:1:3)个rois 训练RCNN部分
OHEM算法也可以用来处理类别不均衡的问题,但只取Loss高的前N个样本训练,忽略了容易分类的样本,这种方式显然过于 Hard。简单样本学习得很好需要保持住,应该赋予其较低的权重(简单样本loss虽然小,但数量大),减小其loss的贡献
所以单阶段对于上述两个问题的解决方向为:
- 减少简单样本的权重,使得模型在训练时更专注于困难样本
- 平衡正负样本
Focal Loss
交叉熵损失
下式为普通的二分类交叉熵损失,
p
p
p 表示预测概率,
y
=
1
y=1
y=1表示正样本。当
y
=
1
y=1
y=1时,预测概率
p
p
p越接近1,预测得越准,损失越小
用
p
t
p_t
pt 代替
p
p
p,则可以将损失表达形式简化:
一般可以通过权重来平衡损失,如:简单地添加一个系数
α
t
\alpha_t
αt,
y
=
1
y=1
y=1 时,
α
t
=
a
\alpha_t=a
αt=a;
y
=
−
1
y=-1
y=−1 时,
α
t
=
1
−
a
\alpha_t=1-a
αt=1−a,
a
∈
[
0
−
1
]
a\in[0-1]
a∈[0−1]。通过改变
a
a
a的值来控制正负样本Loss权重
Focal Loss
而上面的形式相当于一刀砍的形式,没有区别对待难分类样本和易分类样本,因为易分类样本占样本的绝大部分,一刀砍的形式并不能让网络专注于困难样本
为其加上调制系数(modulating factor),就是Focal Loss的形式
预测越准, p t p_t pt越趋于1时(分类正确且为易分类样本),调制系数趋于0,loss贡献小
调制系数让网络更专注于困难样本,加上 α t \alpha_t αt系数(如式3)控制正负样本权重(注意不是比例), γ = 2 , α = 0.25 γ=2,α=0.25 γ=2,α=0.25 的取值组合效果较好(原文:as easy negatives are downweighted, less emphasis needs to be placed on the positives), α \alpha α取0.25意味着还要减小正样本权重!!
Focal Loss形式简单,但体现作者对目标检测中样本不平衡问题的深刻认识
# https://github.com/fizyr/keras-retinanet/blob/master/keras_retinanet/losses.py
def focal(alpha=0.25, gamma=2.0, cutoff=0.5):
""" Create a functor for computing the focal loss.
Args
alpha: Scale the focal weight with alpha.
gamma: Take the power of the focal weight with gamma.
cutoff: Positive prediction cutoff for soft targets
Returns
A functor that computes the focal loss using the alpha and gamma.
"""
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[:, :, :-1]
anchor_state = y_true[:, :, -1] # -1 for ignore, 0 for background, 1 for object
classification = y_pred
# filter out "ignore" anchors
indices = tensorflow.where(keras.backend.not_equal(anchor_state, -1))
labels = tensorflow.gather_nd(labels, indices)
classification = tensorflow.gather_nd(classification, indices)
# compute the focal loss
alpha_factor = keras.backend.ones_like(labels) * alpha
# 预测为正样本alpha_factor=0.25,预测为负样本alpha_factor=0.75
alpha_factor = tensorflow.where(keras.backend.greater(labels, cutoff), alpha_factor, 1 - alpha_factor)
focal_weight = tensorflow.where(keras.backend.greater(labels, cutoff), 1 - classification, classification)
focal_weight = alpha_factor * focal_weight ** gamma
cls_loss = focal_weight * keras.backend.binary_crossentropy(labels, classification)
# compute the normalizer: the number of positive anchors
normalizer = tensorflow.where(keras.backend.equal(anchor_state, 1))
normalizer = keras.backend.cast(keras.backend.shape(normalizer)[0], keras.backend.floatx())
normalizer = keras.backend.maximum(keras.backend.cast_to_floatx(1.0), normalizer)
return keras.backend.sum(cls_loss) / normalizer
return _focal
RetinaNet
结构上主要是Resnet+FPN
【
7
】
^{【7】}
【7】
初始化问题
对 classification subnet 的最后一层 conv 初始化其偏置 b b b 为 − log ( ( 1 − π ) / π ) -\log((1-\pi)/\pi) −log((1−π)/π), π \pi π 代表先验概率,即正样本占比(正样本少),这里代表正样本框占所有框的比重,文中设为0.01
最后一层的激活函数为 Sigmoid 函数,其实就是最后的预测:
p
=
1
1
+
e
−
(
W
x
+
b
)
p= \frac{1}{1+e^{-(Wx+b)}}
p=1+e−(Wx+b)1
假定损失只是简单的交叉损失:
L
o
s
s
=
−
log
(
p
t
)
\mathcal Loss =-\log(p_t)
Loss=−log(pt)
如果初始化
b
b
b 为 0,由于
W
W
W 初始化值是一个很小的数(趋近于 0),而且由于前面层的初始化均很小,中间还有 BN 层,所以
x
x
x 值也不会太大,
W
x
+
b
Wx+b
Wx+b 的值可以认为趋近于 0,则有:
1
1
+
e
−
(
W
x
+
b
)
≈
0.5
⟹
L
o
s
s
=
−
log
(
0.5
)
\frac{1}{1+e^{-(Wx+b)}}\approx 0.5\Longrightarrow Loss =-\log(0.5)
1+e−(Wx+b)1≈0.5⟹Loss=−log(0.5)
这个时候可以认为网络第一次向前传播得到的预测就是瞎蒙,虽然正负样本预测错了惩罚均为 L o s s = − log ( 0.5 ) Loss =-\log(0.5) Loss=−log(0.5) ,但由于负样本数量远远多于正样本,这样负样本的损失占主导地位,不利于收敛。
如果希望一开始就针对正样本学习,那就让预测偏向负样本,即让预测
p
p
p 大概率为一个较小的数
π
\color{blue}\pi
π(小于 0.5 即预测为负样本,文中取的是正样本占比 0.1),对于负样本来说,大部分都预测正确(不容易把负样本预测为正样本),这样一开始就是正样本的损失占据主导地位了,所以有:
1
1
+
e
−
(
W
x
+
b
)
≈
1
1
+
e
−
b
=
π
⟹
b
=
−
log
(
(
1
−
π
)
/
π
)
\frac{1}{1+e^{-(Wx+b)}}\approx \frac{1}{1+e^{-b}}= \pi\Longrightarrow \color{blue}b =-\log((1-\pi)/\pi)
1+e−(Wx+b)1≈1+e−b1=π⟹b=−log((1−π)/π)
实验
![]() |
![]() |
![]() |
![]() |
参考文献
【1】focal loss理解与初始化偏置b设置解释
【2】论文阅读: RetinaNet
【3】RetinaNet(Focal Loss)
【4】何恺明大神的「Focal Loss」,如何更好地理解?
【5】目标检测 | 比 Focal Loss 更强的:Gradient Harmonized Mechanism
【6】堪比Focal Loss!解决目标检测中样本不平衡的无采样方法
【7】FPN