Faster RCNN原理及Pytorch代码解读——RPN(四):损失函数

前两篇博客已经知道了RPN输出的预测值和真值,有了这些我们就可以计算RPN的损失了。
RPN的损失函数主要包含分类与回归两部分,具体公式如下:
L ( P i , t i ) = 1 N c l s ∑ i = 1 L c l s ( P i , P i ∗ ) + λ 1 N r e g ∑ i = 1 P i ∗ L r e g ( t i , t i ∗ ) L({P_{i}, t_i}) =\frac{1}{N_{cls}} \sum_{i=1} L_{cls}(P_i, P^*_i)+ \lambda \frac{1}{N_{reg}} \sum_{i=1} P^*_iL_{reg}(t_i, t^*_i) L(Pi,ti)=Ncls1i=1Lcls(Pi,Pi)+λNreg1i=1PiLreg(ti,ti)
∑ i = 1 L c l s ( P i , P i ∗ ) \sum_{i=1} L_{cls}(P_i, P^*_i) i=1Lcls(Pi,Pi)代表了256个筛选出的Anchors的分类损失, P i P_i Pi为每一个Anchor的类别真值, P i ∗ P^*_i Pi为每一个Anchor的预测类别。由于RPN的作用是选择出Proposal, 并不要求细分出是哪一类前景,因此在这一阶段是二分类,使用的是交叉熵损失。值得注意的是,在F.cross_entropy()函数中集成了Softmax的操作,因此应该传入得分,而非经过Softmax之后的预测值。

P i ∗ L r e g ( t i , t i ∗ ) P^*_i L_{reg}(t_i, t^*_i) PiLreg(ti,ti)代表了回归损失,其中bbox_inside_weights实际上起到了 P i ∗ P^*_i Pi进行筛选的作用, bbox_outside_weights起到了 λ 1 N r e g \lambda \frac{1}{N_{reg}} λNreg1来平衡两部分损失的作用。 回归损失使用了smoothL1函数, 具体公式如下:
L r e g ( t i , t i ∗ ) = ∑ i ∈ x , y , w , h s m o o t h L 1 ( t i − t i ∗ ) L_{reg}(t_i, t^*_i)=\sum_{i \in x,y,w,h} smooth_{L_1}(t_i-t^*_i) Lreg(ti,ti)=ix,y,w,hsmoothL1(titi)
s m o o t h L 1 ( x ) = { 0.5 x 2 , if |x| < 1 ∣ x ∣ − 0.5 , otherwise smooth_{L_1}(x)=\begin{cases} 0.5x^2, & \text {if |x| < 1} \\ |x|-0.5, & \text{otherwise} \end{cases} smoothL1(x)={ 0.5x2,x0.5,if |x| < 1otherwise
从第二个式子中可以看到, s m o o t h L 1 smooth_{L1} smoothL1函数结合了1阶与2阶损失函数,原因在于,当预测偏移量与真值差距较大时,使用2阶函数时导数太大,模型容易发散而不容易收敛,因此在大于1时采用了导数较小的1阶损失函数。

下面看下代码是如何实现的,这一部分的代码在网络结构那一篇已经出现过了,现在再来重新看一遍。

		# rpn_data是上一篇的RPN训练标签的生成的结果,返回的数据有4个,分别是
		# labels、bbox_targets、bbox_inside_weights、bbox_outside_weights
		# 维度分别是(batch, 1, 333, 50)、(batch, 36, 37, 50)、(batch, 36, 37, 50)、(batch, 36, 37, 50)
		rpn_data = self.RPN_anchor_target((rpn_cls_score.data, gt_boxes, im_info, num_boxes))

        # 计算分类损失
        # rpn_cls_score_reshape是原始rpn网络的分类输出(还没有经过softmax),其shape为(batch, 18, 37, 50)
        # 修改后的形状为(batch, 16650, 2)
        rpn_cls_score = rpn_cls_score_reshape.permute(0, 2, 3, 1).contiguous().view(batch_size, -1, 2)
        # rpn_label修改后的形状为(batch, 16650)
        rpn_label = rpn_data[0].view(batch_size, -1)

		# 得到rpn_label中非-1位置的索引,注意这个索引是将rpn_label拉成一维得到的索引
        rpn_keep = rpn_label.view(-1).ne(-1).nonzero().view(-1)
        # 将rpn_keep对应位置的rpn_cls_score的得分和rpn_labe的标签挑选出来
        rpn_cls_score = torch.index_select(rpn_cls_score.view(-1,2), 0, rpn_keep)
        rpn_label = torch.index_select(rpn_label.view(-1), 0, rpn_keep.data)
        rpn_label = rpn_label.long()
        # 先对scores进行筛选得到256个样本的得分,随后进行交叉熵求解
        self.rpn_loss_cls = F.cross_entropy(rpn_cls_score, rpn_label)
        fg_cnt = torch.sum(rpn_label.data.ne(0))

        rpn_bbox_targets, rpn_bbox_inside_weights, rpn_bbox_outside_weights = rpn_data[1:]

        # 利用smoothl1损失函数进行loss计算
        self.rpn_loss_box = _smooth_l1_loss(rpn_bbox_pred, rpn_bbox_targets, rpn_bbox_inside_weights,
                                                        rpn_bbox_outside_weights, sigma=3, dim=[1,2,3])

def _smooth_l1_loss(bbox_pred, bbox_targets, bbox_inside_weights, bbox_outside_weights, sigma=1.0, dim=[1]):
    sigma_2 = sigma ** 2
    box_diff = bbox_pred - bbox_targets
    in_box_diff = bbox_inside_weights * box_diff
    abs_in_box_diff = torch.abs(in_box_diff)
    smoothL1_sign = (abs_in_box_diff < 1. / sigma_2).detach().float()
    in_loss_box = torch.pow(in_box_diff, 2) * (sigma_2 / 2.) * smoothL1_sign \
                  + (abs_in_box_diff - (0.5 / sigma_2)) * (1. - smoothL1_sign)
    out_loss_box = bbox_outside_weights * in_loss_box
    loss_box = out_loss_box
    for i in sorted(dim, reverse=True):
      loss_box = loss_box.sum(i)
    loss_box = loss_box.mean()
    return loss_box

Guess you like

Origin blog.csdn.net/weixin_41693877/article/details/107150709