代码复现2之Loss函数的实现

yolov3代码复现3之图像预测处理
我们在第一篇文章中,我们可以将输入的数据以[b, 255, 13, 13]的形式的出现,总共有三个尺度的数据输出,输出了这三个尺度的数据,那么我们接下来要怎么处理呢?

首先,我们要对数据进行维度转变,将[b, 255, 13, 13]转变为[b, 13, 13, 3, 85],那么为了达到这个目的,我们可以使用pytorch中自带的permute这个函数。

    prediction = prediction.view(prediction.size(0), 3, prediction.size(1)/3, prediction.size(2), prediction.size(3)).permute(0, 1, 3, 4, 2).contiguous()# contiguous:view只能用在contiguous的variable上。如果在view之前用了transpose, permute等,需要用contiguous()来返回一个contiguous copy。# [b, 255, 13, 13]->[b, 3, 13, 13,85]

这些代码里面的prediction指的就是输出的数据,比如[b, 255, 13, 13],注意里面的注释,每次transpose或者permute之后,都加上contiguous,一种解释是有些tensor并不是占用一整块内存,而是由不同的数据块组成,而tensor的view()操作依赖于内存是整块的,这时只需要执行contiguous()这个函数,把tensor变成在内存中连续分布的形式。

好了,我们接下来来想一想[b,3,13,13, 85]的含义,3指的是三个anchor,每个尺度下的anchor不一样,比如,13x13尺度下的anchor是(116, 90), (156, 198), (373, 326),26x26的anchor是(30, 61), (62, 45), (59, 119),52x52的三个anchor是 (10, 13), (16, 30), (33, 23),大家也许发现了,最后输出的尺度越小,对应的anchor也就越大,我看了那些博主的意思,总结一下,其实可以理解为用眼睛看东西,输出尺寸就代表我们离物体的远近,输出尺寸越小,我们离物体就越近,这时候,我们眼睛需要扫描的范围更大才能确定这个物体的真实模样,输出尺寸越大,代表我们离物体越远,这时候,就不要扫描大范围来确定实物的真实模样了。至于anchor的数值是怎么定的,这是论文里面给的,可能是实验得到的,也可能是其他方式得到的,这个不需要深究。

接下来咱们看看85的含义,上篇文章,我们说了这85对应的是x,y,w,h,置信度和80个类别,后面我想了想,为什么会这么定义,其实这很可能是我们或者论文作者自己定义的,这85个值,都是经过一系列函数得到的最终值,我们需要根据这些数值来确定85个数据中每个数据对应的最后的函数,所以,你只需要在这85个数据中定义5个为x,y,w,h,置信度,其余定义为类别都可以,为了方便,我们就按照默认的定义。
好了,懂得这个之后,我们开始考虑loss函数怎么求,loss函数,只在yolov1的论文中有明确的公式,至于yolov3,每个版本都有些不一样,其实,我个人觉得,这些loss函数,只要不相差太多就行,本来就是根据这个loss函数求解最佳的解。所以,我们的loss函数中,包含有以下几部分:
x,y的loss,wh的loss,置信度的loss,类别的loss。

要求解xy,wh的loss,我们需要了解一个概念,边框回归,对这个概念没有了解的同学,点击它去学习一下。
了解到边框回归之后,我们就可以开始考虑了x,y,w,h的真实含义了,我最开始就是把这几个数值理解为字面上的意思了,导致我纠结了好几天,看其他博主的代码总感觉这不对那不对,最后我搞清楚了,这几个数值的真实含义不是字面上的意思,我们先看看下面几张图,这几张图是上面那链接里面的截图。
经过我的思前虑后,我发现,神经网络输出的x,y,w,h应该分别对应上面这张图的,只有这样,才能将这这几个参数的真实含义对应起来,因为这些参数的具体数值就是经过一系列函数得到的。

理解了这些含义之后,我们就可以开始为xy,wh的loss做准备了,预测的xywh已经知道了,但是target目标的xywh我们依旧不知道,这时候,就需要我们想办法来得到了。

我们可以查看coco数据集里面的标签文件,数据是类似以下这种,16 0.328250 0.769577 0.463156 0.242207 ,第一个数值代表的是类别,后面的数据就代表了xywh了,只不过这些值是经过均值化了的,所谓均值化,我的理解就是单位化,比如原照片尺寸为416x416,中心点的x坐标为120,那么我们最后存储的数值为120/416,这就是上面数据的由来。知道这个之后,我们要注意,一张照片里面,多个边框是常见的事情,也就是一张照片有多个标签,对于这些边框,我们要一个个的进行处理。

那么,怎么处理呢?首先,我们要知道,每个边框在每个输出尺寸下有三个anchor来预测,我们要找到最合适的anchor来对这个边框进行预测,那么,怎么来判断最合适的anchor呢?我们通过iou这个概念来确定,什么是Iou?简单来说,就是两个边框的重合度。而用iou来判断边框,我们又面临着边框的中心坐标没有给定的情况,这时候,我们可以将两个边框的左上角进行对齐,然后再求解iou,我们称这个为bbox_wh_iou,代码情况如下:

def bbox_wh_iou(wh1, wh2):wh2 = wh2.unsqueeze(0)w1, h1 = wh1[:,0], wh1[:,1]w2, h2 = wh2[:,0], wh2[:,1]inter_area = torch.min(w1, w2) * torch.min(h1, h2) # 之所以这样处理,就是将两个边框移到左上角后,重合的部分等于这两个数值相乘union_area = (w1 * h1 + 1e-16) + w2 * h2 - inter_area# print('->', inter_area / union_area)return inter_area / union_area

在实际的模型训练中,我们一次性的会将多个数据放到gpu或者cpu上,经常性的是一次性放64个数据,那么,我们在处理这些标签之前,我们就要加上这种标签对应的图片编号,最后标签的形势就当成一个list,targets = [image, class, x, y, w, h],基本的代码如下:

'''this code was desinged by nike hu'''
anchors = [(116, 90), (156, 198), (373, 326), (30, 61), (62, 45), (59, 119), (10, 13), (16, 30), (33, 23)]
def build_target(target, iou_binary=0.4):nt = len(target)# 这里统计传入的target的行数,targets = [image, class, x, y, w, h], 这里的image是一个数字,代表是当前batch的第几个图片,x,y,w,h都进行了归一化,除以了宽或者高txys, twhs, tcls,tindexs = [], [], [], []target = torch.Tensor(target) # 转化为torch类型for i in range(nt): # 对每一个边框进行检测哪个anchor最适合txy, twh, tcl, tindex = [], [], [], []for j in range(3): # 有三种尺度下的anchorimage_anchors = torch.Tensor(anchors[3*j: 3*j+3]) # 分别对应不同尺度的三个anchor,->[3, 2]# print('最初->', image_anchors)target_change = torch.zeros_like(target[i])target_change[0:2] = target[i, 0:2] # 需要一个新的变量来更新target,这里维度减少,->[n]if j == 0: # 不同尺度下,放缩尺度不一样image_anchors /= 32 # 13X13,图片放缩32倍target_change[2:] = target[i, 2:] * 13 # 由于target目标是以单位为参照对象,这里我们要放大if j == 1:image_anchors /= 16target_change[2:] = target[i, 2:] * 26if j == 2:image_anchors /= 8target_change[2:] = target[i, 2:] * 52# print('image_anchor->', image_anchors, 'target->', target_change)wh_iou = bbox_wh_iou(image_anchors, target_change[4:]) # 统计原图中边框和anchor的iou# print(wh_iou)wh_iou_id = wh_iou.max(dim=0)[1] # 还是使用最大值来删选# print('下标->', wh_iou_id)anchor_id = torch.arange(3) # anchor的下标# wh_iou_id = (wh_iou > iou_binary) # 大于iou阈值的部分,返回[trur, flase....这种txy.append(target_change[2:4] - target_change[2:4].long())gj, gi = target_change[2:4].long() # 这是中心点对应网格的坐标a = anchor_id[wh_iou_id].long() # 这是每一层对应的三个anchor中选取最合适的那个tindex.append((target_change[0].long(), a, gj, gi)) # 分别对应图片编号,anchor下标, 中心网格的y,xgwh = target_change[4:] # 这是标签的宽和高twh.append(torch.log(gwh / image_anchors[a])) # 这是存储放缩的倍数tcl.append(target_change[1].long())# print('TXY->', txy)# print('tindex->', tindex)# print('twh->', twh)# print('tcl->', tcl)# print('..................................')tcls.append(tcl)tindexs.append(tindex)twhs.append(twh)txys.append(txy)return txys, twhs, tcls,tindexs

对于上面这些代码,旁边的注释可能会出现词不达意的情况,因为很多时候这些注释都是我敲代码的过程中用我能懂得语言写上的,对以上代码再解释一下吧,由于我们一张图片中可能存在多个标签,所以这些代码就是针对多个标签来处理的,最后的输出是一个list, list里面有多个list,输出结果如下:

xy-> [[tensor([0.2673, 0.0045]), tensor([0.5345, 0.0090]), tensor([0.0690, 0.0180])], [tensor([0.6748, 0.8784]), tensor([0.3495, 0.7567]), tensor([0.6991, 0.5134])], [tensor([0.1904, 0.7650]), tensor([0.3809, 0.5299]), tensor([0.7617, 0.0599])]]

这个最外面的list包含着每个标签对应的中心点,每个标签的中心点又有三个,因为我们要进行多尺度的预测,13, 26, 52这三个尺度对应着这些中心点,其他最后返回的参数也是这种对应的关系。

好了,在代码中的注释中,我们也知道了各个参数的含义,txy对应预测结果的xy,twh对应预测结果的wh,tcls对应预测结果的类别,tindex对应的是置信度,以及标签对应的中心点所在的小框框的左上角。接下来,我们开始介绍loss函数的计算。

对于loss的计算,我们这里采用的损失函数有均方差函数,用来计算xy和wh的loss,还有多分类损失函数和单分类损失函数,具体的代码如下,注释也在里面:

'''this code was desinged by nike hu'''
def computer_loss(pretection, targert):# prediction这里应该是([b, 3, 13, 13, 85],[b, 3, 26, 26, 85],[b,3, 52, 52, 85])MSE = nn.MSELoss() # 均方损失函数,loss(xi,yi)=(xi−yi)2,用于计算x,y,w,h的损失,将pred,target逐个元素求差,然后求平方,再求和,再求均值,CE = nn.CrossEntropyLoss() # 这个损失函数用于多分类问题虽然说的是交叉熵,是nn.logSoftmax()和nn.NLLLoss()的整合,# loss(x,class)=weight[class](−x[class]+log(j∑​exp(x[j])))等价nn.logSoftmax()和nn.NLLLoss()的整合,# CrossEntropyLoss用于多类别分类,这用来类别的Loss计算,这种函数,第二个参数只需要一个数值就行BCE = nn.BCEWithLogitsLoss() # BCEWithLogitsLoss = Sigmoid+BCELoss,当网络最后一层使用nn.Sigmoid时,就用BCELoss,# 当网络最后一层不使用nn.Sigmoid时,就用BCEWithLogitsLoss。(BCELoss)BCEWithLogitsLoss用于单标签二分类或者多标签二分类,# 这里用来置信度的loss计算,BCELoss是−1n∑(yn×lnxn+(1−yn)×ln(1−xn))−n1​∑(yn​×lnxn​+(1−yn​)×ln(1−xn​))、# ,这个函数,第二个参数和第一个参数的维度个数要一致txys, twhs, tcls,tindexs = build_target(targert) # 由于一张图片中很可能有多个标签,所以返回的每一个数值都是一个元祖losses = []for i in range(len(txys)): # 对每一个标签求lossloss_one = torch.zeros(1, 3) # [1, 4],每个值对应的是每一层的Lossfor j in range(len(pretection)): # 这里相当于是对每一层求取Lossindex_i = tindexs[i][j] # 第几个标签的第几层,image_id, achor_id, jy,jxtconf = torch.zeros_like(pretection[j][...,0]) # 创建一个向量来最后和置信度求解loss,=》[b,3, 13, 13, 1]pretection_label = pretection[j][index_i] # 第几层的输出图[b, 3, 13, 13, 85].....=->[85]tconf[index_i] = 1 # 对于我们找到的中心点的那个框,我们将这个框对应的置信度设置为1loss1 = MSE( torch.sigmoid(pretection_label[:2]), txys[i][j]) # xy的lossloss2 = MSE( pretection_label[2:4], twhs[i][j]) # wh的lossloss3 = CE( pretection_label[5:], tcls[i][j]) #类别的lossloss4 = BCE( pretection_label[4], tconf) # 置信度的Lossloss_sum = loss1 + loss2 + loss3 + loss4 # 每一层的Loss总和,[1]loss_one[0,j] = loss_sum # 最后应该变为[1,3]每个值对应每一层对某个标签的Losslosses.append(loss_one.sum()) # 将每个标签三层的loss总和保存losses_sum = sum(loss_sum) # 求解最后的总和,代表一张照片中所有标签的loss总和return losses_sum

我个人觉得,这块代码的注释还是很详细了,就不在累述了。这一块代码,我没有选取相关的数据来进行测试,所以可能会有一些错误,大家有兴趣可以自己弄一些数据来测试一下,我不愿弄了,因为我要自己读取几张图片的数据,还要自己造一些标签的文件,感觉有些麻烦,就没做了,基本原理应该是这样的。

终于可以结束这一块的解释了,说实话,这一块是我用的时间最多,最纠结的地方,可能也跟我接触神经网络时间很短,理解不够的原因。希望我能帮大家减少一些坑。打算还写一篇,来介绍怎么处理输出的三个尺度的数据,来将最后预测的边框和类别画在图像上。

2020 4 13

yolov3 原理代码复现2相关推荐

  1. DL之YoloV3:Yolo V3算法的简介(论文介绍)、各种DL框架代码复现、架构详解、案例应用等配图集合之详细攻略

    DL之YoloV3:Yolo V3算法的简介(论文介绍).各种DL框架代码复现.架构详解.案例应用等配图集合之详细攻略 目录 Yolo V3算法的简介(论文介绍) 0.YoloV3实验结果 1.Yol ...

  2. YOLOv3最全复现代码合集(含PyTorch/TensorFlow和Keras等)

    点击上方"CVer",选择"置顶公众号" 重磅干货,第一时间送达 前戏 2018年3月26日,CVer第一时间推文:YOLOv3:你一定不能错过 2019年3月 ...

  3. Pytorch | yolov3原理及代码详解(二)

    阅前可看: Pytorch | yolov3原理及代码详解(一) https://blog.csdn.net/qq_24739717/article/details/92399359 分析代码: ht ...

  4. 【神经网络】(15) Xception 代码复现,网络解析,附Tensorflow完整代码

    各位同学好,今天和大家分享一下如何使用 Tensorflow 构建 Xception 神经网络模型. 在前面章节中,我已经介绍了很多种轻量化卷积神经网络模型,感兴趣的可以看一下:https://blo ...

  5. 经典神经网络论文超详细解读(三)——GoogLeNet InceptionV1学习笔记(翻译+精读+代码复现)

    前言 在上一期中介绍了VGG,VGG在2014年ImageNet 中获得了定位任务第1名和分类任务第2名的好成绩,而今天要介绍的就是同年分类任务的第一名--GoogLeNet . 作为2014年Ima ...

  6. 未授权访问漏洞原理及复现

    本文转自行云博客https://www.xy586.top/ 文章目录 未授权访问 前提知识 ssh免密登录 客户端 服务端 安装Redis数据库 Redis语法 原理 攻击复现 利用计划任务执行命令 ...

  7. SENet代码复现+超详细注释(PyTorch)

    在卷积网络中通道注意力经常用到SENet模块,来增强网络模型在通道权重的选择能力,进而提点.关于SENet的原理和具体细节,我们在上一篇已经详细的介绍了:经典神经网络论文超详细解读(七)--SENet ...

  8. 论文学习笔记: Learning Multi-Scale Photo Exposure Correction(含pytorch代码复现)

    论文学习笔记: Learning Multi-Scale Photo Exposure Correction--含pytorch代码复现 本章工作: 论文摘要 训练数据集 网络设计原理 补充知识:拉普 ...

  9. log4j漏洞原理分析复现检测复盘

    凡事要自发,自然而为,即要顺从一切处于自然状态的事物,允许它们自发地转变.这样,道即达到了一种"无为而无不为"的状态.在日常生活中,道表现为"不自傲"或&quo ...

  10. 【机器学习】总结了九种机器学习集成分类算法(原理+代码)

    大家好,我是云朵君! 导读: 本文是分类分析(基于Python实现五大常用分类算法(原理+代码))第二部分,继续沿用第一部分的数据.会总结性介绍集成分类算法原理及应用,模型调参数将不在本次讨论范围内. ...

最新文章

  1. Linux 性能測试工具
  2. SpringBoot学习:整合shiro(身份认证和权限认证),使用EhCache缓存
  3. 流程控制--for序列
  4. 数据库:悲观锁与乐观锁
  5. android studio 集成 第三方sdk,Android FrameWork集成第三方SDK的jar包和so庫
  6. 阿里巴巴最新开源项目 - [HandyJSON] 在Swift中优雅地处理JSON
  7. 计算机云文档,计算机的云计算论文.doc
  8. Swiper 在vue中的使用,loop=true获取真实index,数据更新刷新初始化swiper
  9. python水仙花数的编程讲解_《scratch编程+数学》课程:找寻水仙花数
  10. Apache Lucene 3.x推荐教程
  11. SAS笔记#SAS中的SQL语言
  12. macos 设置内外网同时访问
  13. vue 中 自定义按钮实现video暂停和播放
  14. java 线框图_你真的搞懂什么是线框图,什么是原型图了吗?
  15. Hack The Box-meow
  16. Intel Xeon Platinum 8269CY(Cascade Lake)处理器性能评测
  17. 12月18日第壹简报,星期日,农历十一月廿五
  18. Android学习笔记之在图片特效
  19. 内网渗透之mimikatz+procdump 提取 Windows 明文密码
  20. 软件生成问候图片_图片生成器软件-图片生成器下载 v1.0免费版--pc6下载站

热门文章

  1. 网络游戏营销植入案例
  2. 加权移动平均法 java_加权平均和移动平均
  3. 计算机进行定理的自动证明属于,使用计算机进行定理的自动证明,属于计算机在()应用领域的应用...
  4. java 罗马数字_罗马数字 | 学步园
  5. 软件工程课程设计-ch小说网站
  6. Python求解最大子列和
  7. python英文参考文献格式_英文参考文献的正确引用格式详解
  8. ognl.OgnlException: target is null for setProperty(null, offset, [Ljava.lang.String;@1667f3c) 解决方法
  9. 各种风格的Android面试题进来了解一下,面试必问
  10. 云计算机房架构图,云计算架构技术与实践