將FPN中的一些層改成C++
因為需要用C++的程式碼跑測試,因而需要將prototxt中一些原來用python寫的層改成c++格式。
之前跑faster rcnn的時候,將rpn層參考部落格修改過,但是因為對原始碼不瞭解,這次改三個層無從下手,下面記錄一下過程。需要修改的三個層分別為rpn-data,roi-data和proposal層。
rpn-data:
layer { name: 'rpn-data' type: 'Module' bottom: 'rpn_cls_score/p2' bottom: 'rpn_cls_score/p3' bottom: 'rpn_cls_score/p4' bottom: 'rpn_cls_score/p5' bottom: 'rpn_cls_score/p6' bottom: 'gt_boxes' bottom: 'im_info' top: 'rpn_labels' top: 'rpn_bbox_targets' top: 'rpn_bbox_inside_weights' top: 'rpn_bbox_outside_weights' module_param { module: "modules" type: "FPNAnchorTarget" param_str: "{ 'feat_strides': [4,8,16,32,64] }" } propagate_down: 0 propagate_down: 0 propagate_down: 0 propagate_down: 0 propagate_down: 0 propagate_down: 0 propagate_down: 0 }
roi-data:
layer { name: 'roi-data' type: 'Module' bottom: 'rpn_rois' bottom: 'gt_boxes' top: 'rois/h2' top: 'rois/h3' top: 'rois/h4' top: 'rois/h5' # top: 'rois/h6' top: 'labels' top: 'bbox_targets' top: 'bbox_inside_weights' top: 'bbox_outside_weights' propagate_down: 0 propagate_down: 0 module_param { module: 'modules' type: 'FPNProposalTarget' } }
proposal:
layer { name: 'proposal' type: 'Module' bottom: 'fpn_out_reshape/p2' bottom: 'rpn_bbox_pred/p2' bottom: 'fpn_out_reshape/p3' bottom: 'rpn_bbox_pred/p3' bottom: 'fpn_out_reshape/p4' bottom: 'rpn_bbox_pred/p4' bottom: 'fpn_out_reshape/p5' bottom: 'rpn_bbox_pred/p5' bottom: 'fpn_out_reshape/p6' bottom: 'rpn_bbox_pred/p6' bottom: 'im_info' top: 'rpn_rois' module_param { module: 'modules' type: 'FPNProposal' param_str: "{'feat_strides': [4,8,16,32,64]}" } propagate_down: 0 propagate_down: 0 propagate_down: 0 propagate_down: 0 propagate_down: 0 propagate_down: 0 propagate_down: 0 propagate_down: 0 propagate_down: 0 propagate_down: 0 propagate_down: 0 include { phase: TRAIN } }
注意到其中的module_param和model的'modules'都是需要根據實際情況改掉的。在呼叫module_param的地方,呼叫方法為this->layer_param_.module_param(),而直接編譯caffe的時候是無法通過編譯的,會報找不到module_param()的error。分析了原始碼,發現程式碼中使用YAML解析結構(在faster rcnn中為用LayerParameter直接定義解析引數,之後也可以嘗試用這種方法),因此需要在caffe.proto裡新增Moduleparameter。
(雖然yaml-cpp安裝成功了,但是還是報錯undefined yaml,所以打算嘗試上面的方法:
- (備份後)將train和test的pt裡的module_param更改成fpn_param
- 在caffe.proto裡新增parameter,FPNParameter fpn_param;然後發現,根據caffe.proto中,引數的型別不同,會在caafe.pb.h和.cc中自動生成不同的引數。對於optional變數,會生成has_xx,對於repeated變數,會生成xx.size(),可以對比dummy_data_layer.cpp和pooling_layer.cpp兩個檔案,而生成引數的主要作用是用來在.cpp中取值的時候做判斷)
事實上嘗試失敗了,因為沒有搞清楚層與層之間的關係
該prototxt在roi_pooling層只寫了一個feat_strides,而實際上在Faster Rcnn中,還需要anchor_scales和anchor_ratios。因此查看了fpn_proposal_layer.cpp,發現其中的結構是,如果pt中為空,則自動設定。(可以在後期加在prototxt中修改試一試)
另外可以看到,在roi-data層中,c++版本是沒有另外傳入引數的,而python版本是添加了num_class引數。然後發現該n_class引數是在frcnn_param裡傳入的,而裡面的引數是從指令碼中parse出來的,因此不需要在這裡設定了。
另外,在fpn_proposal_layer.cu裡,有cub/cub.cuh,找不到檔案,所以先註釋掉了。
================分界線======================
11.07 我又回來了,並搞明白了幾個問題,為:
-
cub.cub.cuh為cuda的一個頭檔案。
-
c++版本中的另外新增引數,是在輸入層用window_data這個層傳入進來的。而我之前的思路是要把train的pt檔案中python層也改成c++,於是我一開始的思路是把所需要的引數全部傳進來,而現在思路更改為只需要改test的pt檔案中的python層為c++格式,這種情況下只需要把cfg中在此呼叫的引數重新傳進來就行了。
那麼,在此重新分析一下邏輯:
1.首先要找到需要更改的python層,並簡要分析
test的prototxt裡,Python層為「input-data」,「rpn-data」,「proposal」,「roi-data」
test的prototxt裡,Python層為「proposal」,「as-rois」
在這裡的proposal層在python中呼叫的是(rpn.proposal_layer),roi-data是(rpn.proposal_target_layer),as-rois是(rpn.as_rois)。
自此可以看出,需要更改的只有propsal層和as-rois層。那麼我們去看python檔案。
2.分析as-rois層
開啟python的這個檔案,可以看到該層呼叫的外部函式只有config.py函式,並只用了其中的「cfg.TEST.RPN_POST_NMS_TOP_N」這一個引數變數,而該層的作用是將proposal和gt對比,並分類,那麼該層應該還是挺好改的,因為只涉及了自己一個指令碼檔案。
3.分析propsal層
這個檔案呼叫的函式比較多,而python中把呼叫的函式寫在了多個檔案中,畫個表表示:
fast_rcnn.config | cfg |
generate_anchors | generate_anchors |
fast_rcnn.bbox_transform | bbox_transform_inv , clip_boxes |
fast_rcnn.nms_wrapper | nms |
參照之前修改的rpn的c++檔案,其將這些函式寫在了一個c++檔案中。
4.分析Faster Rcnn和FPN的python指令碼檔案
主要去看了proposal_layer這個檔案,其中區別最大的是FPN是有多個feat_stride,而Faster Rcnn只有一個,其他的函式基本一樣。
分析完需要修改的檔案後,整理一下需要了解的背景知識:
1.Python層的邏輯
Python中每個class需要有四個層,分別為『setup』、『forward』、『backward』、『reshape』。如果有其他需要的函式,可以在class外面定義。
其中backward的引數為(self,top,propagate_down,bottom),其他三個為(self,top,bottom)。
bottom和top的引數順序根據prototxt中順序決定。
2.C++層的邏輯
c++中邏輯比較麻煩,因為要標頭檔案定義很多引數。
3.Python層的一些引數解釋
im_info:bottom[0].data[0,:],存放的是(height , width , scale)
4.caffe層的一些引數解釋
caffe_set:用引數alpha對引數Y進行初始化
template <typename Dtype>
void caffe_set(const int N, const Dtype alpha, Dtype* Y) {
if (alpha == 0) {
memset(Y, 0, sizeof(Dtype) * N); // NOLINT(caffe/alt_fn)
return;
}
for (int i = 0; i < N; ++i) {
Y[i] = alpha;
}
}
caffe_axpy:Y=alpha*X+Y ,N為X和Y中element個數
template <>
void caffe_axpy<float>(const int N, const float alpha, const float* X,
float* Y) { cblas_saxpy(N, alpha, X, 1, Y, 1); }
CopyFrom()
template <typename Dtype>
void Blob<Dtype>::CopyFrom(const Blob& source, bool copy_diff, bool reshape) {
}
//從source 拷貝資料 , copy_diff控制是拷貝diff還是data
_axpy:
================分界線======================
11.08 我又回來了,出現了一些問題:
我測試了之前該rpn的faster rcnn,發現直接跑測試是無法跑的,但是直接detect是可以的,問題出在blob的尺寸問題上,目前還沒有解決。
猜測原因是config指令碼設定的問題
解決思路
1.檢視rpn的top格式
2.檢視roipooling的bottom格式
================分界線======================
11.09 我又回來了,解決問題了!!!哭了哭了
昨天確定出錯的位置在於,c++版本用blob傳遞資訊(雖然python也是),但是很坑很關鍵的一點是,python中很多地方是用numpy計算的,而as we all know,blob是四維的,於是在我使用的環境下,就是在test.py指令碼中,根據rois得到的boxes,呼叫了bbox_transform_inv函式,由於用到了其本身的資料型別和blob的型別,四維和二維不匹配於是出錯了。
在test指令碼中,net.blob['rois'].data.shape可以檢視rois的維度,查到是四維。而實際上沒改C++版本之前,傳遞的該引數維度是二維,很巧和的是,前者shape為[1,5,1,1],後者為[1,5],於是我就分別去看c++和python的rpn層檔案,發現c++層在最後reshape成了4維。於是經我不縝密的猜測,最後兩位資料其實只是為了對其的,其實沒有用,也就是說,只需要在python檔案中需要rois的地方,把原資料的前兩維拷貝就好了
解決方法也很簡單,在test.py中用到rois的地方(boxes)的shape改掉:
rois = net.blobs['rois'].data.copy()
boxes = rois[:,1:5] / im_scales[0]
box_min_resize = min(cfg.TEST.RPN_POST_NMS_TOP_N,boxes.shape[0])
boxes.resize([box_min_resize,4])
接下來就開始搞as_rois.py了,github上沒有對應的層真的哭了!!本來計劃週日稍微改改就能跑的c++模型,沒想到搞到了今天,低估了工作量但是對caffe 的這幾個層的瞭解更深刻了。。。
================分界線======================
11.13 我又回來了,全部改好並且能跑了,問題就是結果不對- -
記錄一些問題
1.一開始把vector的變數定義在.hpp中,然後在cpp中操作,這造成一個問題,就是執行速度會越來越慢,可能造成了記憶體洩漏沒有清理空間。於是我用了一個簡單的方法就是把hpp中定義的變數放cpp中定義了,這樣就相當於他在程式執行結束後就自動銷燬了。
2.如果新增cpp層需要make clean,如果只是修改cpp層,不需要make clean