一、问题概述
在目标检测问题中,现有的方法通常被分为两大类:One-Stage Detector(一步法) 和 Two-Stage Detactor(两步法),并且,该领域的研究者基本都达成了一种共识,即:两步法拥有更好的精度但是速度会相对较慢,一步法则反之。
对该领域的研究者(哪怕是刚进入该领域不久的新人)而言,以上的结论肯定已经十分熟悉,甚至可能认为该结论并不需要过多的解释。但是,如果深究其中缘由,却往往不能清楚地做出解释,因此,不妨给自己提出两个问题,即:为什么两步法拥有比一步法更好的精度,但速度往往较差?这种结论一定是正确的吗?
二、两种方法的比较
一步法与两步法最大的区别就是两步法会使用某种区域提议方法生成一定数量的候选区域(如R-CNN的Select Search、Faster R-CNN和Mask R-CNN的RPN),而一步法则没有生成候选区域的步骤。
但是这种说法其实并不完全正确,因为即使是一步法也有一个bounding boxes集合,只是这个bounding boxes集合是默认生成的,而不经过任何筛选。如SSD就使用与Faster R-CNN的锚框机制类似的方法,为每一层次的特征图的每一个单元格都生成一个bounding boxes集合。因此在广义上看来,两类方法都是先获得bounding boxes,然后对bouding boxes做分类与回归。二者的不同在于:
- 二步法使用候选区域提议筛除了绝大多数非前景的bounding boxes,如下图的RPN网络所示,网络头部对原始的bounding boxes集合做了分类(前景与背景的二分类)与回归(前景的粗略定位);与之相比,一步法不对原始bounding boxes集合做任何处理,因此一步法的bounding boxes在数量上是两步法的上百倍,并且前景与背景的bounding boxes比例悬殊。
根据上述内容,是否能对二者的精度、速度问题进行解释呢?就速度而言,一步法没有区域提议过程,因此相对而言更快,这一点毋庸置疑,但bounding boxes集合的差异对二者精度的影响却依旧值得深究。
三、一步法中bounding boxes集的问题
在训练过程中,一步法需要将生成的bounding boxes集合与标注信息进行匹配,然后将该集合分为正样本和负样本,从而计算最终的Loss。这种操作是合理的,但是因为一步法的bounding boxes集合本身的某些缺陷,可能会使训练过程变得比较困难,并影响到最终的训练结果。
前面已经分析过,因为一步法不对其bounding boxes集合进行任何的筛除,所以该集合中属于前景与背景的bounding boxes的数量比例十分悬殊。这将导致bounding boxes集中负样本将远远多于正样本(因为绝大多数的bounding boxes都属于背景),并且,集合中的简单样本也会远远多于困难样本。因此,样本不均衡将最终影响到网络训练与训练结果,这就是一步法精度受限的一部分原因。
Note:但是此出还有疑问,为什么样本不均衡会影响网络训练?这可能也会被认为是一个不需要解释的问题,但我考虑的是,能否就样本不均衡对网络训练的影响做出完备的数学解释?
四、样本不均衡问题的解决方案
1.SSD中解决方案
SSD中解决样本不均衡问题的方法是hard negative mining,即训练时只选取置信度损失最大的负样本,以此将负样本与正样本的比例控制在3 : 1。下面是SSD中选取置信度损失最大的负样本的具体实现,很明显,每次选取bounding boxes的数量都会使用参数top_k和keep_top_k来控制。
def detected_bboxes(self, predictions, localisations,
select_threshold=None, nms_threshold=0.5,
clipping_bbox=None, top_k=400, keep_top_k=200):
"""Get the detected bounding boxes from the SSD network output.
"""
# Select top_k bboxes from predictions, and clip
rscores, rbboxes = \
ssd_common.tf_ssd_bboxes_select(predictions, localisations,
select_threshold=select_threshold,
num_classes=self.params.num_classes)
rscores, rbboxes = \
tfe.bboxes_sort(rscores, rbboxes, top_k=top_k)
# Apply NMS algorithm.
rscores, rbboxes = \
tfe.bboxes_nms_batch(rscores, rbboxes,
nms_threshold=nms_threshold,
keep_top_k=keep_top_k)
# if clipping_bbox is not None:
# rbboxes = tfe.bboxes_clip(clipping_bbox, rbboxes)
return rscores, rbboxes
def bboxes_sort(classes, scores, bboxes, top_k=400):
"""Sort bounding boxes by decreasing order and keep only the top_k
"""
# if priority_inside:
# inside = (bboxes[:, 0] > margin) & (bboxes[:, 1] > margin) & \
# (bboxes[:, 2] < 1-margin) & (bboxes[:, 3] < 1-margin)
# idxes = np.argsort(-scores)
# inside = inside[idxes]
# idxes = np.concatenate([idxes[inside], idxes[~inside]])
idxes = np.argsort(-scores)
classes = classes[idxes][:top_k]
scores = scores[idxes][:top_k]
bboxes = bboxes[idxes][:top_k]
return classes, scores, bboxes
2.RetinaNet中的解决方案
RetinaNet设计了一种新的损失函数Focal Loss来解决样本不均衡问题。如下所示是交叉熵定义的损失函数,
是RetinaNet中使用的损失函数。
以下是的图像,其中红色曲线
(等价于
),蓝色曲线
,绿色曲线
。仅仅通过观察函数图像可以发现,
的超参数
变大将使曲线下降,这意味着那些
接近1的样本的损失值将随着
变大而变小。也就是说,新的损失函数
可以使网络尽可能得专注于
接近0.5的样本(困难样本),而忽略
接近1的样本(简单样本)。
def focal_loss_(labels, pred, anchor_state, alpha=0.25, gamma=2.0):
# filter out "ignore" anchors
indices = tf.reshape(tf.where(tf.not_equal(anchor_state, -1)), [-1, ])
labels = tf.gather(labels, indices)
pred = tf.gather(pred, indices)
logits = tf.cast(pred, tf.float32)
onehot_labels = tf.cast(labels, tf.float32)
ce = tf.nn.sigmoid_cross_entropy_with_logits(labels=onehot_labels, logits=logits)
predictions = tf.sigmoid(logits)
predictions_pt = tf.where(tf.equal(onehot_labels, 1), predictions, 1.-predictions)
alpha_t = tf.scalar_mul(alpha, tf.ones_like(onehot_labels, dtype=tf.float32))
alpha_t = tf.where(tf.equal(onehot_labels, 1.0), alpha_t, 1-alpha_t)
loss = ce * tf.pow(1-predictions_pt, gamma) * alpha_t
positive_mask = tf.cast(tf.greater(labels, 0), tf.float32)
return tf.reduce_sum(loss) / tf.maximum(tf.reduce_sum(positive_mask), 1)
3.更多的解决方案
除以上两种方法之外,现在还有一些其他的方法可用于解决样本不均衡问题,这些也是值得学习的内容。
网友评论