引言
本文从希望从零开始实现 YOLO目标检测架构。它将不描述网络的优点/缺点或每个设计选择的原因。相反,它专注于它是如何工作的。在阅读本文之前,你应该对神经网络有一个基本的了解,特别是 CNNS。
这篇文章中的所有描述都与 YOLO 的原始论文有关:You Only Look Once: Unified, Real-Time Object Detection by Joseph Redmon, Santosh Divvala, Ross Girshick and Ali Farhadi (2015). 尽管从那时起,已经有很多改进被提出来,这些改进被结合到了最新的 YOLOv2版本中,我可能会在另一个时间再写。首先理解这个原始版本,然后检查进行了哪些更改以及为什么进行了更改,这样会更容易。
什么是 YOLO?
YOLO 是目标检测的一个网络结构。目标检测任务包括确定图像中某些物体出现的位置,以及对这些物体进行分类。以前的方法,如 R-CNN 及其变体,使用多个步骤执行此任务。这可能运行缓慢,也很难优化,因为每个单独的组件必须单独进行训练。YOLO,这一切都是通过一个单一的神经网络完成的。来自:
我们重新思考并把目标检测作为一个单一的回归问题,直接从图像像素到边界框坐标和类的概率。
所以,简单来说,你把一幅图像作为输入,通过一个类似于普通 CNN 的神经网络,然后你得到一个 bounding box 和类预测的矢量输出。
那么,这些预测是什么样的呢?
预测向量
理解 YOLO 的第一步是它如何进行结果的输出。输入图像被分割成一个 S * S 网格的单元格。对于图像上出现的每个物体,一个网格单元被称为“负责”预测它。这是物体中心落入的单元格。
每个网格单元都可以预测 B bounding boxes 以及 C 类别预测。其中bounding box 预测有5个组成部分:(x,y,w,h,置信度)。(x,y)坐标表示框的中心,相对于网格单元的位置(请记住,如果框的中心不在网格单元内,那么这个单元对此不负责)。这些坐标被标准化为0到1之间的值。相对于图像大小,(w,h)框的尺寸也被归一化到[0,1]之间。让我们来看一个例子:
在 bounding box 预测中还有一个组成部分,那就是置信度得分:
在形式上,我们把置信度定义为 Pr (Object) * IOU (pred,truth) 。如果该单元格中不存在对象,则置信度得分应为零。否则,我们希望置信度得分等于预测框和ground truth之间的IOU。
请注意,置信度反映了任何类的对象是否存在。
现在我们理解了 bounding box 预测的5个组成部分,请记住每个网格单元对这些都可以得到B,因此总共有与 bounding box 预测相关的 S * S * B * 5 个输出。
对于预测类的概率来说它也是必要的,Pr(Class(i) | Object)。这个概率取决于包含一个对象的网格单元。实际上,这意味着如果网格单元中没有对象,损失函数将不会因为错误的类预测而惩罚它,我们将在后面看到。这个网络只预测每个网格的一组类别概率,而不管 B 的数量如何。这使得 S * S * C 类的概率总和。
将类的预测值加到输出向量中,得到一个 S * S * (B * 5 + C)张量作为输出。
网络结构
一旦你理解了预测是如何进行编程的,剩下的就很简单了。网络结构看起来像一个普通的 CNN,有卷积和最大池化层,最后是两个完全连接的层:
┌────────────┬────────────────────────┬───────────────────┐
│ Name │ Filters │ Output Dimension │
├────────────┼────────────────────────┼───────────────────┤
│ Conv 1 │ 7 x 7 x 64, stride=2 │ 224 x 224 x 64 │
│ Max Pool 1 │ 2 x 2, stride=2 │ 112 x 112 x 64 │
│ Conv 2 │ 3 x 3 x 192 │ 112 x 112 x 192 │
│ Max Pool 2 │ 2 x 2, stride=2 │ 56 x 56 x 192 │
│ Conv 3 │ 1 x 1 x 128 │ 56 x 56 x 128 │
│ Conv 4 │ 3 x 3 x 256 │ 56 x 56 x 256 │
│ Conv 5 │ 1 x 1 x 256 │ 56 x 56 x 256 │
│ Conv 6 │ 1 x 1 x 512 │ 56 x 56 x 512 │
│ Max Pool 3 │ 2 x 2, stride=2 │ 28 x 28 x 512 │
│ Conv 7 │ 1 x 1 x 256 │ 28 x 28 x 256 │
│ Conv 8 │ 3 x 3 x 512 │ 28 x 28 x 512 │
│ Conv 9 │ 1 x 1 x 256 │ 28 x 28 x 256 │
│ Conv 10 │ 3 x 3 x 512 │ 28 x 28 x 512 │
│ Conv 11 │ 1 x 1 x 256 │ 28 x 28 x 256 │
│ Conv 12 │ 3 x 3 x 512 │ 28 x 28 x 512 │
│ Conv 13 │ 1 x 1 x 256 │ 28 x 28 x 256 │
│ Conv 14 │ 3 x 3 x 512 │ 28 x 28 x 512 │
│ Conv 15 │ 1 x 1 x 512 │ 28 x 28 x 512 │
│ Conv 16 │ 3 x 3 x 1024 │ 28 x 28 x 1024 │
│ Max Pool 4 │ 2 x 2, stride=2 │ 14 x 14 x 1024 │
│ Conv 17 │ 1 x 1 x 512 │ 14 x 14 x 512 │
│ Conv 18 │ 3 x 3 x 1024 │ 14 x 14 x 1024 │
│ Conv 19 │ 1 x 1 x 512 │ 14 x 14 x 512 │
│ Conv 20 │ 3 x 3 x 1024 │ 14 x 14 x 1024 │
│ Conv 21 │ 3 x 3 x 1024 │ 14 x 14 x 1024 │
│ Conv 22 │ 3 x 3 x 1024, stride=2 │ 7 x 7 x 1024 │
│ Conv 23 │ 3 x 3 x 1024 │ 7 x 7 x 1024 │
│ Conv 24 │ 3 x 3 x 1024 │ 7 x 7 x 1024 │
│ FC 1 │ - │ 4096 │
│ FC 2 │ - │ 7 x 7 x 30 (1470) │
└────────────┴────────────────────────┴───────────────────┘
关于架构的一些认知:
- 注意这个架构是为了在 Pascal VOC 数据集中使用而精心设计的,其中作者使用了 S = 7,B = 2和 C = 20。这解释了为什么最终的特性映射是7x7,并解释了输出的大小(7x7x (2 * 5 + 20))。使用具有不同网格大小或不同类数的网络可能需要调整层维度
- 作者提到了 YOLO 的一个快速版本,只有很少的卷积层。
- 1x1缩小图层和3x3卷积图层的灵感来自于 GoogLeNet (Inception)模型
- 最后一层使用线性激活函数,其他所有层使用 leaky RELU (Φ(x) = x, 假设 x>0; 0.1x 否则(x) = x)
- 如果你不熟悉卷积网络,可以参考这个链接:http://cs231n.github.io/convolutional-networks/
损失函数
关于损失函数有很多要说的,所以让我们分部分来做,开始是这样的:
这个公式计算与预测的bounding box位置(x,y)相关的损失。现在不用担心 λ,只要把它当作一个给定的常数就行了。该函数计算每个bounding box预测器(j = 0...B)每个网格单元(i = 0...S ^ 2).???? obj 的定义如下:
- 如果对象出现在网格单元格中,则代表这个bounding box预测器对这个预测负责
- 否则设为0
但是我们怎么知道哪个预测器对这个对象负责呢:
YOLO 预测每个网格单元有多个bounding box。在训练时,我们只需要一个bounding box预测器对每个对象负责。我们分配了一个预测器来“负责”预测一个对象的基础上,预测与ground truth具有最大IOU值的对象。
公式中的其他项应该很容易理解: (x,y)是预测的bounding box的位置,(x̂, ŷ)是来自训练数据的真实坐标值。
让我们进入第二部分:
这是与预测的 box 的 width/height 相关的损失。除了平方根外,这个方程看起来和第一个方程很相似。这是怎么回事?
我们发现一个问题:较大预测框中的偏差要比较小预测框中的偏差要小。为了部分解决这个问题,我们使用预测边界框的宽度和高度的平方根,而不是直接预测宽度和高度。
接下来是第三部分:
在这里,我们计算与每个 bounding box 预测器的置信得分相关的损失。C 是置信度得分,Ĉ 是预测的bounding box与 ground truth 的 IOU。当单元中有一个对象时,???? obj 等于1,否则为0。
这里和第一部分中出现的 λ 参数是用来对损失函数的不同部分加权的。这对于增加模型的稳定性是必要的。
损失函数的最后一部分是分类损失:
它看起来类似于一个正常的平方和误差的分类,除了 ???? obj。使用这个术语是因为当某个网格中没有对象时,我们不会计算分类错误(因此前面讨论的条件类概率)。
训练
作者以下面的方式描述了训练过程:
- 224x224 首先,使用 ImageNet 1000类的数据集预训练前20个卷积层,输入大小为224x224
- 然后,将输入分辨率提高到448x448
- 使用批量大小为64,动量为0.9,衰减为0.0005的方法训练整个网络大约135个 epoch
- 学习率策略:在第一个epoch,学习率从0.001缓慢提高到0.01。训练大约75个epoch,然后开始减少它
- 使用随机缩放和平移的方式进行数据增强,并随机调整曝光和饱和度
总结
我花了一些时间才弄清这篇论文的所有细节。如果你正在阅读这篇文章,我希望通过我分享的文章可以让你的工作变得更简单。
我相信,检查你是否真正理解了一个算法的最好测试是尝试自己从头开始实现它。有许多细节在文本中并不明确,直到你亲自动手并试图用它来建立某些东西时,你才会意识到这一点。