大白话分析——SSD目标检测网络从训练到预测(中)

一. 背景

本文档以ssd300作为背景网络进行解读,以Tensorflow,Keras为框架
原始代码:https://github.com/pierluigiferrari/ssd_keras
分析后的代码:https://github.com/Freshield/LEARN_detection/tree/master/a4_github_better_ssd/a2_analyze_model
目前网上基本都网络部分讲的比较多,但是真正训练和预测部分都相对粗略,所以自己网上找了一个相对比较好的ssd检测作为蓝本来分析,然后把相应的过程用大白话给表达出来,方便大家可以更好的理解网络。

二 到 三 请查找上一篇

https://blog.****.net/freshield/article/details/105235281

四. 数据部分

https://blog.****.net/freshield/article/details/105493183

五. Loss部分

在训练前最后需要的就是Loss部分的编写了,回顾一下,我们目前得到了预测的值,shape为(batch, 8732, 33)其中33为21(分类预测)+4(坐标预测)+8(先验框位置以及variance)。同时我们也得到并转换了label为同样的尺寸,也就是(batch, 8732, 33)这里的顺序和预测都是相对应的,那么我们就是要分别来计算分类的Loss和坐标的Loss。在这里分类使用的交叉熵Loss而坐标使用的是L1 Smooth Loss。

1. 计算全部框的分类损失,对每个框进行计算

首先,我们提取出33中的0-20也就是所有分类的结果,直接计算交叉熵损失,注意这里我们可以直接计算交叉熵因为这里的计算各个先验框都是独立计算的,互不相关的,然后我们并不会用到所有8732个先验框的结果,会从中进行挑选,然后最后加到我们的Loss当中。
这里计算的方法就和正常的分类网络的计算一样了,同时也叫做先验框的置信度。在这步之后我们得到了(batch, 8732)也就是每个先验框的分类损失。

2. 计算全部框的定位损失L1 Smooth Loss, 对每个框进行计算

然后,同样的我们开始计算定位损失,和分类损失一样,我们计算的每个先验框只和预测结果和label有关。
这里先解释一下什么是L1 Smooth Loss,它的公式和图像如下:
大白话分析——SSD目标检测网络从训练到预测(中)
简单来说损失函数就是y_true和y_pred的差值,如果你这个差值大于1,则差值要减去0.5,如果差值小于1则等于0.5乘以差值的平方。这样的好处是在0的位置依然可微,可以让网络更好的学习。
这里要计算的是y_pred(batch, 8732, 21:25)和label(batch, 8732, 21:25),进行计算,也就是loss其实就是简单的减法,然后再根据L1 Smooth Loss的方法进行一下变换而已,最后把四个差值加到一起就是我们的Loss。
所以最后我们得到的定位损失大小也是(batch, 8732),同样的我们并不会使用所有这8732个框的损失,而会之后进行挑选

3. 得到所有正例

我们之后需要计算Loss时,需要所有正例的Loss,这里的正例也就是和label对应的,有分类值的框。和一部分的反例的Loss,所以我们这里需要两个mask,一个是所有正例的mask,和挑选后的反例的mask。
首先,所有反例的mask很好得到,label矩阵的第0维只有背景类才为1,其他都为0,这就天然的成为了反例的mask。
另外,正例的mask也很好得到,我们取数据(batch, 8732, 33)的label[:, :, 1:21],再求它的axis=-1的reduce_max就得到了正例的数量。这里reduce_max就是得到当前维度,也就是第2维,也就是每个框的分类结果的最大值。这里因为我们是从第一维开始取的,所以所有不是背景类的数据都为1,而背景类也就是反例为0。
注意这里并不能简单的使用1-背景的mask,因为在数据部分时候,我们对于一些先验框的类别数置零,导致这些先验框既不属于正例也不属于反例,所以1-背景的mask并不是正例的mask,而是正例加上忽略的先验框的mask。

4. 计算真正分类损失(需要计算正反例)

在这步的计算中,我们会得到最终的分类损失。这里的分类损失由两个部分组成,一个是正例的分类损失,一个是反例的分类损失。这个反例的分类损失部分又称为hard negative mining,就是相当于我们不单学习那些正确的,同时我们要让网络学习知道哪些是不正确的,而这里的难点在于选择哪些框的值来让网络进行学习。

a. 得到正例的分类损失

在上一步我们已经得到了正例的mask,所以我们只需要让正例的mask乘上全部分类的损失,就相当于挑选出了正例的分类损失,然后我们再sum到一起就好。

b. 得到反例的mask

首先我们把正例的mask求sum,这就相当于是正例的个数,因为只有正例才有值且值为1。
然后我们根据下边的规则来得到要得到的反例的个数:
把正例的个数乘以正反例比例,得到反例的个数,这里在程序中一般为3,也就是一个正例对应着三个反例
然后用反例的个数和网络设定的n_neg_min值进行比较,如果小于n_neg_min则让反例数等于n_neg_min,如字面的意思,就是设定的最小反例个数,不过程序一般为0,所以这里不影响
最后,先得到反例mask中的非零个数,进行比较。如果比非零个数多则设反例数为非零个数,因为这里的非零个数就代表了反例的个数,选的反例个数不能比反例的总数多。另外这里需要比较的原因是之后会通过top_k算法进行计算,如果不进行限制可能会选到正例。
在得到了反例的个数之后,这里直接调用tensorflow的top_k函数,返回反例矩阵中值最大的k个数的值和索引,这里的k就是我们刚才得到的反例。所以可以看出这里的取的依据是loss的值,也就是我们取loss最大的k个值。
在得到最大k个数的值和索引后,我们就可以根据索引乘以分类总loss矩阵再sum到一起得到所有要计算的反例的loss,最后和正例的loss相加就得到了我们的分类损失或者说是置信度损失。

5. 计算真正的回归损失

这里回归损失非常好算,因为我们想回归损失相当于是你预测的位置和真实框的位置差产生的损失,而反例没有位置,所以就没法计算回归损失,所以我们的回归损失只需要计算正例的回归损失就可以了。而正例的mask我们在上边已经得到了,就用所有框的回归损失乘以正例的mask再sum到一起就得到了我们的回归损失或者说是坐标损失。

6. 计算最终的loss

最终的loss就是把分类的损失加上回归的损失,不过注意这里回归损失还要乘一个alpha,作为平衡分类损失和回归损失重要性的目的。不过代码里这个alpha一般设为1,也就是直接相加。
最后这里还要除以我们之前得出的正例的个数,因为每张图我们的正例数是不一样的,那么明显正例数多的数据要比正例数少的数据Loss大很多,这样会造成网络的波动,所以这里除以正例的个数得到平均值,这就是网络最终的Loss。

至此,我们的SSD网络已经可以开始训练了~