layer output 激活函数_深入理解YOLO v3实现细节 - 第3篇 构建v3的Loss_layer

ac42f1b2095ae8495f7045e0382c9675.png

深入理解YOLO v3实现细节系列文章,是本人根据自己对YOLO v3原理的理解,结合开源项目tensorflow-yolov3,写的学习笔记。如有不正确的地方,请大佬们指出,谢谢!


目录

第1篇 数据预处理

第2篇 backbone&network

第3篇 构建v3的 Loss_layer

1. 解读YOLOv3 的损失函数

在分析yolo v3的损失函数之前,先来回顾一下yolo v1的损失函数。

一般来说系统设计需要平衡边界框坐标损失置信度损失分类损失。论文中设计的损失函数如下:

4077716f922d682a22384424515f335b.png

对于 YOLOv3 的损失函数, Redmon J 在论文中并没有进行讲解。从darknet 源代码中的forward_yolo_layer函数中找到 l.cost ,通过解读,总结得到 YOLOv3 的损失函数如下:

865c9f0ff2d9d4d835b42f1b4cfa39df.png

与v1 Loss类似,主要分为三大部分: 边界框坐标损失, 分类损失置信度损失。

1b95d2ac9c6df993e75c8ee596294294.png
边界框坐标损失

表示置信度,判断网格内有无物体。

与yolo v1的边界框坐标损失类似,v3中使用误差平方损失函数分别计算(x, y, w, h)的Loss,然后加在一起。v1中作者对宽高(w, h)做了开根号处理,为了弱化边界框尺寸对损失值的影响。在v3中作者没有采取开根号的处理方式,而是增加1个与物体框大小有关的权重,权重=2 - 相对面积,取值范围(1~2)

38eb583d9fef560b4139c186fc58c893.png
分类损失

表示置信度,判断网格内有无物体。使用
误差平方损失函数计算 类别class 的Loss。

d550ba5e01bfce8eca78c63044341fa3.png
置信度损失

使用误差平方损失函数计算置信度conf 的Loss。

42d7aca1f5fdc3e0ada41c42532b93ba.png
YOLO v3网络输出

yolo v3三种不同尺度的输出,一共产生了(13*13*3+26*26*3+52*52*3)=10647个预测框。

fe1c2b2db8a14d678c803067ff455f34.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
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

7a6c4baf6380be35b6cc637868db5beb.png

是不同类别的分类概率, 权重因子
是个[0,1]间的小数,缩放因子
是个大于0的值。
都是固定值,不参与训练。
的最优值是相互影响的,所以在评估准确度时需要把两者组合起来调节。作者在论文中给出
= 0.25 (即正负样本比例为1:3)、
= 2 时性能最佳 此外,实验结果表面在计算
时用sigmoid方法比softmax准确度更高。

讲了这么多,我们来看代码吧!

先定义权重因子

和缩放因子
target分为前景类和背景类,当target是前景类时为1,背景类时为0。
    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

3d7853ffcc252a6321e1ee3c56a1422a.png

1fe727f800602601980b1155323132dd.png

计算置信度损失必须清楚conv_raw_confpred_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 对应计算公式中的

,
pred_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
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_w3cschool​www.w3cschool.cn
942ac5047a2bbbad4c3e7491f810de74.png

tf.nn.sigmoid_cross_entropy_with_logits函数使用方法:

TensorFlow函数教程:tf.nn.sigmoid_cross_entropy_with_logits_w3cschool​www.w3cschool.cn
942ac5047a2bbbad4c3e7491f810de74.png

tf.expand_dims函数使用方法:

TensorFlow函数教程:tf.keras.backend.expand_dims_w3cschool​www.w3cschool.cn
942ac5047a2bbbad4c3e7491f810de74.png

Focal Loss详细讲解和公式推导过程可以参考这篇文章:

Focal Loss 论文理解及公式推导​www.aiuai.cn
252432c9e08384e524cd84a4f830408d.png

softmax_cross_entropy和sigmoid_cross_entropy之间的区别可以参考这篇文章:

损失函数softmax_cross_entropy、binary_cross_entropy、sigmoid_cross_entropy之间的区别与联系​www.jianshu.com
742728d1aca2c470b4060f67216ce877.png

谢谢观看,觉得好就点个赞呗!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux创始人LinusTorvalds有一句名言:Talk is cheap, Show me the code.(冗谈不够,放码过来!)。 代码阅读是从入门到提高的必由之路。尤其对深度学习,许多框架隐藏了神经网络底层的实现,只能在上层调包使用,对其内部原理很难认识清晰,不利于进一步优化和创新。  YOLOv3是一种基于深度学习的端到端实时目标检测方法,以速度快见长。YOLOv3实现Darknet是使用C语言开发的轻型开源深度学习框架,依赖少,可移植性好,可以作为很好的代码阅读案例,让我们深入探究其实现原理。  本课程将解析YOLOv3实现原理和源码,具体内容包括: YOLO目标检测原理  神经网络及Darknet的C语言实现,尤其是反向传播的梯度求解和误差计算 代码阅读工具及方法 深度学习计算的利器:BLAS和GEMM GPU的CUDA编程方法及在Darknet的应用 YOLOv3的程序流程及各层的源码解析本课程将提供注释后的Darknet的源码程序文件。  除本课程《YOLOv3目标检测:原理与源码解析》外,本人推出了有关YOLOv3目标检测的系列课程,包括:   《YOLOv3目标检测实战:训练自己的数据集》  《YOLOv3目标检测实战:交通标志识别》  《YOLOv3目标检测:原理与源码解析》  《YOLOv3目标检测:网络模型改进方法》 建议先学习课程《YOLOv3目标检测实战:训练自己的数据集》或课程《YOLOv3目标检测实战:交通标志识别》,对YOLOv3的使用方法了解以后再学习本课程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值