本文主要是介绍R-FCN解读,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
最近一直做检测,发现检测领域好多好玩的东西啊,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: 7stride: 1pool: AVE}
}
layer {bottom: "pool5"top: "fc1000"name: "fc1000"type: "InnerProduct"inner_product_param {num_output: 1000}
}
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=(1, 1), num_filter=1024, name="conv_new_1", lr_mult=3.0)
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=(1, 1), num_filter=7*7*num_classes, name="rfcn_cls")rfcn_bbox = mx.sym.Convolution(data=relu_new_1, kernel=(1, 1), num_filter=7*7*4*num_reg_classes, name="rfcn_bbox")psroipooled_cls_rois = mx.contrib.sym.PSROIPooling(name='psroipooled_cls_rois', data=rfcn_cls, rois=rois, group_size=7, pooled_size=7,output_dim=num_classes, spatial_scale=0.0625)psroipooled_loc_rois = mx.contrib.sym.PSROIPooling(name='psroipooled_loc_rois', data=rfcn_bbox, rois=rois, group_size=7, pooled_size=7, output_dim=8, spatial_scale=0.0625)
# vote
cls_score = mx.sym.Pooling(name='ave_cls_scors_rois', data=psroipooled_cls_rois, pool_type='avg', global_pool=True, kernel=(7, 7))bbox_pred = mx.sym.Pooling(name='ave_bbox_pred_rois', data=psroipooled_loc_rois, pool_type='avg', global_pool=True, kernel=(7, 7))cls_score = mx.sym.Reshape(name='cls_score_reshape', data=cls_score, shape=(-1, num_classes))bbox_pred = mx.sym.Reshape(name='bbox_pred_reshape', data=bbox_pred, shape=(-1, 4 * num_reg_classes))# softmax计算loss
cls_prob = mx.sym.SoftmaxOutput(name='cls_prob', data=cls_score, label=label, normalization='valid', grad_scale=1.0)bbox_loss_ = bbox_weight * mx.sym.smooth_l1(name='bbox_loss_', scalar=1.0, 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
这篇关于R-FCN解读的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!