深入理解YOLO v3实现细节系列文章,是本人根据自己对YOLO v3原理的理解,结合开源项目tensorflow-yolov3,写的学习笔记。如有不正确的地方,请大佬们指出,谢谢!
目录
第1篇 数据预处理
第2篇 backbone&network
第3篇 构建v3的 Loss_layer
1. 解读YOLOv3 的损失函数
在分析yolo v3的损失函数之前,先来回顾一下yolo v1的损失函数。
一般来说系统设计需要平衡边界框坐标损失、置信度损失和分类损失。论文中设计的损失函数如下:
对于 YOLOv3 的损失函数, Redmon J 在论文中并没有进行讲解。从darknet 源代码中的forward_yolo_layer函数中找到 l.cost ,通过解读,总结得到 YOLOv3 的损失函数如下:
与v1 Loss类似,主要分为三大部分: 边界框坐标损失, 分类损失和置信度损失。
与yolo v1的边界框坐标损失类似,v3中使用误差平方损失函数分别计算(x, y, w, h)的Loss,然后加在一起。v1中作者对宽高(w, h)做了开根号处理,为了弱化边界框尺寸对损失值的影响。在v3中作者没有采取开根号的处理方式,而是增加1个与物体框大小有关的权重,权重=2 - 相对面积,取值范围(1~2)。
使用误差平方损失函数计算置信度conf 的Loss。
yolo v3三种不同尺度的输出,一共产生了(13*13*3+26*26*3+52*52*3)=10647个预测框。
这个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就容易理解了。
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的计算公式:
讲了这么多,我们来看代码吧!
先定义权重因子
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的计算公式:
计算置信度损失必须清楚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.分类损失
这里分类损失采用的是二分类的交叉熵,即把所有类别的分类问题归结为是否属于这个类别,这样就把多分类看做是二分类问题。
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.cntf.nn.sigmoid_cross_entropy_with_logits函数使用方法:
TensorFlow函数教程:tf.nn.sigmoid_cross_entropy_with_logits_w3cschoolwww.w3cschool.cntf.expand_dims函数使用方法:
TensorFlow函数教程:tf.keras.backend.expand_dims_w3cschoolwww.w3cschool.cnFocal Loss详细讲解和公式推导过程可以参考这篇文章:
Focal Loss 论文理解及公式推导www.aiuai.cnsoftmax_cross_entropy和sigmoid_cross_entropy之间的区别可以参考这篇文章:
损失函数softmax_cross_entropy、binary_cross_entropy、sigmoid_cross_entropy之间的区别与联系www.jianshu.com谢谢观看,觉得好就点个赞呗!