![ac42f1b2095ae8495f7045e0382c9675.png](https://i-blog.csdnimg.cn/blog_migrate/627f8216e26d14cf1cd54a9d33ca8895.jpeg)
深入理解YOLO v3实现细节系列文章,是本人根据自己对YOLO v3原理的理解,结合开源项目tensorflow-yolov3,写的学习笔记。如有不正确的地方,请大佬们指出,谢谢!
目录
第1篇 数据预处理
第2篇 backbone&network
第3篇 构建v3的 Loss_layer
1. 解读YOLOv3 的损失函数
在分析yolo v3的损失函数之前,先来回顾一下yolo v1的损失函数。
一般来说系统设计需要平衡边界框坐标损失、置信度损失和分类损失。论文中设计的损失函数如下:
![4077716f922d682a22384424515f335b.png](https://i-blog.csdnimg.cn/blog_migrate/c2cc96a90184b782e8842f2bb3e25b7c.jpeg)
对于 YOLOv3 的损失函数, Redmon J 在论文中并没有进行讲解。从darknet 源代码中的forward_yolo_layer函数中找到 l.cost ,通过解读,总结得到 YOLOv3 的损失函数如下:
![865c9f0ff2d9d4d835b42f1b4cfa39df.png](https://i-blog.csdnimg.cn/blog_migrate/01c554e8b75998d2bbf76b9ff5010ad4.png)
与v1 Loss类似,主要分为三大部分: 边界框坐标损失, 分类损失和置信度损失。
![1b95d2ac9c6df993e75c8ee596294294.png](https://i-blog.csdnimg.cn/blog_migrate/a41a8f963ee9d2022aad7ec4ea6b4056.png)
与yolo v1的边界框坐标损失类似,v3中使用误差平方损失函数分别计算(x, y, w, h)的Loss,然后加在一起。v1中作者对宽高(w, h)做了开根号处理,为了弱化边界框尺寸对损失值的影响。在v3中作者没有采取开根号的处理方式,而是增加1个与物体框大小有关的权重,权重=2 - 相对面积,取值范围(1~2)。
![38eb583d9fef560b4139c186fc58c893.png](https://i-blog.csdnimg.cn/blog_migrate/9c0d3d95ab5ea6754d36fc2759be3b40.png)
![d550ba5e01bfce8eca78c63044341fa3.png](https://i-blog.csdnimg.cn/blog_migrate/a3f11b9d6928ed63b4bfb4f1d64df6b5.png)
使用误差平方损失函数计算置信度conf 的Loss。
![42d7aca1f5fdc3e0ada41c42532b93ba.png](https://i-blog.csdnimg.cn/blog_migrate/c6ce8a0368b77ecb494b20fee62616ae.jpeg)
yolo v3三种不同尺度的输出,一共产生了(13*13*3+26*26*3+52*52*3)=10647个预测框。
![fe1c2b2db8a14d678c803067ff455f34.png](https://i-blog.csdnimg.cn/blog_migrate/9a304d4f5697f263a50d45eb656a4067.png)
这个10647就是这么来的。
最终Loss采用和的形式而不是平均Loss, 主要原因为预测的特殊机制, 造成正负样本比巨大, 尤其是置信度损失部分, 以一片包含一个目标为例, 置信度部分的正负样本比可以高达1:10646, 如果采用平均损失, 会使损失趋近于0, 网络预测变为全零, 失去预测能力。
参考上述原始的损失函数,加以优化。开始动手构建v3的Loss_layer!
2. 边界框损失
对IoU和GIoU不了解的读者可以看我写的另外一篇文章: 目标检测中的IOU&升级版GIOU
目前目标检测中主流的边界框优化采用的都是BBox的回归损失(MSE loss, L1-smooth loss等),这些方式计算损失值的方式都是检测框的“代理属性”—— 距离,而忽略了检测框本身最显著的性质——IoU。由于IOU有2个致命缺点,导致它不太适合作为损失函数。GIoU延续了IoU的优良特性,并消除了IoU的致命缺点。以下使用GIoU Loss作为边界框损失。
2.1 GIoU的计算
def bbox_giou(self, boxes1, boxes2):
# 将boxes[x,y,w,h]化为[x_min, y_min, x_max, y_max]的形式
boxes1 = tf.concat([boxes1[..., :2] - boxes1[..., 2:] * 0.5,
boxes1[..., :2] + boxes1[..., 2:] * 0.5], axis=-1)
boxes2 = tf.concat([boxes2[..., :2] - boxes2[..., 2:] * 0.5,
boxes2[..., :2] + boxes2[..., 2:] * 0.5], axis=-1)
boxes1 = tf.concat([tf.minimum(boxes1[..., :2], boxes1[..., 2:]),
tf.maximum(boxes1[..., :2], boxes1[..., 2:])], axis=-1)
boxes2 = tf.concat([tf.minimum(boxes2[..., :2], boxes2[..., 2:]),
tf.maximum(boxes2[..., :2], boxes2[..., 2:])], axis=-1)
# 计算boxes1、boxes2的面积
boxes1_area = (boxes1[..., 2] - boxes1[..., 0]) * (boxes1[..., 3] - boxes1[..., 1])
boxes2_area = (boxes2[..., 2] - boxes2[..., 0]) * (boxes2[..., 3] - boxes2[..., 1])
# 计算boxes1和boxes2交集的左上角坐标和右下角坐标
left_up = tf.maximum(boxes1[..., :2], boxes2[..., :2])
right_down = tf.minimum(boxes1[..., 2:], boxes2[..., 2:])
# 计算交集区域的宽高,如果right_down - left_up < 0,没有交集,宽高设置为0
inter_section = tf.maximum(right_down - left_up, 0.0)
# 交集面积等于交集区域的宽 * 高
inter_area = inter_section[..., 0] * inter_section[..., 1]
# 计算并集面积
union_area = boxes1_area + boxes2_area - inter_area
# 计算IOU
iou = inter_area / union_area
# 计算最小闭合凸面 C 左上角和右下角的坐标
enclose_left_up = tf.minimum(boxes1[..., :2], boxes2[..., :2])
enclose_right_down = tf.maximum(boxes1[..., 2:], boxes2[..., 2:])
# 计算最小闭合凸面 C的宽高
enclose = tf.maximum(enclose_right_down - enclose_left_up, 0.0)
# 计算最小闭合凸面 C的面积 = 宽 * 高
enclose_area = enclose[..., 0] * enclose[..., 1]
# 计算GIoU
giou = iou - 1.0 * (enclose_area - union_area) / enclose_area
return giou
2.2 GIoU loss 的计算
还记得label是怎么来吗?在 第1篇 数据预处理 的4.3章节中有详细说明,这里不再多说。当你清楚了label是什么,表示什么,respond_bbox就容易理解了。
![d0cdf71eac4d20f121451a126e69e644.png](https://i-blog.csdnimg.cn/blog_migrate/c42c60cd74bbb4cbe04171eef9b54e05.png)
respond_bbox = label[:, :, :, :, 4:5] # 置信度,判断网格内有无物体
...
giou = tf.expand_dims(self.bbox_giou(pred_xywh, label_xywh), axis=-1)
# 2 - 相对面积
bbox_loss_scale = 2.0 - 1.0 * label_xywh[:, :, :, :, 2:3] * label_xywh[:, :, :, :, 3:4] / (input_size ** 2)
giou_loss = respond_bbox * bbox_loss_scale * (1 - giou)
- respond_bbox 的意思是如果网格单元中包含物体,那么就会计算边界框损失;
- box_loss_scale = 2 - 相对面积,值的范围是(1~2),边界框的尺寸越小,bbox_loss_scale 的值就越大。box_loss_scale可以弱化边界框尺寸对损失值的影响;
- 两个边界框之间的 GIoU 值越大,giou 的损失值就会越小, 因此网络会朝着预测框与真实框重叠度较高的方向去优化。
3. 置信度损失
3.1 引入Focal Loss
论文发现,密集检测器训练过程中,所遇到的极端前景背景类别不均衡(extreme foreground-background class imbalance)是核心原因。为了解决 one-stage 目标检测器在训练过程中出现的极端前景背景类不均衡的问题,引入Focal Loss。Focal Loss, 通过修改标准的交叉熵损失函数,降低对能够很好分类样本的权重(down-weights the loss assigned to well-classified examples),解决类别不均衡问题.
Focal Loss的计算公式:
![f9ef88eef8cf6a20cab4ab33bfd994d4.png](https://i-blog.csdnimg.cn/blog_migrate/d3a3dfcee5c0c8f45cd200dc7558a306.png)
![7a6c4baf6380be35b6cc637868db5beb.png](https://i-blog.csdnimg.cn/blog_migrate/6dde6d30668eab7e760686e4fffa399f.png)
讲了这么多,我们来看代码吧!
先定义权重因子
def focal(self, target, actual, alpha=0.25, gamma=2):
focal_loss = tf.abs(alpha + target - 1) * tf.pow(tf.abs(target - actual), gamma)
return focal_loss
鉴于论文实验结果表明sigmoid方法比softmax准确度更高,下面选用sigmoid_cross_entropy交叉熵来计算置信度损失
3.2 计算置信度损失
conf_loss的计算公式:
![b2e527419a18de42a29eb1a6a82d40d1.png](https://i-blog.csdnimg.cn/blog_migrate/c6620c602466fd7b865689f388c269ff.png)
![3d7853ffcc252a6321e1ee3c56a1422a.png](https://i-blog.csdnimg.cn/blog_migrate/4b95a4054f61a04ae426e87ae170cbb3.png)
![1fe727f800602601980b1155323132dd.png](https://i-blog.csdnimg.cn/blog_migrate/9d30f641e88c627020cff6c4b51d2ed8.png)
计算置信度损失必须清楚conv_raw_conf和pred_conf是怎么来的。还记得上一篇文章提到的边界框预测decode函数吗?对,就是从那来的。
def decode(self, conv_output, anchors, stride):
"""
return tensor of shape [batch_size, output_size, output_size, anchor_per_scale, 5 + num_classes]
contains (x, y, w, h, score, probability)
"""
conv_shape = tf.shape(conv_output)
batch_size = conv_shape[0]
output_size = conv_shape[1]
anchor_per_scale = len(anchors) # 3
# 将v3网络最终输出的255维特征向量的形状变为anchor_per_scale, x + y + w + h + score + num_class
conv_output = tf.reshape(conv_output, (batch_size, output_size, output_size, anchor_per_scale, 5 + self.num_class))
# v3网络输出的score
conv_raw_conf = conv_output[:, :, :, :, 4:5]
# 计算预测框里object的置信度
pred_conf = tf.sigmoid(conv_raw_conf)
conv_raw_conf 对应计算公式中的
nice,开始动手构建conf_loss!
iou = bbox_iou(pred_xywh[:, :, :, :, np.newaxis, :], bboxes[:, np.newaxis, np.newaxis, np.newaxis, :, :])
# 找出与真实框 iou 值最大的预测框
max_iou = tf.expand_dims(tf.reduce_max(iou, axis=-1), axis=-1)
# 如果最大的 iou 小于阈值,那么认为该预测框不包含物体,则为背景框
respond_bgd = (1.0 - respond_bbox) * tf.cast( max_iou < IOU_LOSS_THRESH, tf.float32 )
conf_focal = self.focal(respond_bbox, pred_conf)
# 计算置信度的损失(我们希望假如该网格中包含物体,那么网络输出的预测框置信度为 1,无物体时则为 0)
conf_loss = conf_focal * (
respond_bbox * tf.nn.sigmoid_cross_entropy_with_logits(labels=respond_bbox, logits=conv_raw_conf)
+
respond_bgd * tf.nn.sigmoid_cross_entropy_with_logits(labels=respond_bbox, logits=conv_raw_conf)
)
4.分类损失
这里分类损失采用的是二分类的交叉熵,即把所有类别的分类问题归结为是否属于这个类别,这样就把多分类看做是二分类问题。
![16a0eac7971e50e34a25c2a69f5a0749.png](https://i-blog.csdnimg.cn/blog_migrate/1c827082dba7f3561975291628068966.png)
prob_loss = respond_bbox * tf.nn.sigmoid_cross_entropy_with_logits(labels=label_prob, logits=conv_raw_prob)
5. 最终Loss计算
将各部分损失值的和,除以均值,累加,作为最终的图片损失值。
giou_loss = tf.reduce_mean(tf.reduce_sum(giou_loss, axis=[1,2,3,4]))
conf_loss = tf.reduce_mean(tf.reduce_sum(conf_loss, axis=[1,2,3,4]))
prob_loss = tf.reduce_mean(tf.reduce_sum(prob_loss, axis=[1,2,3,4]))
return giou_loss, conf_loss, prob_loss
补充
tf.pow函数具体使用方法:
TensorFlow幂值计算函数:tf.pow_w3cschoolwww.w3cschool.cn![942ac5047a2bbbad4c3e7491f810de74.png](https://i-blog.csdnimg.cn/blog_migrate/a553a8528924244451d7e05c38cd7bf6.jpeg)
tf.nn.sigmoid_cross_entropy_with_logits函数使用方法:
TensorFlow函数教程:tf.nn.sigmoid_cross_entropy_with_logits_w3cschoolwww.w3cschool.cn![942ac5047a2bbbad4c3e7491f810de74.png](https://i-blog.csdnimg.cn/blog_migrate/a553a8528924244451d7e05c38cd7bf6.jpeg)
tf.expand_dims函数使用方法:
TensorFlow函数教程:tf.keras.backend.expand_dims_w3cschoolwww.w3cschool.cn![942ac5047a2bbbad4c3e7491f810de74.png](https://i-blog.csdnimg.cn/blog_migrate/a553a8528924244451d7e05c38cd7bf6.jpeg)
Focal Loss详细讲解和公式推导过程可以参考这篇文章:
Focal Loss 论文理解及公式推导www.aiuai.cn![252432c9e08384e524cd84a4f830408d.png](https://i-blog.csdnimg.cn/blog_migrate/341d8b29eb8d274aad167f4d835e49e9.jpeg)
softmax_cross_entropy和sigmoid_cross_entropy之间的区别可以参考这篇文章:
损失函数softmax_cross_entropy、binary_cross_entropy、sigmoid_cross_entropy之间的区别与联系www.jianshu.com![742728d1aca2c470b4060f67216ce877.png](https://i-blog.csdnimg.cn/blog_migrate/83f6781b7a3cf2683f3113bad3b8ac79.png)
谢谢观看,觉得好就点个赞呗!