天天看點

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)

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

R-FCN解讀

在最後一個卷積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

R-FCN解讀
//使用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上一個值,如左上角
           

其他細節

  1. ResNet101的res5c是2048channel,為了減少計算量,先用了個1*1的卷積降維到了1024,然後才有position-sensitive的score map
  2. 加了洞算法,調整了步長,使最戶的feature map為1/16
  3. 經過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的向量)也是适用的

實驗

R-FCN解讀
  • 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解讀

這兩個表說明,R-FCN節省了很多訓練和測試時間,且和标準的faster rcnn相比精度還要高一點點

R-FCN解讀

這兩個表說明這個政策泛化性很強,無論是不同深度的網絡還是不同的proposal的提取方式,該論文的政策都适用。

參考:

msracver/Deformable-ConvNets

繼續閱讀