Faster-Rcnn代码实现的理解之损失函数

在《Faster-Rcnn代码之网络架构》中,我们采用DetectionTeamUCAS中的Faster-RCNN Tensorflow实现,对Faster-Rcnn的网络架构的代码进行了理解,在这个文章中,我们将对Faster-RCNN的损失函数的定义有关的代码做一些笔记。笔记中难免会出现一些理解的偏差,如有错误,还请各位看官不吝指出,共同学习,共同提高。

先放上faster-rcnn中RPN 网络的loss的公式:

     

这里的两个slice 来源于:https://www.slideshare.net/xavigiro/faster-rcnn-towards-realtime-object-detection-with-region-proposal-networks

其中loss函数中的t参量以及在边框回归中使用的smooth_l1 函数定义为下图:

之前的Fast-RCNN网络引入了multi-task loss,在网络采用了全连接网络作为分类和边框回归,因此只有上图中第二个slice的Loss函数,在Faster-RCNN网络中,引入了RPN网络,在训练RPN网络时候,则引入了RPN网络的Loss函数,如上图中第一个slice。

有了如上的概念后,根据我们的网络,我们需要获取的参量为:

RPN网络:

1.anchor的类别预测 pi 

2.ground truth的类别标签 pi* 

3. 256个位置对应的 256×(scale×ratios) 个anchor对应的编码后的 t 预测矩阵,

4.每一个anchor对应的最大重叠率的ground truth bbox的 t target 矩阵

Fast-RCNN网络:

1.真实的类别标签 u

2.预测的类概率 p

3.真实的ground truth 对应的 t 矩阵

4.预测的bbox 对应的 t 矩阵

有了这些基本的定义,我们回到代码中:

loss_dict = self.build_loss(
                                        rpn_box_pred=rpn_box_pred,
                                        rpn_bbox_targets=rpn_bbox_targets,
                                        rpn_cls_score=rpn_cls_score,
                                        rpn_labels=rpn_labels,
                                        bbox_pred=bbox_pred,
                                        bbox_targets=bbox_targets,
                                        cls_score=cls_score,
                                        labels=labels)

final_bbox, final_scores, final_category = self.postprocess_fastrcnn(
                                        rois=rois,
                                        box_ppred=bbox_pred,
                                        scores=cls_prob,
                                        img_shape=img_shape)    

在代码中,定义了一个loss_dict,用来保存RPN网络和Faster-RCNN的loss,其接收的参量如代码所示。回到build_loss 函数,其定义如下:

 with tf.variable_scope('build_loss') as sc:
            with tf.variable_scope('rpn_loss'):
                利用smooth_l1_loss_rpn 函数计算边框回归误差
                rpn_bbox_loss = losses.smooth_l1_loss_rpn(bbox_pred=rpn_box_pred,
                                                          bbox_targets=rpn_bbox_targets,
                                                          label=rpn_labels,
                                                          sigma=cfgs.RPN_SIGMA)、

                # rpn_bbox_loss = tfapi_loss.smooth_l1_loss_rpn(bbox_pred=rpn_box_pred,
                #                                               bbox_targets=rpn_bbox_targets,
                #                                               label=rpn_labels,
                #                                               sigma=cfgs.RPN_SIGMA)
                # rpn_cls_loss:
                # rpn_cls_score = tf.reshape(rpn_cls_score, [-1, 2])
                # rpn_labels = tf.reshape(rpn_labels, [-1])
                # ensure rpn_labels shape is [-1]
                只选出那些 label 为 0 与 1 的框label 所对应的 rpn_cls_score 
                rpn_select = tf.reshape(tf.where(tf.not_equal(rpn_labels, -1)), [-1])
                rpn_cls_score = tf.reshape(tf.gather(rpn_cls_score, rpn_select), [-1, 2])               
                选出那些label所对应的 ground_truth 对应的标签 函数接收的时候为所有的anchor对应的,针对label的赋值,来小批量的处理
                rpn_labels = tf.reshape(tf.gather(rpn_labels, rpn_select), [-1])
                
                采用交叉熵衡量分类误差,这里只有 有目标与没目标之间两类
                rpn_cls_loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=rpn_cls_score,
                                                                                             labels=rpn_labels))
                对分类以及回归误差分别进行加权后return
                rpn_cls_loss = rpn_cls_loss * cfgs.RPN_CLASSIFICATION_LOSS_WEIGHT
                rpn_bbox_loss = rpn_bbox_loss * cfgs.RPN_LOCATION_LOSS_WEIGHT

在计算smooth_l1 误差时候函数计算为:

def _smooth_l1_loss_base(bbox_pred, bbox_targets, sigma=1.0):
    '''

    :param bbox_pred: [-1, 4] in RPN. [-1, cls_num+1, 4] in Fast-rcnn
    :param bbox_targets: shape is same as bbox_pred
    :param sigma:
    :return:
    '''
    sigma_2 = sigma**2

    box_diff = bbox_pred - bbox_targets
    
    abs_box_diff = tf.abs(box_diff)

    smoothL1_sign = tf.stop_gradient(
        tf.to_float(tf.less(abs_box_diff, 1. / sigma_2)))
    这里得到的是根据smooth_l1 公式得到的 , 这里 x 是 bbox的差值,如果真实值与预测
    值之间的差值小于1/sigma_2 则 采用第一种计算方式,如果大于 1/sigma_2 则采用第二种
    计算方式
    loss_box = tf.pow(box_diff, 2) * (sigma_2 / 2.0) * smoothL1_sign \
               + (abs_box_diff - (0.5 / sigma_2)) * (1.0 - smoothL1_sign)
    return loss_box

#这个代码实现的作者,并没有直接采用0.5 而是再引入了 一个sigma 因子来约束


def smooth_l1_loss_rpn(bbox_pred, bbox_targets, label, sigma=1.0):
    '''

    :param bbox_pred: [-1, 4]      all encode rois  every feature map location with 9 anchors
    #this is the RPN direct output
    :param bbox_targets: [-1, 4]   all anchors t matrix computed by gt_boxes
    :param label: [-1]             define all anchors pos or negative
    :param sigma:
    :return:
    '''
    value = _smooth_l1_loss_base(bbox_pred, bbox_targets, sigma=sigma)
    value = tf.reduce_sum(value, axis=1)  # to sum in axis 1
    这一块对 相应的 tx+ty+tw+th 求和,获得每一个anchor对应损失值
    rpn_select = tf.where(tf.greater(label, 0))
        
    # rpn_select = tf.stop_gradient(rpn_select) # to avoid
    在所有的value中,只选择那些为正例的样本,因为为
    负例的样本 label 为 0 因此在公式中不参与贡献,但是最后均值的时候确实 正负一起考虑的。
    selected_value = tf.gather(value, rpn_select)
    non_ignored_mask = tf.stop_gradient(
        1.0 - tf.to_float(tf.equal(label, -1))) # positve is 1.0 others is 0.0

    bbox_loss = tf.reduce_sum(selected_value) / tf.maximum(1.0, tf.reduce_sum(non_ignored_mask))  
    这里讲选出来的正样本所有的bbox的loss求和后除以这个batch中 label为0 与
    label为1 的样本的总数,起到了公式中除以N(pos+neg)的作用 

    return bbox_loss

 在计算RPN的分类误差的时候,定义如下:

rpn_select = tf.reshape(tf.where(tf.not_equal(rpn_labels, -1)), [-1])
rpn_cls_score = tf.reshape(tf.gather(rpn_cls_score, rpn_select), [-1, 2])
rpn_labels = tf.reshape(tf.gather(rpn_labels, rpn_select), [-1])


rpn_cls_loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=rpn_cls_score,
                                                                             labels=rpn_labels))

首先从所有的rpn_label 中选出仅仅作为minibatch参与计算loss的label,获得相关的rpn网络对于minibatch中anchor的二分类值,最后计算标签与预测值之间的交叉熵,得到分类的误差,得到这两类误差后,就可以对其进行加权相加得到RPN网络的总误差值。在计算Fast-RCNN的边框回归误差以及分类误差的时候,由于RPN与Fast-RCNN采用的loss相同,只是在具体的类别上不一致,因此采用了与RPN网络同样的误差函数:

with tf.variable_scope('FastRCNN_loss'):
    if not cfgs.FAST_RCNN_MINIBATCH_SIZE == -1:
          bbox_loss = losses.smooth_l1_loss_rcnn(
                        bbox_pred=bbox_pred,
                        bbox_targets=bbox_targets,
                        label=labels,
                        num_classes=cfgs.CLASS_NUM + 1,
                        sigma=cfgs.FASTRCNN_SIGMA)

 这里的输入为 bbox_pred ,是特征图上对应的roi经过Pooling,全连接层得到bbox_pred,bbox_targets 是 roi对应的具有最大重叠率的ground truth 框的映射因子t矩阵,labels 则是每一个target对应的类别。具体自己算Fast-RCNN的损失函数如下:

def smooth_l1_loss_rcnn(bbox_pred, bbox_targets, label, num_classes, sigma=1.0):
    '''

    :param bbox_pred: [-1, (cfgs.CLS_NUM +1) * 4]
    :param bbox_targets:[-1, (cfgs.CLS_NUM +1) * 4]
    :param label:[-1]
    :param num_classes:
    :param sigma:
    :return:
    '''

    outside_mask = tf.stop_gradient(tf.to_float(tf.greater(label, 0)))
    选出那些需要计算损失的roi所在的标签
    bbox_pred = tf.reshape(bbox_pred, [-1, num_classes, 4]) 
    每一个roi,对所有的类别预测位置
    bbox_targets = tf.reshape(bbox_targets, [-1, num_classes, 4])

    value = _smooth_l1_loss_base(bbox_pred,
                                 bbox_targets,
                                 sigma=sigma)

    value = tf.reduce_sum(value, 2)
    value = tf.reshape(value, [-1, num_classes])
    得出roi 在每个类上的预测误差
    inside_mask = tf.one_hot(tf.reshape(label, [-1, 1]),
                             depth=num_classes, axis=1)
    得到相关的roi的类别标签的one-hot编码
    inside_mask = tf.stop_gradient(
        tf.to_float(tf.reshape(inside_mask, [-1, num_classes])))
    
    normalizer = tf.to_float(tf.shape(bbox_pred)[0])  

    bbox_loss = tf.reduce_sum(
        tf.reduce_sum(value * inside_mask, 1)*outside_mask) / normalizer
    参与计算的roi(数量为roi_per_img,定义在cfg文件中) 的平均回归误差
    return bbox_loss

 在计算Fast-RCNN的分类误差时,采用所有类的交叉熵,得到回归误差与分类误差加权。

cls_loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(
                        logits=cls_score,
                        labels=labels))  # beacause already sample before
cls_loss = cls_loss * cfgs.FAST_RCNN_CLASSIFICATION_LOSS_WEIGHT 
bbox_loss = bbox_loss * cfgs.FAST_RCNN_LOCATION_LOSS_WEIGHT

根据论文的公式,最后将RPN网络的(分类,回归)误差与Fast-RCNN的(分类,回归)误差相加后作为总的误差进行训练即可。

这一章主要介绍了经过RPN网络以及Fast-RCNN网络后得到的anchors与rois怎么建立起和ground truth 类别与座标之间的关系,误差的建立,下一章则介绍训练的方式。

因为代码量较大,先开始整体理解了一下,后面分了几次对于关键函数做了理解,写作上免不了有疏漏,错误,理解上的问题等,如有问题,还请及时纠正,谢谢!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章