一阶段物体检测 One-stage object detection

对象检测是一种计算机视觉技术,用于在图像中查找感兴趣的对象:

对象检测示例

这比分类更高级,分类仅告诉您图像的“主要主题”是什么,而对象检测可以找到多个对象,对其进行分类,然后定位它们在图像中的位置。

一个对象检测模型可以预测边界框边界框用于找到的每个对象,以及每个对象的分类概率。

对象检测通常会预测太多的边界框。每个盒子都有一个置信度得分,该得分表明模型认为该盒子确实包含一个对象的可能性。作为后处理步骤,我们将分数低于某个阈值(也称为非最大抑制)的框过滤掉。

对象检测比分类更棘手。您将遇到的问题之一是,训练图像中可以包含从零到数十个对象中的任何一个,并且该模型可能会输出多个预测,因此您如何确定应该将哪个预测与哪个地面进行比较?损失函数中的真值边界框?

由于我使用移动设备,因此我最感兴趣的是一级对象检测器。

Faster R-CNN这样的高端模型会首先生成所谓的区域建议(图像中可能包含对象的区域),然后针对这些区域中的每个区域进行单独的预测。效果很好,但速度也很慢,因为它需要多次运行模型的检测和分类部分。

另一方面,一级检测器只需要单次通过神经网络,就可以一次预测所有边界框。这样速度更快,更适合于移动设备。一级目标检测器的最常见示例是YOLOSSDSqueezeDetDetectNet

不幸的是,这些模型的研究论文遗漏了许多重要的技术细节,并且也没有太多关于培训此类模型的深入博客文章。因此,要弄清楚这些细节,我花了很多时间试图使文章更有意义,挖掘其他人的源代码,并从头开始训练我自己的对象检测器。

在这篇(很长!)博客文章中,我将尝试解释这些单级检测器如何工作以及如何进行训练和评估。

注意:您可能也对我的其他 文章感兴趣,有关iOS上使用YOLO进行对象检测。

顺便说一句,YOLO代表您只看一次,而SSD代表单发MultiBox检测器(我想SSMBD并不是一个很好的缩写)。

为什么对象检测很棘手

分类器将图像作为输入并产生单个输出,即各个类的概率分布。但这只为您提供整个图像的摘要,当图像具有多个感兴趣的对象时,效果不佳。

在下一张图像上,分类器可能会识别出该图像包含一定数量的“猫眼”和一定数量的“狗眼”,但这是它所能做到的最好。

图像分类器如何处理多个对象

另一方面,对象检测模型将通过预测每个对象的边界框来告诉您各个对象在哪里:

对象检测器如何处理多个对象

由于它现在可以专注于对边界框内的事物进行分类,而忽略外部的所有事物,因此该模型能够对单个对象给出更加自信的预测。

如果您的数据集带有边界框注释(所谓的“地面实况框”),则可以很容易地将本地化输出添加到模型中。只需简单地预测另外4个数字,边界框的每个角即可获得一个数字。

将回归输出添加到神经网络以预测边界框

现在该模型具有两个输出:

  1. 分类结果的通常概率分布,以及
  2. 边界框回归。

模型的损失函数只是将边界框的回归损失添加到分类的交叉熵损失中,通常具有均方误差(MSE):

outputs = model.forward_pass(image)
class_pred = outputs[0]
bbox_pred = outputs[1]
class_loss = cross_entropy_loss(class_pred, class_true)
bbox_loss = mse_loss(bbox_pred, bbox_true)
loss = class_loss + bbox_loss
optimize(loss)

现在,您可以像往常一样使用SGD来优化模型,但要避免这种综合损失,并且实际上效果很好。这是一个示例预测:

使用单个回归头进行本地化的结果

模型已正确找到该对象(狗)的类及其在图像中的位置。红色框是地面真相,而青色框是预测。这不是一个完美的匹配,但是非常接近。

注意:此处给出的52.14%的分数是类别分数(狗为82.16%)和置信盒实际包含对象的可能性的置信度分数(63.47%)的组合。

为了对预测框与地面真相的匹配度进行评分,我们可以计算两个边界框之间的IOU(或联合交集,也称为Jaccard索引)。

IOU是介于0到1之间的数字,越大越好。理想情况下,预测的盒子和地面真实的IOU为100%,但实际上,超过50%的任何东西通常都被认为是正确的预测。对于上面的示例,IOU为74.9%,您可以看到这些框非常匹配。

使用回归输出来预测单个边界框会产生良好的结果。但是,当图像中有多个感兴趣的对象时,就像分类不能很好地起作用一样,这种简单的定位方案也会失败:

有多个对象时,使用单个回归头进行的本地化失败

该模型只能预测一个边界框,因此它必须选择一个对象,但该框最终会出现在中间的某个位置。实际上,这里发生的事情是完全合理的:该模型知道有两个对象,但是只有一个边界框可以放开,因此它会折衷并将预测的框置于两匹马之间。箱子的大小也在两匹马的大小之间。

注意:您可能希望模型现在可以在两个对象周围绘制框,但这不会发生,因为还没有经过培训来做到这一点。数据集中的真实注释始终在单个对象周围绘制,而不是在对象组周围绘制。

您可能会想,“这听起来很容易解决,让我们通过为模型提供额外的回归输出来添加更多的边界框检测器。” 毕竟,如果模型可以预测N个边界框,那么它应该最多可以找到N个对象,对吗?听起来像是一个计划……但行不通。🤕

即使使用具有多个这些检测器的模型,我们仍然会得到全部位于图像中间的边界框:

使用多个回归头不起作用

为什么会这样?问题在于模型不知道应该将哪个边界框分配给哪个对象,并且为了安全起见,它将它们都放置在中间的某个位置。

该模型无法决定:“我可以在左边的马周围放置边界框1,在右边的马周围放置边界框2。” 取而代之的是,每个检测器仍然尝试预测所有对象,而不只是其中之一。即使模型具有N个检测器,但它们不能作为一个团队一起工作。具有多个边界框检测器的模型的行为仍然与仅预测一个边界框的模型完全一样。

我们需要某种使边界框检测器专门化的方法,以便每个检测器将尝试仅预测单个对象,而不同的检测器将找到不同的对象。

在非专门模型中,每个检测器都应该能够处理图像中任何可能位置的所有可能种类的物体。这实在是太多了……现在,该模型很想学习预测始终位于图像中心的框,因为在整个训练集上,实际上可以最大程度地减少错误的发生。

从SGD的角度来看,这样做平均可以得到不错的结果,但实际上在实践中并不是真正有用的结果……因此我们需要更加聪明地训练模型。

YOLO,SSD和DetectNet等一级检测器均通过将每个边界框检测器分配到图像中的特定位置来解决此问题。这样,探测器就可以学习专门研究某些位置的物体。为了获得更好的结果,我们还可以让检测器专门研究物体的形状和大小。

输入网格

使用固定的检测器网格是为一级检测器提供动力的主要思想,这使它们与基于区域提议的检测器(如R-CNN)脱颖而出。

让我们考虑这种模型的最简单的架构。它存在一个充当特征提取器的基础网络。像大多数特征提取器一样,它通常在ImageNet上进行训练。

在YOLO的情况下,特征提取器将416×416像素的图像作为输入。SSD通常使用300×300图像。这些要大于用于分类的图像(通常是224×224),因为我们不希望丢失小的细节。

该模型是一个特征提取器,后面是更多的卷积层

基本网络可以是任何东西,例如Inception或ResNet或YOLO的DarkNet,但是在移动设备上,可以使用小型,快速的体系结构,例如SqueezeNet或MobileNet

(在我自己的实验中,我使用了受过224×224图像训练的MobileNet V1特征提取器。我将其放大到448×448,然后在130万张图像的ILSVRC 2012数据集上对该网络进行了7个时期的微调,使用随机裁剪和色彩抖动等基本数据增强。在这些较大的图像上对特征提取器进行重新训练,可以使模型在用于对象检测的416×416输入上更好地工作。)

在特征提取器之上是几个附加的卷积层。对它们进行了微调,以学习如何预测这些边界框内的对象的边界框和类概率。这是模型的对象检测部分。

有许多用于训练对象检测器的通用数据集。为了这篇博客文章的缘故,我们将使用Pascal VOC数据集,该数据集具有20个类。因此,神经网络的第一部分在ImageNet上进行了训练,第二部分在VOC上进行了训练。

当您查看YOLO或SSD的实际体系结构时,它们比这个简单的示例网络(带有跳过连接等)要复杂一些,但是稍后我们将进行介绍。目前,以上模型就足够了,并且实际上可以用于构建快速且相当准确的物体检测器。(它与我之前写过的“ Tiny YOLO”架构非常相似。)

最后一层的输出是要素地图(上图中的绿色)。对于我们的示例模型,这是具有125个通道的13×13特征图。

注意:此处的数字13来自416像素的输入大小,并且这里使用的特定基础网络具有五个池化层(或跨度为2的卷积层),它们将输入缩小32倍,并且416/32 =13。如果要使用更细的网格,例如19×19,则输入图像的宽度和高度应该为19×32 = 608像素(或者可以使用跨度较小的网络)。

我们将此特征图解释为由13个单元格组成的13个网格。该数字是奇数,因此中心只有一个单元格。网格中的每个像元都有5个独立的对象检测器,这些检测器中的每个检测器都预测一个边界框。

探测器网格

这里的关键是检测器的位置是固定的:它只能检测位于该像元附近的对象(实际上,对象的中心必须在网格像元内部)。这就是让我们避免上一部分检测器具有太多自由度的问题的原因。使用此网格,图像左侧的检测器将永远无法预测位于右侧的物体。

每个物体检测器产生25个数字:

  • 20个包含类概率的数字
  • 4个边界框坐标(中心x,中心y,宽度,高度)
  • 1置信度

由于每个单元有5个检测器,并且5×25 = 125,这就是为什么我们有125个输出通道的原因。

与常规分类器一样,类别概率的20个数字也已应用softmax。我们可以通过查看最高人数来找到获胜的班级。(尽管将其视为多标签分类也是很常见的,在这种情况下,这20个类是独立的,我们使用Sigmoid代替softmax。)

置信度得分为0和1(或100%)之间的数字,并介绍了模型如何可能认为这预测边框包含一个真正的对象。它也被称为“客观性”得分。请注意,该分数仅说明了这是否是一个对象,而没有说明这是什么类型的对象,这就是类概率的含义。

该模型始终预测相同数量的边界框:13×13单元乘以5个检测器得出845个预测。显然,这些预测中的绝大多数都是不好的-毕竟,大多数图像最多只包含少数对象,最多不超过800个。置信度得分告诉我们可以忽略哪些预测框。

通常,我们最终会得到十几个模型认为不错的预测。其中一些会重叠-发生这​​种情况是因为附近的小区可能都对同一个对象做出了预测,有时单个小区会做出了多个预测(尽管在训练中不建议这样做)。

单个对象的多个预测

物体检测通常具有多个很大程度上重叠的预测。标准的后处理技术是应用非最大抑制(NMS)来删除此类重复项。简而言之,NMS会以最高的置信度分数保留预测,并删除与这些重叠超过一定阈值(例如60%)的任何其他框。

通常,我们只会保留10个左右的最佳预测,而忽略其他预测。理想情况下,我们只希望图像中的每个对象只有一个边界框。

好的,它描述了使用网格进行对象检测预测的基本管道。但是为什么行得通呢?

约束是好的

我已经提到过,将每个边界框检测器分配到图像中的固定位置是使一级对象检测器工作的诀窍。我们使用13×13网格作为空间约束,以使模型更容易学习如何预测对象。

使用此类(体系结构)约束是机器学习的有用技术。实际上,卷积本身也是一个约束:卷积层实际上只是完全连接(FC)层的一个更严格的版本。(这就是为什么您可以使用FC层实现卷积,反之亦然的原因-它们本质上是同一件事。)

如果我们仅使用普通FC层,那么机器学习模型就很难学习图像。卷积层上的约束(一次只看几个像素,并且连接共享相同的权重)有助于模型从图像中提取知识。我们使用这些约束来消除自由度,并指导模型学习我们想要学习的东西。

同样,网格会强制模型学习专门针对特定位置的对象检测器。左上方单元格中的检测器将仅预测位于该左上方单元格附近的对象,而不会预测更远的对象。(对模型进行训练,以使给定网格单元中的检测器仅负责检测中心落在该网格单元内的对象。)

该模型的朴素版本没有这样的约束,因此其回归层从未得到仅在特定位置显示的提示。

锚点

网格是限制的有用约束,其中所述图像中的检测器可以找到对象。我们还可以添加另一个约束,以帮助模型做出更好的预测,这是对对象形状的约束。

我们的示例模型有13×13网格单元,每个单元有5个检测器,因此总共有845个检测器。但是,为什么每个网格单元有5个检测器而不是一个?好吧,就像检测器很难学会如何预测可以放置在任何地方的物体一样,检测器也很难学会预测任何形状或大小的物体。

我们使用网格使检测器专门化,使其仅查看特定的空间位置,并且通过在每个网格单元中具有多个不同的检测器,我们还可以使这些对象检测器中的每一个也都具有特定的对象形状。

我们对探测器进行5种特定形状的训练:

锚盒

红色框是训练集中五个最典型的对象形状。青色框分别是训练集中的最小和最大对象。请注意,这些边界框以模型的416×416像素输入分辨率显示。(该图还以浅灰色显示了网格,因此您可以看到这五个形状与网格单元之间的关系。每个网格单元在输入图像中覆盖32×32像素。)

这五个形状称为或锚框。锚仅是宽度和高度的列表:

anchors = [1.19, 1.99,     # width, height for anchor 1
           2.79, 4.60,     # width, height for anchor 2
           4.54, 8.93,     # etc.
           8.06, 5.29,
           10.33, 10.65]

锚点描述数据集中5个最常见的(平均)对象形状。“形状”实际上是指它们的宽度和高度,因为我们一直在这里使用基本矩形。

有5个锚点并不是偶然的。网格单元中的每个检测器都有一个锚点。正如网格对检测器施加位置限制一样,锚点会迫使检测器内部的检测器专门处理特定的对象形状。

单元中的第一个检测器负责检测大小与第一个锚点相似的对象,第二个检测器负责检测大小与第二个锚点相似的对象,依此类推。因为每个单元有5个检测器,所以我们也有5个锚点。

因此,小物体将被探测器1拾取,稍大的物体将被探测器2拾取,长而平坦的物体将被探测器3拾取,高而薄的物体将被探测器4拾取,大物体被探测器5拾取。

注意:上方代码段中锚点的宽度和高度以网格的13×13坐标系表示,因此第一个锚点的宽度略大于1个网格单元,高度接近2个网格单元。最后一个锚点覆盖了超过10×10个单元的几乎整个网格。YOLO就是这样存储锚的。但是,SSD具有几个不同大小的不同网格,因此对锚使用归一化坐标(介于0和1之间),因此它们与网格的大小无关。两种方法都可以。

了解这些锚点是预先选择的,这一点很重要。它们是常数,在训练过程中不会改变

由于锚点只是宽度和高度,它们是预先选择的,因此YOLO论文也称它们为“尺寸先验”。(YOLO的官方源代码Darknet称它们为“偏见”,我认为是正确的-检测器偏向于预测某种形状的物体-但是使该术语超载会造成混淆。)

YOLO通过在所有训练图像的所有边界框上运行k均值聚类来选择锚点(k = 5,从而找到五个最常见的对象形状)。因此,YOLO的锚点特定于您正在训练(和测试)的数据集

k均值算法找到一种将所有数据点划分为群集的方法。这里的数据点是数据集中所有地面真确边界框的宽度和高度。如果在Pascal VOC数据集中的框中运行k-means,则会发现以下5个簇:

k均值发现的聚类

这些聚类表示此数据集中存在的不同对象形状的五个“平均值”。您可以看到k均值发现有必要将蓝色簇中的很小的对象,红色簇中的稍大的对象和绿色对象归为一组。它决定将中等物体分成两组:一组边界框的宽度大于高度(黄色),另一组边界的宽度大于宽度(紫色)。

但是5个锚点是最佳选择吗?我们可以在不同数量的集群上运行k-means数次,并计算地面真相框和它们最接近的锚点框之间的平均IOU。毫不奇怪,使用更多的质心(较大的k值)会产生更高的平均IOU,但这也意味着我们在每个网格单元中需要更多的检测器,这会使模型运行速度更慢。对于YOLO v2,他们选择了5个定位点,以在召回率和模型复杂性之间进行权衡。

平均借条数与锚点数

SSD不使用k均值来查找锚点。相反,它使用数学公式来计算锚点大小。因此,SSD的锚点与数据集无关(顺便说一下,SSD论文称它们为“默认框”)。您可能认为特定于数据集的锚会提供更好的预测,但我不确定它是否真的很重要(YOLO作者似乎认为确实如此)。

另一个小区别:YOLO的锚点只是宽度和高度,而SSD的锚点也具有x,y位置。YOLO只是假设锚点的位置始终在网格单元的中心。(对于SSD,这也是默认的操作。)

多亏了锚点,检测器不必费力就可以做出不错的预测,因为预测所有零将仅输出锚点框,该锚点框将合理地接近真实对象(平均而言)。这使培训变得容易得多!如果没有锚点,每个检测器将不得不从头开始学习不同的边界框形状是什么样的……一项艰巨的任务。

注意:在本文中谈论YOLO时,通常是指YOLO v2或v3。如果您正在阅读2015-2016年左右的论文或博客文章,并且他们提到YOLO,那么他们经常谈论的是YOLO v1,两者之间有很大的不同。版本1的网格较小(每个单元只有7个7×7的检测器,每个像元),使用完全连接的层而不是卷积层来预测网格,并且不使用锚点。该版本的YOLO现​​在已过时。v2和v3之间的差异要小得多。有了v3,YOLO在许多方面已变得与SSD非常相似。

该模型实际上预测了什么?

让我们仔细看一下示例模型的输出。由于它只是一个卷积神经网络,因此前向传递看起来像这样:

该模型是卷积神经网络

您输入一个416×416像素的RGB图像,卷积层在图像的像素上应用了各种变换,并且输出是13×13×125的特征图。由于这是回归输出,因此不会将激活函数应用于最终层。输出的只是21,125个实数,我们必须以某种方式将这些数转换为边界框。

它需要4个数字来描述边界框的坐标。有两种常见的方法可以做到这一点:要么xmin, ymin, xmax, ymax描述盒子的边缘,要么使用center x, center y, width, height。两种方法都可以,但是我们将使用后者(知道盒子的中心在哪里,使盒子与网格单元的匹配变得更加容易)。

模型为每个边界框预测的不是其在图像中的绝对坐标,而是四个“增量”值或偏移量:

  • delta_xdelta_y:网格单元内框的中心
  • delta_wdelta_h:锚框宽度和高度的缩放比例

每个检测器都相对于其锚框进行预测。锚框应该已经是实际对象大小的一个很好的近似值(这就是我们使用它们的原因),但是它不是精确的。这就是为什么我们要预测比例因子(该比例因子表示该框比锚点大或小)以及位置偏移量(该位置偏移量表明该框距该网格中心的距离)的原因。

要获得像素坐标中边界框的实际宽度和高度,请执行以下操作:

box_w[i, j, b] = anchor_w[b] * exp(delta_w[i, j, b]) * 32
box_h[i, j, b] = anchor_h[b] * exp(delta_h[i, j, b]) * 32

其中ij是网格中的行和列(0 – 12),并且b是检测器索引(0 – 4)。

预测框比原始图像宽和/或高可以,但框的宽度或高度为负值没有意义。这就是为什么我们采用预测数字的指数

如果预测delta_w值小于0,exp(delta_w)则为0到1之间的数字,从而使该框小于锚定框。如果delta_w大于0,则exp(delta_w)数字> 1,使框变宽。如果delta_w正好为0,则exp(0) = 1预测框的宽度与锚定框的宽度完全相同。

顺便说一下,我们乘以32,因为锚点坐标在13×13网格中,每个网格单元覆盖416×416输入图像中的32个像素。

注意:有趣的是,在损失函数中,我们实际上将使用上述公式的逆版本。除了采用exp()预测值外,我们将采用log()真实值。有关此的更多信息。

要获得像素坐标中预测框的中心x,y位置,我们需要执行以下操作:

box_x[i, j, b] = (i + sigmoid(delta_x[i, j, b])) * 32
box_y[i, j, b] = (j + sigmoid(delta_y[i, j, b])) * 32

YOLO的一个关键特征是,仅当它发现中心位于检测器网格单元内的对象时,它才会鼓励检测器预测边界框。这有助于避免虚假检测,从而使多个相邻的网格单元不会全部找到相同的对象。

要强制执行此操作,delta_x并且delta_y必须将其限制为介于0和1之间的数字,该数字是网格单元内的相对位置。这就是S形函数的作用。

然后,我们将网格单元坐标i和和j(均为0 – 12)相加,然后乘以每个网格单元的像素数(32)。现在box_xbox_y是原始416×416图像空间中预测边界框的中心。

SSD的操作略有不同:

box_x[i, j, b] = (anchor_x[b] + delta_x[i, j, b]*anchor_w[b]) * image_w
box_y[i, j, b] = (anchor_y[b] + delta_y[i, j, b]*anchor_h[b]) * image_h

此处预测的增量值实际上是锚框宽度或高度的倍数,并且没有S型激活。这意味着对象的中心实际上可以位于带有SSD的网格单元外部。

还要注意,使用SSD时,预测坐标是相对于锚框中心而不是网格单元中心。实际上,锚定框的中心将与网格单元的中心完全对齐,但是它们使用不同的坐标系。SSD的锚点坐标在[0,1]范围内,以使其独立于网格大小。(之所以这样做,是因为SSD使用多个不同大小的网格。)

如您所见,尽管YOLO和SSD通常以相同的方式工作,但是当您开始查看细微的细节时,它们是不同的。

除了坐标外,该模型还可以预测边界框的置信度得分。因为我们希望它是0到1之间的数字,所以我们使用标准技巧,并将其粘贴在S型曲线中:

confidence[i, j, b] = sigmoid(predicted_confidence[i, j, b])

回想一下,我们的示例模型始终预测845个边界框,不多也不少。但通常情况下,图像中只有很少的真实对象。在训练过程中,我们仅鼓励单个检测器对每个地面真相进行预测,因此只有少数预测具有较高的置信度。未发现物体的探测器的预测(到目前为止最多)应该具有非常低的置信度。

最后,我们预测班级概率。对于Pasval VOC数据集,每个边界框都是20个数字的向量。像往常一样,我们应用softmax使其具有良好的概率分布:

classes[i, j, b] = softmax(predicted_classes[i, j, b])

除了softmax,您还可以使用S型激活。这使其成为一个多标签分类器,在这种情况下,每个预测的边界框实际上可以同时具有多个类。(这就是SSD和YOLO v3的功能。)

请注意,SSD不使用这种置信度得分。相反,它向分类器添加了一个特殊的类(“背景”)。如果预测到该背景类别,则检测器未找到物体。这与YOLO给出低置信度分数相同。

由于我们还有更多需要的预测,而且大多数预测都是不好的,因此我们现在将分数非常低的预测过滤掉。对于YOLO,我们通过将框的置信度得分(表示“该框包含一个对象的可能性”)与最大的类概率(即“该框包含该对象的可能性”)相结合来做到这一点。此类对象”。

confidence_in_class[i, j, b] = classes[i, j, b].max() * confidence[i, j, b]

置信度低表示模型不确定该框是否确实包含对象。分类概率低意味着模型不确定此框中的对象是什么。这两个分数都必须很高,才能使预测得到认真对待。

由于大多数盒子将不包含任何对象,因此我们现在可以忽略所有confidence_in_class低于某个阈值(例如0.3)的盒子,然后对其余盒子进行非最大抑制以消除重复。我们通常最终得到1到大约10个预测之间的任何位置。

是卷积的,宝贝!

拥有检测器网格实际上是使用卷积神经网络的自然选择。

13×13网格是卷积层的输出。众所周知,卷积是在输入图像上滑动的小窗口(或内核)。该内核的权重在每个输入位置均相同。我们的示例模型的最后一层有125个这样的内核。

卷积层

为什么是125?有5个检测器,每个检测器有25个卷积核。这25个内核中的每个内核都可以预测该检测器边界框的一个方面:x,y,宽度,高度,置信度得分,20个类别概率。

注意:通常,如果您的数据集具有K个类别,并且模型具有B个检测器,则网格需要具有B × (4 + 1 + K)输出通道。

这125个内核在13×13特征图中的每个位置上滑动,并在每个位置上进行预测。然后,我们将这125个数字解释为组成该网格位置的5个预测边界框及其类分数(这是损失函数的工作,更多有关下面的内容)。

最初,在每个网格位置预测的125个数字将是完全随机且毫无意义的,但是随着训练的进行,损失函数将指导模型学习做出更有意义的预测。

现在,即使我一直说每个网格单元中有5个检测器,对于总共845个检测器,该模型实际上总共只能学习5个检测器-每个网格单元中没有5个唯一的检测器。这是因为卷积层的权重在每个位置都相同,因此在网格单元之间共享

该模型实际上为每个锚点学习一个检测器。它将这些检测器滑过图像以获得845个预测,网格上每个位置5个。因此,尽管我们总共只有5个唯一的检测器,但由于卷积,这些检测器与它们在图像中的位置无关,因此无论它们位于何处,都可以检测到对象。

它是给定位置的输入像素与为该检测器/卷积核学习的权重的组合,确定了该位置的最终边界框预测。

这也解释了为什么模型总是预测边界框对于网格单元中心的位置。由于该模型具有卷积特性,因此无法预测绝对坐标。由于卷积核在图像上滑动,因此其预测始终相对于其在特征图中的当前位置。

YOLO与SSD

上面关于一级对象检测器如何工作的描述几乎适用于所有这些对象。确切地解释输出的方式可能会有细微的差异(例如,在类概率上采用S型而不是softmax),但是总体思路是相同的。

但是,YOLO和SSD的各种版本之间存在一些有趣的体系结构差异。

这是YOLO v2和v3以及SSD的不同体系结构的示意图:

不同的神经网络架构

正如您所看到的,虽然它们通过不同的方法得出了最终的网格大小(YOLO使用上采样,SSD下采样),但YOLO v3和SSD总体上非常相似。

在YOLO v2(以及我们的示例模型)仅具有单个13×13输出网格的情况下,SSD具有多个大小不同的网格。所述MobileNet + SSD版本具有6个与网格尺寸19×19,10×10,5×5,3×3,2×2和1×1。

因此,SSD网格的范围从非常精细到非常粗糙。这样做是为了在更广泛的对象尺度上获得更准确的预测。

细小的19×19网格(其网格单元非常靠近)负责最小的对象。最后一层产生的1×1网格负责对基本上占据整个图像的大型对象做出反应。来自其他层的网格覆盖了介于两者之间的对象大小。

YOLO v2尝试对其跳过连接执行类似的操作,但这似乎效果不佳。YOLO v3更类似于SSD,它使用3个具有不同比例的网格来预测边界框。

与YOLO一样,每个SSD网格单元都可以进行多个预测。每个网格单元的检测器数量各不相同:在更大,更细粒度的特征图上,SSD每个网格单元具有3或4个检测器,在较小的网格中,每个网格单元具有6个检测器。(YOLO v3在每个尺度上每个网格单元使用3个检测器。)

坐标预测也相对于锚点(在SSD纸中称为“默认框”),但不同之处在于SSD的预测中心坐标可以超出其网格单元。锚定框位于单元格的中心,但SSD不会将S形应用于预测的x,y偏移量。因此,从理论上讲,模型右下角的框可以预测边界框,其中心一直位于图像的左上角(但实际上可能不会发生)。

与YOLO不同,没有置信度分数。每个预测仅包含4个边界框坐标和类概率。YOLO使用置信度得分来表明该预测是实际物体的机会。SSD通过具有特殊的“背景”类别来不同地解决此问题:如果类别预测是针对该背景类别的,则意味着该检测器没有找到对象。这与在YOLO中的低置信度分数相同。

SSD的锚点与YOLO的锚点略有不同。因为YOLO必须从单个网格进行所有预测,所以它使用的锚点的范围从小(大约单个网格单元的大小)到大(大约整个图像的大小)。

SSD更保守。在19×19网格上使用的锚框小于在10×10网格上使用的锚框,而在5×5网格上使用的锚框较小,依此类推。与YOLO不同,SSD不使用锚来使检测器专门用于对象大小,而是使用不同的网格。

SSD锚通常用于使检测器专门研究对象形状的各种可能的纵横比,而不是其尺寸。如前所述,SSD的锚点是使用简单的公式计算的,而YOLO的锚点是通过对训练数据运行k-means聚类来找到的。

由于SSD使用3到6个锚点,并且有6个网格而不是一个,因此实际上总共使用了32个以上的唯一检测器(此数字根据您使用的确切模型架构而有所变化)。

由于SSD具有更多的网格和检测器,因此它还会输出更多的预测。与1917年的MobileNet-SSD相比,YOLO产生了845个预测。更大的版本SSD512甚至可以输出24,564个预测!好处是您更有可能找到图像中的所有对象。缺点是您最终必须进行更多的后处理才能确定要保留哪些预测。

由于存在这些差异,SSD和YOLO之间的真相边界框与检测器的匹配方式略有不同。损失函数也略有不同。但是,当我们开始培训时,我们将对此进行介绍。

注意:还有一个称为SSDLite的变体,它与SSD相同,但是使用深度可分离的卷积而不是常规卷积层来实现。因此,它比常规SSD快得多,并且非常适合在移动设备上使用。在这里查看我基于金属的MobileNetV2 + SSDLite实现

好的,关于这些模型如何进行预测的理论就足够了。现在,让我们看一下训练对象检测模型所需的数据类型。

数据

有几个用于训练对象检测模型的流行数据集-Pascal VOC,COCO,KITTI等。让我们看一下Pascal VOC,因为它是一个重要的基准,并且是YOLO论文中使用的。

VOC数据集由图像以及用于不同任务的注释组成。我们仅对对象检测任务感兴趣,因此我们将仅查看带有对象注释的图像。有20个对象类:

aeroplane    bicycle  bird   boat       bottle
bus          car      cat    chair      cow
diningtable  dog      horse  motorbike  person
pottedplant  sheep    sofa   train      tvmonitor

所述VOC数据集带有一个建议的列车/验证分裂即大致50 / 50。数据集不是很大,因此仅将50%的数据用于验证就显得有些愚蠢。因此,通常将训练集和验证集组合为一个大的训练集“ trainval”(总共16,551张图像),并从这些图像中随机选择10%左右用于验证。

由于有答案,因此可以在2007年的测试集上测试您的模型。还有一个2012年的测试集,但是答案是秘密的。(对于2012年测试集的提交,习惯上也将2007年测试集包含在训练数据中。更多的数据是更好的数据。)

注意:即使Pascal VOC竞赛不再活跃,您仍然可以提交预测,以查看您的模型在排行榜上的得分情况。

组合的2007 + 2012训练集包含8218张带有对象注释的图像,验证集包含8333张图像,而2007测试集包含4952张图像。这比ImageNet的130万张图片要少得多,因此,使用某种转移学习而不是从头开始训练模型是一个好主意。这就是为什么我们从在ImageNet上进行过预训练的特征提取器开始的原因。

注解

注释描述图像中的内容。换句话说,注释提供了我们训练所需的目标

注释采用XML格式,每个训练图像一个。批注文件包含一个或多个<object>部分,这些部分具有类的名称,以给出的边界框xmin, xmax, ymin, ymax,以及每个地面真实对象的一些其他属性。

如果将一个对象标记为difficult,我们将忽略它。这些通常是很小的对象。VOC挑战的官方评估指标也忽略了这些对象。

这是示例注释文件VOC2007 / Annotations / 003585.xml

<annotation>
    <folder>VOC2007</folder>
    <filename>003585.jpg</filename>
    <source>
        <database>The VOC2007 Database</database>
        <annotation>PASCAL VOC2007</annotation>
        <image>flickr</image>
        <flickrid>304100796</flickrid>
    </source>
    <owner>
        <flickrid>Huw Lambert</flickrid>
        <name>huw lambert</name>
    </owner>
    <size>
        <width>333</width>
        <height>500</height>
        <depth>3</depth>
    </size>
    <object>
        <name>person</name>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>138</xmin>
            <ymin>183</ymin>
            <xmax>259</xmax>
            <ymax>411</ymax>
        </bndbox>
    </object>
    <object>
        <name>motorbike</name>
        <truncated>0</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>89</xmin>
            <ymin>244</ymin>
            <xmax>291</xmax>
            <ymax>425</ymax>
        </bndbox>
    </object>
</annotation>

该图像为333×500像素,有两个对象:一个人和一辆摩托车。这两个对象都不应该被认为是困难的或被截断的(部分在图像之外)。像许多深度学习图像一样,原始照片来自Flickr。

注意: Pascal VOC坐标从1开始计数,而不是0。这可能是因为他们使用了像数学家一样从1开始索引的MATLAB。

如果我们绘制此训练图像及其边界框,则看起来像这样:

一个人和一辆摩托车的训练图

对于合并的2007年和2012年的数据集,我们具有以下统计信息:

dataset   images    objects
------------------------------
train     8218      19910
val       8333      20148
test      4952      12032     (2007 only)

大约一半的图像只有一个对象,其他图像则有两个或更多。您可以在直方图中清楚地看到这一点(这是针对训练集的):

每个图像的对象数量直方图

一幅图像中最多可容纳39个对象。验证和测试集的直方图相似。

有趣的是,这是将宽度和高度归一化为[0,1]范围后,训练集中所有边界框的面积的直方图:

边界框区域的直方图

如您所见,许多对象相对较小。1.0处的峰值是因为有很多物体大于图像(例如,只部分可见的人),因此边界框填充了整个图像。

这是查看此数据的另一种方法,是真实的边界框宽度与其高度的关系图。这张图片中的“斜率”显示了盒子的长宽比。

框的宽度和高度的散点图

我发现使这些类型的图很有用,因为它使您对数据的样子有所了解。

资料扩充

由于数据集非常小,因此在训练时通常会使用大量数据增强功能,例如随机翻转,随机裁剪,颜色变化等。

重要的是要记住,对图像所做的任何操作也必须对边界框进行!因此,如果翻转训练图像,则还必须翻转地面真实框的坐标。

YOLO执行以下操作来加载训练图像:

  • 加载图像而不调整大小
  • 通过随机添加/减去原始大小的20%来选择新的宽度和高度
  • 裁剪图像的该部分,如果新图像的一面或多面比原始图像大,则填充为零
  • 调整为416×416,使其变为正方形
  • 水平随机翻转图像(概率为50%)
  • 随机扭曲图像的色相,饱和度和曝光(亮度)
  • 还可以通过移动和缩放边界框坐标来调整边界框坐标,以调整较早进行的裁剪和调整大小,以及水平翻转

旋转也是一种常见的数据增强技术,但是这很棘手,因为我们还需要旋转边界框。因此通常不会完成。

SSD论文还建议进行以下增强:

  • 随机选择一个图像区域,以使图像中对象的最小IOU为0.1、0.3、0.5、0.7或0.9。该IOU越小,模型检测对象的难度就越大。
  • 使用“缩小”增强可有效缩小图像,从而创建带有小物体的额外训练示例。这有助于训练模型在小物体上做得更好。

随机裁剪可能会导致对象部分(或完全)掉落在裁剪后的图像之外。因此,我们只想保留中心位于该作物区域某处的地面真相框,而不希望保留其中心现在位于可见图像之外的框。

当心长宽比!

注意:请随时跳过本节。它处理的问题对于了解全局并不重要。但是我还没有真正看到它在其他任何地方得到解决,因此我认为值得写下来。

我们在正方形网格(13×13)上进行预测,而输入图像也是正方形(416×416)。但是训练图像通常不是正方形的,我们将要进行推理的测试图像也不是正方形的。图像甚至都没有相同的尺寸。

这是VOC训练集中图像的所有长宽比的可视化:

图像的纵横比

红色框的长宽比宽于高。青色的盒子比宽的盒子高。有很多奇怪的长宽比,但最常见的长宽比是1.333(4:3),1.5(3:2)和0.75(3:4)。

如您所见,有些图像非常宽。这是一个极端的例子:

非常宽的图像

神经网络可处理尺寸为416×416的正方形图像,因此我们必须将训练图像拟合到该正方形中。我们有几种方法可以做到这一点:

  1. 非均匀地调整为416×416,这会挤压图像
  2. 将最小的一面调整为416,然后从图像中获取416×416的裁切
  3. 将最大边调整为416,用零填充较小边,然后进行裁切

所有方法都是有效的方法,但是每种方法都有其自己的副作用。

挤压图像时,我们将其宽高比更改为1:1。如果原始图像的宽度大于高度,则所有对象都会比平常更窄。如果原件比宽高,则所有对象都变得更平。

通过裁剪,纵横比保持不变,但是我们可能会切碎图像的重要部分,从而使模型更难于看到物体的真实含义。该模型现在可能需要预测部分位于图像外部的边界框。(使用选项3时,它可能会使物体变得太小而无法检测到,特别是在宽高比极高的情况下。)

为什么这很重要?

训练前,我们将划分边界框的xmin,并xmax通过图像宽度,yminymax通过图像高度,所以他们0和1之间。这样做是为了使培训相互独立图像的实际像素大小的正常化坐标。

但是输入图像通常不是正方形,因此x坐标除以与y坐标不同的数字。对于每个图像,这些除数可能会有所不同,具体取决于图像的尺寸和宽高比。这影响了我们如何对待边界框坐标和锚点。

压缩(选项1)是最简单的选项,即使它暂时破坏了图像的长宽比。如果所有图像的长宽比都差不多(在VOC中没有),或者长宽比不是太高,则神经网络仍可以正常工作。卷积对于对象的“厚度”更改似乎相当健壮。

使用裁剪(选项#2和#3)时,在对边界框坐标进行归一化时,应牢记长宽比。现在,边界框可能变得比输入图像大,因为我们不是在看整个图像,而只是在裁剪的部分。由于对象可能会部分落在图像的外部,因此边界框也可能会落在图像外部。

裁剪的不利之处在于,我们可能会丢失图像的重要部分,这可能比压扁物体还要糟。

压榨还是修剪也会影响从数据集中计算锚点的方式。使用锚点的全部要点是,这些锚点类似于数据集中最常见的对象形状。

裁剪时仍然如此。现在,某些锚点可能会部分落在图像之外,但至少它们的纵横比可以真正代表训练数据中的对象。

使用压扁时,由于忽略了不同的纵横比,因此计算出的锚点并不能真正代表真实的盒子,因为每个训练图像的压扁方式都稍有不同。现在,锚点在许多不同图像尺寸上的平均值更高,而所有图像尺寸均以不同方式失真。

数据扩充在这里也有作用。通过拍摄图像的随机大小的作物,然后将其调整为416×416的尺寸,这些作物也会弄乱纵横比(大概是故意的)。

长短短:我们将坚持挤压图像,而忽略边界框的长宽比,因为这是最简单的。这也是YOLO和SSD所做的。

一种看待这种情况的方法是,我们不是在试图使模型尊重长宽比,而是在试图使其对它们不变。(如果知道您将始终处理固定尺寸的输入图像,例如1280×720,那么使用裁切可能更有意义。)

你怎么训练这个东西?

现在,我们几乎掌握了所有内容,以了解如何训练这种对象检测模型。

该模型使用非常直接的卷积神经网络进行预测。我们将这些预测数字变成边界框。数据集包含地面真实的盒子,说出训练图像中实际存在哪些对象,因此,要训练这种模型,我们需要提出一个损失函数,将预测的盒子与地面真实的对象进行比较。

问题在于,图像之间的地面真实盒子的数量可能会有所不同,从零到几十个不等。这些框可以按任何顺序位于整个图像上。有些会重叠。在训练过程中,我们必须将每个检测器与这些真实框之一匹配,以便我们可以为每个预测框计算回归损失。

如果我们天真地执行此匹配,例如,始终将第一个地面真实对象分配给第一个探测器,将第二个物体始终分配给第二个探测器,依此类推,或者通过将地面真实对象随机分配给探测器,则每个探测器将被训练来预测各种各样的物体:有些会很大,有些会很小,有些会在图像的一个角,有些会在对角,有些会在中心,依此类推。

这是我在本博文开头提到的问题,为什么仅向模型中添加一堆回归输出却行不通。解决方案是使用带有固定数量检测器的网格,其中每个检测器仅负责检测位于图像那部分中的对象,并且仅负责特定大小的对象。

现在,损失函数需要知道哪个地面真相对象属于哪个网格单元中的哪个检测器,同样,哪个检测器不具有与之关联的地面真相。这就是我们所说的“匹配”。

将地面真相箱与探测器相匹配

这种匹配如何工作?有不同的策略。YOLO的方法是仅使一个检测器负责检测图像中的给定对象。

首先,我们找到边界框中心所在的网格单元。该网格单元将负责此对象。如果任何其他网格单元也预测此对象,则损失函数将对其进行惩罚。

VOC的注释给出了边界框坐标xminyminxmaxymax。由于该机型采用了网格,我们决定使用基于地面实况框的中心,网格单元,它是有道理的箱子坐标转换为center xcenter ywidth,和height

在这一点上,我们还希望将框坐标标准化到该范围,[0, 1]以便它们独立于输入图像的大小(因为并非所有训练图像都具有相同的尺寸)。

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

注意:由于数据扩充会随机裁剪和翻转,因此我们必须在每个时期重新计算地面真相框和检测器之间的匹配。这不是我们可以预先计算和缓存的内容,因为匹配可能会根据此特定图像所使用的增强而改变(每个时期都会改变)。

仅挑选细胞是不够的。每个网格单元都有多个检测器,我们只希望这些检测器之一来找到对象,因此我们选择其锚框与对象的地面真实框最匹配的检测器。这是通过通常的IOU指标完成的。

这样,将最小的对象分配给检测器1(具有最小的锚定框),将非常大的对象分配给检测器5(具有最大的锚定框),依此类推。

该单元中只有该特定检测器才可以预测该对象。此规则可帮助不同的检测器专门处理形状和大小类似于锚框的对象。(请记住,对象不必与锚完全相同,因为模型可以预测相对于锚框的位置偏移和尺寸偏移。锚框仅是一个提示。)

因此,对于给定的训练图像,某些检测器将具有与之关联的对象,而其他所有检测器则不会。如果训练图像中有3个唯一的对象,因此有3个地面真相框,则845个检测器中只有3个可以进行预测,而其他842个检测器则可以预测“无对象”(就物体而言)我们的模型输出是具有非常低的置信度得分(理想值为0%)的边界框。

目标张量如何填充

从现在开始,我将说积极的例子是指具有真实性的检测器,而消极的例子是指没有与之关联的对象的检测器。负面示例有时也称为“无对象”或背景。

注意:在分类中,我们使用“示例”一词来指代训练图像的整体,但此处指的是图像内部的对象,而不是图像本身。

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

在目标张量中,我们仅填充负责对象的检测器的边界框(和单热编码类向量)。我们将预期的置信度得分设置为1(因为我们100%确信这是真实对象)。

对于所有其他检测器(带有负示例的检测器),目标张量包含全零。边界框坐标和类矢量在这里并不重要,因为它们将被损失函数忽略,置信度得分为0,因为我们100%确信此处没有对象。

因此,当训练循环要求一批新的图像及其目标时,得到的是张量为B×416×416×3的图像张量和张量为B×13×13×125的数字,这些张量代表了真实的盒子我们希望每个探测器都能预测到。由于大多数检测器将不负责预测对象,因此该目标张量中的大多数数字将为0。

匹配时还有一些其他细节需要考虑。例如,当有一个以上的地面真理盒子的中心恰好落在同一单元格中时,会发生什么情况?实际上,这可能不是什么大问题,尤其是在网格足够精细的情况下,但是我们仍然需要一种方法来处理这种情况。

从理论上讲,如果每个盒子都基于最佳的IOU重叠而选择不同的探测器(例如,盒子A与探测器2的IOU重叠最大,盒子B与探测器4的最大重叠),那么我们可以将两个地面真相匹配至不同的探测器这个细胞。然而,这并不能避免让两个地面真理的盒子想要同一个探测器的问题。

YOLO通过首先随机混洗地面真相来解决此问题,然后仅选择与该单元格匹配的第一个。因此,如果将一个新的真实盒子与已经负责另一个对象的单元格匹配,则我们将忽略该新盒子。下一个时代更好的运气!

这意味着在YOLO中,每个单元格最多只有一个检测器被赋予一个对象-该单元格中的其他检测器不应检测到任何东西(如果检测到,则将受到惩罚)。

请注意,还有其他可能的匹配策略。例如,SSD可以将具有多个检测器的同一个地面真相盒与之匹配:它首先选择具有最佳IOU的检测器,然后还选择任何(未分配)其锚定盒的IOU大于0.5的检测器(未分配)。(我假设这些检测器不必全部都位于同一单元中,甚至不必位于同一网格中,但是本文没有说明。)

这样做可以简化模型的学习过程,因为它不必在哪个检测器应该预测一个对象之间进行选择-现在,多个检测器都可以预测这个对象。

注意:这些设计选择似乎有些矛盾。YOLO将地面真实物体分配给单个检测器(对于该单元,其他对象则分配“无物体”),以帮助检测器专业化。但是SSD表示,多个探测器可以预测同一物体是可以的。谁是对的?我不知道。我不知道在这些特定选择之间进行任何消融研究。对于SSD,这可能是可以的,因为检测器专注于长宽比(形状)而不是尺寸。

损失函数

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

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

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

  1. 该检测器没有与之关联的地面真相。这是一个负面的例子。它不应该检测任何对象(即,它应该预测置信度为0的边界框)。
  2. 该探测器确实有一个真相盒。这是一个积极的例子。检测器负责从地面真相盒中检测物体。

对于不应该检测物体的检测器,当它们预测置信度得分大于0的边界框时,我们将对其进行惩罚。

由于图像中的此位置不存在任何对象,因此将此类检测计为误报。误报过多会降低模型的精度

相反,如果一个探测器确实有一个真实的盒子,我们想惩罚它:

  • 坐标错误时
  • 当置信度分数太低时
  • 当班错了

理想情况下,检测器会预测一个与地面真实盒子完全重叠,具有相同类别标签并且具有高置信度得分的盒子。

当置信度得分太低时,该预测将计为假阴性。该模型找不到真正存在的对象。

但是,如果置信度得分不错,但坐标错误类别错误,则该预测将被计为误报。即使模型声称它找到了一个对象,也可能是错误的对象或放置在错误的位置。

这意味着相同的预测可以算作假阴性(减少模型的召回率)和假阳性(减少精度)。kes。只有在所有三个方面(坐标,置信度,分类)正确时,预测才算是真正的肯定

由于可能发生许多不同的事情,因此损失函数由几个部分组成,所有这些部分都度量了模型做出的预测的另一种“错误”。将这些部分相加即可得出总体损耗指标。

SSD,YOLO,SqueezeDet,DetectNet和其他一级检测器变体都使用略有不同的损耗函数。但是,它们往往由相同的元素组成。让我们看一下不同的部分!

没有真实性的检测器(负示例)

损失函数的这一部分仅涉及置信度得分-由于此处没有地面真实性框,因此我们没有任何坐标或类别标签可将预测与之进行比较。

仅针对负责检测物体的检测器计算该损耗项。如果此类检测器确实找到了物体,则将在此处受到惩罚。

回想一下,置信度得分表示检测器是否认为此网格单元中有一个中心为中心的对象。对于这种检测器,目标张量中的真实置信度得分设置为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]为小区的预测置信度ij以及检测器b该小区内。请注意,我们sigmoid()通常将预测的置信度限制为0到1之间的数字(将其转化为逻辑回归)。

要计算此损耗项,我们只需取真实值与预测值之差,然后求平方(误差平方和或SSE)即可。

no_object_scale是一个超参数。通常为0.5,因此损失项的这一部分与其他部分的计数不一样。由于该图像只有少量的地面真相框,因此大多数845个探测器将仅受到这种“无物体”损失的惩罚,而不会受到我将在下面显示的其他任何损失项的惩罚。

因为我们不希望模型学习对“没有对象”,损失的这部分不应该成为比该探测器的损失更重要的有对象。

上式是针对单个单元中的单个检测器的。为了找到总没有对象的损失,我们增加了no_object_loss对所有细胞ij和所有的探测器b。对于探测器负责寻找对象时,no_object_loss始终是0。SqueezeDet,总无物损失也以“没有对象”的探测器数量,以得到平均值,但在YOLO我们不这样做, 。

实际上,YOLO还要袖手旁观。如果检测器的预测与图像中的任何真实框之间的最佳IOU大于例如60%,则将no_object_loss[i, j, b]其设置为0。

换句话说,如果检测器不应该预测物体,但实际上可以做出很好的预测,那么原谅它甚至鼓励它继续预测物体可能是个好主意。(事后看来,我们确实应该使该检测器对那个物体负责。)

我不确定这个技巧对最终结果是否有任何实际影响,而且似乎有些怪异。但是,嘿,没有骇客的机器学习又会在哪里呢?

SSD没有此无对象丢失项。相反,它向可能的类添加了特殊的背景类。如果这是预测的类别,则该检测器的输出将计为“无物体”。

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

为什么求平方误差而不是均方误差?我不确定100%,但这可能是因为每个图像中都有不同数量的对象(正例)。如果我们要取平均值,那么其中只有1个对象的图像可能最终会损失与具有10个对象的图像相同的损失。使用SSE,后一个图像的损失将大约大10倍,这可能被认为更公平。

也可能是因为Darknet是用于YOLO的深度学习框架,是用C编写的,与TensorFlow或PyTorch不同,它没有自动区分功能。与MSE相比,使用平方和误差给出的梯度更简单(它只是地面真实值减去预测值)。

具有真实性的检测器(正例)

上一节描述了不负责查找对象的检测器发生了什么。他们唯一会做错的事情就是找到一个不存在的对象。现在,我们来看看剩下的探测器:即那些认为找对象。

这些检测器在找不到对象或对对象进行错误分类时可能是错误的。有三个单独的损失条款处理此问题。我们只计算探测器这些损失方面b的网格单元ij如果检测器有一个地面实况箱(即,如果在目标张量置信度设置为1)。

置信度分数

置信度分数的损失项为:

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

这与极为相似no_object_loss,但这里的地面真相为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]应表示预测的边界框和地面真值框之间的IOU。理想情况下,这是1或100%,以获得完美的匹配。但是,YOLO并没有将预测分数与该理想数字进行比较,而是查看了两个方框之间的实际IOU。

这是有道理的:如果IOU小,则置信度应小;如果IOU大,则置信度也应大。

与无对象丢失不同,在无对象丢失中,我们希望预测的置信度始终为0,而​​在这里,我们不仅希望模型始终预测100%的置信度。相反,模型应该学会估计其边界框坐标的质量。这正是IOU告诉您的。

如前所述,SSD无法预测置信度得分,因此没有此损失项。

类概率

每个检测器还预测对象的类别。这与边界框坐标无关。从本质上讲,我们训练了5个单独的分类器,所有这些分类器都被教导以查看不同大小的对象。

YOLO v1和v2使用以下损失项来预测类别概率:

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

在此,true_class是20个数字的单热编码矢量(对于Pascal VOC),并且pred_class是预测对数的矢量。请注意,即使我们将softmax应用于预测,该损失项也不会使用交叉熵。(我认为他们可能使用了平方和误差,因为这使该损失项更易于与其他损失项进行平衡。实际上,甚至softmax是可选的。)

YOLO v3和SSD采用不同的方法。他们不认为这是一个多类分类问题,而是一个多标签问题。因此,他们不使用softmax(总是选择一个标签作为获胜者),而是使用逻辑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])) * 32
box_y[i, j, b] = (j + sigmoid(pred_y[i, j, b])) * 32
box_w[i, j, b] = anchor_w[b] * exp(pred_w[i, j, b]) * 32
box_h[i, j, b] = anchor_h[b] * exp(pred_h[i, j, b]) * 32

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

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

true_x[i, j, b] = ground_truth.center_x - grid[i, j].center_x
true_y[i, j, b] = ground_truth.center_y - grid[i, j].center_y
true_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_w并且true_h是锚点尺寸的适当缩放比例。填写目标张量时,应用此逆变换非常重要,否则损失函数将是对苹果和橙子的比较。

SSD再次使用略有不同的损耗项。其本地化损失称为“平滑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**2
else:
    coord_loss_x[i, j, b] = difference - 0.5

以此类推,其他坐标也是如此。据认为,这种损失对异常值不那么敏感。

准备训练!

就是这样。现在,所有训练模型的步骤都已完成。你有:

  1. 提供图像和注释的数据集(例如Pascal VOC)(真实框)
  2. 具有以网格组织的许多对象检测器的模型
  3. 将地面真相框放入此网格以创建目标张量的匹配策略
  4. 以及将预测与该目标进行比较的损失函数。

现在您需要做的就是让SGD放松一下!

顺便说一句,考虑到我刚刚描述的损失函数,您似乎需要使用一些嵌套循环来计算损失,因为正例的检测器使用的损失项与负例的检测器使用不同的损失项。

也许是这样的(用伪代码):

for i in 0 to 12:
  for j in 0 to 12:
    for b in 0 to 4:
      gt = target[i, j, b]   # ground-truth
      pred = grid[i, j, b]   # prediction from model

      # is this detector responsible for an object?
      if gt.conf == 1:
        iou = IOU(gt.coords, pred.coords)
        object_loss[i, j, b] = (iou - sigmoid(pred.conf[i, j, b]))**2
        coord_loss[i, j, b] = sum((gt.coords - pred.coords)**2)
        class_loss[i, j, b] = cross_entropy(gt.class, pred.class)
      else:
        no_object_loss[i, j, b] = (0 - sigmoid(pred.conf[i, j, b]))**2

然后,总损失为:

loss = no_object_scale * sum(no_object_loss) + 
          object_scale * sum(object_loss) + 
           coord_scale * sum(coord_loss) + 
           class_scale * sum(class_loss)

但实际上,您可以对这些循环进行矢量化处理,以使整个损失函数可以在GPU上运行,如下所示:

# the mask is 1 for detectors that have an object, 0 otherwise
mask = (target.conf == 1)

# compute IOUs between each detector's predicted box and
# the corresponding ground-truth box from the target tensor
ious = IOU(target.coords, grid.coords)

# compute the loss terms for the entire grid at once:
object_loss = sum(mask * (ious - sigmoid(grid.conf))**2)
coord_loss = sum(mask * (target.coords - grid.coords)**2)
class_loss = sum(mask * (target.class - softmax(grid.class))**2)
no_object_loss = sum((1 - mask) * (0 - sigmoid(grid.conf))**2)

现在,我们始终为所有检测器计算所有损耗项,但是我们使用掩码来丢弃不需要计数的结果。

尽管我们在损失函数中所做的工作比图像分类要复杂得多,但是一旦您了解了所有单独部分的用途后,它实际上还算不错。而且,由于YOLO,SSD和其他单阶段模型都使用这些损失条款的细微变化,因此在选择方式方面似乎还有很多回旋余地。

值得一提的还有一些用于训练这些网络的技巧:

  • 多尺度培训。实际上,物体检测器将与(许多)不同大小的图像一起使用,而这些图像又包含不同大小的物体。使网络在各种输入维度上都能很好地预测的一种方法是每10批随机选择一个新的输入大小。并非总是对416×416图像进行训练,而是随机对320×320到608×608的图像进行训练。
  • 热身训练。YOLO在早期训练步骤中,在每个单元格的中央添加了一个假的地面真实框,并使用此框来计算额外的坐标损失。这样做是为了鼓励预测开始匹配检测器的锚。
  • 艰苦的负面采矿。我已经指出几次,大多数探测器将不负责探测任何物体。这意味着负面的例子比正面的例子还要多。YOLO通过使用超参数(no_object_scale)处理此问题,但SSD使用硬负挖矿。它没有使用损失中的所有否定示例,而是仅使用最错误的示例(具有最高置信度的误报)。

即使模型经过训练后可能会很好用,但有时您仍需要这些技巧来启动模型以学习任何东西。

该模型的效果如何?

要了解分类模型的性能如何,您可以简单地计算测试集上正确预测的数量,然后除以总测试图像即可得出分类精度。

使用对象检测,我们可以为以下几项计算分数:

  • 每个检测到的物体的分类精度
  • 预测对象与实际对象重叠的量(IOU)
  • 模型是否实际找到图像中的所有对象(称为“调用”)

仅靠这些本身是不够的。

例如,如果IOU与地面真相的边界框重叠超过50%,则IOU可以将其计算为正确的预测(真阳性或TP),否则将其计算为不正确的预测(假阳性或FP)。

但是,这还不足以了解您的模型的运行状况,因为它不会告诉您模型何时错过了对象-如果存在地面真假盒子,则该模型未针对(假阴性或FN)产生任何预测。

真阳性,假阳性,假阴性

注意:在物体检测中没有真正的负面因素。真正的否定是指不负责预测对象的检测器正确地预测那里什么也没有。这是一件好事,但是大多数时候我们并不关心对象不在哪里,而只在乎它们在哪里。;–)

要将所有这些不同方面组合为一个数字,我们通常使用平均平均精度mAP。mAP越高,您的模型越好。Pascal VOC 2012测试集上当前得分最高的模型的mAP为77.5%(refine_denseSSD自2018年5月14日起)。相比之下,YOLO v2得分为48.8%,SSD得分为64%(这可能是SSD的大版本,而不是快速移动版)。

根据您使用的数据集,有几种不同的方法来计算mAP。由于我一直在谈论Pascal VOC,因此让我们研究一下他们的方法。

计算mAP

对于Pascal VOC mAP,首先我们分别计算20个类别中每个类别的平均精度(或AP),然后对这20个数字取平均值,以获得最终的mAP分数。因此,mAP得分是平均值的平均值。

机器学习中的精度一词是非常具体的含义,它是真实肯定的数量除以检测的总数:

precision = TP / (TP + FP)

在我们的案例中,误报是指已检测到但实际上不存在于图像中的对象。当预测的边界框与图像中的任何地面真实框有太大差异时,或者当预测的类别不相同时,就会发生这种情况。

注意:在这一点上,预测来自哪个检测器都没有关系。在评估模型时,我们不会分配检测器来负责特定的对象-只会在训练期间发生。在这里,我们只是将预测的边界框与地面真相进行比较,以查看我们发现了多少个对象。

经常与精度一起使用的另一种指标是回想率,也称为真实阳性率或灵敏度:

recall = TP / (TP + FN)

这些公式的唯一区别在于,精度使用分母中的误报数,而召回使用误报数。当没有对某个对象做出预测时,或者当置信度得分太低(这实际上与“没有预测”相同)时,就会出现假阴性。

非正式地,精度可以衡量:您预测的所有对象中有多少是“猫”,猫到底有多少?(在这里,FP有多少只预测的猫不是真正的猫-甚至根本不是物体。)

召回率衡量您实际发现的图像中FN有多少只猫(即您错过了多少只猫)。

例如,如果您预计有三只猫的图像,但其中一人是真正的狗,另一个是不是在所有的对象,猫类的精度为1 / 3 = 0.33。(三个预测中的一个正确。)

如果图像中的实际4个猫,猫召回是1 / 4 = 0.25,因为你只找到了其中的一个。而且,如果在该图像中只有一只狗,则由于误认为这只猫是猫,因此狗的精确度和召回率均为0。

以下是用伪代码计算TP和FP数量的方法:

sort the predictions by confidence score (high to low)

for each prediction:
    true_boxes = get the annotations with same class as the prediction
                     and that are not marked as "difficult"

    find IOUs between true_boxes and prediction
    choose ground-truth box with biggest IOU overlap

    if biggest IOU > threshold (which is 0.5 for Pascal VOC):
        if we do not already have a detection for this ground-truth box:
            TP += 1
        else:
            FP += 1
    else:
        FP += 1

如果预测与地面真实对象具有相同的类,并且其边界框重叠超过50%,则该预测算作是真正的阳性。如果重叠小于50%,则该预测算作误报。

根据Pascal VOC规则,如果在相同的地面边界框内,两个或多个预测的IOU大于50%,我们必须仅选择一个作为正确的预测。其他检测将被计为误报。这样做是为了鼓励模型预测每个对象只有一个盒子。(我们选择置信度得分最高的预测作为正确的预测,而不一定是IOU最大的预测。)

由于对同一个对象的多个预测会受到惩罚,因此首先应用非最大抑制(NMS)来尽可能多地消除重叠的预测是很明智的。

放弃具有极低置信度得分的预测(例如,得分低于0.3的任何东西)也是一个好主意。否则,这些将被视为误报。对于诸如YOLO之类的模型,该模型总是产生845个预测,或者具有1917个预测的SSD,总是会有比真实对象更多的预测(绝大多数图像仅包含1-3个真实对象)。

目前我们还没有计算出假阴性,但是我们确实不需要。召回的分母为TP + FN,实际上与图像中的真实框的数量相同(对于我们正在查看的特定类)。

现在,我们拥有计算精度和召回率所需的一切。但是,单个精度和召回分数并不能真正说明该模型,因此我们将计算整个精度范围,召回对并将结果绘制为一条曲线,称为……等待……精确召回曲线

我们将为20个类中的每个类创建一条曲线。然后,一类的平均精度(AP)是其曲线下的面积。

精度调用曲线

这是我的一个实验模型中“狗”类的精确召回曲线:

单个类别的精确召回曲线

在x轴上是召回范围,从0(未找到地面真实对象)到1(已找到所有对象)。在y轴上是精度。请注意,精度是根据召回率给出的,这就是为什么曲线下的面积是此类精度的平均值。这就是度量“平均平均精度”的名称:我们想知道不同召回率值的精度。

注意,上面的精确调用曲线还不是很好。曲线下方的区域(“狗”类的平均精度)仅为0.42。嘿,就像我说的那样,它来自实验模型。;–)

如何解释这条曲线?

始终通过计算不同阈值下的精度和召回得分来创建精确召回曲线。对于二元分类器,这是阈值,在此阈值之后,我们可以得出预测为肯定的结论。在对象检测模型的情况下,我们要改变的阈值是预测框的置信度得分。

首先,我们为第一个预测(最高阈值)计算精度和召回率,然后为第一个和第二个预测(稍微较低的阈值),然后为前三个预测(甚至更低的阈值)计算精度和召回率,依此类推直到到达列表的末尾,我们在该列表中计算精度并回想所有预测(最低阈值)。

这些精度/召回值中的每一个都是曲线上的一个点(一个工作点),x轴上的召回率,y轴上的精度。

在高阈值情况下,召回率将很低,因为只包含了一些预测,因此会有很多假阴性。您可以在曲线的左侧看到这一点:精度高达100%,但这是因为我们只包含了我们非常确定的预测。但是这里的召回率很低,因为我们遗漏了很多对象。

随着阈值的降低,并且我们在结果中包含了更多对象,召回率也随之增加。精度可以上升和下降,尽管由于现在发现了更多的误报,所以它趋于降低。

在最低阈值处,召回率最高,因为我们现在正在研究模型所做的所有预测。在我的实验模型中,狗的召回率最高为42%,因此即使在此最低阈值下,平均每10条狗中也只能发现4条。这里还有改进的空间!

我希望这可以弄清楚该模型所预测的误报与误报之间总会有一个权衡。我们使用精确召回曲线的原因是为了衡量该折衷,并为置信度得分找到一个好的阈值。

  • 选择高阈值意味着我们保留更少的预测,因此我们将拥有更少的误报(我们会犯更少的错误),但是我们也会有更多的误报(我们错过更多的对象)。
  • 阈值越低,我们包含的预测就越多,但是它们通常质量较低。

理想情况下,每次召回的精度都很高。对所有可能的召回值取平均值的平均值,可以为我们提供一个有用的模型总结,该模型对检测此特定类别的对象的效果如何。

获得所有不同阈值的精度和召回率后,我们可以通过找到该曲线下的面积来计算平均精度(AP)。在Pascal VOC中,实际上有两种不同的方法:2007年的测试集使用了一种近似方法。2012年的版本更加精确(使用集成),并且得分较低。

最终的mAP分数只是20个类别的平均准确率的平均值。

自然,分数越高越好。但这并不意味着mAP至关重要。YOLO的得分低于某些竞争对手,但它也更快。尤其是对于在移动设备上使用的情况,我们希望使用在速度和准确性之间取得良好折衷的模型。

https://machinethink.net/blog/object-detection/

标签

发表评论