fast rcnn的网络结构:stage1_fast_rcnn_train.pt
首先来看数据的准备阶段:
name: "ZF"
layer {
name: 'data'
type: 'Python'
top: 'data'
top: 'rois'
top: 'labels'
top: 'bbox_targets'
top: 'bbox_inside_weights'
top: 'bbox_outside_weights'
python_param {
module: 'roi_data_layer.layer'
layer: 'RoIDataLayer'
param_str: "'num_classes': 21"
}
}
进入roi_data_layer.layer文件查看forward函数:
其实,这个函数我们在train_rpn的时候已经用到过一次了,现在只不过参数设置不同,里面的细节有一些变化:
首先,还是获取blobs:blobs =self._get_next_minibatch(),
下面进入_get_next_minibatch函数:
首先从图片数据中随机抽取两张图片数据:db_inds =self._get_next_minibatch_inds(),在_get_next_minibatch_inds函数中,由于参数cfg.TRAIN.IMS_PER_BATCH=2,所以每次是抽取了两张图片,也就是说我们每次处理的是两张图片(和train_rpn的时候不同,train_rpn抽取的是一张图片)。
回到_get_next_minibatch函数中,下一步获取图片数据:minibatch_db = [self._roidb[i] for i in db_inds],这是一个列表,最后利用get_minibatch函数返回结果:return get_minibatch(minibatch_db,self._num_classes)
进入get_minibatch函数:
首先,将图片缩放并获取缩放比例:im_scales,然后将列表数据minibatch_db 转化成caffe需要的blob格式:
im_blob, im_scales = _get_image_blob(roidb, random_scale_inds),这里的im_blob的batch=2。
然后,把im_blob添加到blobs字典:blobs = {'data': im_blob}
因为参数cfg.TRAIN.HAS_RPN=False,因此,这里执行else语句,首先初始化一些空的变量,用于向blobs 字典中添加数据:
rois_blob = np.zeros((0,5),dtype=np.float32)
labels_blob = np.zeros((0),dtype=np.float32)
bbox_targets_blob = np.zeros((0,4 * num_classes),dtype=np.float32)
bbox_inside_blob = np.zeros(bbox_targets_blob.shape,dtype=np.float32)
然后,循环图片列表(其实只有两张图片)并求得labels, overlaps, im_rois, bbox_targets, bbox_inside_weights等变量:
for im_i in range(num_images):
labels, overlaps, im_rois, bbox_targets, bbox_inside_weights \
= _sample_rois(roidb[im_i], fg_rois_per_image, rois_per_image, num_classes)
下面进入_sample_rois函数:
先看一下输入数据:
roidb[im_i]:第im_i张图片的roidb数据
rois_per_image:默认值为64
fg_rois_per_image:默认值为16
num_classes:21
再来说一下_sample_rois函数的作用:主要就是将我们得到的proposals(<=2000个)限制在64个,其中前景proposals的个数<=16个(根据cfg.TRAIN.FG_THRESH,多的话随机抽取),背景proposals的个数大于等于48个,小于等于64个(多的话随机抽取)。当然,在将proposals的个数限制在64个之后,也将这些保留下来的proposals改名为了:rois。
好了,来一下函数的返回结果是什么:
labels = roidb['max_classes']
overlaps = roidb['max_overlaps']
rois = roidb['boxes']
如果保留的rois的索引号为:keep_inds,那么返回的结果为:
labels = labels[keep_inds] # 前景的labels设置为:对应的物体类别号
labels[fg_rois_per_this_image:] =0 # 将背景的labels设置为:0
overlaps = overlaps[keep_inds]
rois = rois[keep_inds]
最后,还有两个需要输入的结果:
bbox_targets, bbox_inside_weights = _get_bbox_regression_labels(
roidb['bbox_targets'][keep_inds, :], num_classes)
其实是由_get_bbox_regression_labels函数的返回值。
下面来看一下_get_bbox_regression_labels函数:
输入是roidb中元素字典的bbox_targets:roidb['bbox_targets'][keep_inds, :],当然这里的bbox_targets也和rois一样,只保留对应于keep_inds索引的值,还有一个输入是num_classes=21
再开说一下_get_bbox_regression_labels函数的作用:其实就是把roidb['bbox_targets'][keep_inds, :]矩阵,由原来的len(keep_inds)行5列,转变成了len(keep_inds)行84列,而且返回的矩阵bbox_targets在每一行中,只有对应的物体号的那4列的值为非0元素(这4列的取值,其实就是原来的roidb['bbox_targets'][keep_inds, :]矩阵后4列的值),其余80列的值都为0,当然,如果某一行对应的是背景,那么一整行的元素取值都为0。
在_get_bbox_regression_labels函数中,还有一个返回值bbox_inside_weights,这也是一个len(keep_inds)行84列的矩阵,和bbox_targets是对应的,只不过在bbox_targets的取值为非0的取值不同,默认取值为:(1.0, 1.0, 1.0, 1.0)。
下面返回get_minibatch函数,根据_sample_rois函数,得到了一些列的返回值:labels, overlaps, im_rois, bbox_targets 和 bbox_inside_weights。接下来在循环中对这些返回值进行操作:
rois = _project_im_rois(im_rois, im_scales[im_i]) :把im_rois中的坐标对应到缩放之后的图片上,因为我们的处理都是在缩放之后的图片上进行的,而im_rois的坐标是相对于原图来说的。
然后,给rois在最前面增加1列数据:
batch_ind = im_i * np.ones((rois.shape[0],1))
rois_blob_this_image = np.hstack((batch_ind, rois))
从这里可以看出,rois_blob_this_image 是一个rois.shape[0]行5列的矩阵,如果第1列的元素为0,那么代表的是这个batch中的第1张图片,如果第1列的元素为1,代表的是这个batch中的第2张图片。(其实,关于这里的np.hstack有一个隐患,就是两张图片的rois.shape[0]有可能不相等,这样的话就会报错。当然,除非是极端情况,要不然不可能发生。因为我们得到的proposals的数量够多,2000个,而rois_per_image又比较小,只有64,所以,每张图片rois.shape[0]最终的取值大概率都会是:64)
然后把结果rois_blob_this_image 合并到rois_blob 中:
rois_blob = np.vstack((rois_blob, rois_blob_this_image)),结果两次循环之后,rois_blob 里面就包含了两张图片的数据。
同样的,对其他变量labels_blob、bbox_targets_blob、bbox_inside_blob也进行数据的合并,即:把两张图片的数据合并在一起:
labels_blob = np.hstack((labels_blob, labels))
bbox_targets_blob = np.vstack((bbox_targets_blob, bbox_targets))
bbox_inside_blob = np.vstack((bbox_inside_blob, bbox_inside_weights))
最后,把得到的上述数据添加到blobs字典中:
blobs['rois'] = rois_blob
blobs['labels'] = labels_blob
blobs['bbox_targets'] = bbox_targets_blob
blobs['bbox_inside_weights'] = bbox_inside_blob
blobs['bbox_outside_weights'] = np.array(bbox_inside_blob >0).astype(np.float32) # 由0和1组成的矩阵,其中bbox_inside_blob中不为0的地方对应的位置 取值为1,其余地方取值为0
最后,get_minibatch函数返回字典blobs。
然后,回到_get_next_minibatch函数和forward函数,我们得到blobs 字典:blobs =self._get_next_minibatch()。
接下来把blobs字典中的取值取出并传递给top返回,top也即是forward函数的输出结果。
OK,这样我们就完成了数据的准备工作,接下来,把得到的上述数据输入接下来的网络进行传播。
最开始,是5个卷积层,已经见过多次了:
#========= conv1-conv5 ============
layer {
name: "conv1"
type: "Convolution"
bottom: "data"
top: "conv1"
param { lr_mult: 1.0 }
param { lr_mult: 2.0 }
convolution_param {
num_output: 96
kernel_size: 7
pad: 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"
}
接下来是ROI池化层:
roi层的代码是在fast rcnn中的cpp文件中。
roi层之后,在接两个全连接层:
layer {
name: "fc6"
type: "InnerProduct"
bottom: "roi_pool_conv5"
top: "fc6"
param { lr_mult: 1.0 }
param { lr_mult: 2.0 }
inner_product_param {
num_output: 4096
}
}
layer {
name: "relu6"
type: "ReLU"
bottom: "fc6"
top: "fc6"
}
layer {
name: "drop6"
type: "Dropout"
bottom: "fc6"
top: "fc6"
dropout_param {
dropout_ratio: 0.5
scale_train: false
}
}
layer {
name: "fc7"
type: "InnerProduct"
bottom: "fc6"
top: "fc7"
param { lr_mult: 1.0 }
param { lr_mult: 2.0 }
inner_product_param {
num_output: 4096
}
}
layer {
name: "relu7"
type: "ReLU"
bottom: "fc7"
top: "fc7"
}
layer {
name: "drop7"
type: "Dropout"
bottom: "fc7"
top: "fc7"
dropout_param {
dropout_ratio: 0.5
scale_train: false
}
}
紧接着fc7分两个方向,一个方向预测:物体类别,另一个方向预测:box的坐标。
layer {
name: "cls_score"
type: "InnerProduct"
bottom: "fc7"
top: "cls_score"
param { lr_mult: 1.0 }
param { lr_mult: 2.0 }
inner_product_param {
num_output: 21
weight_filler {
type: "gaussian"
std: 0.01
}
bias_filler {
type: "constant"
value: 0
}
}
}
这个是用了一个全连接层来预测类别,输出结果为:cls_score。
layer {
name: "bbox_pred"
type: "InnerProduct"
bottom: "fc7"
top: "bbox_pred"
param { lr_mult: 1.0 }
param { lr_mult: 2.0 }
inner_product_param {
num_output: 84
weight_filler {
type: "gaussian"
std: 0.001
}
bias_filler {
type: "constant"
value: 0
}
}
}
同样是用来一个全连接层来预测box的坐标,输出结果为:bbox_pred。
最后,根据 cls_score 和 bbox_pred 来计算loss:
layer {
name: "loss_cls"
type: "SoftmaxWithLoss"
bottom: "cls_score"
bottom: "labels"
propagate_down: 1
propagate_down: 0
top: "cls_loss"
loss_weight: 1
loss_param {
ignore_label: -1
normalize: true
}
}
layer {
name: "loss_bbox"
type: "SmoothL1Loss"
bottom: "bbox_pred"
bottom: "bbox_targets"
bottom: "bbox_inside_weights"
bottom: "bbox_outside_weights"
top: "bbox_loss"
loss_weight: 1
}
最后,回到train_fast_rcnn函数:
得到了训练的fast rcnn的网络,保存在model_paths列表中,然后,移除model_paths列表中保存的网络文件,只保留最新的网络:
for iin model_paths[:-1]:
os.remove(i)
把列表中剩余的唯一一个元素保存在fast_rcnn_model_path变量中:fast_rcnn_model_path = model_paths[-1]
把fast_rcnn_model_path 以字典的形式推入的子进程的队列中:
queue.put({'model_path': fast_rcnn_model_path})
到这里,创建子进程结束:p = mp.Process(target=train_fast_rcnn,kwargs=mp_kwargs)
下面,启动进程:p.start()
从子进程队列中获取训练得到的fast rcnn的网络:fast_rcnn_stage1_out = mp_queue.get()
等待子进程结束:p.join()
网友评论