1. 程式人生 > >faster-rcnn 之 RPN網路的結構解析

faster-rcnn 之 RPN網路的結構解析

【說明】:歡迎加入:faster-rcnn 交流群 238138700,我想很多人在看faster-rcnn的時候,都會被RPN的網路結構和連線方式糾結,作者在文中說的不是很清晰,這裡給出解析;

【首先】:大家應該要了解卷積神經網路的連線方式,卷積核的維度,反向傳播時是如何靈活的插入一層;這裡我推薦一份資料,真是寫的非常清晰,就是MatConvet的使用者手冊,這個框架底層借用的是caffe的演算法,所以他們的資料結構,網路層的連線方式都是一樣的;建議讀者看看,很快的;

【前面5層】:作者RPN網路前面的5層借用的是ZF網路,這個網路的結構圖我截個圖放在下面,並分析下為什麼是這樣子的;


1、首先,輸入圖片大小是 224*224*3(這個3是三個通道,也就是RGB三種)

2、然後第一層的卷積核維度是 7*7*3*96 (所以大家要認識到卷積核都是4維的,在caffe的矩陣計算中都是這麼實現的);

3、所以conv1得到的結果是110*110*96 (這個110來自於 (224-7+pad)/2 +1 ,這個pad是我們常說的填充,也就是在圖片的周圍補充畫素,這樣做的目的是為了能夠整除,除以2是因為2是圖中的stride, 這個計算方法在上面建議的文件中有說明與推導的);

4、然後就是做一次池化,得到pool1, 池化的核的大小是3*3,所以池化後圖片的維度是55*55*96  (  (110-3+pad)/2 +1 =55 );

5、然後接著就是再一次卷積,這次的卷積核的維度是5*5*96*256 ,得到conv2:26*26*256;

6、後面就是類似的過程了,我就不詳細一步步算了,要注意有些地方除法除不盡,作者是做了填充了,在caffe的prototxt檔案中,可以看到每一層的pad的大小;

7、最後作者取的是conv5的輸出,也就是13*13*256送給RPN網路的;

【RPN部分】:然後,我們看看RPN部分的結構:


1、前面我們指出,這個conv feature map的維度是13*13*256的;

2、作者在文章中指出,sliding window的大小是3*3的,那麼如何得到這個256-d的向量呢? 這個很簡單了,我們只需要一個3*3*256*256這樣的一個4維的卷積核,就可以將每一個3*3的sliding window 卷積成一個256維的向量;

這裡讀者要注意啊,作者這裡畫的示意圖 僅僅是 針對一個sliding window的;在實際實現中,我們有很多個sliding window,所以得到的並不是一維的256-d向量,實際上還是一個3維的矩陣資料結構;可能寫成for迴圈做sliding window大家會比較清楚,當用矩陣運算的時候,會稍微繞些;

3、然後就是k=9,所以cls layer就是18個輸出節點了,那麼在256-d和cls layer之間使用一個1*1*256*18的卷積核,就可以得到cls layer,當然這個1*1*256*18的卷積核就是大家平常理解的全連線;所以全連線只是卷積操作的一種特殊情況(當卷積核的大小與圖片大小相同的時候,其實所謂的卷積就是全連線了);

4、reg layer也是一樣了,reg layer的輸出是36個,所以對應的卷積核是1*1*256*36,這樣就可以得到reg layer的輸出了;

5、然後cls layer 和reg layer後面都會接到自己的損失函式上,給出損失函式的值,同時會根據求導的結果,給出反向傳播的資料,這個過程讀者還是參考上面給的文件,寫的挺清楚的;

【作者關於RPN網路的具體定義】:這個作者是放在./models/pascal_voc/ZF/faster_rcnn_alt_opt/stage1_rpn_train.pt 檔案中的;

我把這個檔案拿出來給註釋下:

name: "ZF"
layer {
  name: 'input-data' #這一層就是最開始資料輸入
  type: 'Python'
  top: 'data' # top表示該層的輸出,所以可以看到這一層輸出三組資料,data,真值框gt_boxes,和相關資訊im_info
  top: 'im_info' # 這些都是儲存在矩陣中的
  top: 'gt_boxes'
  python_param {
    module: 'roi_data_layer.layer'
    layer: 'RoIDataLayer'
    param_str: "'num_classes': 21"
  }
}

#========= conv1-conv5 ============

layer {
	name: "conv1"
	type: "Convolution"
	bottom: "data" # 輸入data
	top: "conv1" # 輸出conv1,這裡conv1就代表了這一層輸出資料的名稱,儲存在對應的矩陣中
	param { lr_mult: 1.0 }
	param { lr_mult: 2.0 }
	convolution_param {
		num_output: 96
		kernel_size: 7
		pad: 3  # 這裡可以看到卷積1層 填充了3個畫素
		stride: 2
	}
}
layer {
	name: "relu1"
	type: "ReLU"
	bottom: "conv1"
	top: "conv1"
}
layer {
	name: "norm1"
	type: "LRN"
	bottom: "conv1"
	top: "norm1" # 做歸一化操作,通俗點說就是做個除法
	lrn_param {
		local_size: 3
		alpha: 0.00005
		beta: 0.75
		norm_region: WITHIN_CHANNEL
    engine: CAFFE
	}
}
layer {
	name: "pool1"
	type: "Pooling"
	bottom: "norm1"
	top: "pool1"
	pooling_param {
		kernel_size: 3
		stride: 2
		pad: 1 # 池化的時候,又做了填充
		pool: MAX
	}
}
layer {
	name: "conv2"
	type: "Convolution"
	bottom: "pool1"
	top: "conv2"
	param { lr_mult: 1.0 }
	param { lr_mult: 2.0 }
	convolution_param {
		num_output: 256
		kernel_size: 5
		pad: 2
		stride: 2
	}
}
layer {
	name: "relu2"
	type: "ReLU"
	bottom: "conv2"
	top: "conv2"
}
layer {
	name: "norm2"
	type: "LRN"
	bottom: "conv2"
	top: "norm2"
	lrn_param {
		local_size: 3
		alpha: 0.00005
		beta: 0.75
		norm_region: WITHIN_CHANNEL
    engine: CAFFE
	}
}
layer {
	name: "pool2"
	type: "Pooling"
	bottom: "norm2"
	top: "pool2"
	pooling_param {
		kernel_size: 3
		stride: 2
		pad: 1
		pool: MAX
	}
}
layer {
	name: "conv3"
	type: "Convolution"
	bottom: "pool2"
	top: "conv3"
	param { lr_mult: 1.0 }
	param { lr_mult: 2.0 }
	convolution_param {
		num_output: 384
		kernel_size: 3
		pad: 1
		stride: 1
	}
}
layer {
	name: "relu3"
	type: "ReLU"
	bottom: "conv3"
	top: "conv3"
}
layer {
	name: "conv4"
	type: "Convolution"
	bottom: "conv3"
	top: "conv4"
	param { lr_mult: 1.0 }
	param { lr_mult: 2.0 }
	convolution_param {
		num_output: 384
		kernel_size: 3
		pad: 1
		stride: 1
	}
}
layer {
	name: "relu4"
	type: "ReLU"
	bottom: "conv4"
	top: "conv4"
}
layer {
	name: "conv5"
	type: "Convolution"
	bottom: "conv4"
	top: "conv5"
	param { lr_mult: 1.0 }
	param { lr_mult: 2.0 }
	convolution_param {
		num_output: 256
		kernel_size: 3
		pad: 1
		stride: 1
	}
}
layer {
	name: "relu5"
	type: "ReLU"
	bottom: "conv5"
	top: "conv5"
}

#========= RPN ============
# 到我們的RPN網路部分了,前面的都是共享的5層卷積層的部分
layer {
  name: "rpn_conv1"
  type: "Convolution"
  bottom: "conv5"
  top: "rpn_conv1"
  param { lr_mult: 1.0 }
  param { lr_mult: 2.0 }
  convolution_param {
    num_output: 256
    kernel_size: 3 pad: 1 stride: 1 #這裡作者把每個滑窗3*3,通過3*3*256*256的卷積核輸出256維,完整的輸出其實是12*12*256,
    weight_filler { type: "gaussian" std: 0.01 }
    bias_filler { type: "constant" value: 0 }
  }
}
layer {
  name: "rpn_relu1"
  type: "ReLU"
  bottom: "rpn_conv1"
  top: "rpn_conv1"
}
layer {
  name: "rpn_cls_score"
  type: "Convolution"
  bottom: "rpn_conv1"
  top: "rpn_cls_score"
  param { lr_mult: 1.0 }
  param { lr_mult: 2.0 }
  convolution_param {
    num_output: 18   # 2(bg/fg) * 9(anchors)
    kernel_size: 1 pad: 0 stride: 1 #這裡看的很清楚,作者通過1*1*256*18的卷積核,將前面的256維資料轉換成了18個輸出
    weight_filler { type: "gaussian" std: 0.01 }
    bias_filler { type: "constant" value: 0 }
  }
}
layer {
  name: "rpn_bbox_pred"
  type: "Convolution"
  bottom: "rpn_conv1"
  top: "rpn_bbox_pred"
  param { lr_mult: 1.0 }
  param { lr_mult: 2.0 }
  convolution_param {
    num_output: 36   # 4 * 9(anchors)
    kernel_size: 1 pad: 0 stride: 1 <span style="font-family: Arial, Helvetica, sans-serif;">#這裡看的很清楚,作者通過1*1*256*36的卷積核,將前面的256維資料轉換成了36個輸出</span>
    weight_filler { type: "gaussian" std: 0.01 }
    bias_filler { type: "constant" value: 0 }
  }
}
layer {
   bottom: "rpn_cls_score"
   top: "rpn_cls_score_reshape" # 我們之前說過,其實這一層是12*12*256的,所以後面我們要送給損失函式,需要將這個矩陣reshape一下,我們需要的是144個滑窗,每個對應的256的向量
   name: "rpn_cls_score_reshape"
   type: "Reshape"
   reshape_param { shape { dim: 0 dim: 2 dim: -1 dim: 0 } }
}
layer {
  name: 'rpn-data'
  type: 'Python'
  bottom: 'rpn_cls_score'
  bottom: 'gt_boxes'
  bottom: 'im_info'
  bottom: 'data'
  top: 'rpn_labels'
  top: 'rpn_bbox_targets'
  top: 'rpn_bbox_inside_weights'
  top: 'rpn_bbox_outside_weights'
  python_param {
    module: 'rpn.anchor_target_layer'
    layer: 'AnchorTargetLayer'
    param_str: "'feat_stride': 16"
  }
}
layer {
  name: "rpn_loss_cls"
  type: "SoftmaxWithLoss" # 很明顯這裡是計算softmax的損失,輸入labels和cls layer的18個輸出(中間reshape了一下),輸出損失函式的具體值
  bottom: "rpn_cls_score_reshape"
  bottom: "rpn_labels"
  propagate_down: 1
  propagate_down: 0
  top: "rpn_cls_loss"
  loss_weight: 1
  loss_param {
    ignore_label: -1
    normalize: true
  }
}
layer {
  name: "rpn_loss_bbox"
  type: "SmoothL1Loss" # 這裡計算的框迴歸損失函式具體的值
  bottom: "rpn_bbox_pred"
  bottom: "rpn_bbox_targets"
  bottom: "rpn_bbox_inside_weights"
  bottom: "rpn_bbox_outside_weights"
  top: "rpn_loss_bbox"
  loss_weight: 1
  smooth_l1_loss_param { sigma: 3.0 }
}

#========= RCNN ============
# Dummy layers so that initial parameters are saved into the output net

layer {
  name: "dummy_roi_pool_conv5"
  type: "DummyData"
  top: "dummy_roi_pool_conv5"
  dummy_data_param {
    shape { dim: 1 dim: 9216 }
    data_filler { type: "gaussian" std: 0.01 }
  }
}
layer {
  name: "fc6"
  type: "InnerProduct"
  bottom: "dummy_roi_pool_conv5"
  top: "fc6"
  param { lr_mult: 0 decay_mult: 0 }
  param { lr_mult: 0 decay_mult: 0 }
  inner_product_param {
    num_output: 4096
  }
}
layer {
  name: "relu6"
  type: "ReLU"
  bottom: "fc6"
  top: "fc6"
}
layer {
  name: "fc7"
  type: "InnerProduct"
  bottom: "fc6"
  top: "fc7"
  param { lr_mult: 0 decay_mult: 0 }
  param { lr_mult: 0 decay_mult: 0 }
  inner_product_param {
    num_output: 4096
  }
}
layer {
  name: "silence_fc7"
  type: "Silence"
  bottom: "fc7"
}


ps:等原始碼看完之後,我再註釋得更詳細些;

作者:香蕉麥樂迪--sloanqin-覃元元