忙里偷闲,写几篇长文,从 YOLO 的 v1 到 v5。没想到时隔多年会回来重新看 YOLO 系列的东西,相比两阶段检测,YOLO 真的太快了,加上一些训练的 trick,mAP 也不会很低。网上看了好多教程不明所以,索性还是直接去读原论文了,读了原论文有些东西还是不理解,索性又去读了源程序。不过为了便于理解,有的地方不会按照论文顺序进行整理。少问问题,读论文产生的疑问在代码里都有解答,不看代码永远不能被称为学会了。
如果对本文有疑问或者想找男朋友,可以联系我,点击此处有我联系方式。
YOLO v1
YOLO v1 将目标检测定义为回归问题,直接读入全部图像,回归出边界框和分类概率。与同时期的 Faster RCNN 对比,算法快了不少,也没有 RPN 以及后处理,也避免里滑动窗口这样的暴力检测。所以能用到一些实时系统中,也是我花精力看这一些列论文的原因。
但是对于 v1 的 YOLO 存在一些缺陷,作者在论文中也进行了阐述:准确率低、定位不准尤其是小目标的定位。
算法流程
首先将图片分为 $S\times S$ 个网格,论文中 $S=7$,如果一个物体的中心落入这个格子中,那么这个格子负责预测这个目标。设每个格子负责预测 $B$ 个物体的盒子参数和置信度得分,盒子参数指明物体的位置,置信度表示盒子含有目标且预测准确的可信程度。即对于图片的每个格子,会输出 $B$ 个 $(x,y,w,h,c)$,论文中 $B=2$。
既然有了输出,那么就需要 label 进行损失计算。$(x,y,w,h,c)$ 是人工标注的数据,$c$ 初始化为 1。论文定义网络输出的置信度标签是一个分段函数,如果格子没有目标,置信度是 0;如果有目标,置信度是预测框和真实框的 IOU 值,公式描述为 $\text{Pr(Object)} * \text{IOU}_{\text{pred}}^\text{truth}$。
目标检测和分类是分不开的,为了达到分类的目的,每个格子也会输出 $C$ 个类别的概率,公式表述为 $\text{Pr(Class}_i|\text{Object})$,即格子里面得是个目标,才能计算分类的概率和损失。而每个格子输出一组预测,即使输出了 $B$ 组数据,这就限制了网络的表达。
在测试阶段,类别置信度的分数就是分类概率和置信度相乘,即盒子中「有这个类别的概率」和「网络预测这个类别的概率」的乘积:
\begin{equation}
\text{Pr(Class}_i|\text{Object})*\text{Pr(Object)}*\text{IOU}_{\text{pred}}^\text{truth}=\text{Pr(Class}_i) * \text{IOU}_{\text{pred}}^\text{truth}
\end{equation}
网络结构
这种东西还是代码清楚,只放了最关键的检测输出:
1 | nn.Linear(4096, S * S * (5 * B + C)) |
损失函数
\begin{aligned}
{ } & \lambda_{coord} \sum_{i=0}^{S^2} \sum_{j=0}^{B} \mathbb{I}_{ij}^{\text{obj}} [(x_i-\hat{x}_i)^2 + (y_i-\hat{y}_i)^2] \\
{ } &+ \lambda_{coord} \sum_{i=0}^{S^2} \sum_{j=0}^{B} \mathbb{I}_{ij}^{\text{obj}} [(\sqrt{w_i}-\sqrt{\hat{w}_i})^2+(\sqrt{h_i}-\sqrt{\hat{h}_i})^2] \\
{ } &+ \sum_{i=0}^{S^2} \sum_{j=0}^{B} \mathbb{I}_{ij}^{\text{obj}} (C_i - \hat{C}_i)^2 \\
{ } &+ \lambda_{noobj} \sum_{i=0}^{S^2} \sum_{j=0}^{B} \mathbb{I}_{ij}^{\text{noobj}} (C_i - \hat{C}_i)^2 \\
{ } &+ \sum_{i=0}^{S^2} \mathbb{I}_{ij}^{\text{obj}} \sum_{c\in classes} (p_i(c)-\hat{p}_i(c))^2
\end{aligned}
- $\lambda_{coord}$ 是前景的权重,$\mathbb{I}_{ij}^{\text{obj}}$ 是指示函数,取值只有 0 和 1
- 前两行表示 bound box 的损失
- 第三行是前景置信度的损失
- 第四行是背景置信度的损失
- 第五行是分类的损失
缺陷
- 每个网格只能检测一个类别和两个目标,类间竞争严重,网络表达受限,对于密集群体的检测性能会下降;
- 定位不准确,因为网络直接预测 bounding box 的坐标,一开始的偏移可能会很大,导致定位不准确,读完代码能深刻理解这里的缺陷;
- 对于检测问题而言,大多情况背景居多,前景居少,也就是样本不均衡。YOLO v1 的损失中,并没有计算背景的 bound box 损失,只计算了前景的,YOLO v1 回避了样本不均衡的问题,这会影响网络的稳定性与背景的识别。
程序解析
「如果对算法有疑问,就去读代码吧」这一经验帮助我理解了很多算法的困惑之处,不仅仅是 YOLO。如果要看懂一个深度学习的算法,核心有三要素,首先是网络模型,理解输入、输出和结构;其次是数据与损失,理解加载什么格式的数据,理解网络预测数据和加载的数据如何计算损失,所以这俩常常放在一起;最后是细枝末节,即数据增强、学习率策略、整体训练流程等。所以接下来整理网络模型和损失。训练策略那些不是 YOLO 的重点。
网络模型
我们知道网络的输出是 x.view(-1, S, S, 5 * B + C)
这种类型的格式,这是预测数据,即 $S \times S $ 组 $5 \times B + C$ 这样的数据,$C$ 是类别数量。那么可想而知,在训练阶段,同样需要提供同等尺寸大小的标签数据。
数据与损失
YOLO 处理目标时,使用的是目标中心点的坐标相对图像大小的占比。如果一张图像的大小是 224 X 224,目标中心点位于 112 X 112,那么中心点的坐标是 $(0.5,0.5)$。这有两点好处:
- 如果一个图像的尺寸是 1920 X 1080,目标中心点的坐标是 1000 X 1000,直接输出 1000 对于网络来说难以把控,会造成梯度爆炸的现象。而占比只需要输出 [0,1] 之间的小数,不会导致梯度爆炸。
- 方便图像的标准化处理。网络常常使用多个 batch 进行训练,每个 batch 的数据要求大小统一,对于不同尺寸的图像应选择
resize
。如果直接用坐标,resize
后会导致坐标错位,而如果用占比,位于之前图像 (0.5, 0.5) 处的点在resize
后的坐标仍然是 (0.5, 0.5)。
由于 YOLO 最初设计的方案是:物体中心落到哪个格子,就由这个格子预测这个目标,这一观点需要仔细阅读代码才能理解。
1 | # 和网络输出同等大小的标签 |
在损失计算阶段,代码真的太长了不便展示,这里只记录核心要素:
- 对于有目标计算损失,无目标忽略这一点,是通过对真实标签进行掩码处理实现的,只取出真实标签中置信度为 1 的标签记录维度,并在 predict 中取出同维度的数据就算损失,其余数据忽略。假设这一步保留了 $X$ 个盒子。
- 对于 $X$ 个盒子中的 $B$ 组数据继续处理,对于每组数据而言,选择和真实标签 IOU 最大的盒子计算损失,其余盒子忽略,也就是没有正负样本的概念。这里需要注意,如果网络初期计算到 IOU 为 0,那么默认第一个盒子和真实标签进行损失计算。
结语
我已经正负样本划分、格子、bounding box、anchor box 的概念已经搞混了,论文里不会写这太细节的东西,不然我也不会来读代码。毕竟整理理论知识太简单了,也容易自欺欺人,并不清楚网络的流程。所以 YOLO v2, v3, v4, v5 和 x 的内容等下几篇博客了。