ronghuaiyang · 2020年07月01日

一阶段的物体检测器,从直觉到细节的方方面面(三)

作者:MATTHIJS HOLLEMANS
编译:ronghuaiyang
首发:AI公园公众号

导读

今天是第3部分,给大家说说训练和损失函数相关的细节。

(书接上回)

你该如何训练这些东西?

现在,我们几乎已经了解了如何训练这种物体检测模型的所有部分。

该模型使用一个非常简单的卷积神经网络进行预测。我们把这些预测的数字转换成边界框。数据集包含了表示训练图像中实际存在哪些物体的 ground-truth,因此为了训练这种模型,我们需要一个损失函数来比较预测框和 ground-truth。

问题是,在不同的图像之间,ground-truth 框的数量可能不同,从 0 个到几十个不等。这些框可能以任何顺序遍布整个图像。有些还会重叠。在训练期间,我们必须将每个检测器与其中一个 ground-truth 框匹配,以便我们能够计算每个预测框的回归损失。

如果我们天真地执行此匹配,例如总是把第一个 ground-truth 分配给第一个检测器,,第二个 ground-truth 分配给第二个检测器,等等,或者通过随机分配 ground-truth 给检测器,然后每个检测器将被训练来预测各种物体:有些大,有些小,有些会在角落里,有些在中心,等等。

这就是我在这篇文章一开始就提到的问题,以及为什么仅仅向模型中添加一些回归输出是行不通的。解决方案是使用一个固定数量检测器的网格,其中每个检测器只负责检测图像中位于该部分的物体,并且只负责一定大小的物体。

现在,损失函数需要知道哪个 ground-truth 属于哪个网格单元中的哪个检测器,同样地,哪些检测器没有与它们相关的 ground-truth。这就是我们所说的“匹配”。

把 ground-truth 框和检测器进行匹配

这种匹配是如何工作的?有不同的策略。YOLO 的做法是只让一个检测器负责检测图像中给定的物体。

首先,我们找到包围框中心所在的网格单元格。该网格单元将负责此物体。如果任何其他网格单元也预测这个物体,它们将受到损失函数的惩罚。

VOC 标注给出的边界框坐标为xmin, ymin, xmax, ymax。由于模型使用网格,并且我们根据 ground-truth 框的中心来决定使用哪个网格单元,因此将框坐标转换为center x, center y, widthheight是有意义的。

在这一点上,我们还希望将 box 坐标归一化到[0,1]范围,这样它们就独立于输入图像的大小(因为不是所有的训练图像都具有相同的尺寸)。

这也是我们应用数据增强的地方,比如随机的水平翻转和颜色变化(图像和它的边界框)。

:由于数据的增强,即随机裁剪和翻转,我们必须重新计算每个 epoch 中 ground-truth 框和检测器之间的匹配。这不是我们可以预先计算和缓存的东西,因为匹配可能会根据特定图像使用的增强而改变(每个 epoch 都会改变)。

仅仅选择网格单元是不够的。每个网格单元都有多个检测器,我们只需要其中一个检测器来查找物体,因此我们选择其锚框与物体的 ground-truth 框最匹配的检测器。这是用通常的 IOU 度量来完成的。

这样,最小的物体分配给检测器 1(它有最小的锚框),非常大的物体分配给检测器 5(它有最大的锚框),等等。

只有那个网格里的特定检测器才能预测这个物体。这个规则帮助不同的检测器专门处理形状和大小与锚盒相似的物体。(请记住,物体不必与锚点的大小完全相同,因为模型预测的是相对于锚点的位置偏移量和大小偏移量。锚框只是一个提示。)

因此,对于给定的训练图像,一些检测器会有一个与之相关的物体,而所有其他检测器不会。如果训练图像有 3 个不一样的物体,也就是 3 个 ground-truth 框,那么在 845 个检测器中,只有 3 个探测器应该作出预测,其他 842 个探测器应该预测出“没有物体”(我们的模型的输出是一个边界框,分数很低的置信度,理想的是 0%)。

640.jpg

从现在开始,我说的正样本意味着一个检测器有一个 ground-truth,负样本指的是探测器没有相匹配的物体。负样本有时也被称为“没有物体”或背景。

注意:在分类中,我们使用“样本”一词来表示训练图像的整体,但这里它指的是图像内部的物体,而不是图像本身。

由于模型的输出是一个 13×13×125 张量,所以损失函数使用的目标张量也将是 13×13×125 的张量。同样,这个数字 125 来自:5 个检测器,每个检测器预测物体的 20 个类的概率值 + 4 个边界框坐标 + 1 个置信度。

在目标张量中,我们只填充负责物体的检测器的边界框和独热编码的类向量。我们将预期的置信度设置为 1(因为我们 100%确定这是一个真实的物体)。

对于所有其他的检测器 —— 带有负样本的检测器 —— 目标张量中所有都是 0。边界框坐标和类向量在这里并不重要,因为它们将被 loss 函数忽略,而置信度评分为 0,因为我们 100%确定这里没有物体。

因此,当训练循环要求新的一批图像和它们的目标时,它得到的是一个 B×416×416×3 张量的图像和一个 B×13×13×125 张量的数字,它们代表我们期望每个检测器预测的 ground-truth。这个目标张量中的大多数数字都是 0,因为大多数检测器不负责预测一个物体。

在匹配时需要考虑一些额外的细节。例如,如果有多个 ground-truth 盒子,其中心恰好落在同一个单元格中,会发生什么情况?在实践中,这可能不是一个大问题,特别是如果网格足够精细的时候,但是我们仍然需要一种方法来处理这种情况。

从理论上讲,如果每个框都喜欢一个基于最佳 IOU 重叠的不同的检测器 —— 例如,盒子 a 与检测器 2 的 IOU 重叠最大,盒子 B 与检测器 4 的 IOU 重叠最大 —— 那么我们可以将两个 ground-truth 与这个单元中不同的检测器匹配。然而,这并不能避免两个需要相同检测器的 ground-truth 的问题。

YOLO 通过随机对 ground-truth 打乱来解决这个问题,然后它只选择第一个与单元格匹配的 ground-truth。因此,如果一个新的 ground-truth 框与一个已经负责另一个物体的单元格匹配,那么我们只需忽略这个新框。然后期待下个 epoch 运气好一点。

这意味着在 YOLO 中,每个单元格最多只能有一个检测器,其他检测器不应该检测到任何东西(如果检测到,就会受到惩罚)。

注意,还有其他可能的匹配策略。例如,SSD 可以将同一个 ground-truth 框与多个检测器相匹配:它首先选择具有最佳 IOU 的检测器,然后还会选择锚框中 IOU 大于 0.5 的检测器(没有匹配其他的)。(我假设这些检测器不一定都在同一个单元中,甚至不一定在同一个网格中,但论文没有说。)

这应该会使模型更容易学习,因为它不必在哪个探测器应该预测这个物体之间进行选择 —— 多个检测器现在都有机会预测这个物体。

:似乎有些设计选择是相互矛盾的。为了帮助检测器专门化,YOLO 将一个 ground-truth 物体分配给单个检测器(而将“no object”分配给该单元的其他检测器)。但 SSD 表示,多个探测器可以预测同一个物体。谁是正确的?我不知道。我不知道在这些特定的选择之间有任何消融研究。在 SSD 的情况下,这可能是可以的,因为检测器专门用于长宽比(形状)而不是大小。

损失函数

与往常一样,损失函数实际上告诉模型它应该学习什么。对于物体检测,我们需要一个损失函数来鼓励模型预测正确的边界框以及这些框的正确类。另一方面,模型不应该预测不存在的物体。

这是一个具有多个组件的复杂任务。因此,我们的损失包括几个不同的术语(这是一个多任务损失)。其中一些术语用于回归,因为它们预测实值数字,其他的用于分类。

对于任何给定的检测器,有两种可能的情况:

  1. 这个检测器没有与之相关的 ground-truth。这是一个负样本,它不应该检测到任何物体(例如,它应该预测一个边界框的置信度为 0)。
  2. 这个检测器确实有一个 ground-truth 框。这是一个正样本。检测器负责从 ground-truth 框中检测目标。

对于不应该检测出物体的检测器,我们将在它们预测边界框时惩罚它们,该边界框的置信度值大于 0。

这样的检测被认为是假阳性,因为在图像中的这个位置没有物体存在。太多的误报降低了模型的“精度”。

相反,如果探测器确实有一个 ground-truth,我们想惩罚它:

  • 当坐标错误时
  • 当置信度分数太低时
  • 当类别出错时

理想情况下,检测器可以预测一个与 ground-truth 框完全重叠的框,具有相同的类标签,并且具有较高的置信度。

当置信度分值太低时,该预测将被视为假阴性。模型没有找到真正存在的物体。

但是,如果置信度得分是好的,但是坐标是错误的或类别是错误的,预测将被视为假阳性。即使该模型声称它发现了一个物体,但它不是这个类别的物体,或者它在错误的地方。

这意味着相同的预测可以同时算作假阴性(减少模型的_召回_)和假阳性(减少_精度_)。只有当所有三个方面 —— 座标、置信度、类别—— 都正确时,预测才算得上是真阳性

由于许多不同的事情都可能出错,损失函数由几个部分组成,所有这些部分都度量模型所做预测出的不同的“错误”。把这些部分加起来就得到了总的损失指标。

SSD、YOLO、SqueezeDet、DetectNet 和其他一阶段检测器变体都使用略微不同的损失函数。不过,它们往往是由相同的元素组成的。让我们看看不同的部分!

没有 ground-truth 的检测器(负样本)

损失函数的这一部分只涉及置信度分数 —— 因为这里没有 ground-truth 框,我们没有任何坐标或类标签来比较预测。

这个损失项只计算那些“不”负责检测物体的检测器。如果这样的检测器“确实”发现了一个物体,这就是它受到惩罚的地方。

回想一下,置信度分数表示检测器是否认为有一个中心位于网格单元中的物体。对于这样的探测器,目标张量的真实置信度分数设置为 0,因为这里没有物体。预测的分数也应该是 0,或者接近 0。

置信度分数的损失需要以某种方式将预测分数与真实分数进行比较。在 YOLO 中,它是这样的:

no_object_loss[i, j, b] = no_object_scale * (0 - sigmoid(pred_conf[i, j, b]))**2

其中pred_conf[i, j, b]是坐标为i, j的单元格中检测器b的预测置信度。注意,我们使用sigmoid()将预测的置信度限制在 0 到 1 之间(使其成为逻辑回归)。

要计算这个损失项,我们只需取 ground-truth 与预测值之间的差值,然后平方(平方和误差或 SSE)。

no_object_scale是一个超参数。通常是 0.5,所以损失项的这部分没有其他部分那么重要。由于图像将只有少数的 ground-truth,845 个检测器中的大多数将只受到“无目标”损失的惩罚,而没有任何其他的损失,我将在下面展示。

因为我们不希望模型只学习“没有物体”,所以这部分损失不应该比有物体的检测器的损失更重要。

上述公式适用于单个单元中的单个检测器。为了找到总的无物体损失,我们将所有单元格i, j和所有检测器bno_object_loss相加。对于负责查找物体的检测器,no_object_loss总是 0。在 SqueezeDet 中,总的无物体损失也要除以“无目标”检测器的数量来得到平均值,但是在 YOLO 中我们不这样做。

事实上,YOLO 还有另一个妙招。如果检测器的预测和图像中任何地面真相框之间的最佳 IOU 大于,比如说 0.6,那么no_object_loss[i, j, b]被设置为 0。

换句话说,如果一个探测器本来不应该预测一个物体,但它实际上做出了一个非常好的预测,那么原谅它可能是一个好主意,甚至鼓励它继续预测物体。(事后看来,我们真的应该让这个探测器负责那个物体。)

我不确定这个 trick 对最终的结果是否有任何实际的影响,它看起来有点取巧。但是,嘿,如果没有取巧,机器学习将会在哪里呢?

SSD 没有这个无物体损失项。相反,它为可能的类添加了一个特殊的背景类。如果预测出来是背景类,那么该检测器的输出将被视为“无物体”。

注: YOLO 对所有这些损失项使用平方和误差,而不是更常见的均值平方误差(MSE)用于回归,或交叉熵用于分类。

为什么是平方和误差而不是均方误差?我不是 100%肯定,但它可能是因为每幅图像有不同数量的物体(正样本)。如果我们取均值,那么只有一个物体的图像可能会和有 10 个物体的图像损失相同。有了 SSE,后一种图像的损失将大约是 10 倍,这可能被认为是更公平的。

这也可能是因为用于 YOLO 的深度学习框架 Darknet 是用 C 语言编写的,与 TensorFlow 或 PyTorch 不同,它没有自动微分。与 MSE 相比,使用平方和误差让梯度计算更简单(ground-truth 减去预测就可以了)。

有 ground-truth 的检测器 (正样本)

前一节描述了不负责查找物体的检测器的情况。他们唯一可能做错的事情就是在没有物体的地方找到了一个没有物体。现在我们来看看剩下的检测器:那些“应该”找到物体的检测器。

当这些检测器找不到它们的物体,或者对物体进行错误分类时,它们就会出错。有三个单独的损失项。我们只计算网格单元i, j 中检测器b的损失项,如果检测器有一个 ground-truth 框(即如果目标张量中的置信度分数设置为 1)。

置信度得分

置信度分数的损失项为:

object_loss[i, j, b] = object_scale * (1 - sigmoid(pred_conf[i, j, b]))**2

这与no_object_loss非常相似,只是这里的 ground-truth 是 1,因为我们 100%确定这里有一个物体。

事实上,YOLO 做了一些更有趣的事情:

object_loss[i, j, b] = object_scale *         (IOU(truth_coords, pred_coords) - sigmoid(pred_conf[i, j, b]))**2

预测的置信度评分pred_conf[i, j, b]应该表示预测边界框和 ground-truth 框之间的 IOU。理想情况下,这是 1 或 100%,完美匹配。但是 YOLO 并没有将预测的分数与这个理想的数字进行比较,而是和两个框之间的“实际”IOU 进行比较。

这是有道理的:如果 IOU 很小,那么置信度也应该很小,如果 IOU 很大,那么置信度也应该很大。

不像无物体损失,我们希望预测的置信度总是 0,这里我们不希望模型总是预测 100%的置信度。相反,模型应该学会估计它的边界框坐标有多好。这就是 IOU 告诉你的。

如前所述,SSD 不能预测置信度分数,因此没有这个损失项。

类别概率

每个检测器还要预测物体的类别。这与边界框坐标无关。本质上,我们训练了 5 个独立的分类器,它们都被教去观察不同大小的物体。

YOLOv1 和 v2 使用以下损失项表示预测的类别概率:

class_loss[i, j, b] = class_scale * (true_class - softmax(pred_class))**2

在这里,true_class是一个包含 20 个数字的独热编码向量(对于 Pascal VOC),而pred_class是预测 logits 的向量。注意,即使我们在预测中应用了 softmax,这个损失项也没有使用交叉熵。(我想他们可能用了平方和误差,因为这使得损失项更容易与其他损失项平衡。事实上,甚至 softmax 也是可选的。)

YOLOv3 和 SSD 采取了不同的方法。他们不认为这是一个多类分类问题,而是一个多标签问题。因此他们不使用 softmax(总是选择一个标签作为获胜者),而是使用允许选择多个标签的 logistic sigmoid。他们使用标准的二进制交叉熵来计算这个损失项。

由于 SSD 不能预测置信度分数,因此它有一个特殊的“背景”类来达到这个目的。如果探测器预测出了背景,那么这就意味着探测器没有发现物体(我们就忽略这些预测)。顺便说一下,SSD 论文将这部分中的损失项称为置信度损失,而不是分类损失(只是为了防止混淆)。

包围框的坐标

最后是边界框坐标的损失项,也称为定位损失。这是构成边界框的四个数字之间的简单回归损失。

coord_loss[i, j, b] = coord_scale * ((true_x[i, j, b] - pred_x[i, j, b])**2                                   + (true_y[i, j, b] - pred_y[i, j, b])**2                                   + (true_w[i, j, b] - pred_w[i, j, b])**2                                   + (true_h[i, j, b] - pred_h[i, j, b])**2)

比例因子coord_scale用于使边界框坐标预测的损失比其他损失项更重要。这个超参数的典型值是 5。

这个损失项非常简单,但重要的是要了解上面等式中的true_*pred_*值是什么。回想一下“模型实际预测了什么?”,我给出了下面的代码来找到实际的边界框坐标:

box_x[i, j, b] = (i + sigmoid(pred_x[i, j, b])) * 32box_y[i, j, b] = (j + sigmoid(pred_y[i, j, b])) * 32box_w[i, j, b] = anchor_w[b] * exp(pred_w[i, j, b]) * 32box_h[i, j, b] = anchor_h[b] * exp(pred_h[i, j, b]) * 32

我们需要做一些后处理来从模型的预测中得到有效的坐标。预测的 x 和 y 值用了 sigmoid。预测的宽度和高度实际上是比例因子首先需要取幂然后乘以锚的宽度和高度。

由于模型不能直接预测有效坐标,因此损失函数中使用的 ground-truth 也不应该是真实坐标。在我们可以在损失函数中使用 ground-truth 框之前,我们需要对它进行转换:

true_x[i, j, b] = ground_truth.center_x - grid[i, j].center_xtrue_y[i, j, b] = ground_truth.center_y - grid[i, j].center_ytrue_w[i, j, b] = log(ground_truth.width / anchor_w[b])true_h[i, j, b] = log(ground_truth.height / anchor_h[b])

现在true_xtrue_y与网格单元有关,true_wtrue_h是锚点尺寸的适当比例因子。当我们填充目标张量时用这个逆变换是很重要的,否则损失函数将会是比较苹果和橘子。

SSD 再次使用了一个稍微不同的损失术语。其定位损失称为“Smooth L1”损失。这个损失不是简单地取平方差,而是做一些更有趣的事情:

difference = abs(true_x[i, j, b] - pred_x[i, j, b])if difference < 1:    coord_loss_x[i, j, b] = 0.5 * difference**2else:    coord_loss_x[i, j, b] = difference - 0.5

对于其它坐标也是如此。这种损失应该对异常值不那么敏感。

(未完待续)

—END—

英文原文:https://machinethink.net/blog...

推荐阅读


关注图像处理,自然语言处理,机器学习等人工智能领域,请点击关注AI公园专栏
欢迎关注微信公众号
AI公园 公众号二维码.jfif
推荐阅读
关注数
8256
内容数
210
关注图像处理,NLP,机器学习等人工智能领域
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息