Hard Example Mining
Hard Negatie Mining与Online Hard Example Mining(OHEM)都属于难例挖掘,它是解决目标检测老大难问题的常用办法,运用于R-CNN,fast R-CNN,faster rcnn等two-stage模型与SSD等(有anchor的)one-stage模型训练时的训练方法。
难例挖掘的思想可以解决很多样本不平衡/简单样本过多的问题,比如说分类网络,将hard sample 补充到数据集里,重新丢进网络当中,就好像给网络准备一个错题集,哪里不会点哪里。
ps:本文中基本可以将“region proposal 区域提议”与“Region of Interest (RoI)”看成一个东西。
最后,大家可能注意到OHEM和难负例挖掘名字上的不同。
-
- Hard Negative Mining只注意难负例
- OHEM 则注意所有难例,不论正负(Loss大的例子)
什么是难例挖掘?
难例挖掘与非极大值抑制 NMS 一样,都是为了解决目标检测老大难问题(样本不平衡+低召回率)及其带来的副作用。
首先,目标检测与图像分类不同,图像分类往往只有一个输出,但目标检测的输出个数却是未知的。除了Ground-Truth(标注数据)训练时,模型永远无法百分百确信自己要在一张图上预测多少物体。
所以目标检测问题的老大难问题之一就是如何提高召回率。召回率(Recall)是模型找到所有某类目标的能力(所有标注的真实边界框有多少被预测出来了)。检测时按照是否检出边界框与边界框是否存在,可以分为下表四种情况:
检测出边界框 | 未检出边界框 | |
---|---|---|
边界框存在 | 真阳性(TP) | 假阴性(FN) |
边界框不存在 | 误报(FP) | 真阴性(TN) |
召回率是所有某类物体中被检测出的概率,并由下式给出:
为了提高这个值,很直观的想法是“宁肯错杀一千,绝不放过一个”。因此在目标检测中,模型往往会提出远高于实际数量的区域提议(Region Proposal,SSD等one-stage的Anchor也可以看作一种区域提议)。
但此时就会遇到一个问题,因为区域提议实在太多,导致在训练时绝大部分都是负样本,这导致了大量无意义负样本的梯度“淹没”了有意义的正样本。
根据Focal Loss论文的统计,通常包含少量信息的“easy examples”(通常是负例),与包含有用信息的“hard examples”(正例+难负例)之比为100000:100!这导致这些简单例的损失函数值将是难例损失函数的40倍!
因此,为了让模型正常训练,我们必须要通过某种方法抑制大量的简单负例,挖掘所有难例的信息,这就是难例挖掘的初衷。
难负例挖掘(Hard Negative Mining)就是在训练时,尽量多挖掘些难负例(hard negative)加入负样本集,这样会比easy negative组成的负样本集效果更好。
R-CNN中的hard negative mining
对于现在的我们,首先遇到难负例挖掘应该是R-CNN的论文,论文关于hard negative mining的部分引用了两篇论文:
上述论文原文节选:
Bootstrapping methods train a model with an initial subset of negative examples, and then collect negative examples that are incorrectly classified by this initial model to form a set of hard negatives. A new model is trained with the hard negative examples, and the process may be repeated a few times.
we use the following “bootstrap” strategy that incrementally selects only those “nonface” patterns with high utility value:
- Start with a small set of “nonface” examples in the training database.
- Train the MLP classifier with the current database of examples.
- Run the face detector on a sequence of random images. Collect all the “nonface” patterns that the current system wrongly classifies as “faces”.Add these “nonface” patterns to the training database as new negative examples.
- Return to Step 2.
而R-CNN中的难负例挖掘就是采用了这种自举法(bootstrap)的方法:
-
- 先用初始的正负样本训练分类器(此时为了平衡数据,使用的负样本也只是所有负样本的子集)
- 用(1)训练好的分类器对样本进行分类,把其中错误分类的那些样本(hard negative)放入负样本子集,
- 再继续训练分类器,
- 如此反复,直到达到停止条件(比如分类器性能不再提升).
也就是说,R-CNN的Hard Negative Mining相当于给模型定制一个错题集,在每轮训练中不断“记错题”,并把错题集加入到下一轮训练中,直到网络效果不能上升为止。
R-CNN中Hard Negative Mining的实现代码:
rcnn/rcnn_train.m at master · rbgirshick/rcnn Line:214开始的函数定义
在源文件rcnn_train.m中函数 sample_negative_feature用于采样负例特征,对函数定义的关键代码稍作解释。
d.feat = rcnn_pool5_to_fcX(d.feat, opts.layer, rcnn_model); %将pool5层特征前向传播到fc7层特征
d.feat = rcnn_scale_features(d.feat, opts.feat_norm_mean); % 缩放特征norm值,具体参见源码注释
neg_ovr_thresh = 0.3;
if first_time % 首次直接取 最大IOU < 0.3 的region作为负例,用于后面训练SVM
for cls_id = class_ids
I = find(d.overlap(:, cls_id) < neg_ovr_thresh);
X_neg{cls_id} = d.feat(I,:);
keys{cls_id} = [ind*ones(length(I),1) I];
end
else % 非首次负例采样
% 先用当前更新过的SVM 预测region,即,应用SVM到 fc7的特征上,y'=w*x+b
zs = bsxfun(@plus, d.feat*rcnn_model.detectors.W, rcnn_model.detectors.B);
for cls_id = class_ids % 每个分类独立使用SVM
z = zs(:, cls_id); % 对当前分类,获取 SVM 计算值 y'
% 下一行代码是关键
I = find((z > caches{cls_id}.hard_thresh) & ...
(d.overlap(:, cls_id) < neg_ovr_thresh));
% Avoid adding duplicate features
keys_ = [ind*ones(length(I),1) I];
if ~isempty(caches{cls_id}.keys_neg) && ~isempty(keys_)
[~, ~, dups] = intersect(caches{cls_id}.keys_neg, keys_, 'rows');
keep = setdiff(1:size(keys_,1), dups);
I = I(keep);
end
% Unique hard negatives
X_neg{cls_id} = d.feat(I,:);
keys{cls_id} = [ind*ones(length(I),1) I];
end
end
上述代码片段中,非首次负例采样时,要筛选出 难负例example,需要满足两个条件:
-
- 负例,即最大
IOU
小于 阈值d.overlap(:, cls_id) < neg_ovr_thresh
- 分类错误,既然是负例,那么SVM计算值wx+b 应该小于 1,
- 负例,即最大
以下图简单的回顾一下SVM,
位于H1和H2超平面上的实例就称为支持向量,对于y=1
的正例点,有H1: wx + b =1
对于y=-1
的负例点,H2: wx + b = -1
分类正确时应满足,
wx+b>=1, if y = 1
wx+b <=-1, if y = -1
但是,SVM为了处理非严格线性可分的数据集,引入了松弛变量,于是如下图,
于是 只要 wx+b>-1
,都可以认为是正例,只不过 wx+b
越小,置信度越低。
在inference阶段,同样是这个思路,比如rcnn_detect.m文件中
thresh = -1
scores = bsxfun(@plus, feat*rcnn_model.detectors.W, rcnn_model.detectors.B);
for i = 1:num_classes
I = find(scores(:, i) > thresh);
scored_boxes = cat(2, boxes(I, :), scores(I, i));
keep = nms(scored_boxes, 0.3);
dets{i} = scored_boxes(keep, :);
end
Fast RCNN中的hard negative mining
Fast RCNN中,将真实标签边框(gt boxes)和区域提议边框(selective search boxes)一起打包为roidb。最后通过采样,将每个minibatch的数据输入训练。
ps:在Faster RCNN中,由于使用了RPN生成区域提议,所以网络输入roidb只有gt boxes。
对每个大小为N的mini-batch进行采样?
随机选取N个图片(通常N=2),然后每个图片中选择64个ROIs(Region of Interests),其中25%为正例,即ROIs的最大IOU 大于等于0.5,剩余的为负例,选择最大IOU位于[0.1,0.5)范围内的roi作为负例。
ps:如果正例数量不足25%,则增加负例数量,使得每个image中采样ROI数量为64,于是一个mini-batch中共有128个ROIs。
Fast RCNN 采用 random sampling 策略,即训练时随机取样
如何确定正负例?
Fast RCNN 中以区域提议与 groud truth 的 IoU 为标准区分正负例,假设阈值1为0.5,阈值2为0.1:
-
- [0.5,1] 为正例
- [0.1, 0.5) 之间标记为负例,
- [0, 0.1) 的 example 用于 hard negative mining.
不过很吊诡的是,即使直到要有难负例挖掘,训练的时候依然是用负例采样,那难负例哪去了?
难负例哪去了?
hard negative,顾名思义是难以正确分类的样本,也就是说在对负样本分类时候,loss比较大(label与prediction相差较大)的那些样本,也可以说是容易将负样本看成正样本的那些样本;
那就怪了,比方说,如果有个IoU=0.4999的区域提议,它也会被当成负例。显然这张图片更难和正样本区分,那么训练不应该更难吗?
我们可以先验的认为, 如果 RoI(或者说区域提议)里没有物体,全是背景,这时候分类器很容易正确分类成背景,这个就叫 easy negative, 如果RoI(或者说区域提议)里有二分之一个物体,标签仍是负样本,这时候分类器就容易把他看成正样本,这时候就是 hard negative。
确实, 不是一个框中背景和物体越混杂, 越难区分吗? 框中都基本没有物体特征, 不是很容易区分吗?
我看了很多文章的解释,目前只能认为 Fast RCNN 是这么想的:
为了解决正负样本不均衡的问题(负例太多了), 我们应该剔除掉一些容易分类负例, 那么与 ground truth 的 IOU 在 [0, 0.1)之间的由于包含物体的特征很少, 应该是很容易分类的, 也就是说是 easy negitive, 为了让算法能够更加有效, 也就是说让算法更加专注于 hard negitive examples, 我们认为 hard negitive examples 包含在[0.1, 0.5) 的可能性很大, 所以训练时, 我们就在[0.1, 0.5)区间做 random sampling, 选择负例。
但是,我们先验的认为 IoU 在[0, 0.1)之内的RoI是 easy example,是一种一厢情愿的想法。
事实上,剔除IoU过低的数据后,模型可能反而对这些“背景”感到陌生。所以要对其做额外的 hard negitive mining, 找到其中的 hard negitive examples 用于训练网络。
总而言之,在two-stage 模型中,提出的RoI Proposal在输入R-CNN子网络前,对正负样本(背景类和前景类)的比例进行调整。
通常,背景类的RoI Proposal个数要远远多于前景类,Fast R-CNN的处理方式是随机对两种样本进行上采样和下采样,以使每一batch的正负样本比例保持在1:3,这一做法缓解了类别比例不均衡的问题,是两阶段方法相比单阶段方法具有优势的地方,也被后来的大多数工作沿用。
四、Online Hard Example Mining(OHEM)
CVPR2016的Oral论文:Training Region-based Object Detectors with Online Hard Example Mining[6]将难分样本挖掘(hard example mining)机制嵌入到SGD算法中,使得Fast R-CNN在训练的过程中根据区域提议的损失,自动选取合适的区域提议作为正负例训练。
在之前,RCNN采用了错题集的办法进行挖掘,Fast RCNN使用的是IoU阈值+随机采样的方法进行Hard Negative Mining,也就是都必须先结束一轮训练,有点事后诸葛亮的感觉。OHEM的工作中,作者提出:
用R-CNN子网络对RoI Proposal预测的分数,来决定每个batch选用的样本,这样,输入R-CNN子网络的RoI Proposal总为表现不好的样本,提高了监督学习的效率。
实际操作中,维护两个完全相同的R-CNN子网络,其中:
-
- 一个只进行前向传播来为RoI Proposal的选择提供指导,
- 另一个则为正常的R-CNN,参与损失的计算并更新权重,并且将权重复制到前者以使两个分支权重同步。
OHEM以额外的R-CNN子网络的开销来改善RoI Proposal的质量,更有效地利用数据的监督信息,成为两阶段模型提升性能的常用部件之一。
即:训练的时候选择hard negative来进行迭代,从而提高训练的效果。
-
- 前向时: 全部的ROI通过网络,根据loss排序;
- 反向时:根据排序,选择Batch-Size/N(minibatch中proposal数除以输入图片数)个loss值最大的(worst)样本来后向传播更新model的weights。
在此之前必须注意,位置相近的ROI在map中可能对应的是同一个位置,loss值相近,所以选取minibatch的RoI之前,要先对Hard程度(loss)做NMS,然后再选择Batch-Size/N个ROI反向传播,这里nms选择的IoU阈值为0.7。
ps:这里的NMS与这篇笔记提到的用于目标检测的NMS类似,不过这里的排序依据不是置信度,而是损失函数的大小。
还有注意的一点是,前反向时使用的不是同一个网络:
-
- 一个只用来前向传播的部分来筛选RoI,
- 另一个把选完的ROIs进行后向传播,
这样的虽然要在内存维护两个网络的参数,但好处是不需要(在计算所有区域提议loss的同时)计算所有区域提议的反向传播了。是一种空间换时间的策略!
给定图像和选择性搜索结果的RoI(现在不用selective search了,但道理一样),卷积网络计算转换特征映射。 在(a)中,只读RoI网络在特征映射和所有RoI上运行正向传递(以绿色箭头显示)。 然后Hard RoI模块使用这些RoI损失来选择B个样本。 在(b)中,RoI网络使用这些硬性示例来计算前向和后向通道(以红色箭头示出)。
实验结果表明使用OHEM(Online Hard Example Mining)机制可以使得Fast R-CNN算法在VOC2007和VOC2012上mAP提高 4%左右。
五、结语
难例挖掘与非极大值抑制一样,是一种流行于计算机科学领域数十年的经典算法,经典到老前辈不认为它需要细讲。然而对一些从深度学习算法入手的萌新来说,这种细枝末节的东西就有些麻烦了。
不过也正因如此,难例挖掘的确是一个用于平衡样本的好办法,因此它可以运用的范围远远不止于目标检测中。分类、分割等问题都可以找到它的影子。