今天看了一天 Yolo 代码,总是感觉不得要领,回家路上,重新整理思路,突然感觉若有所得。于是有了这篇文章。
我们不妨把目标识别当做是一道数学题
已知条件:数据集。每个标签包括:图片,图片中 box 的个数,
每个 box 的对角坐标[xmin, ymin, xmax, ymax] 图片所属分类。
求解:一个神经网络模型可以识别新的图片
首先,Yolo 的核心思想是 one-stage 的端到端学习。输入一张图片,输出预测分类, box 个数,box 坐标,所属分类。
定义输出
输入图片经过一个网络之后,输出为 [batch_size, h, w, 4+1 + num_class],把输出划分为 h* w 个 网格,每个网格预测一个物体,共 h * w 个物体, 4 为 box 的坐标,1 为包括物体的可能性(完全可能没有物体),num_class 为物体的分类,为 one_hot 矩阵(这在深度学习分类问题是标准表示方法)
解决一个输出点存在多个物体的问题
那么,会有一个问题,由于物体的尺寸是不定的,也就是,一张图片的不同物体的宽高比是不一样的。因此,输出调整为 [batch_size, h, w, B * (4+1 + num_class)],此时,每个网格预测 B 个物体,B 的引入是为了解决在当前网格存在多个物体,每个物体的尺寸是不一样的问题。因此,B 应该为不同的宽高比(一组高比为一个 anchor)。那该如何选择这 B 个宽高比呢?通过聚类,机器学习中的 k-mean 算法,可以将所有图片的所有尺寸划分了 B 组,每组图片之间的尺寸最接近。这样,通过 k-mean 算法,可以非常方便地找到数据中最可能的宽高比。
标签到输出的转换
现在,预测看起来没有什么问题了。但是,如何生成标签,由于数据集中的标签的维度为 [batch_size, num_boxes, 4],即 num_boxes 为每张图片的物体数量,4 为每个物体的坐标。由于维度不一样,损失函数无法计算。因此,需要对标签做处理,即转换为与预测相同的维度,即 [batch_size, h, w, B * (4+1 + num_class)]。
解决办法就是将 [num_boxes, 4] 个真实的标签找到其在 [h, w, B * (4+1 + num_class)] 的映射,首先,肯定能找到标签的 box 映射到输出的某个点。算法如下
假设 box 坐标 [xmin, ymin, xmax, ymax] 转换为 [x, y, w, h],x, y 为中心坐标,w, h 为 box 的宽度和高度。 假设输入图片尺寸为 416x416,输出特征图片为 13x13,输入到输出下采样倍数为 32,由于 box 是相对原图的,因此,需要映射到输出特征图中,方法就是除以采样倍数,得到 [x/32, y/32 , w/32, h/32],此时,任意一张图片的 box 都必然落在 13x13 的某一个 grid cell 里面。至此已经定位到所属 grid_cell,那么如何定位所属的 anchor 呢?最直观的想法就是与 grid cell 的所有 anchor 计算 IOU, 设置该 box 与 IOU 最大的那个 anchor 匹配,并设置该 grid_cell 的该 anchor 的坐标为 box 的坐标。所有图片的所有 box 经过上述步骤都可以映射到输出特征图的某一个 grid cell 中的某一 anchor 中。至此,标签就生成好了。
这假设的问题是没有两个 box 落在同一 grid cell 的同一 anchor 里面,如果出现这种情况,那么就会出现识别错误的情况。这个时候,调整 anchor 数量,或者增加 grid 尺寸都是一种选择。而事实上,YoloV3 就是通过多尺寸提高了性能。
损失函数
损失函数的设计要遵循两个原则
1)在于如果预测错误,值就非常大, 预测正确值,值就非常小的原则来设计。
2)如果预测和标签完全一致,那么损失函数应该为 0
有了预测输出,标签,下一步就是设计损失函数。物体预测的损失函数包含三部分,一,是否包含物体损失;二,物体所属分类偏差损失;三,物体 box 偏差损失。
设计之前必须要明确,如果要计算所属的分类及 box 损失,前提是该 grid cell 的 anchor 必须包含物体才行,如果不包含物体,计算分类和 box 损失是没意义的,不符合原则二。
对于每一个预测输出的每一个元素,标签有可能包含物体,有可能不包含物体,因此,必须分情况考虑。
因此,损失函数首先要计算的 loss 为是否包含物体的损失。这个时候要对标签没有物体和标签有物体都有惩罚,来让系统正确学习到是否包含物体。
当预测是否包含物体变得准确之后,物体分类和 box 的损失只要考虑有物体的情况即可。由于标签已经可知道是否有物体,因此,损失函数的分类损失和 box 损失直接以标签中是否包含物体即可,这样就不需要分阶段训练。
训练
有了预测输出,标签,损失函数设计好了,最后就是用 BP 迭代,等待模型收敛即可。
其他
由于为了训练的稳定性,一般预测的时候不直接预测坐标,而是预测相对偏移或者 log 变换。于是在生成标签的时候,需要对 box 进行解码;而预测部分对 box 要进行编码。
YoloV1 到 YoloV2 的变化
- 高分辨率的分类器,先在 224x224 训练,之后 fine tuning 在 448x448
- Yolov2 引入了 anchor,使得每一个 grid cell 从预测一个物体到 B 个物体
- 损失函数设计考虑模型稳定性,收敛性
- 更强大的基础网络 DarkNet19,比如引入 passthrough, BN
- 输入由 448x448 变为了 416x416,使得特征图更好预测中心的大物体。
- 引入 WordTree 可以预测 9000 多个分类。但这其实与 Yolo 本身关系不大
YoloV2 到 YoloV3 的变化
- 新的基础网络 Darknet53
- 多尺度预测,对物体预测更加鲁棒,Yolov2 用 multi-scale,YoloV3 改为多尺度特征图
- 每个 grid cell 中 Anchor 数量由 5 个变为三个
- 分类用 logstic 替代 softmax
其实总结起来,就是要把握住以下几个原则:
- 如果计算输出的;
- 如何计算标签的;
- 如何设计损失函数的。
- 准确度不高,模型不稳定怎么解决的。
相信如果抓住以上点,基本就不会迷失在代码中了。
以上就是 Yolo 的一些思考,相信也使用与目标检测的其他方法。发现终于把书读薄了。
文于 2018-07-01