YOLOv1 损失函数

相关文章

项目地址:YOLOv1 VOC 2007

笔者训练的权重地址:阿里云盘分享

10 秒文章速览

对于 YOLOv1 的损失函数,使用 Python 程序实现损失函数的计算

关于损失函数的计算,在《YOLOv1 论文简要》一文中已经进行了较详细的解释。只不过,在本文中,需要以代码的形式表达出来

平方和误差

在 YOLOv1 使用的是平方和误差,so,在这里需要由我们自定义一个损失函数。平方和误差的计算也不难,和 MSE 大差不差
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> S S E = ∑ i = 1 n ( y − y ^ ) 2 SSE = \sum_{i=1}^{n} (y-\widehat{y} )^{2} </math>SSE=i=1∑n(y−y )2

python 复制代码
# 平方和误差(sum-squared error)
def SSE(y_true, y_pred):
    return tf.reduce_sum(tf.square(y_pred - y_true), axis=-1)

IOU 计算

IOU 函数用于计算预测框与标签的之间的 IOU ,对于 IOU 的计算其实没什么好说的。只是这里需要注意,先依据 (x, y, w, h) 复原出与边界框的坐标

代码中 w_imageh_image 表示图片的尺寸,x_gridy_grid 表示网格的尺寸

python 复制代码
def IOU(y_true, y_pred):
    bbox1_x, bbox1_y, bbox1_w, bbox1_h = y_true
    bbox2_x, bbox2_y, bbox2_w, bbox2_h = y_pred

    # 复原出与边界框的坐标
    x1_min = bbox1_x*w_grid - bbox1_w/2*w_image
    y1_min = bbox1_y*h_grid - bbox1_h/2*h_image
    x1_max = bbox1_x*w_grid + bbox1_w/2*w_image
    y1_max = bbox1_y*h_grid + bbox1_h/2*h_image

    x2_min = bbox2_x*w_grid - bbox2_w/2*w_image
    y2_min = bbox2_y*h_grid - bbox2_h/2*h_image
    x2_max = bbox2_x*w_grid + bbox2_w/2*w_image
    y2_max = bbox2_y*h_grid + bbox2_h/2*h_image

    # 计算矩形面积
    s1 = (x1_max - x1_min) * (y1_max - y1_min)
    s2 = (x2_max - x2_min) * (y2_max - y2_min)

    # 计算相交矩形
    x_min = tf.maximum(x1_min, x2_min)
    y_min = tf.maximum(y1_min, y2_min)
    x_max = tf.minimum(x1_max, x2_max)
    y_max = tf.minimum(y1_max, y2_max)

    w = tf.maximum(0., x_max - x_min)
    h = tf.maximum(0., y_max - y_min)

    a1 = w * h
    a2 = s1 + s2 - a1
    iou = a1 / a2

    return iou

输出与标签

似乎在前两篇文章中都没有对模型的输出与标签进行过具体的说明,这里是时候有个解释了🤔

对于(对于?难道和标签的shape不一样吗,这还真是的)模型的输出最后的shape都是 (b, y, x, n),下面解释下这个形状的含义

  • b:batch size、批大小
  • y、x:记得图片是要分割成S×S的吧,y与x便表示S×S的网格分布
  • n:包含了网格预测的所有内容,包括类别、(x, y, w, h, confidence)

由于 YOLOv1 将图像分为7×7的网格,所以y=x=7

对于n再进一步的解释下。由于 PASCAL VOC 2007 数据集有20个类别,每个网格输出2个预测框,而预测框中包含 (x, y, w, h, confidence),所以n等于30(30是怎么算出来的再之前的文章中有过解释)。至此,可以视为每个网格都会输出一个长度为30的向量

在这个长度为30的向量中,我们指定

  • 0:20:表示20个类别
  • 20:25:表示第一个预测框的输出 (x, y, w, h, confidence)
  • 25:30:表示第二个预测框的输出 (x, y, w, h, confidence)

所以,可知模型输出的shape是 (b, 7, 7, 30)。好了,再回过头去,来解释解释标签的shape,标签的shape是 (b, 7, 7, 25),其实区别并不大,虽然每个网格生成两个预测框,但在测试阶段,网格只会选取置信度分数最大的预测框作为最终输出,所以在标签里只需要考虑一个预测框就行了

损失计算

好了,从这里开始,就要变得烧脑了🤯,跟上笔者的节奏🎶。如果在阅读过程中感到头晕😵‍💫,请放轻松,头晕是正常的

笔者先贴出完整的代码,然后进行进一步的解释

python 复制代码
def get_loss1(y_true, y_pred):
    # 定义各个值的损失
    xy_loss = 0
    wh_loss = 0
    cond_loss = 0
    classes_loss = 0

    # 遍历批大小
    for b in range(tf.shape(y_true)[0]):
        # y 和 x 遍历网格
        for y in range(7):
            for x in range(7):
                # one_obj 用于判断网格中是否存在目标
                one_obj = y_true[b, y, x, 24]
                # 存在目标时
                if one_obj == 1:
                    # 计算两个预测框与真实框的 IOU
                    bbox1_iou = IOU(y_true[b, y, x, 20:24], y_pred[b, y, x, 20:24])
                    bbox2_iou = IOU(y_true[b, y, x, 20:24], y_pred[b, y, x, 25:29])
                    # 只关注 IOU 大的预测框
                    if bbox1_iou >= bbox2_iou:
                        # 计算bbox1的 (x, y) 的损失
                        xy_loss += 5 * SSE(y_true[b, y, x, 20:22], y_pred[b, y, x, 20:22])
                        # 计算bbox1的 (w, h) 的损失
                        wh_loss += 5 * SSE(tf.sqrt(y_true[b, y, x,  22:24]), tf.sqrt(y_pred[b, y, x, 22:24]))

                        # 计算bbox1的 confidence 的损失
                        cond_loss += SSE(bbox1_iou, y_pred[b, y, x, 24:25])
                        cond_loss += 0.5 * tf.square(y_pred[b, y, x, 29:30])
                    else:
                        # 计算bbox2的 (x, y) 的损失
                        xy_loss += 5 * SSE(y_true[b, y, x, 20:22], y_pred[b, y, x, 25:27])
                        # 计算bbox2的 (w, h) 的损失
                        wh_loss += 5 * SSE(tf.sqrt(y_true[b, y, x, 22:24]), tf.sqrt(y_pred[b, y, x, 27:29]))

                        # 计算bbox2的 confidence 的损失
                        cond_loss += SSE(bbox2_iou, y_pred[b, y, x, 29:30])
                        cond_loss += 0.5 * tf.square(y_pred[b, y, x, 24:25])

                    # 计算 classes 的损失
                    for i in range(20):
                        classes_loss += SSE(y_true[b, y, x, i:i+1], y_pred[b, y, x, i:i+1])

                # 不存在目标时
                else:
                    cond_loss += 0.5 * (tf.square(y_pred[b, y, x, 24]) + tf.square(y_pred[b, y, x, 29]))

    return xy_loss + wh_loss + cond_loss + classes_loss

接下来建议结合《YOLOv1 论文简要》的损失函数解读部分进行阅读

变量 one_obj 提取的标签中的置信度,当存在目标时 one_obj=1,否则 one_obj=0。实现了 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 i j o b j 1^{obj}_{ij} </math>1ijobj 的效果

当网格不存在目标的时候, <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 i j o b j = 0 , 1 i j n o o b j = 1 1^{obj}{ij}=0,\ 1^{noobj}{ij}=1 </math>1ijobj=0, 1ijnoobj=1,所以只需要对不含目标的预测框进行置信度的损失计算。在 cond_loss += 0.5 * (tf.square(y_pred[b, y, x, 24]) + tf.square(y_pred[b, y, x, 29]))

  • 0.5:表示 <math xmlns="http://www.w3.org/1998/Math/MathML"> λ n o o b j λ_{noobj} </math>λnoobj 参数
  • tf.square(y_pred[b, y, x, 24]):计算第一个预测框的置信度损失
  • tf.square(y_pred[b, y, x, 29]):计算第二个预测框的置信度损失

当网格存在目标的时候,我们则需要对 (x, y, w, h, confidence) 以及类别都进行损失计算。一个网格会生成2个预测框,而在计算损失时,并非是两个预测框都直接参与计算,要有取舍。前文就提过,在多个预测框中,我们选取与真实框之间 IOU 最高的预测框来负责预测该目标,所以我们要先计算两个预测框与真实框的 IOU

计算(x, y)与(w, h)的损失比较简单,都是先提取从 y_predy_true 中提取出(x, y)或(w, h)再带入SSE即可,5就是指 <math xmlns="http://www.w3.org/1998/Math/MathML"> λ o b j λ_{obj} </math>λobj 参数

而对于置信度的损失计算,分了两个步骤。cond_loss += SSE(bbox1_iou, y_pred[b, y, x, 24:25]) 以与真实框之间的 IOU 作为标签,计算了包含目标的预测框的置信度损失。cond_loss += 0.5 * tf.square(y_pred[b, y, x, 29:30]) 将与真实框之间的 IOU 较小的预测框视为不包含目标来计算损失

最后我们将所有的损失相加,来计算最终损失

再回首,损失计算

可别太天真了噢,兄嘚😏,若真要像上面所说来计算损失,这速度得多慢啊。上面只是个开头,为了好理解而已,下面将给出更加高效的计算方式

所谓的"高效",其实就是从对单个数值的计算转变为对矩阵的计算

IOU 的计算

python 复制代码
def IOU(y_true, y_pred, B):
    # 获取两个预测框的输出
    bbox1_x, bbox1_y, bbox1_w, bbox1_h = y_true[:, :, :, 20], y_true[:, :, :, 21], y_true[:, :, :, 22], y_true[:, :, :, 23]
    bbox2_x, bbox2_y, bbox2_w, bbox2_h = y_pred[:, :, :, 20+B], y_pred[:, :, :, 21+B], y_pred[:, :, :, 22+B], y_pred[:, :, :, 23+B]

    # 根据模型输出,还原出边界框在原图上的坐标
    x1_min = bbox1_x * w_grid - bbox1_w / 2 * w_image
    y1_min = bbox1_y * h_grid - bbox1_h / 2 * h_image
    x1_max = bbox1_x * w_grid + bbox1_w / 2 * w_image
    y1_max = bbox1_y * h_grid + bbox1_h / 2 * h_image
    x2_min = bbox2_x * w_grid - bbox2_w / 2 * w_image
    y2_min = bbox2_y * h_grid - bbox2_h / 2 * h_image
    x2_max = bbox2_x * w_grid + bbox2_w / 2 * w_image
    y2_max = bbox2_y * h_grid + bbox2_h / 2 * h_image

    # 计算矩形面积
    s1 = (x1_max - x1_min) * (y1_max - y1_min)
    s2 = (x2_max - x2_min) * (y2_max - y2_min)

    # 计算相交矩形
    x_min = tf.maximum(x1_min, x2_min)
    y_min = tf.maximum(y1_min, y2_min)
    x_max = tf.minimum(x1_max, x2_max)
    y_max = tf.minimum(y1_max, y2_max)

    w = tf.maximum(0., x_max - x_min)
    h = tf.maximum(0., y_max - y_min)

    a1 = w * h
    a2 = s1 + s2 - a1
    iou = a1 / a2

    return iou

损失的计算

python 复制代码
def get_loss(y_true, y_pred):
    one_obj = y_true[:, :, :, 24]
    # 计算两个预测框与真实框的 IOU
    bbox1_iou = IOU(y_true, y_pred, 0)
    bbox2_iou = IOU(y_true, y_pred, 5)
    # 在两个预测框中选出哪个预测框与真实框之间的 IOU 更大
    index = tf.argmax([bbox1_iou, bbox2_iou], axis=0)
    bbox2_iou_idx = tf.cast(index, 'float32')
    bbox1_iou_idx = 1 - bbox2_iou_idx
    bbox1_iou = tf.expand_dims(bbox1_iou, axis=-1)
    bbox2_iou = tf.expand_dims(bbox2_iou, axis=-1)

    # 计算 (x, y) 的损失
    xy_loss = 5 * one_obj * (SSE(y_true[:, :, :, 20:22], y_pred[:, :, :, 20:22]) * bbox1_iou_idx +
                             SSE(y_true[:, :, :, 20:22], y_pred[:, :, :, 25:27]) * bbox2_iou_idx)

    # 计算 (w, h) 的损失
    wh_loss = 5 * one_obj * (SSE(tf.sqrt(y_true[:, :, :, 22:24]), tf.sqrt(y_pred[:, :, :, 22:24])) * bbox1_iou_idx +
                             SSE(tf.sqrt(y_true[:, :, :, 22:24]), tf.sqrt(y_pred[:, :, :, 27:29])) * bbox2_iou_idx)

    # 计算 conditional 的损失
    # 计算包含目标的预测框的损失
    cond_loss = one_obj * (SSE(bbox1_iou, y_pred[:, :, :, 24:25]) * bbox1_iou_idx +
                           SSE(bbox2_iou, y_pred[:, :, :, 29:30]) * bbox2_iou_idx)
    # 计算包不含目标的预测框的损失
    cond_loss += 0.5 * one_obj * (tf.square(y_pred[:, :, :, 24]) * bbox2_iou_idx +
                                  tf.square(y_pred[:, :, :, 29]) * bbox1_iou_idx)
    cond_loss += 0.5 * (1 - one_obj) * (tf.square(y_pred[:, :, :, 24]) +
                                        tf.square(y_pred[:, :, :, 29]))

    # 计算 classes 的损失
    classes_loss = one_obj * SSE(y_true[:, :, :, 0:20], y_pred[:, :, :, 0:20])

    return tf.reduce_sum(xy_loss +
                         wh_loss +
                         cond_loss +
                         classes_loss, axis=[1, 2])
相关推荐
用户6915811416530 分钟前
Ascend Extension for PyTorch的源码解析
人工智能
-Nemophilist-1 小时前
机器学习与深度学习-1-线性回归从零开始实现
深度学习·机器学习·线性回归
神仙别闹1 小时前
基于tensorflow和flask的本地图片库web图片搜索引擎
前端·flask·tensorflow
成富1 小时前
文本转SQL(Text-to-SQL),场景介绍与 Spring AI 实现
数据库·人工智能·sql·spring·oracle
CSDN云计算2 小时前
如何以开源加速AI企业落地,红帽带来新解法
人工智能·开源·openshift·红帽·instructlab
艾派森2 小时前
大数据分析案例-基于随机森林算法的智能手机价格预测模型
人工智能·python·随机森林·机器学习·数据挖掘
hairenjing11232 小时前
在 Android 手机上从SD 卡恢复数据的 6 个有效应用程序
android·人工智能·windows·macos·智能手机
小蜗子2 小时前
Multi‐modal knowledge graph inference via media convergenceand logic rule
人工智能·知识图谱
SpikeKing2 小时前
LLM - 使用 LLaMA-Factory 微调大模型 环境配置与训练推理 教程 (1)
人工智能·llm·大语言模型·llama·环境配置·llamafactory·训练框架
黄焖鸡能干四碗3 小时前
信息化运维方案,实施方案,开发方案,信息中心安全运维资料(软件资料word)
大数据·人工智能·软件需求·设计规范·规格说明书