重读 YOLOv1

发布时间 2023-12-11 23:02:21作者: cold_moon

You Only Look Once: Unified, Real-Time Object Detection

image
yolov1结构图,除了第一个 7*7 卷积(pad=3)外,其他卷积的 (pad=1)。

问题1:Unified 指的是什么?

Unified 指的是 单个神经网络架构,用一个模型输出得到目标检测的分类和定位

问题2:R-CNN like 算法怎么做的?缺点是什么?

R-CNN use region proposal methods to first generate potential bounding boxes in an image and then run a classifier on these proposed boxes. After classification, post-processing is used to refine the bounding boxes, eliminate duplicate detections, and rescore the boxes based on other objects in the scene.

refine the bounding boxes: 回归

eliminate duplicate detections, and rescore the boxes based on other objects in the scene:NMS

缺点:速度慢 and hard to optimize because each individual component must be trained separately.

问题3:介绍下 YOLOv1? 优点和缺点有哪些?

object detection as a single regression problem.

image

预测时候:

(1) resize: 448 × 448
(2) CNN 直接回归
(3) 后处理-NMS

优点:

(1)速度快,相同速度下精度高
(2)YOLO sees the entire image during training and test time so it implicitly encodes contextual information about classes as well as their appearance. Fast R-CNN can't see the larger context,容易把背景误识别为 前景。而 YOLO 比 Fast R-CNN 这种错误少。
(3)艺术品数据集上也表现较好,说明 YOLO 泛化能力强
(4)端到端训练

缺点:

(1)accuracy 较低
(2)小目标检测不好

问题4:为了解决训练的过拟合,训练时候用了什么技巧和数据增强?

  • dropout:第一个 FC 之后加了个 dropout 层,rate = .5
  • data augmentation:
    • random scaling and translations of up to 20% of the original image size
    • HSV:randomly adjust the exposure and saturation of the image by up to a factor of 1.5

目前,基于深度学习的目标检测模型的架构一般分为三部分:backbone、Neck 和 Head

那什么是 Backbone?

所谓的 Backbone 其实是一个特征提取器,它的输入是 原始的图像,输出是 特征图,特征图一般为一个 Tensor, 不考虑批次的话就是 3D Tensor(H,W,D)。一般 H,W 相对于原始图像会减小 n 倍,而 D 会相对图像的输入维度增加 m 倍。

那怎么获得 Backbone 呢?

深度神经网络有一个关键的问题,模型的参数怎么初始化?当然,我们可以直接初始化一个 Backbone 用于训练一个目标检测任务。但是迁移学习的观点是 预训练可以大大加速模型的收敛速度。直接初始化的方式通过训练更多的 epochs 或许也可以达到相应的精度,但是预训练绝对是一个好的方法,因为我们的 自定义数据集的数据量可能是不够的,在大量图像上预训练的 Backbone 无疑是一个好的解决参数冷启动的方案。

一般,我们通过分类任务获得预训练的 Backbone, 分类的数据集一般采用 ImagNet 数据集,从而获得一个好的 目标检测的 Backbone 的起点。

如果你需要修改 Backbone 或者设计一个 你自己的专属的目标检测模型,那么你就需要很多算力来预训练一个 图像分类模型。

因此,目标检测的创新你如果想要大改模型结构,那么你就需要利用别人的预训练的 Backbone。或者自己花费算力和时间设计一个好的 Backbone,并且在大型数据集上做预训练。无疑,选择前者是大部分人的更优选择,而这正是拼手速的时候,看谁把新发布的模型集成进来。

1. 从图像分类到目标检测?

我们之所以觉得分类任务简单,比如对于图像分类,模型的输入无非是一张图像,而无论经过多么复杂的模型,模型的输出也无非是一个类别向量。对于图像分类这种任务,通过有监督学习,我们将图像从不可分的像素空间映射到了可分的语义空间。

有监督学习的关键在于怎么去训练模型,而训练模型的关键在于构造损失函数,而构造损失函数的关键在于怎么去定义输出的 Tensor 含义,而定义输出的Tensor 含义的关键在于怎么定义正负样本。之所以我们觉得训练图像分类模型简单,简单的原因在于构造正负样本的简单。

通常情况下,我们将一张图像的 label 定义为一个 one-hot 向量。举例来说,对于猫狗二分类问题,一张猫的标签是 【0,1】,那么有监督学习会让输出的 2个值,一个逼近 1,一个逼近 0,这就相当于让模型知道这是一只猫,而不是一只狗;对于标签中的 1 那个维度,这张图是一个正样本,而对于标签中 0 的维度,该图是 负样本。之所以一般要使用个 softmax 还是因为这是 闭集分类的问题。否则,我们可以就定义第一个值是 0-1 用来识别狗,第二个值也是 0-1 用来识别猫,把该问题开集问题建模为一个回归问题。因为这其实可以有包含关系,比如其中一个维度是 猫,而另一个维度是 狮子猫,显然对于一张狮子猫的图像,它也是猫,这更符合人的分类规则。如果对于一张模型从来没看过,也不是标签向量维度内的类别的图像,也就是 openset 中的一张图像,那么对于后者,显然我们感觉模型会有更好的表现,因为每个维度都可以很低,也不用在乎 和为1 的约束,如果你训练了一个二分类模型,那么该图像的模型输出必然有一个大于 50%。

因此,上面的例子表明,图像分类问题的正负样本划分其实是针对那个标签维度来说的,人们把这个维度定义为猫,那么只有猫对于该维度才是正样本,其他图像就是负样本。当我们输入一张图像训练时,我们不光在约束正样本那个维度,同时我们也在约束其他维度。多分类问题也是如此。

曾经 YOLO(You Only Live Once) 是一个流行词。而在目标检测任务中,YOLO(You Only Look Once) 的意思是 使用单个神经网络就可以完成目标检测任务,这主要是和两阶段的方法进行区别。而论文中的 Unified 也是同样的意思,即把目标检测中的分类和定位使用一个神经网络就解决了。

另外一个我们认为基于 YOLO 的目标检测问题比图像分类问题难以理解的地方在于,对于图像分类,训练时候的输出的 Tensor 大小和推理时候是一样的。而对于目标检测,其实训练时候就是我们常在论文中看到的那些结构图,输出是一个高维度的Tensor,而推理时候不一样,我们有后处理,比如 NMS,输出变少了。人们直觉上就觉得目标检测难于图像分类,其实在本质上二者是一样的,无非都是基于有监督的,有监督的关键在于对于输出的 Tensor 的标签是什么。

我们暂且不关心什么复杂的神经网络,再复杂的神经网络也无非是输入和输出,输入没得说就是一张图像,而输出才是关键所在,如何去定义输出是一个非常重要的问题。

当我们谈到 YOLOv1 的时候,我们觉得 YOLO 最核心的地方在哪里呢?有人说是 把图像划分为网格,有的人说是把复杂的目标检测任务建模为 直接回归问题,有人说是一阶段端到端的神经网络结构,有人说是相对于 grid cell 来回归位置。如上文所述,YOLO 的目标检测也是有监督学习,那么最关键的还是在于怎么定义输出是什么?无疑,在 YOLOv1 中,对于一张图像,输出就是一个 7*7*30 的 Tensor。这其中最巧妙的设计在于那个 confidence 的标签,这也是最关键的地方,我们正是通过 confidence 来确定哪些是正样本,哪些是负样本,从而我们才能确定 7*7*30 的 输出 Tensor 对应的标签到底是什么。从而我们才可以构造出损失函数,从而才可以端到端的训练起模型。

而在训练完模型后,在推理阶段,这个 confidence 也是最核心的地方。因为在训练中,对于负样本, 我们赋予它的标签是 0,对于正样本,它的标签是 预测bbox 和 GT 的 IoU。所以类似于图像分类的输出解读,对于目标检测的输出 Tensor 的 confidence 标签的那个维度,来一个负样本,那么我们就让它的标签是 0,来一个正样本,它的标签就是 预测bbox 和 GT 的 IoU。通过这个 confidence, 我们乘以类别概率,就得到了用于去重的 NMS 的置信度。当然,YOLOv1 中的 confidence, 对应的就是 YOLOv2 中的 objectness。

2. 基于 anchor box 的目标检测?

YOLOv1 将目标检测建模为 直接回归问题,导致了目标检测是一个比较难的任务。因为预测框相当于无中生有,直接让模型来预测出来。faster RCNN 提出来了 Anchor box,无疑这个名字起的不好,让人难以理解,叫做 先验框 更合适。Anchor 本义 锚 的意思,就是船的锚用于把船定到某个位置,可能作者取这个名字的意思就是对于 特征图中的每个点,对应到原图上是一块区域,每块区域中心抛下去一个 anchor,然后就定到了这里,这里会产生 n 个 anchor box 的意思吧。我更喜欢叫它 先验框。因为这 n 个 anchor box 的形状和大小是从数据中来的,我们可以通过手工设计的方式使得其贴合数据集,也可以通过 k-means 无监督聚类的方式得到。因此,anchor box 更多的是反应数据集的先验信息。

当我们设计了 n 个 anchor box,那么在 图像每块区域上,我们就有了这些 固定的先验框。在训练中,这些 先验框是不变的,模型把目标检测建模为一个 间接回归问题,也就是 回归一组 GT 相对于 先验框的变换值。在推理时,这些先验框也是不变的,模型已经学习到怎么回归 先验框到 GT 框的变换值,因此我们就可以完成目标检测任务。

当然,这种基于先验框的方法,依旧有正负样本分配问题。YOLO 的核心就在于那个 objectness,它的标签依旧是 正样本的预测框 和 GT 的 IoU。那么什么时候 objectness 为 0,什么时候 objectness 为 IoU 这就是 怎么分配正负样本的问题。一般来说,我们多了一个维度,即 anchor box 的维度,假设有 5 个 anchor box,本来 YOLOv1, 我们的输出是 7*7*30 , 现在对应起来就是 7*7*5*(5+20),从回归 3D tensor 变成了 回归 4D tensor。

那么怎么分配基于 anchor box 的正负样本呢?其实就是要确定 7*7*5 个 anchor box,哪些是正样本,哪些是负样本。分配正负样本只是针对 训练的。我们一般会计算 先验框和GT 的 IoU 来确定,GT 是有一个目标中心的,那么这个中心肯定会落在 某个特征图对应的那块区域上,而这个区域有 5个先验框,因此,这 5个先验框和这个 GT 算 IoU,从而确定 5个中哪个是 正样本,我们就会把该先验框对应的那个 objectness的值设为 该先验框对应的预测框 和 GT 的 IoU。显然,这种方式,对于某张图的某个 GT,它的正样本每次训练迭代都是 那个对应的先验框,这是不变的。

既然 anchor box 的 形状和大小 在训练时候是固定下来的,如果 anchor box 的 形状和大小 是变化的那怎么样,会让模型更稳健吗?如果 anchor box 也可以被预测那么怎么样, 比如根据 anchor 每次训练时候都预测一个新的 anchor, 这有点类似于 faster rcnn, 但是好像比它要简洁? 上述方式的正负样本分配是固定的,那不通过 anchor box 与 GT 计算 IoU,通过其他的方式动态计算会怎么样呢? 这些都需要思考。待续......