文章目录
本次主要介绍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=0∑s2j=0∑Bℓijobj[(xi−xi^)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=0∑s2j=0∑Bℓijobj[(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=0∑s2ℓiobjj=0∑B[(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=0∑s2j=0∑Bℓijobj[(Ci−CI^)2]+λnoobji=0∑s2j=0∑Bℓijnoobj[(Ci−CI^)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