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

参考链接:http://www.yueye.org/2018/weighted-object-detection-using-detectron.html

 

使用深度学习解决分类问题时,类间不平衡是一个常见的问题,我们也有很多常用的方法去解决这一问题。比如,对类别少的样本进行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同样意义不大,所以我们进行加权,主要加权的部分就是后面的Fast-RCNN部分的classification loss。

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

2.Detectron的caffe2

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

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

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

  1. import numpy as np
  2. from caffe2.python import core, workspace
  3.  
  4. labels_array = np.array(...)
  5. labels = workspace.FeedBlob('labels', labels_array)

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

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

  1. from caffe2.python import core, workspace
  2.  
  3. labels = workspace.FetchBlob('labels')
  4. # labels = workspace.FetchBlob(core.ScopedName('labels'))

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

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

 

注意:这里给的链接都是老版本的链接,需要在github上搜一下,找到相应的文件

 

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即可。

 

如下:在背景后面添加权重值

    # Label is the class each RoI has max overlap with
    sampled_labels = roidb['max_classes'][keep_inds]
    sampled_labels[fg_rois_per_this_image:] = 0  # Label bg RoIs with class 0
    #add weights,refer to the web blog
    weights=(np.where(sampled_labels==9,20,1)).astype(np.float32)

 

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

  1. blob_dict = dict(
  2. labels_int32=sampled_labels.astype(np.int32, copy=False),
  3. #add this line
  4. weights=weights,
  5. rois=sampled_rois,
  6. bbox_targets=bbox_targets,
  7. bbox_inside_weights=bbox_inside_weights,
  8. bbox_outside_weights=bbox_outside_weights
  9. )

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

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

  1. # Add weights
  2. if is_training:
  3. 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函数。如下:

  1. def augment_for_training(roidb):
  2. """Augment roidb entries that have some RoIs.
  3. """
  4. num = len(roidb)
  5. auged_roidb = []
  6. for db in roidb:
  7. if np.sum(db['gt_classes']==2)>=1:
  8. auged_roidb.append(db)
  9. auged_roidb.append(db)
  10. else:
  11. auged_roidb.append(db)
  12. num_after = len(auged_roidb)
  13. logger.info('Augment {} roidb entries: {} -> {}'.
  14. format(num_after - num, num, num_after))
  15. return auged_roidb

然后在在combined_roidb_for_training(dataset_names, proposal_files)函数中,调用该函数

    roidb = filter_for_training(roidb)
    roidb = augment_for_training(roidb)

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

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值