通过加权解决Detectron训练object detection模型时的类间不平衡问题

在 2018-05-04 发布于 人工智能 下以来已有187人读过本文 | 0条评论 发表评论

使用深度学习解决分类问题时,类间不平衡是一个常见的问题,我们也有很多常用的方法去解决这一问题。比如,对类别少的样本进行augment,或者重采样;对类别多的样本进行降采样;根据不同类别的样本数目对损失函数进行加权;或者简单粗暴地对较少的样本在数据集内进行复制;等等。

不过这些方法中,考虑到数据对深度学习的重要性,一般不会使用对数据进行降采样的方法;而augment、重采样以及复制数据,都需要对数据进行直接操作,也并不如修改损失函数进行加权的方法简单优雅。这种对损失函数进行加权的方法如下:

Loss = class_of_less_samples_loss * weights + class_of_more_samples_loss

不过,这种方法虽然简单优雅,对分类问题进行处理也简单方便,但如果是目标检测问题,则可能需要考虑更多东西;如果我们使用的是像Detectron这样包装严密的框架,处理起来可能难上加难。本文,我们就以Detectron里的Faster-RCNN来详细描述一下这一问题,以期提供一些经验参考。

1.Faster-RCNN的loss

Faster-RCNN一共有4个loss,包括前面的RPN的classification loss和regression loss,以及后面的classification loss和regression loss。但RPN部分除了前景、背景的分类之外,一般不按具体的类别进行区分,所以,我们无法对这一部分进行加权。而且由于这一部分仅仅进行region proposal,与具体的类别关系不大,所以对其进行加权也意义不大。至于后面的Fast-RCNN部分,对边框进行回归的regression loss同样意义不大,所以我们进行加权,主要加权的部分就是这里的classification loss。

当然,除了对不同类别的分类进行加权之外,我们还考虑到这样一个情况,即有时候分类错误的损失影响较大,而只要检测出来,检测到的物体的边框并不一定要求特别精确,所以,通常会针对具体问题,对这个地方进行修改,改变两者的权重占比。

2.Detectron的caffe2

caffe2是Facebook推出的主要面向产品部署等场景的深度学习框架,而针对于研究等方向,Facebook则推出的是简单易用的PyTorch;因此,caffe2过于关注了性能,其易用性并不好。所以,基于caffe2的Detectron用起来虽然训练速度很好,但想对其进行修改,却并非易事。

与caffe类似,caffe2也是使用Blob来管理数据的。所以,在caffe2中,传进传出的参数,通常只有一个名字,这让很多人摸不着头脑,也使得调试十分困难。

在caffe2中,新建一个Blob可以使用如下代码:

import numpy as np
from caffe2.python import core, workspace

labels_array = np.array(...)
labels = workspace.FeedBlob('labels', labels_array)

这样,我们就新建了一个名为labels的Blob。

然后,我们可以在其他地方使用,使用时,直接把数据fetch出来即可:

from caffe2.python import core, workspace

labels = workspace.FetchBlob('labels')
# labels = workspace.FetchBlob(core.ScopedName('labels'))

如果labels是一个全局名,可以直接进行fetch;如果它是定义在一个scope里面的Blob,则需要首先对名字进行一下处理,否则可能提示找不到相应的Blob。

在Detectron中,有很多使用Blob的例子,如这个,以及这个

3.修改Detectron中Faster-RCNN的loss_cls和loss_bbox的比例

Faster-RCNN中,建立这两个loss是在fast_rcnn_heads.py中的add_fast_rcnn_losses函数,该函数中,分别使用model.net.SoftmaxWithLoss和model.net.SmoothL1Loss得到了相应的loss值。而SoftmaxWithLoss这个函数中的参数scale=model.GetLossScale(),即是控制比例的地方。函数中,这个比例根据使用GPU的数量进行调节,我们为了最小修改原来的内容,直接在这个函数后面加上修改的比例即可,比如,将其修改为:scale=model.GetLossScale()*2,即将loss_cls的比重扩大到原来的2倍。如果与此同时,对SmoothL1Loss的scale不作调整,则相当于将classification的loss权重提高到了regression loss的2倍。

4.根据不同类别的Object,修改其在loss中的权重

我们首先来看3中的loss_cls,这个loss是使用model.net.SoftmaxWithLoss来生成的。查阅这个函数的文档,可以看到,其输入值,除了logits、labels,还可以输入一个Blob作为weights。这就是我们需要修改的地方。

minibatch.py里的get_minibatch函数中,可以看到,读取RPN的Blob是通过roi_data.rpn.add_rpn_blobs来完成的,而读取Fast-RCNN的数据,则是通过roi_data.fast_rcnn.add_fast_rcnn_blobs来完成的。因此,如上分析,我们需要修改进行加权的操作,是需要在后面这个函数中进行的。

fast_rcnn.py里的add_fast_rcnn_blobs函数中,进行了数据的读取操作,具体来说,读取操作是在函数中的_sample_rois(entry, im_scales[im_i], im_i)来完成的。

我们跳转到_sample_rois这个函数里,我们找到labels生成的地方,然后根据labels,来生成我们需要的weights即可。如下:

# Add weights
weights = (np.where(sampled_labels==2, 5, 1)).astype(np.float32)

在这里,我们将label为2的类别的权重设置为了5,而其他类别保持了不变。然后,在下面要反悔的blob_dict里添加上这个weights即可,如下:

blob_dict = dict(
      labels_int32=sampled_labels.astype(np.int32, copy=False),
      weights=weights,
      rois=sampled_rois,
      bbox_targets=bbox_targets,
      bbox_inside_weights=bbox_inside_weights,
      bbox_outside_weights=bbox_outside_weights
)

不过,在此之前,需要添加一个名为weights的Blob。

fast_rcnn.py里的get_fast_rcnn_blob_names函数中,直接添加即可,如下:

# Add weights
if is_training:
  blob_names += ['weights']

如此,即可为指定类别的Object进行加权,从而突出其在object detection时的重要性,对其进行特殊对待。

5.对某些图片进行加权

有时候,我们不仅想要对这些类别进行加权,我们还希望对出现了这个类别的图片进行加权,这时候应该这么处理呢?

这个比较简单,直接在最开始读数据roidb时进行处理即可。在train.py里的combined_roidb_for_training函数,是建立数据roidb用于训练的。该函数定义在roidb.py文件中,在该函数中,我们可以看到一个过滤roidb中数据的函数,该函数用来“Remove roidb entries that have no usable RoIs based on config settings”。我们可以仿照这个函数,构建一个augment_roidb_for_training函数。如下:

def augment_for_training(roidb):
  """Augment roidb entries that have some RoIs.
  """
  num = len(roidb)
  auged_roidb = []
  for db in roidb:
    if np.sum(db['gt_classes']==2)>=1:
      auged_roidb.append(db)
      auged_roidb.append(db)
    else:
      auged_roidb.append(db)
  num_after = len(auged_roidb)
  logger.info('Augment {} roidb entries: {} -> {}'.
              format(num_after - num, num, num_after))
  return auged_roidb

这样,我们将含有label为2的图片复制成了两份,即相当于对其完成了加权。

发表评论

您的昵称 *

您的邮箱 *

您的网站