参考链接: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来详细描述一下这一问题,以期提供一些经验参考。
Faster-RCNN一共有4个loss,包括前面的RPN的classification loss和regression loss,以及后面的classification loss和regression loss。但RPN部分除了前景、背景的分类之外,一般不按具体的类别进行区分,所以,我们无法对这一部分进行加权。而且由于这一部分仅仅进行region proposal,与具体的类别关系不大,所以对其进行加权也意义不大。至于后面的Fast-RCNN部分,对边框进行回归的regression loss同样意义不大,所以我们进行加权,主要加权的部分就是后面的Fast-RCNN部分的classification loss。
当然,除了对不同类别的分类进行加权之外,我们还考虑到这样一个情况,即有时候分类错误的损失影响较大,而只要检测出来,检测到的物体的边框并不一定要求特别精确,所以,通常会针对具体问题,对这个地方进行修改,改变两者的权重占比。
caffe2是Facebook推出的主要面向产品部署等场景的深度学习框架,而针对于研究等方向,Facebook则推出的是简单易用的PyTorch;因此,caffe2过于关注了性能,其易用性并不好。所以,基于caffe2的Detectron用起来虽然训练速度很好,但想对其进行修改,却并非易事。
与caffe类似,caffe2也是使用Blob来管理数据的。所以,在caffe2中,传进传出的参数,通常只有一个名字,这让很多人摸不着头脑,也使得调试十分困难。
在caffe2中,新建一个Blob可以使用如下代码:
这样,我们就新建了一个名为labels的Blob。
然后,我们可以在其他地方使用,使用时,直接把数据fetch出来即可:
如果labels是一个全局名,可以直接进行fetch;如果它是定义在一个scope里面的Blob,则需要首先对名字进行一下处理,否则可能提示找不到相应的Blob。
在Detectron中,有很多使用Blob的例子,如这个,以及这个。
注意:这里给的链接都是老版本的链接,需要在github上搜一下,找到相应的文件
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倍。
我们首先来看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即可,如下:
不过,在此之前,需要添加一个名为weights的Blob。
在fast_rcnn.py里的get_fast_rcnn_blob_names函数中,直接添加即可,如下:
如此,即可为指定类别的Object进行加权,从而突出其在object detection时的重要性,对其进行特殊对待。
有时候,我们不仅想要对这些类别进行加权,我们还希望对出现了这个类别的图片进行加权,这时候应该这么处理呢?
这个比较简单,直接在最开始读数据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函数。如下:
然后在在combined_roidb_for_training(dataset_names, proposal_files)函数中,调用该函数
roidb = filter_for_training(roidb)
roidb = augment_for_training(roidb)
这样,我们将含有label为2的图片复制成了两份,即相当于对其完成了加权。