1、首先我们看一下他的输入参数

model_loss = Lambda(yolo_loss, output_shape=(1,), name='yolo_loss',arguments={'anchors': anchors, 'num_classes': num_classes, 'ignore_thresh': 0.5})([*model_body.output, *y_true])
def yolo_loss(args, anchors, num_classes, ignore_thresh=.5, print_loss=False)

(1)args包括两部分,第一部分是*model_body.output,就是三组 (batchsize, grid, grid, 75)的darknet输出;第二部分是*y_true,就是上一篇文章咱们说到的 三组(batchsize, grid, grid, 3, 25)的Y真实值

(2)anchors就是[[10,13],  [16,30],  [33,23],  [30,61],  [62,45],  [59,119],  [116,90],  [156,198],  [373,326]]9组anchors

(3)num_classes=20(COCO数据集为80)

(4)ignore_thresh指的是iou的最小达标值

2、在函数体内,经过了一个核心函数(yolo_head),这个函数的主要功能就是把darknet输出的值跟我们的y_true对应上。

grid, raw_pred, pred_xy, pred_wh = yolo_head(yolo_outputs[l],anchors[anchor_mask[l]], num_classes, input_shape, calc_loss=True)

函数体如下:

def yolo_head(feats, anchors, num_classes, input_shape, calc_loss=False):"""Convert final layer features to bounding box parameters.[Nxgridxgridx75], [[116, 90], [156, 198], [373,326]], 20, [416, 416]yolo_outputs[l],anchors[anchor_mask[l]], num_classes, input_shape, calc_loss=True[Nxgridxgridx75][[116, 90], [156, 198], [373,326]], 20, [416, 416]20416x416True[Nxgridxgridx75], [[116,90],[156,198],[373,326]], 20, (416, 416)"""# 3num_anchors = len(anchors)# Reshape to batch, height, width, num_anchors, box_params.# [1x1x1x3x2]anchors_tensor = K.reshape(K.constant(anchors), [1, 1, 1, num_anchors, 2])# [gridxgrid]grid_shape = K.shape(feats)[1:3]# 建立x、y轴的坐标系# 向下展开grid_y = K.tile(K.reshape(K.arange(0, stop=grid_shape[0]), [-1, 1, 1, 1]),[1, grid_shape[1], 1, 1])# 向右展开grid_x = K.tile(K.reshape(K.arange(0, stop=grid_shape[1]), [1, -1, 1, 1]),[grid_shape[0], 1, 1, 1])# 建立横纵坐标系,跟feature map大小一致# [gridxgridx1x2]# TODO 有点难以理解的点,维度太抽象grid = K.concatenate([grid_x, grid_y])''':return[gridxgridx1x2]'''grid = K.cast(grid, K.dtype(feats))# [Nxgridxgridx75]# ---->>>> [Nxgridxgridx3x25]feats = K.reshape(feats, [-1, grid_shape[0], grid_shape[1], num_anchors, num_classes + 5])''':returnNxgridxgridx3x25'''# sigmoid(x+grid) / 13# [Nxgridxgridx3x2] + [gridxgridx1x2] = [Nxgridxgridx3x2]# 加上偏移,其实这个地方,我理解他跟卷积层中加bias的道理是一样的box_xy = (K.sigmoid(feats[..., :2]) + grid) / K.cast(grid_shape[::-1], K.dtype(feats))# [Nxgridxgridx3x2] * [1x1x1x3x2]# 对应anchor的feature map根据对应的anchor大小相乘box_wh = K.exp(feats[..., 2:4]) * anchors_tensor / K.cast(input_shape[::-1], K.dtype(feats))# 是否是物体的概率box_confidence = K.sigmoid(feats[..., 4:5])box_class_probs = K.sigmoid(feats[..., 5:])if calc_loss:# [gridxgrid], [Nxgridxgridx3x25],[Nxgridxgridx3x2], [Nxgridxgridx3x2]return grid, feats, box_xy, box_wh

这是整个yolo框架中三大核心思想之一。

(1)在所有的设计过程中,基本上跟reshape和expand_dims相关的操作,目的无外乎两个,一个是为了跟我们的真实数据进行匹配,一个是为了能够进行广播。

(2)

grid_y = K.tile(K.reshape(K.arange(0, stop=grid_shape[0]), [-1, 1, 1, 1]), [1,grid_shape[1], 1, 1])
grid_x = K.tile(K.reshape(K.arange(0, stop=grid_shape[1]), [1, -1, 1, 1]), [grid_shape[0], 1, 1, 1])

这两行代码,根据源论文的意思,是为了构造偏移量设计的,对应着上式的c变量。但是在观察代码的设计时,我个人比较偏向于认为这个是构造bias的,表面理解这两个是一个意思,但是其实在坐标体系里面,偏移更能代表的是相比于基准点的一个微小偏移量,而在神经网络中bias更趋向于一个线性非线性函数最终结果的一个截距,在原理和用法上是不同的。所以,那我们姑且把这两者看做一个东西,认为它是一个对具体坐标的最终优化偏移量。

(3)

feats = K.reshape(feats, [-1, grid_shape[0], grid_shape[1], num_anchors, num_classes + 5])

这一步就把yolobody的输出,转成跟y_true对应的维度数据

(4)

box_xy = (K.sigmoid(feats[..., :2]) + grid) / K.cast(grid_shape[::-1], K.dtype(feats))
box_wh = K.exp(feats[..., 2:4]) * anchors_tensor / K.cast(input_shape[::-1], K.dtype(feats))
box_confidence = K.sigmoid(feats[..., 4:5])
box_class_probs = K.sigmoid(feats[..., 5:])
return grid, feats, box_xy, box_wh

以上四个式子,把网络的输出做了一套转换。

对xy坐标,先经过sigmoid函数,后加上偏移量,最后除以grid

对wh长度,先进行e为底的幂计算,后跟anchor进行乘法计算,然后除以grid

对置信度和类别,直接经过sigmoid函数。

也许很多人都会为,为啥子要这么算,为啥子要那么算。在这里,我们不对人家设计的计算逻辑做原理探究,我们只需要知道yolo在darknet输出部分做了这些数据的转换,并且在大量的数据集上都证明这些计算逻辑是有效的,我们就能认为他的设计是有道理有意义的。一个很简答的例子,为什么卷积进行很多层以后,能对特征对高度抽象提取,为什么lstm模块能对前后数据进行记忆,甚至为什么乌龟能大老远从海对面跑回来下蛋。我们不需要知道为什么这些数经过这套计算方法能比较好的体现特征,只需要这套计算原理,并且在该计算原理上我们得到了有效的输出就够了。

(5)

raw_true_xy = y_true[l][..., :2] * grid_shapes[l][::-1] - grid
raw_true_wh = K.log(y_true[l][..., 2:4] / anchors[anchor_mask[l]] * input_shape[::-1])

在对y_true和y_pred进行loss计算之前,需要转成为一个标准的数据,所以把我们生成的真实数据,按照yolo_head的标准转回去。标准化是对所有数据进行处理的必要步骤。

(6)

raw_true_wh = K.switch(object_mask, raw_true_wh, K.zeros_like(raw_true_wh))

这一步函数的功能,跟nump的where函数功能一致,不细说了,各位看官移步numpy的文档吧。主要是为了把原始图像上没有标注的区域给置零

(7)

box_loss_scale = 2 - y_true[l][..., 2:3] * y_true[l][..., 3:4]

这个变量,在我看来,它是一个制衡值。大家先记着这个值,我们在后面再细聊

(8)

        def loop_body(b, ignore_mask):# 保留具有相同bool位置的元素# object_mask是指有物体的点位# nn x 4true_box = tf.boolean_mask(y_true[l][b, ..., 0:4], object_mask_bool[b, ..., 0])# 计算iou,# grid x grid x 3 x 4  ----  nn x 4iou = box_iou(pred_box[b], true_box)''':returngrid x grid x 3 x 10'''# grid x grid x 3best_iou = K.max(iou, axis=-1)# b x grid x grid x 3ignore_mask = ignore_mask.write(b, K.cast(best_iou < ignore_thresh, K.dtype(true_box)))return b + 1, ignore_mask# b x grid x grid x 3_, ignore_mask = K.control_flow_ops.while_loop(lambda b, *args: b < m, loop_body, [0, ignore_mask])ignore_mask = ignore_mask.stack()ignore_mask = K.expand_dims(ignore_mask, -1)

这一坨,是计算置信度的损失做准备的,这是一段非常难懂的代码块,主要功能是找出没有物体的值,即:ignore_mask

,这个值表达的是当前grid下的所有木有物体的索引,shape=(batchsize, grid, grid, 3)

(9)OK,开始计算loss

a)xy loss

xy_loss = object_mask * box_loss_scale * K.binary_crossentropy(raw_true_xy, raw_pred[..., 0:2], from_logits=True)

这里我们先对(7)遗留的问题进行阐述。

box_loss_scale = 2 - w * h,于是有w*h越小,则box_loss_scale 越大;

但同时w*h越小,其面积(w*h就是面积)就越小,面积越小,在和anchor做比较的时候,iou必然就小,导致“存在物体”的置信度就越小。也就是object_mask越小。

于是,object_mask * box_loss_scale在这里形成了一个制衡条件,这也就是我把box_loss_scale看做一个制衡值的原因。

另外,还对t_true和y_pred计算了一个二分类的交叉熵。

于是xy的loss就是一个制衡函数 * 一个二分类的交叉熵,这就能对wh做了权衡,也对xy做了权衡

b)同理,wh的损失也是类似的,只是

wh_loss = object_mask * box_loss_scale * 0.5 * K.square(raw_true_wh - raw_pred[..., 2:4])

后半部分的损失函数换成了方差

c)置信度损失

confidence_loss = object_mask * K.binary_crossentropy(object_mask, raw_pred[..., 4:5], from_logits=True) + (1 - object_mask) * K.binary_crossentropy(object_mask, raw_pred[..., 4:5], from_logits=True) * ignore_mask

这个还挺简单的,只是对真实值和预测值做了一个制衡

d)这里补充一下,有位美女问我,为什么多分类问题用二值交叉熵解决?

这里,我们可以理解为,“所有的分类都预测正确”为一个类1,否则就是另一个类0。这样就把多分类看做是二分类问题,当且仅当所有的分类都预测对时,loss最小。

class_loss = object_mask * K.binary_crossentropy(true_class_probs, raw_pred[..., 5:], from_logits=True)

类别损失也只是对真实值和预测值做了制衡

e)最后,对loss进行简单的加总求和取均值

xy_loss = K.sum(xy_loss) / mf
wh_loss = K.sum(wh_loss) / mf
confidence_loss = K.sum(confidence_loss) / mf
class_loss = K.sum(class_loss) / mf
loss += xy_loss + wh_loss + confidence_loss + class_loss

到此,yolo神秘的损失函数就完成了

为解决一些小伙伴说我讲得粗的问题,新开了一篇比较详细的文章

欢迎移步:

https://blog.csdn.net/weixin_42078618/article/details/87787919

https://blog.csdn.net/weixin_42078618/article/details/87787919

https://blog.csdn.net/weixin_42078618/article/details/87787919

YOLOv3庖丁解牛(三):YOLOv3损失函数相关推荐

  1. Tensorflow2.0 实现 YOLOv3(三):yolov3.py

    文章目录 文章说明 传入参数 YOLOv3 decode bbox_iou bbox_giou compute_loss 完整代码 文章说明 本系列文章旨在对 Github 上 malin9402 提 ...

  2. 海思3159A运行yolov3(三)——darknet2caffe

    可以参考原作者:https://github.com/ChenYingpeng/darknet2caffe 一.环境 Python2.7CaffePytorch >= 0.40 二.caffe参 ...

  3. DL之yolov3:使用yolov3算法时需要对Ubuntu系统进行配置的简介、过程步骤之详细攻略

    DL之yolov3:使用yolov3算法时需要对Ubuntu系统进行配置的简介.过程步骤之详细攻略 目录 yolov3算法时需要对Ubuntu系统进行配置的简介 Ubuntu系统进行配置的过程步骤 第 ...

  4. DL之YoloV3:YoloV3论文《YOLOv3: An Incremental Improvement》的翻译与解读

    DL之YoloV3:YoloV3论文<YOLOv3: An Incremental Improvement>的翻译与解读 目录 YoloV3论文翻译与解读 Abstract 1. Intr ...

  5. iou画 yolov3_专栏 | 【从零开始学习YOLOv3】4. YOLOv3中的参数进化

    原标题:专栏 | [从零开始学习YOLOv3]4. YOLOv3中的参数进化 前言:YOLOv3代码中也提供了参数进化(搜索),可以为对应的数据集进化一套合适的超参数.本文建档分析一下有关这部分的操作 ...

  6. 都2021年了,不会还有人连深度学习都不了解吧(三)- 损失函数篇

    一.前言 深度学习系列文章陆陆续续已经发了两篇,分别是激活函数篇和卷积篇,纯干货分享,想要入门深度学习的童鞋不容错过噢!书接上文,该篇文章来给大家介绍" 选择对象的标准 "-- 损 ...

  7. 【从零开始学习YOLOv3】3.YOLOv3的数据组织和处理

    前言:本文主要讲YOLOv3中数据加载部分,主要解析的代码在utils/datasets.py文件中.通过对数据组织.加载.处理部分代码进行解读,能帮助我们更快地理解YOLOv3所要求的数据输出要求, ...

  8. 【从零开始学习YOLOv3】3. YOLOv3的数据加载机制和增强方法

    前言:本文主要讲YOLOv3中数据加载部分,主要解析的代码在utils/datasets.py文件中.通过对数据组织.加载.处理部分代码进行解读,能帮助我们更快地理解YOLOv3所要求的数据输出要求, ...

  9. 从头开始复现YOLOv3(三)训练模型

    YOLOv3模型训练 1 迁移学习 (1)两种权重文件 (2)导入权重方法 (3)保存模型的方法 2 标签转化函数 3 模型训练 4.模型评价 (1)mAP的计算原理 (2)mAP的计算程序 (3)在 ...

最新文章

  1. leetcode 16. 3Sum Closest | 16. 最接近的三数之和(双指针)
  2. Linux命令行上传文件到百度网盘
  3. How to Use Hive-based Registry IN WINCE.NET
  4. 论文浅尝 | Global Relation Embedding for Relation Extraction
  5. python的pygame库使用方法_[宜配屋]听图阁
  6. Navicat(服务器对象) -2之MySQL 或 MariaDB 对象
  7. Android:自定义滚动边缘(EdgeEffect)效果
  8. python程序基本结构有哪三种_【Python基础】Python程序结构有哪些
  9. JSP 新手入门理解教程(附带案例)
  10. U盘写保护,不能被格式化
  11. 西工大计算机课程表,工大、高新、交大、爱知等7所名校初一作息时间表课表新鲜出炉!...
  12. 强烈推荐www.wikipedia.org英文版
  13. 转载:【菜鸟专用】使用LaTeX轻松撰写精美个人简历
  14. ng : 无法加载文件 C:\Users\Administrator\AppData\Roaming\npm\ng.ps1,因为在此系统上禁止运行脚本
  15. 虚幻引擎学习之路:动画模块之基础篇
  16. HTML超出文本显示省略号...[text-overflow]
  17. 搭建wnmp开发环境
  18. 工商管理如何利用计算机思维,论述工商管理人才素质的重要性
  19. 2022西山居--技术训练营--一面(已oc)
  20. 热对流方程加速的OpenMP实现

热门文章

  1. 标准规范概览——智能密码钥匙相关规范
  2. 微分方程5_如何理解$e^i*pi
  3. 在卖咖啡这件事上,究竟怎样才是“新零售”
  4. Java中,生产者和消费者的问题
  5. uni-app APP,H5图片上传添加文字水印,图片只显示左上角bug(H5,安卓App可用,其他暂未测试)
  6. sinee303a变频器说明书_SINEE EM303A变频器用户手册.pdf
  7. matlab函数_连通区域 bwareaopen bwarea
  8. html5如何绑定域名,墨涩网 - Coding搭建html静态网站后绑定域名+SSL证书——墨涩网...
  9. yum 安装Python3
  10. 2018网易计算机视觉岗实习生笔试题解