看了不少讲YOLO
的文章,总感觉没有把关键细节说清楚,看了好像懂了,但细想到操作层面,又没完全懂。尝试简单总结捋一下,主要是v1,后面的不定期补充吧。
YOLOv1
整体思路是通过一些技巧,把目标检测转化为一个回归问题,一步到位,提高检测速度。原论文网络结构叫做darknet
,各种卷积块+两个全连接层,当时还没发明批量归一化batch normalization
,现在可以加上,中间的激活层选用Leaky ReLU
,当然也可以使用其它深度网络,比如ResNet
等。
首先看训练。将448*448
(目标检测的分辨率要高于图像分类)的图片平均分成7*7=49
个cell
,或叫grid
。每个cell
预测2个框bounding box
,简称bbox
,类似锚框anchor
。这个bbox
尺寸最小为0,最大为整个图片。每个bbox
的定位localization
只需要4个值,有不同的格式,如两点式(左上角和右下角坐标:x1, y1, x2, y2
)、左上角式(左上角+宽高:x, y, w, h
)、中心点式(中心点+宽高:x, y, w, h
),原论文是中心点式。每个bbox
还有1个置信度Conf
,这就一共(4+1)*2=10
个值。除了预测bbox
,还要从比如20个物体种类里预测1个类别,每个类别给出一个概率,一共20个值,最后输出N*7*7*30
,N
为图片数。这些数值可以随机初始化,两个bbox
可以一大一小初始化,提高效率。
因为是回归任务,所以x, y, w, h
都要进行归一化,x, y
为相对于cell
左上角的坐标,其值为0~1之间,w, h
要除以图片的宽高,也就是448*448
,其值也为0~1之间。有的文章将其除以cell
的宽高,这导致w, h
的值为0~7之间,也能计算,但感觉不合适。
刚才提到了置信度Conf
。虽然1个cell
预测了2个bbox
,但YOLOv1
里,1个cell
只预测1个object
,两个bbox
其实是竞争关系,最终只有一个bbox
得到了更新。训练计算损失时,看谁的Conf
大,谁才被考虑进损失函数进行更新。而Conf=Pr(Object)*IoU
,IoU
为预测的bbox
与ground_truth_box
的交并比。再看Pr(Object)
,如果ground_truth_box
的中心坐标落在某cell
里,那么Pr(Object)=1
,否则为0。所以当Pr(Object)=1
时,就是比谁的IoU
大,当Pr(Object)=0
时,label_Conf
也为0。可以认为,最终预测输出的也是Pr(Object)
与IoU
的乘积,只不过预测的Pr(Object)
是一个连续值,而不是label
的非1即0。
插一些闲话,这里有几个单词术语让人头大,试着自己理解下,不一定对:
-
grount truth
为真实值,简称gt
,是真实的目标物体的框子,所以,感觉也可以说成是object
、target
。 -
label
是标号,范围比gt
大。gt
只是正值,而在没有目标的区域,也是一个label
。 -
prediction
是预测,detection
是检测,infer
是训练完了最后用于测试集的推理预测。其实意思差不多少。 - 说到测试集
test
,一般这样理解:来了一批带label
的数据,可以将其划分为训练集train
和验证集validation
,如果训练后的模型参数在验证集的表现还不错,那就把模型用在没有label
的测试集test
上预测推理,进行实践应用,所以test
只用一次,且你不知道结果是否正确。不过,现在不少文章、代码所说的测试集其实是验证集,得自行分辨。
目标类别label_classes
采用onehot
编码,比如某个label_box
是第5类,且中心在某个cell
,那么label_classes
就是[0,0,0,0,1, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0]
,否则全为0。训练或推理时,输出就没这么干净了,每一个都是0~1之间的概率值,这个概率值其实是条件概率Pr(Class-i|Object)
,所以最终还要将这个概率值Pr(Class-i|Object)
乘以Conf
,然后取这20个中的最大值为最终预测种类,这样更保险,意思是你必须先有目标,再来谈这是个什么类别。
每个cell
只有1个label_box
,所以label
形状其实应该是N*7*7*25
,但为了与训练的N*7*7*30
一致,会在后面补0。总结一下:
- 如果一个
cell
包含了gt
的中心点,那么label
最后一维的张量类似这样:[0,0,0,0,1, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, x, y, w, h, 1, 0,0,0,0,0]
,最后5个0为补足;该cell
的训练值为[c0,... c19, x1, y1, w1, h1, Conf1, x2, y2, w2, h2, Conf2, ]
,如果bbox1
的定位[x1, y1, w1, h1]
与目标值[x, y, w, h]
的IoU
更大,那么[c0,... c19, x1, y1, w1, h1, Conf1]
会向label
靠拢。至于bbox2
的[x2, y2, w2, h2, Conf2]
,可能是自生自灭吧?这30个量的顺序不关键,有一些不同的排列法。 - 如果一个
cell
没有包含了label_box
的中心点,那么最后一维的张量全是0,也就是torch.zeros(25)
,也补足至torch.zeros(30)
。该cell
的两个bbox
的训练值的Conf1
和Conf2
都会向0靠拢,其它值自生自灭?
可以看到,输出N*7*7*30
中会有一些值自生自灭并不进行更新,那最终呈现结果时,会对输出进行筛选整理,比如筛掉低Conf
、进行NMS
等。
再看看原论文的损失函数。
其中
S=7
, B=2
,1obj(i)
表示如果label_box
中心戳中了第i个cell
,则值为1,否则为0。1obj(i,j)
表示如果label_box
中心戳中了第i个cell,且第j个bbox
与label_box
的IoU
较大,则值为1,否则为0。1noobj(i,j)
与其相反。第1行,
1obj(i,j)
的bbox
坐标与label_box
的方差,一般权重λcoord=5
。第2行,类似第1行,但宽高必须开方参与计算,避免大尺寸偏差重要性大于小尺寸的偏差。
第3行,类似1/2行,
1obj(i,j)
的bbox
的Conf
的方差,label_Conf
一般为1。第4行,没有被目标中心戳中的
cell
的2个bbox
的Conf
的方差,一般权重λnoobj=0.5
,label_Conf
为0。第5行,
1obj(i)
的cell
的类别概率的方差损失。
推理时,预测了7*7*2=98
个bbox
,首先筛掉低Conf
的,比如小于0.4的,然后使用非极大值抑制NMS
。NMS
一般是按照预测类别分别独立计算的(也有所有类一视同仁的做法),比如都是预测猫,那么选择一个Conf
最大的bbox
,如果其它也预测猫的bbox
与这个最大Conf
的bbox
的IoU
超过了一个阈值,例如0.5,那么就筛掉。这一部分不只是针对Yolo
,而是目标检测通用做法。
怎么评价预测结果呢?这里引入一个全类平均正确率mAP
,也是先按类计算,首先看预测框的IoU
是否超过了阈值(比如0.5),超过了为TP
,否则FP
。然后将这些框按Conf
倒序排列,每个gt最多一个框与之对应,开始统计Recall=TP/(TP+FN)
和Prec=TP/(TP+FP)
,然后求出Recall-Prec
关系图的面积,即为AP
,最后对所有种类求平均,则为mAP
。mAP
越大越好,0.9就很不错了。更详细的做法是针对多个阈值进行计算再取平均,记为mAP@0.5:0.05:0.95
。
- 问题1:如果两个目标中心戳中同一个
cell
,会发生什么事?
超纲了,YOLOv1
只能考虑一个cell
一个object
。 - 问题2:如果是同一类,但是是不同的目标呢?
NMS
怎么处理?
这两个目标如果在同一个cell
,见问题1;否则,这两个目标的bbox
的IoU
会很小或者为0,所以NMS
会都保留。 - 问题3:如果置信度>1,或预测种类的概率之和>1,会怎样处理?
对于置信度,数值看起来不合理,但应该也不影响最终结果的呈现,训练中可以试试用torch.clamp
进行限制。预测种类的话,可以直接取最大值。另外,同一个目标属于两个种类,也不算离谱吧,比如正在播放猫和老鼠的电视机?后续再研究吧。 - 问题4:如果两个
bbox
初始化后,与label_box
的IoU
都是0呢?
IoU
相同,代码默认取第一个值,所以第一个bbox
会进行学习。
YOLOv2(Yolo 9000)
主要改进:
- 加入了
batch normalization
。 - 在
448*448
的ImageNet
上进行预训练。 -
grid
变成13*13
,且每个cell
预测9个bbox
,并给出先验的大小。再根据COCO
和VOC
的数据进行聚类,最后选择K=5,效果还可以。聚类时用1-IoU
做为距离。
- 摒弃全连接层,全用卷积。
- 优化
bbox
的坐标和宽高公式:bx = σ(tx) + cx
,by = σ(ty) + cy
,bw = pw * exp(tw)
,bh = ph * exp(th)
,Pr * IoU = σ(to)
。t
为需要学习的参数,bx, by, bw, bh
为bbox
的定位,cx, cy
为cell
的左上角坐标,pw, ph
为先验框的尺寸。为啥用指数,可能为了限制为正值? -
passthrough
层将上一层一拆四(尺寸降半),与上一层的卷积结果(刚好也尺寸降半)直接叠加进行输出,从而保留上一层的小尺寸细节。有点残差的意思。 - 分层分类,采用
WordTree
,在分类数据集和检测数据集上联合训练,提高识别种类到9000+。
YOLOv3
主要改进:
- 基础网络升级至
darknet53
,之前是19。 - 分类损失采用二分类交叉熵损失
binary cross-entropy loss
,因为目标可能属于多个分类。 - 使用3个尺度预测,引入
FPN (Feature Pyramid Network)
,特征图大小分别是8*8
,16*16
,32*32
。
分隔线,pjreddie退出YOLO,后续版本重在工程应用。
YOLOv4
主要各种调参、缝合、数据增强。
YOLOv5
小而快,打比赛推荐。
YOLOX
旷世开源,效果不错。
总结
纸上得来终觉浅,试试吧!