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. Flink1.7.2 sql 批处理示例
  2. 全栈技术实践经历告诉你:开发一个商城小程序要多少钱?
  3. plsql直连数据库教程
  4. 四川大学研究生的一封公开信
  5. 转:ibatis动态sql
  6. 集群IPtables转发与防火墙
  7. python三种数据类型_Python-更改Pandas中列的数据类型
  8. java null转integer_java – 从null到int可以转换?
  9. 探索Spring异步代理循环依赖失败的问题
  10. 强制换行的css属性
  11. 手把手教你打造全宇宙最强的专属 Firefox 浏览器
  12. c语言八大排序算法详细版
  13. 毕业设计 嵌入式 stm32车牌识别系统
  14. x264 2pass编码说明
  15. 2023年湖北一级技师二级技师报名时间、考试时间是什么时候?
  16. 取消Steam软件设置在本机上保存账户凭据
  17. 转 全国高校信息数据库,全国高校排序数据库
  18. 计算机资源库在哪,电脑的资源管理在哪里
  19. Python爬虫实战: 爬取网易云歌单
  20. 视频86免费影院-视频电影网聚平台

热门文章

  1. SpringBoot+Shiro+Vue实现身份验证
  2. iOS视频广告(一) GoogleAds-IMA-iOS-SDK
  3. 解决新买的移动硬盘在macOS上无法格式化成APFS以及分区
  4. HTML 网站优化三大标签(title ,description ,keywords)
  5. 处女座与复读机(dp)
  6. STM32CubeMX系列|内部温度传感器
  7. uni-app页面部分模块转化成图片并保存(适用app和h5)以及涉及轮播滚动时,区分轮播内容生成图片方法
  8. 火车购票系统服务器端uml活动图,火车购票标准软件系统UML类图时序图状态图协作图活动图对象图用例图.doc...
  9. 德国kit计算机硕士申请条件,德国KIT的王牌专业突然支持100%英语申请和授课!?...
  10. 了解强化学习动手部分1简介