Bootstrap

YOLO目标检测中损失函数loss的理解及部分代码实现

本次主要介绍YOLO中loss的实现。主要参考的代码是github上使用tensorflow实现yolo的一段程序,完整的程序地址放在下面。
github:https://github.com/nilboy/tensorflow-yolo

理论部分

首先是理论部分,YOLO网络的实现这里就不赘述,本文主要介绍YOLO损失函数这一部分。
在这里插入图片描述
我们先来看loss函数,YOLO的损失函数由4部分组成:(这里按照代码的顺序来讲)
1. 对预测的中心坐标做损失
λ c o o r d ∑ i = 0 s 2 ∑ j = 0 B ℓ i j o b j [ ( x i − x i ^ ) 2 + ( y i − ( ^ y i ) 2 ] \lambda_{coord} \sum^{s^2}_{i=0} \sum^B_{j=0} \ell^{obj}_{ij}[(x_i-\hat{x_i})^2 + (y_i-\hat(y_i)^2] λcoordi=0s2j=0Bijobj[(xixi^)2+(yi(^yi)2]
2. 对预测边界框的宽高做损失
λ c o o r d ∑ i = 0 s 2 ∑ j = 0 B ℓ i j o b j [ ( w i − w i ^ ) 2 + h i − h i ^ ) 2 ] \lambda_{coord} \sum^{s^2}_{i=0} \sum^B_{j=0} \ell^{obj}_{ij}[(\sqrt{w_i}-\sqrt{\hat{w_i}})^2 + \sqrt{h_i}-\sqrt{\hat{h_i}})^2] λcoordi=0s2j=0Bijobj[(wi wi^ )2+hi hi^ )2]
3. 对预测的类别做损失
∑ i = 0 s 2 ℓ i o b j ∑ j = 0 B [ ( p i ( c ) − p i ^ ( c ) ) 2 ] \sum^{s^2}_{i=0} \ell^{obj}_{i} \sum^B_{j=0} [(p_i(c) - \hat{p_i}(c))^2] i=0s2iobjj=0B[(pi(c)pi^(c))2]
4. 对预测的置信度做损失
∑ i = 0 s 2 ∑ j = 0 B ℓ i j o b j [ ( C i − C I ^ ) 2 ] + λ n o o b j ∑ i = 0 s 2 ∑ j = 0 B ℓ i j n o o b j [ ( C i − C I ^ ) 2 ] \sum^{s^2}_{i=0} \sum^B_{j=0} \ell^{obj}_{ij}[(C_i-\hat{C_I})^2] + \lambda_{noobj} \sum^{s^2}_{i=0} \sum^B_{j=0} \ell^{noobj}_{ij}[(C_i-\hat{C_I})^2] i=0s2j=0Bijobj[(CiCI^)2]+λnoobji=0s2j=0Bijnoobj[(CiCI^)2]

接下来分步来说:

1.对预测的中心坐标做损失

该等式计算了了相对于预测的边界框位置(x,y)的loss数值。现在不要担⼼λ,暂且假定λ是⼀个给定的常数。该函数计算了每一个网格单元 ( i = 0 , . . . , S 2 ) (i=0,...,S^2) (i=0,...,S2)的每⼀个边界框预测值 ( j = 0 , . . . , B ) (j=0,...,B) (j=0,...,B)的总和。 ℓ i j o b j \ell^{obj}_{ij} ijobj定义如下:

  • 1,如果⽹网格单元i中存在⽬标,则第j个边界框预测值对该预测有效。
  • 0,如果⽹网格单元i中不不存在目标

但是我们如何知道那个预测器器对该⽬标负责呢?引用原论文:
对每⼀一个⽹网格单元YOLO预测到对个边界框。在训练时,我们对每一个目标只希望有一个边界框预测器。我们根据哪个预测有最高的实时IOU和基本事实,来确认其对于预测一个⽬标有效。这里代码使用了tf.reduce_max函数来实现
等式中的其他项应该是容易理解的:(x,y)是预测边界框的位置,(x̂ , ŷ)是从训练数据中得到的实际位置。

2.对预测边界框的宽高做损失

这是与预测的边界框的宽度/高度相关的损失。除了平方根之外,该等式看起来与第一个类似。这是
怎么回事儿呢?再次引用原论文:
我们的误差度量量反应出大箱子的小偏差要小于小箱子。为了逐步解决这个问题,我们预测了边界框的宽度和高度的平⽅根,而不是直接预测宽度和⾼度。

3.对预测的类别做损失

除了了 ℓ i j o b j \ell^{obj}_{ij} ijobj项外,该等式看起来类似于分类的正常求和平方误差。使用该术语是因为当单元格上没有
对象时(前⾯讨论的条件类概率),我们不会惩罚分类误差。

4.对预测的置信度做损失

此处我们计算了与每个边界框预测值的置信度得分相关的损失。C是置信度得分,Ĉ是预测边界框与
基本事实的交叉部分。当在一个单元格中有对象时, ℓ i o b j \ell^{obj}_i iobj等于1,否则取值为0。
此处以及第⼀部分中出现的λ参数⽤于损失函数的不同加权部分。这对于提⾼模型的稳定性是十分键的。最高惩罚是对于坐标预测 ( λ c o o r d = 5 ) (λ_{coord} = 5) (λcoord=5),当没有探测到目标时,有最低的置信度预测惩罚 ( λ n o o b j = 0.5 ) (λ_{noobj} = 0.5) (λnoobj=0.5)

这里论文里写的 ℓ i o b j \ell^{obj}_i iobj denotes if object appears in cell i,我理解的是只要含有边界框就为1,代码calculate objects tensor中的输出也说明了这一点

代码实现

我们介绍了Loss的理论部分,下一步来看代码实现。

定义

我们先来定义一个简单的图像和x,y,w,h
在这里插入图片描述

image_size = 400 #图像大小
cell_size = 5 # 网格大小
label = [200,120,160,160] # x,y,w,h

calculate objects tensor

min_x = (label[0] - label[2] / 2) / (image_size / cell_size) # x在cell中的位置
max_x = (label[0] + label[2] / 2) / (image_size / cell_size)

min_y = (label[1] - label[3] / 2) / (image_size / cell_size)
max_y = (label[1] + label[3] / 2) / (image_size / cell_size)

min_x = tf.floor(min_x)
min_y = tf.floor(min_y)

max_x = tf.ceil(max_x)
max_y = tf.ceil(max_y)

temp = tf.cast(tf.stack([max_y - min_y, max_x - min_x]), dtype=tf.int32)
objects = tf.ones(temp, tf.float32)

temp = tf.cast(tf.stack([min_y, cell_size - max_y, min_x, cell_size - max_x]), tf.int32)
temp = tf.reshape(temp, (2, 2))
objects = tf.pad(objects, temp, "CONSTANT")

with tf.Session() as sess:
	print(sess.run(objects))

这里我们来看一下输出的值:

[[0. 1. 1. 1. 0.]
 [0. 1. 1. 1. 0.]
 [0. 1. 1. 1. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]]

calculate responsible tensor

center_x = label[0] / (image_size / cell_size)
center_x = tf.floor(center_x)

center_y = label[1] / (image_size / cell_size)
center_y = tf.floor(center_y)

response = tf.ones([1, 1], tf.float32)

temp = tf.cast(tf.stack([center_y, cell_size - center_y - 1, center_x, cell_size -center_x - 1]), tf.int32)
temp = tf.reshape(temp, (2, 2))
response = tf.pad(response, temp, "CONSTANT")

with tf.Session() as sess:
	print(sess.run(response))

这里我们来看一下输出的值:

[[0. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0.]]

这里我们可以与上面的做一下比较,可以看出一个是只要含有物体就认为是1,另一个是中心坐标为1.

获取最大的置信度

这里我们需要看一下原文中置信度是怎么定义的:

首先看cell预测的bounding box中condifence这个维度。confidence表示:cell预测的bounding box包含一个物体的置信度有多高并且该bounding box预测准确度有多大,用公式表示为: P r ( O b j e c t ) ∗ I O U p r e d t r u t h Pr(Object)*IOU^{truth}_{pred} Pr(Object)IOUpredtruth

我们可以看出来置信度是预测*IOU的结果,所以代码的实现也就如下:

iou_predict_truth = self.iou(predict_boxes, label[0:4])
C = iou_predict_truth * tf.reshape(response, [cell_size, cell_size, 1])

#calculate I tensor [CELL_SIZE, CELL_SIZE, BOXES_PER_CELL]
I = iou_predict_truth * tf.reshape(response, (cell_size, cell_size, 1))
    
max_I = tf.reduce_max(I, 2, keepdims=True)

I = tf.cast((I >= max_I), tf.float32) * tf.reshape(response, (cell_size, cell_size, 1))

以上就是Loss计算的几个关键步骤,剩下的无非就是计算和相加,IOU的部分这里就不写了,参考文献是我看到最好的两篇介绍YOLO的博文。

完整的代码可以去原作者的github上去找,在net/yolo_net.py/body1中。

参考文献

1.http://ai.yanxishe.com/page/TextTranslation/1168
2.https://zhuanlan.zhihu.com/p/37850811

;