【转】视觉分类任务中处理不平衡问题的loss比较

转自:http://blog.csdn.net/weixin_35653315/article/details/78327408

问题介绍

在计算机视觉(CV)任务里常常会碰到类别不平衡的问题, 例如:
1. 图片分类任务,有的类别图片多,有的类别图片少
2. 检测任务。现在的检测方法如SSD和RCNN系列,都使用anchor机制。 训练时正负anchor的比例很悬殊.
3. 分割任务, 背景像素数量通常远大于前景像素。
从实质上来讲, 它们可以归类成分类问题中的类别不平衡问题:对图片/anchor/像素的分类。
再者,除了类不平衡问题, 还有easy sample overwhelming的问题。easy sample如果太多,可能会将有效梯度稀释掉。
这两个问题通常都会一起出现。 如果不处理, 可能会对模型性能造成很大伤害。用Focal Loss里的话说,就是训练不给力, 且会造成模型退化:

(1) training is inefficient as most locations are easy negatives…
(2) the easy negatives can overwhelming training and lead to degenerate models.

如果要处理,那么该怎么处理呢? 在CV领域里, 若不考虑修改模型本身, 通常会在loss上做文章, 确切地说,是在样本选择或loss weight上做文章。

常见的解决办法介绍

常见的方法有online的, 也有非online的;有只处理类间不平衡的,有只处理easy example的, 也有同时处理两者的。

  1. Hard Negative Mining, 非online的mining/boosting方法, 以‘古老’的RCNN(2014)为代表, 但在CV里现在应该没有人使用了(吧?)。若感兴趣,推荐去看看OHEM论文里的related work部分。
  2. Mini-batch Sampling,以Fast R-CNN(2015)和Faster R-CNN(2016)为代表。Fast RCNN在训练分类器, Faster R-CNN在训练RPN时,都会从N = 1或2张图片上随机选取mini_batch_size/2个RoI或anchor, 使用正负样本的比例为1:1。若正样本数量不足就用负样本填充。 使用这种方法的人应该也很少了。从这个方法开始, 包括后面列出的都是online的方法。
  3. Online Hard Example Mining, OHEM(2016)。将所有sample根据当前loss排序,选出loss最大的N个,其余的抛弃。这个方法就只处理了easy sample的问题。
  4. Oline Hard Negative Mining, OHNM, SSD(2016)里使用的一个OHEM变种, 在Focal Loss里代号为OHEM 1:3。在计算loss时, 使用所有的positive anchor, 使用OHEM选择3倍于positive anchor的negative anchor。同时考虑了类间平衡与easy sample。
  5. Class Balanced Loss。计算loss时,正负样本上的loss分别计算, 然后通过权重来平衡两者。暂时没找到是在哪提出来的,反正就这么被用起来了。它只考虑了类间平衡。
  6. Focal Loss(2017), 最近提出来的。不会像OHEM那样抛弃一部分样本, 而是和Class Balance一样考虑了每个样本, 不同的是难易样本上的loss权重是根据样本难度计算出来的。

从更广义的角度来看,这些方法都是在计算loss时通过给样本加权重来解决不平衡与easy example的问题。不同的是,OHEM使用了hard weight(只有0或1),而Focal Loss使用了soft weight(0到1之间).

现在依然常用的方法特性比较如下:

Method是否处理Class Inbalance是否处理Easy Example权值方式
OHEMNoYesHard
OHNM(OHEM 1:3)YesYesHard
Class Balanced LossYesNoSoft
Focal LossNoYesSoft

接下来, 通过修改过的Cifar数据集来比较这几种方法在分类任务上的表现,当然, 主要还是期待Focal Loss的表现。

实验数据

实验数据集

Cifar-10, Cifar-100。 使用Cifar的原因没有别的, 就因为穷,毕竟要像Focal Loss论文里那样跑那么多的大实验对大部分学校和企业来说是不现实的。

处理数据得到类间不平衡

将多分类任务转换成二分类:

new_label = label == 1
 
 
  • 1

原始Cifar-10和100里有很多类别,每类图片的数量基本一样。按照这种方式转变后,多分类变成了二分类, 且正负样本比例相差悬殊: 9倍和99倍。

实验模型

一个5层的CNN,完成一个不平衡的二分类任务。使用Cross Entropy Loss,按照不同的方法使用不同的权值方案。以不加任何权重的CE Loss作为baseline。

衡量方式

在这种不平衡的二分类问题里, 准确率已经不适合用来衡量模型的好与坏了。此处使用F-Score作标准.

实现细节

CE(Cross Entroy Loss)

J=1n1nlog(pt)

pt 的含义与Focal Loss论文里一致:
p=sigmoid(x)

pt={p, if y==11p, if y1

用tensorflow实现:
ce_loss = tf.nn.sigmoid_cross_entropy_with_logits(
                    labels = labels, logits = logits)
num_samples = tf.cast(tf.reduce_prod(tf.shape(labels)), tf.float32)
loss = tf.reduce_sum(ce_loss) / num_samples
 
 
  • 1
  • 2
  • 3
  • 4

OHEM

分为以下三步:
1. 计算ce_loss, 同CE
2. 根据ce_loss排序, 选出top N 个sample:

num_examples = tf.reduce_prod(labels.shape)
n_selected = num_examples / 2 # 根据个人喜好只选最难的一半
# find the most wrongly classified examples:

n_selected = tf.cast(n_selected, tf.int32)
vals, _ = tf.nn.top_k(ce_loss, k = n_selected)
th = vals[-1]
selected_mask = ce_loss >= th
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

3 . 只计算被选样sample的loss:

loss_weight = tf.cast(selected_mask, tf.float32) 
loss = tf.reduce_sum(ce_loss * loss_weight) / tf.reduce_sum(loss_weight)
 
 
  • 1
  • 2

OHNM(OHEM 1:3)

比OHEM要多一步:
1. 计算ce_loss, 同CE
2. 保留所有positive sample:

pos_weight = tf.cast(tf.equal(labels, 1), tf.float32)
n_pos = tf.reduce_sum(pos_weight)
 
 
  • 1
  • 2

3 . 在negative sample里选择出3 * n_pos个最难的:

n_neg = tf.reduce_sum(1 - pos_weight)
n_selected = tf.minimum(n_pos * 3, n_neg)# 防止选择数量超过negative sample的个数
n_selected = tf.cast(tf.maximum(n_selected, 1), tf.int32) #防止出现什么也没选

neg_mask = tf.equal(labels, 0)
hardness = tf.where(neg_mask, ce_loss, tf.zeros_like(ce_loss))
vals, _ = tf.nn.top_k(hardness, k = n_selected)
th = vals[-1]
selected_neg_mask = tf.logical_and(hardness >= th, neg_mask)
neg_weight = tf.cast(selected_neg_mask, tf.float32)
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

4 . 计算加权loss:

loss_weight = pos_weight + neg_weight
loss = tf.reduce_sum(ce_loss * loss_weight) / tf.reduce_sum(loss_weight)
 
 
  • 1
  • 2

Class Balance CE

形式多种多样,我个人最喜欢使用:

J=12(Jposnpos+Jnegnneg)

实现步骤:
1 . 找出positive 与negative
pos_weight = tf.cast(tf.equal(labels, 1), tf.float32)
neg_weight = 1 - pos_weight
 
 
  • 1
  • 2

2 . 分别计算loss,然后加起来

n_pos = tf.reduce_sum(pos_weight)
n_neg = tf.reduce_sum(neg_weight)
def has_pos():
     return tf.reduce_sum(ce_loss * pos_weight) / n_pos
def has_neg(): 
     return tf.reduce_sum(ce_loss * neg_weight) / n_neg
def no():
     return tf.constant(0.0)
pos_loss = tf.cond(n_pos > 0, has_pos, no)
neg_loss = tf.cond(n_neg > 0, has_neg, no)

loss = (pos_loss + neg_loss) / 2.0
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Focal Loss

J=αt(1pt)γlog(pt)

其中, αt={α, for y==11α, for y1 pt 的计算方式上面已经写过, α,γ 都是超参数。
实现步骤:
1. 计算 pt :
probs = tf.sigmoid(logits)

alpha_t = tf.ones_like(logits) * alpha
alpha_t = tf.where(labels > 0, alpha_t, 1.0 - alpha_t)
probs_t = tf.where(labels > 0, probs, 1.0 - probs)

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2 . 根据 pt 计算权重:


weight_matrix = alpha_t * tf.pow((1.0 - probs_t), gamma)

 
 
  • 1
  • 2
  • 3

3 .得到loss:

n_pos = tf.reduce_sum(labels)
loss = tf.reduce_sum(weight_matrix * ce_loss)/n_pos
 
 
  • 1
  • 2

优化方法

  • 最简单的SGD, 初始lr=0.1, 每200,000步衰减一次, 衰减系数为0.1。Cifar-100上focal_loss的初始lr=0.01。
  • batch_size = 128.

实验结果

CIFAR-10:

MethodPrecisionRecallF-Score
CE0.9290.9090.919
CE+OHNM0.9520.9210.936
CE+OHEM0.9390.9110.926
CE+Class Balance0.8810.9370.908
CE+Focal Loss (γ=2,α=0.75) 0.9250.9280.926

CIFAR-100:

MethodPrecisionRecallF-Score
CE0.9280.6400.757
CE+OHNM000
CE+OHEM0.9030.6500.756
CE+Class Balance0.4680.9000.516
CE+Focal Loss (γ=2,α=0.75) 0.8260.7100.763
  • 对于简单的task, CE loss也能很好的工作, 但task难度增加,可能就不work了
  • OHNM在Cifar-10时表现尚可, 但在Cifar-100中没有训练出来。 主要原因是Cifar-100中正样本太少( 1% ), batch_size等于128时,平均只有1.28个positive,1.28*3个negative sample被用来计算loss, 训练效率很低。
  • OHEM很鲁棒
  • Class Balance在任务复杂时表现不大好
  • Focal Loss在简单和复杂任务中表现都很好。 γ=2,α=0.75 可以适用于不同task。

Focal Loss的一个补丁

对于CIFAR-100,batch_size=128时, 一个batch内可能会一个positive sample都没有, 即n_pos == 0, 这时,paper里用n_pos来normalize loss 的方式就不可行了。测试过两种简单的选择: 一是用所有weight之和来normalize, 二是直接不normalize。前者很难训练甚至训练不出来, 后者可用。所以上面的Focal loss计算代码应该补充为:

#normalize:
n_pos = tf.reduce_sum(labels)
def has_pos():
    return loss / tf.cast(n_pos, tf.float32)
def no_pos():
    #total_weights = tf.stop_gradient(tf.reduce_sum(weight_matrix))
    #return loss / total_weights # 不可行
    return loss # 可行
loss = tf.cond(n_pos > 0, has_pos, no_pos)
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

经验总结

最终选择哪个loss还是各自在task上的表现, 尝试的顺序可以是:
1. 第一选择是Focal Loss: γ=2,α=0.750.5 。(0.5为paper里的建议值, 0.75为Cifar实验上得到的最佳值,两都可以, 对结果造成的影响很小(小于一个点)
2. 第二选择是OHEM
如果这两个loss上都得不到好的结果,基本可以确定算法有问题。如果可以得到好的结果, 还可以继续尝试OHNM。

Code Available On Github

https://github.com/dengdan/test_tf_models
Branch:focal_loss

References

  1. Focal Loss for Dense Object Detection, https://arxiv.org/pdf/1708.02002.pdf
  2. RCNN, https://arxiv.org/abs/1311.2524
  3. Fast RCNN, http://arxiv.org/abs/1504.08083
  4. Faster-RCNN, http://arxiv.org/abs/1506.01497
  5. Training Region-based Object Detectors with Online Hard Example Mining, https://arxiv.org/abs/1604.03540

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值