最近一直做檢測,發現檢測領域好多好玩的東西啊,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