作者:MATTHIJS HOLLEMANS
编译:ronghuaiyang
首发:AI公园公众号
导读
今天是第二部分,讲解的是模型预测的内容以及为什么要这样预测,以及数据相关的内容。
(书接上回)
模型实际上预测的是什么?
让我们更仔细地看看示例模型的输出。因为它只是一个卷积神经网络,所以前向传递是这样的:
输入一张 416×416 像素的 RGB 图像,卷积层对图像像素进行各种变换,输出一张 13×13×125 的 feature map。因为这是一个回归输出,所以最后一层没有用激活函数。输出只有 21,125 个实数,我们必须把这些数字转换成边界框。
需要 4 个数字来描述边界框的坐标。有两种常见的方法来做到这一点:要么是xmin, ymin, xmax, ymax
来描述框的边界,或使用center x, center y, width, height
。两种方法都可以,但是我们将使用后一种方法(知道框的中心在哪里可以更容易地将框与网格单元匹配起来)。
模型对每个边界框的预测不是它们在图像中的绝对坐标,而是四个“delta”值,即偏移量:
delta_x
,delta_y
:网格单元格内框的中心delta_w
,delta_h
:调整锚框的宽度和高度
每个探测器对它的锚框做一个预测。锚框应该已经是实际物体大小的一个很好的近似值(这就是我们使用它们的原因),但它不会是精确的。这就是为什么我们预测一个比例因子,它表示框比锚点大或小多少,以及一个位置偏移量,它表示预测的框离网格中心有多远。
为了得到边界框在像素坐标中的实际宽度和高度,我们这样做:
box_w[i, j, b] = anchor_w[b] * exp(delta_w[i, j, b]) * 32box_h[i, j, b] = anchor_h[b] * exp(delta_h[i, j, b]) * 32
其中i
和j
是网格中的行和列(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()
,而是取 ground-truth 值的log()
。下面将对此进行详细介绍。
为了得到预测框在像素坐标中的中心 x,y 位置,我们这样做:
box_x[i, j, b] = (i + sigmoid(delta_x[i, j, b])) * 32box_y[i, j, b] = (j + sigmoid(delta_y[i, j, b])) * 32
YOLO 的一个关键特性是,只有它发现物体的中心落在某个检测器的网格单元内的时候,它才鼓励该检测器去预测这个物体的边界框。这有助于避免虚假的检测,因此多个相邻的网格单元不会都找到相同的物体。
要执行此操作,delta_x
和delta_y
必须限制为 0 到 1 之间的数字,该数字是网格单元格内的相对位置。这就是 sigmoid 函数的作用。
然后我们添加网格单元的坐标i
和j
(都是 0 - 12),并乘以每个网格单元的像素数(32)。现在box_x
和box_y
是原 416×416 图像空间中预测边界框的中心。
SSD 的做法略有不同:
box_x[i, j, b] = (anchor_x[b] + delta_x[i, j, b]*anchor_w[b]) * image_wbox_y[i, j, b] = (anchor_y[b] + delta_y[i, j, b]*anchor_h[b]) * image_h
这里预测的 delta 值实际上是锚框宽度或高度的倍数,并且没有激活函数 sigmoid。这意味着用 SSD 的时候,该物体的中心实际上可以位于网格单元之外。
还要注意,使用 SSD 的时候,预测坐标是相对于锚框中心的,而不是网格单元的中心。在实践中,锚框的中心将与网格单元的中心完全对齐,但它们使用不同的坐标系统。SSD 的锚坐标在[0,1]范围内,使它们与网格大小无关。(这是因为 SSD 使用不同大小的多个网格。)
正如你所看到的,尽管 YOLO 和 SSD 通常以相同的方式工作,但当你开始查看小细节时,它们是不同的。
除了坐标,该模型还预测边界框的置信度。因为我们想让这个数字在 0 和 1 之间,我们使用标准的技巧,用了一个 sigmoid:
confidence[i, j, b] = sigmoid(predicted_confidence[i, j, b])
回想一下,我们的示例模型总是预测 845 个边界框,不多不少。但通常情况下,图像中只有几个真实的物体。在训练期间,对于每个 ground-truth,我们只鼓励一个单一的检测器对其进行预测,因此只有少数几个预测具有较高的置信度。对于没有发现物体的检测器的预测——到目前为止大多数的检测器——应该有一个非常低的置信度分数。
最后,我们预测类的概率。对于 Pasval VOC 数据集,对于每个边界框,这是一个包含 20 个数字的向量。像往常一样,我们用一个 softmax 使其成为一个很好的概率分布:
classes[i, j, b] = softmax(predicted_classes[i, j, b])
除了 softmax,你还可以使用 sigmoid 激活。这使它成为一个多标签分类器,在这种情况下,每个预测的边界框实际上可以同时拥有多个类。(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 feature map 的每个位置上滑动,在每个位置上做出预测。然后,我们将这 125 个数字解释为组成 5 个预测边界框和它们在网格位置上的类得分(这是损失函数的工作,下面将详细介绍)。
最初,每个网格位置预测的 125 个数字将是完全随机的,没有意义的,但是随着训练的进行,损失函数将引导模型学习做出更有意义的预测。
现在,尽管我一直在说每个网格单元中有 5 个检测器,但是对于总体上的 845 个检测器,这个模型实际上总共只学习了 5 个检测器,而不是每个网格单元有 5 个唯一的检测器。这是因为卷积层的权值在每个位置都是相同的,因此在网格单元之间也是“共享”的。
这个模型对每个锚点学习一个检测器。它通过在图像上滑动这些探测器得到 845 个预测,网格上每个位置 5 个。因此,尽管我们总共只有 5 个不一样的探测器,但由于是卷积,这些探测器与它们在图像中的位置无关,因此无论它们位于何处,都可以探测到物体。
它是给定位置的输入像素与为该检测器/卷积核学习的权值的组合,决定了该位置的最终边界框的预测。
这也解释了为什么模型总是预测边界框相对于网格单元中心的位置。由于该模型的卷积性,它无法预测绝对坐标。由于卷积核在图像中滑动,它们的预测总是与它们在特征图中的当前位置相关。
YOLO vs. SSD
上面关于单阶段物体检测器工作原理的描述几乎适用于所有的检测器。确切地说,输出的解释方式可能会有一些细微的差异(例如,sigmoid 优于类概率而不是 softmax),但总体思路是相同的。
然而,YOLO 和 SSD 的不同版本之间存在一些有趣的架构差异。
下面是 YOLO v2&v3 和 SSD 不同架构的草图:
正如你所看到的,在高层次上,YOLOv3 和 SSD 非常相似,尽管它们通过不同的方法得到最终的网格大小(YOLO 使用上采样,SSD 是下采样)。
YOLOv2(和我们的示例模型)只有一个 13×13 的输出网格,SSD 有多个不同大小的网格。MobileNet+SSD 版本有 6 个不同大小的网格,大小分别为 19×19、10×10、5×5、3×3、2×2 和 1×1。
所以 SSD 网格从非常精细到非常粗糙都有。它这样做是为了在更大范围内获得更准确的预测。
精细的 19×19 网格,其网格单元非常接近,负责最小的物体。最后一层产生的 1×1 的网格负责对占据整个图像的大型物体做出反应。其他层的网格覆盖了中间的物体大小。
YOLOv2 尝试用它的跳跃连接做一些类似的事情,但这似乎效果不太好。YOLOv3 更像 SSD,因为它使用 3 个不同尺度的网格来预测边界框。
与 YOLO 一样,每个 SSD 网格单元都进行多次预测。每个网格单元的检测器数量各不相同:在更大、更细粒度的特征映射上,SSD 每个网格单元有 3 到 4 个检测器,在更小的网格上,SSD 每个单元有 6 个检测器。(YOLOv3 在每个尺度上每个网格单元使用 3 个检测器。)
坐标预测也与锚相关 —— 在 SSD 论文中称为“默认框” —— 但有一点不同,SSD 预测的中心坐标可以“走出”它们的网格单元。锚框以单元格为中心,但 SSD 不对预测的 x、y 偏移量使用 sigmoid。因此,从理论上讲,模型右下角的一个框可以预测图像左上角的的边界框(但这在实际中可能不会发生)。
与 YOLO 不同,它没有置信度评分。每个预测只包含 4 个边界框坐标和类概率。YOLO 使用置信度来表示这个预测是真实物体的概率。SSD 通过一个特殊的“背景”类来解决这个问题:如果这个类预测是针对这个背景类的,那么它意味着这个检测器没有找到物体。这和 YOLO 的置信度分数低是一样的。
SSD 的锚和 YOLO 的有点不同。因为 YOLO 必须从单个网格中做出所有预测,所以它使用的锚点范围包括了从小(单个网格的大小)到大(整个图像的大小)。
SSD 比较保守。19×19 格的锚框比 10×10 格的锚框小,10×10 格的锚框比 5×5 格的锚框小,以此类推。与 YOLO 不同,SSD 不使用锚点使探测器专注于物体的大小,它使用不同的网格。
SSD 锚主要用于使探测器专注于物体形状的不同可能的纵横比,而不是它们的大小。如前所述,我们使用一个简单的公式计算 SSD 的锚点,而 YOLO 的锚点是通过对训练数据进行 k-means 聚类找到的。
由于 SSD 使用 3 到 6 个锚点,并且它有 6 个网格而不是 1 个,它实际上总共使用了 32 个不同的检测器(这个数字根据你使用的确切的模型架构略有变化)。
由于 SSD 有更多的网格和检测器,它也输出更多的预测。YOLO 有 845 个预测,对比 MobileNet-SSD 的预测为 841917 个。一个更大的版本,SSD512,甚至输出 24,564 个预测!这样做的好处是,你更有可能找到图像中的所有物体。缺点是你不得不做更多的后期处理来找出你想要保留的预测。
由于这些差异,在 SSD 和 YOLO 之间,ground-truth 边界框与检测器的匹配方式略有不同。损失函数也略有不同。但是我们在训练的时候会讲到。
注:也有一个变体称为 SSDLite,与 SSD 相同,但用深度可分卷积来实现,而不是常规卷积层。由于这一点,它比普通 SSD 快得多,非常适合在移动设备上使用。
好了,关于这些模型如何进行预测的理论已经讲得够多了。现在让我们看看我们需要训练物体检测模型的数据类型。
数据
训练物体检测模型有几个流行的数据集 - Pascal VOC,COCO,KITTI。让我们来看看 Pascal VOC,因为它是一个重要的基准,在 YOLO 的论文中也使用了它。
VOC 数据集由图像和用于不同任务的标注组成。我们只对物体检测任务感兴趣,因此我们只查看带有物体标注的图像。有 20 个物体类别:
aeroplane bicycle bird boat bottlebus car cat chair cowdiningtable dog horse motorbike personpottedplant sheep sofa train tvmonitor
VOC 数据集提供了一个建议的训练/验证集划分,大约是 50⁄50。数据集不是很大,所以使用 50%的数据来进行验证看起来有点傻。因此,通常会将训练集和验证集合并成一个大的训练集“trainval”(总共有 16551 张图像),并随机选择 10%左右的图像用于验证。
可以在 2007 年的测试集中测试你的模型,因为答案是可用的。还有 2012 年的测试集,但答案是保密的。(对于 2012 年测试集的提交,通常也会将 2007 年的测试集包含在训练数据中。数据越多越好。)
注:即使 Pascal VOC 竞赛不再活跃了,你仍然可以提交预测来查看你的模型在排行榜上的得分。
合并的 2007+2012 训练集有 8218 张带有物体标注的图像,验证集有 8333 张图像,2007 测试集有 4952 张图像。这比 ImageNet 的 130 万张图片要少得多,因此使用某种形式的迁移学习而不是从头开始训练模型是个好主意。这就是为什么我们从一个已经在 ImageNet 上预先训练过的特征提取器开始。
标注
标注描述了图像中的内容。也就是说,标注提供了我们需要训练的目标。
标注是 XML 格式的,每个训练图像一个标注文件。标注文件包含一个或多个<object>
,其中有类的名称、一个由xmin, xmax, ymin, ymax
给出的包围框,以及每个 ground-truth 物体的一些其他属性。
如果一个物体被标记为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。这可能是因为他们使用的 MATLAB 从 1 开始索引,就像数学家做的那样。
如果我们绘制这个训练图像和它的边界框,看起来像这样:
对于合并的 2007 年和 2012 年的数据集,我们有以下统计:
dataset images objects------------------------------train 8218 19910val 8333 20148test 4952 12032 (2007 only)
大约一半的图片中只有一个物体,其他的有两个或更多。你可以在直方图中清楚地看到这一点(这是训练集):
一张图片中物体的最大数量是 39 个。验证和测试集的直方图类似。
为了好玩,这里是训练集中所有边界框区域的直方图,将宽度和高度归一化到[0,1]范围后:
正如你所看到的,许多物体都相对较小。在 1.0 时达到峰值是因为有很多物体比图像大(例如,一个人的部分可见),所以包围框填充了整个图像。
这是另一种看待这些数据的方式,一个 ground-truth 边界框宽度与高度的图。这张图中的“斜率”显示了框的长宽比。
我发现这些图有用的,因为它能让你了解数据是什么样的。
数据增强
由于数据集比较小,所以在训练时通常会使用大量的数据扩充,比如随机翻转、随机裁剪、颜色变化等。
重要的是要记住,无论你对图像做什么,也必须对边界框做什么!因此,如果你翻转训练图像,你还必须翻转 ground-truth 框的坐标。
YOLO 会做以下事情来加载一个训练图片:
- 加载图像不做缩放
- 通过随机增大或减小原尺寸的 20%来选择一个新的宽度和高度
- 对图像进行裁剪,如果新图像的一个或多个边比原来的大,用零来填充
- 调整大小为 416×416,使其变成正方形
- 随机水平翻转图像(50%概率)
- 随机扭曲图像的色调、饱和度和曝光(亮度)
- 同样通过移动和缩放来调整边界框坐标,以适应之前的剪切和大小调整,以及水平翻转
旋转也是一种常见的数据增强技术,但这很棘手,因为我们还需要旋转边界框。所以通常不会这样做。
SSD 论文还建议增加以下内容:
- 随机选择一个图像区域,使得图像中物体的最小 IOU 为 0.1、0.3、0.5、0.7 或 0.9。这个 IOU 越小,模型就越难检测到物体。
- 使用“缩小”增强,有效地使图像更小,这样就创建了额外的小物体的训练样本。这对于训练模型更好地处理小物体非常有用。
随机裁剪可能会导致物体部分(或全部)落在裁剪后的图像之外。因此,我们只想保留中心位于该 crop 区域的 ground-truth 框,而不希望保留中心位于可视图像之外的框。
当心长宽比!
注:请跳过本节。它处理的是一个不重要的问题。但是我还没有看到它在其他地方被提到过,所以我认为它值得一写。
我们在一个正方形网格(13×13)上进行预测,我们的输入图像也是正方形(416×416)。但是训练图像通常不是方形的,我们将要进行推断的测试图像也不是方形的。这些图像甚至没有相同的大小。
以下是 VOC 训练集中图像的所有长宽比的可视化:
红框的宽高比要比青色的框的宽高比要大。有许多奇怪的宽高比,但最常见的是 1.333(4:3)、1.5(3:2)和 0.75(3:4)。
如你所见,有些图像非常宽。这里有一个极端的例子:
神经网络对大小为 416×416 的正方形图像进行处理,因此我们必须将训练图像拟合到该正方形中。有几种方法可以做到这一点:
- 不均匀地缩放大小为 416×416,这样会挤压图像
- 将最小的边调整为 416,然后从图像中选取 416×416 的裁剪
- 将最大的边调整为 416,用 0 填充较小的边
这些都是有效的方法,但每种方法都有自己的副作用。
在压缩图像时,我们将其宽高比改为 1:1。如果原始图像的宽度大于高度,那么所有物体都会变得比平常更窄。如果原来是高的比宽的,那么所有的物体都变平了。
通过裁剪,宽高比保持不变,但我们可能会剪切图像的重要部分,使模型更难看到物体的真实情况。模型现在可能需要预测一个部分位于图像外部的边界框。(对于选项#3,它可能使物体太小而无法被检测到,特别是在宽高比非常大的情况下。)
为什么这很重要?
在训练之前,我们将边界框的xmin
和xmax
除以图像的宽度,ymin
和ymax
除以图像的高度,以使坐标正常化,使它们位于 0 和 1 之间。这样做是为了使训练独立于每个图像的实际像素大小。
但是输入图像通常不是正方形的,所以 x 坐标除以的数与 y 坐标不同。根据图像的大小和宽高比,每个图像的这些因数可能是不同的。这影响了我们如何处理边界框坐标和锚点。
压缩(选项 1)是最简单的选项,即使它会暂时打乱图像的宽高比。如果所有的图像都有相似的宽高比(VOC 中没有),或者宽高比不是“太”极端,那么神经网络应该还是可以正常工作的。Convnets 似乎对物体的“厚度”变化相当健壮。
使用裁剪(选项 2),我们应该在规范化边界框坐标时考虑宽高比。现在,边界框有可能变得比输入图像更大,因为我们不是在查看整个图像,而是只查看裁剪后的部分。由于物体可能部分地落在图像之外,所以边界框也可能落在图像之外。
裁剪的缺点是我们可能会丢失图像的重要部分,这可能比稍微挤压物体更糟糕。
不管你是缩放还是裁剪,都会对从数据集计算锚点的方式产生影响。使用锚点的全部意义在于它们类似于数据集中最常见的物体形状。
这种情况在裁剪时仍然适用。一些锚点现在可能部分落在图像之外,但至少它们的宽高比真正代表了训练数据中的物体。
通过缩放,计算出的锚点并不能真正代表真实的框,因为不同的长宽比会被忽略,因为每个训练图像都会以稍微不同的方式被压缩。锚点现在在许多不同大小的图像中都是平均的,它们都以不同的方式被扭曲。
数据增强在这里也有作用。通过获取随机大小的 crop 图像,然后调整大小为 416×416,这些 crop 也会打乱宽高比(可能是故意的)。
我们将坚持缩放图像,忽略边框的宽高比,因为这是最简单的。YOLO 和 SSD 也是这么做的。
一种看待这个问题的方法是,与其试图使模型尊重宽高比,我们实际上是试图使它对它们“不变”。(如果你知道你总是要处理固定大小的输入图像,比如 1280×720,那么使用裁剪可能更有意义 )。
(未完待续,明天见)
—END—
英文原文:https://machinethink.net/blog/object-detection/
推荐阅读
关注图像处理,自然语言处理,机器学习等人工智能领域,请点击关注AI公园专栏。
欢迎关注微信公众号