难例挖掘与非极大值抑制 NMS 一样,都是为了解决目标检测老大难问题(样本不平衡+低召回率)及其带来的副作用。
非极大值抑制(NMS)的详解与实现
首先,目标检测与图像分类不同,图像分类往往只有一个输出,但目标检测的输出个数却是未知的。除了Ground-Truth(标注数据)训练时,模型永远无法百分百确信自己要在一张图上预测多少物体。
所以目标检测问题的老大难问题之一就是如何提高召回率。召回率(Recall)是模型找到所有某类目标的能力(所有标注的真实边界框有多少被预测出来了)。检测时按照是否检出边界框与边界框是否存在,可以分为下表四种情况:
是所有某类物体中被检测出的概率,并由下式给出:
为了提高这个值,很直观的想法是“宁肯错杀一千,绝不放过一个”。因此在目标检测中,模型往往会提出远高于实际数量的区域提议(Region Proposal,SSD等one-stage的Anchor也可以看作一种区域提议)。但此时就会遇到一个问题,因为区域提议实在太多,导致在训练时绝大部分都是负样本,这导致了大量无意义负样本的梯度“淹没”了有意义的正样本。根据Focal Loss[1]论文的统计,通常包含少量信息的“easy examples”(通常是负例),与包含有用信息的“hard examples”(正例+难负例)之比为100000:100!这导致这些简单例的损失函数值将是难例损失函数的40倍!
因此,为了让模型正常训练,我们必须要通过某种方法抑制大量的简单负例,挖掘所有难例的信息,这就是难例挖掘的初衷。
难负例挖掘(Hard Negative Mining)就是在训练时,尽量多挖掘些难负例(hard negative)加入负样本集,这样会比easy negative组成的负样本集效果更好。
对于现在的我们,首先遇到难负例挖掘应该是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:
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,需要满足两个条件:
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
Hard negative mining
rcnn中的Hard negative mining方法是如何实现的?