最近一直做检测,发现检测领域好多好玩的东西啊,R-FCN是msra dai老师和kaiming做的,insight很赞,这次翻出来再学习一下。最近旷视科技又发了light RCNN,检测这领域真是日新月异。
Motivation
虽然fast rcnn共享了每个roi的feature map, faster rcnn利用rpn也使proposal的生成共享了feature map,已经比之前的rcnn减少了大量计算方法,快了很多。但是roi pooling后面的多个fc,每个roi之间是没有共享计算的,而且fc的参数巨多,在之前的faster rcnn源码解析中也都讲过,train时有128个送到后面,test时有300个送到后面,大量的重复计算浪费了很多时间。
观察到ResNet和GoogLeNet,他们只在最后一层f是全连接的,不像vgg有隐层fc6 fc7,所以很自然的方法,我们能不能把roi pooling后面的隐层fc去掉,rpn共享前面所有的卷积,roi pooling后直接跟一个fc,但是效果是不行的,所以kaiming的ResNet附录里A. Object Detection Baselines的做法是共享前91个卷积(相当于vgg的前13个卷积),后面10个卷积依然不共享(相当于vgg的隐层fc)。kaiming为什么这样做呢?因为分类是要求translation invariance,而检测要求translation variance,这就矛盾了,所以需要通过roi pooling这种与位置相关的层打破translation invariance,roi后面的卷积就有了translation-invariance性,但是这样还是降低了速度。(后来的faster rcnn v3时是仅仅把vgg的特征换成resnet的,roi 后面还是fc6 fc7 fc8)
所以这篇论文就想能不能共享所有的计算,并提出了Region-based Fully Convolutional Network (R-FCN)

(以RoIpooling为分界线,分为两部分)
# ResNet101 prototxt
layer {
bottom: "res5c"
top: "pool5"
name: "pool5"
type: "Pooling"
pooling_param {
kernel_size:
stride:
pool: AVE
}
}
layer {
bottom: "pool5"
top: "fc1000"
name: "fc1000"
type: "InnerProduct"
inner_product_param {
num_output:
}
}
layer {
bottom: "fc1000"
top: "prob"
name: "prob"
type: "Softmax"
}
Approach
在最后一个卷积res5c后,添加一个1*1的卷积生成k^2(C+1)个channel的位置敏感的分类feature和k^2(C+1)个channel的回归feature。以分类为例,这k^2个score map对应k^2个格子的位置。以k=3,左上角为例,黄色是C+1个channel,每个channel对应一个类别在左上角的score map,经过经过这个pool之后得到(C+1)*k*k,然后vote(avg or max pool),得到(C+1)*1*1,直接softmax,没有fc或者卷积等计算,所有roi都贡献了计算。注意还有个
# conv_new_1
# relu1是C1-C5后加了relu后的feature。
# ResNet-101 has 100 convolutional layers followed by global average pooling and a 1000-class fc layer
# R-FCN把global pool和fc都去掉,加了个1*1的卷积降维,然后这101个卷积都贡献参数和计算
conv_new_1 = mx.sym.Convolution(data=relu1, kernel=(, ), num_filter=, name="conv_new_1", lr_mult=)
relu_new_1 = mx.sym.Activation(data=conv_new_1, act_type='relu', name='relu1')
# rfcn_cls/rfcn_bbox
# rfcn_cls: k*k(C+1)个channel rfcn_bbox: k*k*4(C+1)个channe
rfcn_cls = mx.sym.Convolution(data=relu_new_1, kernel=(, ), num_filter=**num_classes, name="rfcn_cls")
rfcn_bbox = mx.sym.Convolution(data=relu_new_1, kernel=(, ), num_filter=***num_reg_classes, name="rfcn_bbox")
psroipooled_cls_rois = mx.contrib.sym.PSROIPooling(name='psroipooled_cls_rois', data=rfcn_cls, rois=rois, group_size=, pooled_size=,output_dim=num_classes, spatial_scale=)
psroipooled_loc_rois = mx.contrib.sym.PSROIPooling(name='psroipooled_loc_rois', data=rfcn_bbox, rois=rois, group_size=, pooled_size=, output_dim=, spatial_scale=)
# vote
cls_score = mx.sym.Pooling(name='ave_cls_scors_rois', data=psroipooled_cls_rois, pool_type='avg', global_pool=True, kernel=(, ))
bbox_pred = mx.sym.Pooling(name='ave_bbox_pred_rois', data=psroipooled_loc_rois, pool_type='avg', global_pool=True, kernel=(, ))
cls_score = mx.sym.Reshape(name='cls_score_reshape', data=cls_score, shape=(-, num_classes))
bbox_pred = mx.sym.Reshape(name='bbox_pred_reshape', data=bbox_pred, shape=(-, * num_reg_classes))
# softmax计算loss
cls_prob = mx.sym.SoftmaxOutput(name='cls_prob', data=cls_score, label=label, normalization='valid', grad_scale=)
bbox_loss_ = bbox_weight * mx.sym.smooth_l1(name='bbox_loss_', scalar=, data=(bbox_pred - bbox_target))
Position-sensitive RoI pooling
rpn生成的每个proposal对应到这k^2(C+1)个channel的score map上得到每个roi,然后将每个roi划分为k*k个bin,每个bin是位置相关的,比如左上角的bin,只在黄色层(C+1个channel)对应位置做average或max pooling,注意是对应位置,只在黄色层的左上角做,不是在黄色层整个roi上做。比如下面这个只看person这个channel,第一个图是左上角的bin,只在第一个图roi的左上角红色框做pool,不是在这个图的整个roi绿色框做pool。
这里的k相当于roi pooling的pool height/width
//使用CUDA多线程计算
CUDA_KERNEL_LOOP(index, nthreads) { //index为最终score map上所有,共有(C+1)*k*k个值
// The output is in order (n, ctop, ph, pw),类似于图像的BIL逐行扫描
int pw = index % pooled_width; //score map上第i=[0,k-1]列
int ph = (index / pooled_width) % pooled_height; //score map上第j=[0,k-1]行
int ctop = (index / pooled_width / pooled_height) % output_dim; //score map上第ctop个层(class)
int n = index / pooled_width / pooled_height / output_dim; //第n个roi
// ......
int gw = pw;
int gh = ph;
//ctop*group_size*group_size+gh*gh*group_size+gw,计算得到的是第ctop类的(ph,pw)位置索引
//例如,score map上第ctop=1类的第(i,j)=(1,1)位置,c=1*49+1*7+1,对于feature map上第c个颜色层中(实际包含C=21层)的第2(ctop+1)层
int c = (ctop*group_size + gh)*group_size + gw;
// 移动到该层做average pool
// 这是cuda代码,每次计算score map上一个值,如左上角
其他细节
- ResNet101的res5c是2048channel,为了减少计算量,先用了个1*1的卷积降维到了1024,然后才有position-sensitive的score map
- 加了洞算法,调整了步长,使最户的feature map为1/16
- 经过position-sensitive pooling后得到了k^2(C+1)的feature(类似于roipooling后的7*7*512的feature),后面两个分支,一个是分支首先average/max pooling到1*1*(C+1),然后softmax得到分类,另一个是坐标回归,生成4k^2-d的向量,然后做average/max pooling到4-d。此处为了简便,做的是类别无关的回归,但是类别相关的回归(生成4k^2C-d的向量)也是适用的
实验
- Naive Faster R-CNN 就是开始说的直接ResNet拿过来,roi pooling后无隐层fc,区别于kaiming的roi pooling后有10个卷积(76.4% mAP)
- Class-specific RPN,之前的rpn只分为是否是物体,这里直把proposal分为具体哪一个类别
-
R-FCN without position-sensitivity,即k设置为1,类似于分割直接产生C+1个channel,每个channel代表一个类别的score map。这是最直接的做法了
根据表可知R-FCN和标准的faster rcnn(76.4% mAP)差不多了,在roi后没有用任何带参数的层,证明
ps-RoIpooling可以编码到有用的用于定位的空间信息,ps: Class-specific RPN近似于fast rcnn中sliding window的特殊形式
这两个表说明,R-FCN节省了很多训练和测试时间,且和标准的faster rcnn相比精度还要高一点点
这两个表说明这个策略泛化性很强,无论是不同深度的网络还是不同的proposal的提取方式,该论文的策略都适用。
参考:
msracver/Deformable-ConvNets