首先给出某大神画出的训练网络图链接打不开点击此处,本次不对源代码进行分析,仅仅以流程图先大体上对整个的Faster-RCNN有个宏观上的了解,具体的实施细节需要查看从代码或其他理论博客。
首先,看到左上角对矩形框的颜色进行了解释,特别要注意代表loss的粉色。因为在一般的神经网络或者算法中,loss函数一般只有一个,但是该网络有四个(从源代码中可以看到,这四个loss函数进行相加又得出一个total_loss,共五个),而且在四个之中,从流程上看‘距离’还较远,似乎没有联系。
对于整个网络,从data输入框到conv5_3而言,没有什么特别,就是一个VGG网络。就在这个conv5_3开始产生了两个支路:roi_pooling5。关于roi_pooling5要详细做一下介绍,他对于普通的pooling层有什么区别,为什么要用它?
对于神经网络,卷积部分不需要考虑输入进来的图片的长宽,即整个像素的多少。但是全连接需要,因为整个网络定下来以后,全连接的参数的个数就确定了。到了全连接层,每一个参数一一对应上一层输出(近全连接层的输入)元素。如果上一层输出的元素个数不同,肯定造成全连接层参数的不匹配。所以在很多神经网络的输入部分先进行预处理,这其中就包括对图像进行resize,crop或者wrap。无论哪种方式,都是让输入的图像尺寸与当初训练时的确定下来的输入图像一致。
在Faster-RCNN中,需要对图片上的目标进行精确定位,而目标的形状肯定是不一致的。roi_pooling5后经过pool5就是全连接层,并且普通的池化层只是成倍的减少输出参数,并不会根据输入尺寸的不同而输出固定的尺寸。其实roi-pooling也没有什么很难理解的地方,他就是根据输入尺寸的不同来调整ksize和strides。通过TensorFlow中的max_pool函数中的ksize组成[1,k,k,1],第一和第四元素一般不改变,所以改变ksize的k值,strides=[1,s,s,1],同理,改变s的值。通过确定k和s的值,我们就可以得到固定尺寸的池化结果。
s和k的值如何确定:
具体的详细介绍过程可以参考这篇博客,总的来说,k = a/n, 向上取值,s = a/n, 向下取值。比如a = 13,n = 3,那么k就为5,s等于4.。总之就是通过动态的调整ksize和strides来得到相同尺寸的特征图。
另外一种方法是进行分块:例如,在得到特征图后,如何得到一个6×6的全连接层的输入呢?RoI Pooling这样做:将RoI对应的特征图分成6×6块,然后直接从每块中找到最大值。在上图中的例子中,比如原图上的的RoI大小是280×480,得到对应的特征图是18×30。将特征图分成6块,每块大小是3×5,然后在每一块中分别选择最大值放入6×6的对应区域中(引用)。这里取的参数比较巧合,没有出现是浮点然后取整的操作。如果是20×30的操作,20不能被6整除,这样取整的话就会造成misalignment问题。这也是ROI Pooling存在的弊端。在mask-RCNN中的ROI Align池化方式解决了该问题。
另一方面,对于损失函数,首先看rpn(包括rpn_loss_cls, rpn_loss_bbox),此过程在程序中是有anchor_target_layer函数生成,对应图上的名字为rpn-data,绿色背景。该函数的过程大致为:通过anchor规则生成的框与groundtruth相比较,将IOU在0.3-0.7之间的anchor去掉(置为-1,0为北背景,1为前景),保留前景和背景的anchor生成的框。然后根据自己设置的参数,选出一定的前景(label为1)和背景(label为0)(label为-1的在此不参与进来)进行计算作为损失函数的重要参数:bbox_inside_weights和bbox_outside_weights。这里生成了两个损失函数。但是这里生成的这两个重要参数(再加上另外两个,rpn_labels和rpn_bbox_targets)没有和图中红色背景中的rpn_clc_score有什么关系。程序中尽管用到了这个rpn_clc_score的输出,但是仅仅是获取宽和高的信息。主要是按照anchor规则生成的框与groundtruth一起做一定的操作而来。损失函数进行反向传播进行更新变量时,这两个参数直接影响到变量值的多少,而进行推理过程,这些参数就没有什么用了。所有有groundtruth参与的地方,训练过程肯定需要,但是在推理过程不在有用,rpn_loss_bbox在anchor_target_layer没有参与,主要是在求损失函数的时候用到。总之这里可以单独训练,来求解一个二分类加上位置检测问题。所谓的二分类就是求是否有object。另外,求解rpn_smooth_l1时,是有rpn_bbox_pred与rpn_bbox_target,以及求出来的bbox_inside_weights和bbox_outside_weights参与了,rpn_bbox_target是anchor在通过与groundtruth中的第几个roi的IOU最大这一方法筛选留下来的anchor框。
从这里我们也可以看出,这种以anchor点生成建议框是一种非常创新的方式。每个点生成九个框,框的坐标位置判断与是否是物体的判断一一对应。从而可以特征图中的敏感区域,分出输出每一个anchor点对应九个框的判断结果。
在进行另外两个损失函数loss_bbox和loss_cls之前,我们先看proposal。毕竟在走向这两个损失函数之前是需要经过proposal这个函数的。在这里需要几次从特征图到真实图片宽度的变化映射。其最终目的是根据预设参数(包括保留是前景框的个数以及IOU的threshold的值等)选出前景框。这里应当注意的是,计算proposal模块时是直接需要rpn_cls_score和rpn_bbox_pred模块输出的值。例如这里的rpn_cls_score输出的channel个数为18(特征图上一个点对应九个anchor生成的框,每个框的有两个概率事件,即是前景还是背景,这里选出前景框,即从channel的后9个通道选取,因为这里代表的是是前景的score)。即这里和im_info进行‘合作的’是这两个模块的输出,和anchor生成框不在有关系。最终输出的是前景概率比较高的框所对应的信息(例如训练时设定前景为2000个,测试时300个,如果前景概率高的数量超过2000个,需要删除一部分。这里采用NMS确定留下或删除框)。还需要注意的是这里没有和损失函数相关的参数生成。
进入到roi-data模块,主要功能函数为:proposal_target_layer。参与的数据主要为groundtruth和上一层proposal选出来的前景概率框信息rpn_roi。在此模块内需要groundtruth,与上一层输出的rpn_roi进行overlaps。既然进行了overlaps,那么就又有了IOU值的产生,接着就会要对rpn_roi进来的信息重新规划是前景还是背景(这里有点绕,因为proposal里面说过,那里选出的全是全景,这里怎么又区分了全景和背景。这次因为proposal里选出的前景和背景是根据rpn_cls_score产生的概率选出的(在训练阶段,概率值很可能不准确),而这里是重新进行计算(通过与groundtruth进行IOU后,留下来更加准确的值)。也就是在这里是推理过程产生的前后背景,anchor_target_layer是在训练的时候产生的前景背景,因为是训练,所以更新参数后影响这里proposal_target_layer生成的前景背景(channel个数为18,前景在后九个),从而使生成的前景更加准确),另外为每个rpn_roi确定找到了与gt_box中归属类别1-20(很重要),背景的label被置为0。背景和前景的的选取和rpn_data有区别。这里将overlaps后IOU大于0.5的为前景,小于0.5的为背景。当然这里同样对数量进行了控制,如果跟图IOU确定的前景和背景多余规定的数量,要进行随机删除。背景和前景确定后进行compute_targets计算(实际大部分工作为:bbox_transform函数执行)返回bbox_target_data:[标签,dx,dy,dw,dh],此时标签为类别标签,不再是前景和背景,而是各个类别的标记。类别如何确定的呢?我们知道在选出与groundtruth的overlaps后,可以进行排序。即当前候选框与groundtruth框的IOU最大时,此时groundtruth的类别就是该候选框的类别。此时的label因为有groundtruth参与求出可知,该过程只和训练过程相关,在推理过程中在proposal_layre层中生成的rois数据直接进入conv5_3.在程序中应当注意该模块的train和test网络中,name的命名不一样。
然后进入_get_bbox_regression_labels函数,该函数的作用为:求得最终计算loss时使用的ground truth边框值(即把groundtruth边框值映射到特征图的位置)、和bbox_inside_weights ,产生两个(len(rois),4*21)大小的矩阵,其中一个对fg-roi对应引索行的对应类别位置偏移量的4个位置填上(dx,dy,dw,dh), 另一个对fg-roi对应引索行的对应类别的4个位置填上(1,1,1,1)进而表示详细类别label(21类中的某一个,黄色背景labels),这样就是为上一层选出来的只是二分类的检测结果通过与groundtruth框的最大IOU进行详细分类。最后,产生的这些参数(labels, bbox_targets, bbox_inside_weights, bbox_outside_weights)的目的是和由Fast-rcnn的模块中输出的cls_score和bbox_pred产生的预测类别分数以及类别框进行融合,从而产生loss_cls和loss_bbox这两个损失函数。在_get_bbox_regression_labels函数内,生成两个(len(rois),4*21)大小的矩阵,bbox_targets,和bbox_inside_weight。注意列的数量为4*21(21为20个类别加一个背景类),在这里将对应loss_bbox与其组成第二次损失函数;这里进行smooth_l1的求解中,需要的参数为bbox_pred, bbox_targets, bbox_inside_weights, bbox_outside_weights。其中bbox_targets
从产生的损失函数需要的参数(主要是黄色背景)来看,大致就是:anchor产生的框与groundtruth形成的损失参数(rpn-data);由网络慢慢学习生成的rpn_cls_score和rpn_bbox_pred,后经过proposal中的nms过程保留下来的框(roi-data)与groundtruth形成的损失参数。
从代码中的train_net文件中可以看出损失函数组成还是比较复杂的,final loss = cross_entropy + loss_box + rpn_cross_entropy + rpn_loss_box。感觉每一步理解起来也不是那么容易,跟之前的神网组成部分有很大的差别。特别是loss组成部分,从整个流程图上也可以看出,整个loss像从中间另起一个分支一样。对于推理过程,从rpn_cls_prob_reshape和rpn_bbox_pred经过proposal(程序上体现为经过proposal_layer这个函数)后直接产生了rois送给roi_pool5。