制作label,并计算loss。

首先理一下网络输出的shape,和数据label的shape。再看一下计算loss的时候应该怎么把数据转换成容易计算的形式。

1 shape

1.1 网络输出的shape

下图表示分类分支预测输出的feature map。 格子表示featuremap上的像素。可以发现其实featuremap的每一层是预测种类别的信息,我们可以假设有四类别的信息,即x,y,w,h,当然实际预测的是x,y,w,h变换后的值。直接可以看出,每个4个像素对应一个anchor,且位置与原图上的位置相对应。

1.2计算loss时候的shape

因为我们需要计算fpn不同level层的损失(loss),由于不同level层的下采样次数不一样 ,所以featuremap的大小也不一样,这时候我们如果想要简单的将不同层的预测输出放在一个矩阵中,然后和label计算损失是不行的。因为像素宽度不一样,所以不能直接concat,所以我们需要reshape一下,再计算损失。reshape以后矩阵的每一行表示一个anchor。

2 得到监督数据(label)或者说target

其实说起来很简单,就是对于每个anchor只分配一个与其iou最大的目标,注:这里不管目标是相同类别的还是不同类别的,都是取最大。

但是实现起来就比较复杂。首先考虑到有两种情况,同种类别击中一个anchor,或不同类别击中一个anchor。

看了别人代码(https://github.com/yhenon/pytorch-retinanet)以后才发现实现代码的时候一个代码就可以把这两种情况考虑进来。看起来没多少代码,但是感觉还是非常考验逻辑的。

2.1 高维度的广播机制,矩阵广播矩阵

首先用到了高纬度广播的机制。平时用的都是说把一个向量或是常量广播到矩阵或向量。
但是这里却把矩阵和矩阵进行广播。

简单的写一下广播的时候shape的变换

(10, 4) + (2, 4) --对被加的矩阵扩展维度--> 
(10, 4, 1) + (2, 4) --> 
(10, 4, 1) + (2, 4) = (10, 4, 2)

其实可以看做把(2, 4)形状的矩阵的每一行拿出来和(10, 4)形状的矩阵相加,然后把两个结果concat起来。

2.2 数值索引和布尔索引的区别

  • False/true索引是只把true的索引出来。布尔索引要求shape要一致。
  • 数值索引是把数值对应的数据拿出来然后堆叠到一起。数值索引不需要一致,且可以大于被索引的长度。
## 数值索引
In [58]: a = torch.randn(10,2)
Out[58]: 
tensor([[-0.6997,  0.6535],
        [-0.3947,  0.3171],
        [ 0.3076, -1.2978],
        [-0.4225, -0.3197],
        [-1.5714, -1.7430],
        [-0.2521,  0.6664],
        [ 0.6825, -1.2606],
        [-1.1892,  0.3615],
        [-0.3309, -0.0463],
        [ 0.2456, -0.0654]])

In [59]: IoU_max, IoU_argmax = torch.max(a, dim=1)

In [60]: IoU_max
Out[60]: 
tensor([ 0.6535,  0.3171,  0.3076, -0.3197, -1.5714,  0.6664,  0.6825,  0.3615,
        -0.0463,  0.2456])

In [61]: IoU_argmax
Out[61]: tensor([1, 1, 0, 1, 0, 1, 0, 1, 1, 0])

In [76]: bbox_annotation = np.array([[ 1,1,1,1],[2,2,2,2]])

In [77]: bbox_annotation[iou_argmax]
Out[77]: 
array([[2, 2, 2, 2],
       [2, 2, 2, 2],
       [1, 1, 1, 1],
       [2, 2, 2, 2],
       [1, 1, 1, 1],
       [2, 2, 2, 2],
       [1, 1, 1, 1],
       [2, 2, 2, 2],
       [2, 2, 2, 2],
       [1, 1, 1, 1]])

3 坐标回归

坐标回归的是相对anchor的一个偏移量(归一化偏移量)。

  • 中心点回归: 相对于anchor中心点的偏移,再除以anchor的宽度/高度。这里除以宽度相当于是做一个归一化,因为同样的偏移量但对于大目标和小目标的意义是不一样的
  • 宽/高回归:回归一个log(gt/anchor);因为同样的宽高但对于大目标和小目标的意义是不一样的。大目标的宽高如果误差一点点我们希望提供的loss比较小,而小目标宽高如果误差一点点希望提供的loss比大目标的大一些。所以用log函数,因为log曲线的后面部分的值其实是比较小的,值的增长没那么快。
  • keras-retinanet中是回归左上角和右下角的角点坐标,回归的转换和中心点回归时候一样,只是对应的是anchor的角点,代码如下。
targets_dx1 = (gt_boxes[:, 0] - anchors[:, 0]) / anchor_widths
targets_dy1 = (gt_boxes[:, 1] - anchors[:, 1]) / anchor_heights
targets_dx2 = (gt_boxes[:, 2] - anchors[:, 2]) / anchor_widths
targets_dy2 = (gt_boxes[:, 3] - anchors[:, 3]) / anchor_heights

引用

  • https://github.com/yhenon/pytorch-retinanet
  • https://medium.com/@14prakash/the-intuition-behind-retinanet-eb636755607d