YOLOv1 损失函数

相关文章

项目地址:YOLOv1 VOC 2007

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

10 秒文章速览

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

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

平方和误差

在 YOLOv1 使用的是平方和误差,so,在这里需要由我们自定义一个损失函数。平方和误差的计算也不难,和 MSE 大差不差
S S E = ∑ i = 1 n ( y − y ^ ) 2 SSE = \sum_{i=1}^{n} (y-\widehat{y} )^{2} 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。实现了 1 i j o b j 1^{obj}_{ij} 1ijobj 的效果

当网格不存在目标的时候, 1 i j o b j = 0 , 1 i j n o o b j = 1 1^{obj}{ij}=0,\ 1^{noobj}{ij}=1 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:表示 λ n o o b j λ_{noobj} λ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就是指 λ o b j λ_{obj} λ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])
相关推荐
奔跑中的小象1 小时前
统信UOS + 天数AI卡部署SGLang服务手册
人工智能·uos·sglang·天数智芯
DevSecOps选型指南1 小时前
中国版Mythos,为何是悬镜安全灵脉CodeAI?
人工智能·安全
Shockang2 小时前
LangGraph 状态机实战
人工智能
刹那芳华19922 小时前
循环神经网络的从零开始实现(RNN)
人工智能·rnn·深度学习
阿童木写作2 小时前
跨境图片翻译工具多合一,批量图片视频字幕翻译加智能抠图
人工智能·python·音视频·语音识别
科技之门2 小时前
AI3D从建模到贴图、绑骨和动画的完整流程怎么做?V2Fun完整工作流指南
人工智能·3d·贴图
宇宙第一小趴菜2 小时前
二、机器学习的应用领域和发展史
人工智能·机器学习
前端开发江鸟2 小时前
我写过 MCP Server,却一直以为 MCP 只有 Tool
人工智能
天国梦2 小时前
自习室智能化升级避坑指南:天学网AI智习室方案实测与选型建议
大数据·人工智能
阿里云云原生3 小时前
可用性从 99.9% 跃升至 99.995%:畅捷通如何用 AI 重塑运维底座?
运维·网络·人工智能