YOLO-V5 系列算法和代码解析(五)—— 损失函数

news2024/12/24 9:57:31

文章目录

    • 基本简介
    • 调试准备
    • 损失函数

基本简介

  损失函数是神经网络的重要组成部分,用于评估网络的预测值和真实值的差异度。根据偏差的大小,反向调整网络的训练参数,迭代优化使得损失尽量小,也就得到最优的网络参数。

调试准备

  1. debug 设置
    具体设置可以参考上一篇博文: YOLO-V5 系列算法和代码解析(三)—— 训练数据加载。调试过程中,为了避免输出的中间变量太长,关闭【mosaic】数据增强功能。关闭方法:【data/hyps/hyp.scratch-low.yaml,L32】,调整如下

    # mosaic: 1.0  # image mosaic (probability)
    mosaic:0.0
    
  2. 数据准备
    调试损失函数所用图片为【000000000623.jpg
    在这里插入图片描述

    上面图片对应的标签如下:

    # --- 上面图片对应的标签
    # classID,x,y,w,h(归一化)
    13 0.86172 0.38362 0.27656 0.47676
    77 0.4764 0.52584 0.946827 0.92584
    56 0.86248 0.36568 0.265653 0.47968
    0 0.71516 0.49351 0.564507 0.98182
    

损失函数

YOLO-V5 中的损失函数计算类主要包括:

  • 类初始好化:设置参数,以及不同的损失函数API;
  • 扩增正样本:生成更多的正样本;
  • 计算损失 :类别损失,定位损失,置信度损失;
  1. 损失函数的代码结构
    主要从【整体上】领略损失函数的代码结构,明确每一部分的功能,特省略详细的实现代码。整体结构如下,

    class ComputeLoss:
        sort_obj_iou = False
    
        # Compute losses
        def __init__(self, model, autobalance=False):
            device = next(model.parameters()).device  # get model device
            h = model.hyp  # hyperparameters
    
            # Define criteria
            # 定义类别损失和置信度损失
            BCEcls = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([h['cls_pw']], device=device))
            BCEobj = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([h['obj_pw']], device=device))
    
            # Class label smoothing https://arxiv.org/pdf/1902.04103.pdf eqn 3
            self.cp, self.cn = smooth_BCE(eps=h.get('label_smoothing', 0.0))  # positive, negative BCE targets
    
            # Focal loss
            # 目标检测中,用于降低类别不平衡造成的影响
            g = h['fl_gamma']  # focal loss gamma
            if g > 0:
                BCEcls, BCEobj = FocalLoss(BCEcls, g), FocalLoss(BCEobj, g)
    
            m = de_parallel(model).model[-1]  # Detect() module
            self.balance = {3: [4.0, 1.0, 0.4]}.get(m.nl, [4.0, 1.0, 0.25, 0.06, 0.02])  # P3-P7
            self.ssi = list(m.stride).index(16) 
            self.BCEcls, self.BCEobj, self.gr, self.hyp, self.autobalance = BCEcls, BCEobj, 1.0, h, autobalance
            self.na = m.na  # number of anchors
            self.nc = m.nc  # number of classes
            self.nl = m.nl  # number of layers
            self.anchors = m.anchors
            self.device = device
    	
    	# 损失函数计算的核心函数,计算三种损失
    	def __call__(self, p, targets):  # predictions, targets
    		lcls = torch.zeros(1, device=self.device)  # class loss
            lbox = torch.zeros(1, device=self.device)  # box loss
            lobj = torch.zeros(1, device=self.device)  # object loss
            tcls, tbox, indices, anchors = self.build_targets(p, targets)  # targets
    		"""
    		此处省略代码,后续会继续解析该部分代码
    		"""
    	
    	# 扩增正样本的核心函数
    	def build_targets(self, p, targets):
    		"""
    		此处省略代码,后续会继续解析该部分代码
    		"""
    	
    
  2. 扩增正样本
      在目标检测中,通常背景(负样本)占据比例较大,为了降低正负样本的失衡程度,需要扩增正样本。在YOLO-V5的代码中,是在正样本的上,下,左,右四个位置进行扩增。 代码位置:~/yolov5/utils/loss.py,L177.
      在讲解扩增正样本代码之前,需要先直观上理解YOLO-V5中扩增正样本的核心思想。那么具体阅读代码时,也就更容易理解作者代码上的实现逻辑。大致的步骤如下,

    (1) 输入数据准备:扩增正样本是基于网络输出层,Anchor,真实标注框等输入信息进行处理,也是 build_targets() 函数的已知条件,代码片段如下,仔细阅读代码中的注释内容,

    def build_targets(self, p, targets):
        # Build targets for compute_loss(), input targets(image,class,x,y,w,h)
        na, nt = self.na, targets.shape[0]  # number of anchors, targets
        tcls, tbox, indices, anch = [], [], [], []
        gain = torch.ones(7, device=self.device)  # normalized to gridspace gain
        ai = torch.arange(na, device=self.device).float().view(na, 1).repeat(1, nt)  
        # same as .repeat_interleave(nt)
        
        targets = torch.cat((targets.repeat(na, 1, 1), ai[..., None]), 2)  # append anchor indices
    	
    	# ai: 用于给anchor赋予ID
        # ai.shape: [3,4], 每个尺度下有三个anchor, 索引为(0,1,2), 4表示输入图片中总共有4个标注框 
        # tensor([[0., 0., 0., 0.],
        #         [1., 1., 1., 1.],
        #         [2., 2., 2., 2.]], device='cuda:0')
                
        # targets: 真实标签,被复制三份([4,6]->[3,4,6]),便于和anchor进行运算,
    	# 与ai合并后,targets变为[3,4,7], 最后一个维度是anchors的索引,标记anchor和真实框的对应关系
    	# tensor([[[ 0.00000, 13.00000,  0.73501,  0.37763,  0.20976,  0.48214,  0.00000],
        #          [ 0.00000, 77.00000,  0.44276,  0.52145,  0.71813,  0.93628,  0.00000],
        #          [ 0.00000, 56.00000,  0.73559,  0.35948,  0.20149,  0.48509,  0.00000],
        #          [ 0.00000,  0.00000,  0.62385,  0.49260,  0.42815,  0.98520,  0.00000]],
    
        #         [[ 0.00000, 13.00000,  0.73501,  0.37763,  0.20976,  0.48214,  1.00000],
        #          [ 0.00000, 77.00000,  0.44276,  0.52145,  0.71813,  0.93628,  1.00000],
        #          [ 0.00000, 56.00000,  0.73559,  0.35948,  0.20149,  0.48509,  1.00000],
        #          [ 0.00000,  0.00000,  0.62385,  0.49260,  0.42815,  0.98520,  1.00000]],
    
        #         [[ 0.00000, 13.00000,  0.73501,  0.37763,  0.20976,  0.48214,  2.00000],
        #          [ 0.00000, 77.00000,  0.44276,  0.52145,  0.71813,  0.93628,  2.00000],
        #          [ 0.00000, 56.00000,  0.73559,  0.35948,  0.20149,  0.48509,  2.00000],
        #          [ 0.00000,  0.00000,  0.62385,  0.49260,  0.42815,  0.98520,  2.00000]]], 
        # device='cuda:0')
    

    (2) 根据 Anchor 和标注框的宽高比例筛选标注框: 网络输出三层的【shape】分别为【1,3,80,80,85】,【1,3,40,40,85】,【1,3,20,20,85】,这些输出应该与哪些标注框进行损失计算?三组不同尺度的【Anchor】依次从真实框中筛选出符合一定尺度比例的框{Box}集合,这是第一次进行框的筛选,这一步骤为了解决【真实标注框】应该归属那一层输出的问题。具体的公式表达如下图所示,
    在这里插入图片描述
    三组不同尺度的【Anchor】如下图所示,数值已经转换到特征图尺度下的大小,

    在这里插入图片描述
    下面的代码片段则是具体的实现代码,至此则完成每一组尺度下【Anchor】与真实框的匹配过程,

    # Matches
    r = t[..., 4:6] / anchors[:, None]  # wh ratio
    j = torch.max(r, 1 / r).max(2)[0] < self.hyp['anchor_t']  # compare
    # j = wh_iou(anchors, t[:, 4:6]) > model.hyp['iou_t']  # iou(3,n)=wh_iou(anchors(3,2), 
    # gwh(n,2))
    t = t[j]  # filter,筛选符合条件的边框
        
    # j: 表明第三个anchor匹配到2个真实框,
    # j: tensor([[False, False, False, False],
    #            [False, False, False, False],
    #            [ True, False,  True, False]], device='cuda:0')
        
    # t[j]: 过滤没有匹配上的真实框,剩下的真实框如下,最后一列的ID(2)表示与第2个anchor匹配成功2个标签框
    # 后续计算损失的时候,需要该ID取数据
    # tensor([[ 0.00000, 13.00000, 29.40045, 15.10509,  8.39034, 19.28541,  2.00000],
    #         [ 0.00000, 56.00000, 29.42351, 14.37940,  8.05945, 19.40353,  2.00000]]) 	
    

    (3)扩增样本总体概述:(2)解决了归属的问题,当前步骤是增加每个标注框的数量。 基于第一次筛选的{Box}集合,可以进行正样本扩增。在具体讲解正样本扩增代码之前,先直观上理解其基本的思想。扩增的方法,简单说就是每一个边框的中心点坐标更靠近【上、下、左、右】的哪一个Grid,比如下图中的蓝色点,假设坐标为【2.1,4.4】,显然更靠近左、上Grid,那么左、上红绿Grid就为扩增正样本所在Grid;如此,该正样本(黄色)则扩充了两个正样本(红色,绿色),增加了正样本的数量,具体参考下图,
    在这里插入图片描述

    (4) 接下来,从代码的层面,逐步分析正样本扩增的过程
      首先,确认正样本的可扩增方向。gxy是正样本本身的中心点坐标,用于指示【左、上】方向是否可偏移,gxi + gxy = 40(输出特征图尺寸),所以【gxy,gxi】的小数部分的和为1,那么必然是指示【右,下】方向是否可偏移。

    # Offsets
    gxy = t[:, 2:4]  # grid xy
    gxi = gain[[2, 3]] - gxy  # inverse
    j, k = ((gxy % 1 < g) & (gxy > 1)).T
    l, m = ((gxi % 1 < g) & (gxi > 1)).T
    j = torch.stack((torch.ones_like(j), j, k, l, m))
    t = t.repeat((5, 1, 1))[j]
    offsets = (torch.zeros_like(gxy)[None] + off[:, None])[j] 	
    

    参考下面的注释,通过L2-L5行的代码,得到了【左、上、右、下】四个方向是否可偏移。L6行代码则是将正样本本身的grid和其它四个方向的grid组合起来,便于统一运算。注意【j】的形状,行数表示5个grid,列数表示边框数量的一个方向(x或者y方向)。

    # (gxy % 1):取浮点数的小数部分,gxy>1(判断是否位于边界,边界不需要扩增)
        # tensor([[0.40045, 0.10509],
        #         [0.42351, 0.37940]], device='cuda:0')
    # gxy % 1 < g(0.5): 负责该真实框Grid的左上的Grid是否为扩增正样本 
        # tensor([[True, True],
        #         [True, True]], device='cuda:0')
        # 上面的值表明:两个框的中心均靠近左、上的Grid
    
    # gxi % 1:取浮点数的小数部分,负责该真实框Grid的右下的Grid是否为扩增正样本 
        # tensor([[0.59955, 0.89491],
        #         [0.57649, 0.62060]], device='cuda:0')
    # gxi % 1 < g(0.5)
        # tensor([[False, False],
        #         [False, False]], device='cuda:0')
    
    # j,k:负责指示当前Grid的左上的Grid,l,m:负责指示当前Grid的右下Grid
        # j:tensor([True, True], device='cuda:0') # 指示(2个)真实框的X方向都为true,即左侧可扩增
        # k:tensor([True, True], device='cuda:0') # 指示(2个)真实框的Y方向,即上侧可扩增
        # l:tensor([False, False], device='cuda:0') # 指示(2个)真实框的X方向,即右侧不可扩增
        # m:tensor([False, False], device='cuda:0') # 指示(2个)真实框的Y方向,即下侧不可扩增
                
    # j = torch.stack((torch.ones_like(j), j, k, l, m)): 【5,2】分别指示当前Grid,以及Grid的左上,右下
        # 是否需要增加样本
        # tensor([[ True,  True], # 2个真实框本身
        #         [ True,  True], # 2个真实框的左grid为扩增样本
        #         [ True,  True], # 2个真实框的上grid为扩增样本
        #         [False, False], # 2个真实框的右grid不是扩增样本
        #         [False, False]], device='cuda:0') # 2个真实框的下grid不是扩增样本
    

    根据合成的扩增指示数组【j】,L7行的代码用于获取正样本本身和扩增样本。如下注释内容:先将正样本复制相同的5份(5:依次是中,左,上,右,下四个grid);然后,根据【j】索引数据,获取有效的样本。

    # t.repeat((5, 1, 1)):将筛选后的正样本复制5份,便于与j(正样本扩增指示器)进行运算,留下所有的正样本
        # tensor([[[ 0.00000, 13.00000, 29.40045, 15.10509,  8.39034, 19.28541,  2.00000],
        #          [ 0.00000, 56.00000, 29.42351, 14.37940,  8.05945, 19.40353,  2.00000]],
    			  
        #         [[ 0.00000, 13.00000, 29.40045, 15.10509,  8.39034, 19.28541,  2.00000],
        #          [ 0.00000, 56.00000, 29.42351, 14.37940,  8.05945, 19.40353,  2.00000]],
    
        #         [[ 0.00000, 13.00000, 29.40045, 15.10509,  8.39034, 19.28541,  2.00000],
        #          [ 0.00000, 56.00000, 29.42351, 14.37940,  8.05945, 19.40353,  2.00000]],
    
        #         [[ 0.00000, 13.00000, 29.40045, 15.10509,  8.39034, 19.28541,  2.00000],
        #          [ 0.00000, 56.00000, 29.42351, 14.37940,  8.05945, 19.40353,  2.00000]],
    
        #         [[ 0.00000, 13.00000, 29.40045, 15.10509,  8.39034, 19.28541,  2.00000],
        #          [ 0.00000, 56.00000, 29.42351, 14.37940,  8.05945, 19.40353,  2.00000]]], 
        # device='cuda:0')
        
        # t.repeat((5, 1, 1))[j]:根据指示器j,取出样本本身和扩增样本
           # tensor([[ 0.00000, 13.00000, 29.40045, 15.10509,  8.39034, 19.28541,  2.00000],
           #         [ 0.00000, 56.00000, 29.42351, 14.37940,  8.05945, 19.40353,  2.00000],
           #         [ 0.00000, 13.00000, 29.40045, 15.10509,  8.39034, 19.28541,  2.00000],
           #         [ 0.00000, 56.00000, 29.42351, 14.37940,  8.05945, 19.40353,  2.00000],
           #         [ 0.00000, 13.00000, 29.40045, 15.10509,  8.39034, 19.28541,  2.00000],
           #         [ 0.00000, 56.00000, 29.42351, 14.37940,  8.05945, 19.40353,  2.00000]], 
           # device='cuda:0')
    

      其次,获取正样本的偏移量torch.zeros_like(gxy) 全为0,表示正样本本身保留一份,不进行偏移。其它的方向偏移量需要按照样本数量复制相应的份数。off:【5,2】,样本个数:2,所以,(torch.zeros_like(gxy)[None] + off[:, None])的形状为【5,2,2】。这里 off 的数值为0.5或者-0.5,是为了保证样本即使偏移后,也是靠近正样本本身。

    offsets = (torch.zeros_like(gxy)[None] + off[:, None])[j] 	
    
    # (torch.zeros_like(gxy)[None] + off[:, None]): torch.Size([5, 2, 2]),按照off的shape复制5份,也是为
    # 了便于和样本指示器【j】运算,中间的2是正样本的数量,最后的2表示x,y
            # tensor([[[ 0.00000,  0.00000],
            #          [ 0.00000,  0.00000]],
    
            #         [[ 0.50000,  0.00000],
            #          [ 0.50000,  0.00000]],
    
            #         [[ 0.00000,  0.50000],
            #          [ 0.00000,  0.50000]],
    
            #         [[-0.50000,  0.00000],
            #          [-0.50000,  0.00000]],
    
            #         [[ 0.00000, -0.50000],
            #          [ 0.00000, -0.50000]]], device='cuda:0')
                        
        # offsets:经过样本指示器筛选后,留下需要偏移的正样本
            # tensor([[0.00000, 0.00000], # 正样本本身1,无偏移
            #         [0.00000, 0.00000], # 正样本本身2,无偏移
            #         [0.50000, 0.00000], # 左扩增样本1-1
            #         [0.50000, 0.00000], # 左扩增样本2-1
            #         [0.00000, 0.50000], # 上扩增样本1-1
            #         [0.00000, 0.50000]], device='cuda:0') # 上扩增样本1-2
    

      然后,得到正样本的索引值。如下面的代码所示,L2-L3分解所有筛选剩下的正样本,得到图像和anchor的索引值。L4-L5得到左上角grid的整数索引值。

    # Define
    bc, gxy, gwh, a = t.chunk(4, 1)  # (image, class), grid xy, grid wh, anchors
    a, (b, c) = a.long().view(-1), bc.long().T  # anchors, image, class
    gij = (gxy - offsets).long()
    gi, gj = gij.T  # grid indices
    
    # gxy:
        # tensor([[29.40045, 15.10509],
        #         [29.42351, 14.37940],
        #         [29.40045, 15.10509],
        #         [29.42351, 14.37940],
        #         [29.40045, 15.10509],
        #         [29.42351, 14.37940]], device='cuda:0')
    
    
    # gij: 偏移后的 grid indice
        # tensor([[29, 15],
        #         [29, 14],
        #         [28, 15],
        #         [28, 14],
        #         [29, 14],
        #         [29, 13]], device='cuda:0')
    

      最后,要保存特定的变量值,用于后续损失函数的计算。indices保存的是索引值,用于从网络的输出层取有效的数据,计算损失值。tbox保存边框的值(x,y,w,h),anch保存具体的anchor数值,tcls具体的类别值

       # Append
       indices.append((b, a, gj.clamp_(0, shape[2] - 1), gi.clamp_(0, shape[3] - 1)))  # image, anchor, grid
       tbox.append(torch.cat((gxy - gij, gwh), 1))  # box
       anch.append(anchors[a])  # anchors
       tcls.append(c)  # class
    return tcls, tbox, indices, anch
    
    # b,a,gj,gi:根据4个记录的值,可以索引到具体grid的数据,用于后面计算损失
    # b:输入图像的索引
        # tensor([0, 0, 0, 0, 0, 0], device='cuda:0')
    # a:anchor的索引
        # tensor([2, 2, 2, 2, 2, 2], device='cuda:0')
    # gj.clamp_(0, shape[2] - 1)
        # tensor([15, 14, 15, 14, 14, 13], device='cuda:0')
    # gi.clamp_(0, shape[3] - 1)
        # tensor([29, 29, 28, 28, 29, 29], device='cuda:0')
    
    # gxy - gij:框和扩增框的中心坐标,相对grid左上角的偏移量
    # shape: [6,2], 每一个真实框对应三个正样本,总共6个正样本
        # tensor([[0.40045, 0.10509], # 正样本1
        #         [0.42351, 0.37940], # 正样本2
        #         [1.40045, 0.10509], # 扩增正样本1-1
        #         [1.42351, 0.37940], # 扩增正样本2-1
        #         [0.40045, 1.10509], # 扩增正样本1-2
        #         [0.42351, 1.37940]], device='cuda:0') # 扩增正样本2-2
    

    (5) 新样本的表达方式。真实标注框的表达为(x,y,w,h),其中x,y相对于本身Grid的左上角的偏移量,那么新的扩增框的标签应该如何表达?我们应该知道,新的框也是用来表示正样本本身,那么偏移量【x,y】应是相对于正样本本身。以(3)中的扩增正样本为例,新的正样本(1.1,0.4,w,h),(0.1,1.4,w,h),本身正样本(0.1,0.4,w,h);
      如此便得到了与正样本本身表达形式一样的扩增正样本。下图更为直观理解样本的扩增情况,
    在这里插入图片描述

    (6)数据的存储结构
      经过三次循环计算,可以得到 tcls, tbox, indices, anch四个存储数据的列表,列表的大小均为【3】,具体数值和形状如下陈述,

      第一次循环,[[ 1.25000, 1.62500],[ 2.00000, 3.75000],[ 4.12500, 2.87500]],尺度1下的Anchor与标签框匹配,匹配的结果如下图所示,表示并没有找到合适的标签框
    在这里插入图片描述

      第二次循环,[[ 1.87500, 3.81250],[ 3.87500, 2.81250],[ 3.68750, 7.43750]],尺度2下的Anchor与真实框匹配,结果如下图所示(包括扩增样本),匹配 6 个正样本。由于调试的时候,用的batch=1,所以 img index 均为0,
    在这里插入图片描述

      第三次循环,[[ 3.62500, 2.81250],[ 4.87500, 6.18750],[11.65625, 10.18750]],尺度3下的Anchor与真实框匹配,结果如下图所示,匹配 【30】个正样本,
    在这里插入图片描述
    在这里插入图片描述

  3. 损失函数

    (1) 整体概述
      经过正样本扩增后,我们可以得到每一个Anchor尺度下匹配的标注框,通常数量是多于原始的标注框。在【扩增正样本,(6) 数据的存储结构】小节中,我们打印出了正样本数据的存储结构。网络的三层输出形状如下,
          【1,3,80,80,85】
          【1,3,40,40,85】
          【1,3,20,20,85】
      分类损失:类别的损失,只计算正样本,使用二元交叉熵损失函数;
      定位损失:边框的损失,只计算正样本,使用CIOU作为损失函数;
      置信度损失:衡量一个grid内存在目标的概率【0~1】,正样本和负样本都计算置信度,只是计算时的标签不一样,负样本是0,正样本的是CIOU的值;

    损失函数计算的核心代码结构如下所示,依次处理网络输出的每一层,最后将损失汇总求和(仔细阅读注释内容),

    # Losses,网络输出层为三层,所以循环三次,依次计算损失
    for i, pi in enumerate(p):  # layer index, layer predictions
        b, a, gj, gi = indices[i]  # image, anchor, gridy, gridx
        tobj = torch.zeros(pi.shape[:4], dtype=pi.dtype, device=self.device)  # target obj
    
        n = b.shape[0]  # number of targets
        if n: # 正样本数量,没有正样本是不进行下面的损失相关的计算
            """
            省略代码,取出对应位置的预测数据
            """
            
            # Regression
            """
            省略代码,计算定位损失
            """
            
            # Objectness
            """
            计算IOU,用于后面计算置信度损失
            """
    		
    		# Classification
            """
            省略代码,生成one-hot标签,计算分类损失
            """
        
        # 即使没有正样本,仍需计算置信度损失,
        obji = self.BCEobj(pi[..., 4], tobj)
    
        # 为了改善不同样本的识别难度,每一层的置信度权重系数是不一样的【4,1,0.4】
        lobj += obji * self.balance[i]  # obj loss
        if self.autobalance:
            self.balance[i] = self.balance[i] * 0.9999 + 0.0001 / obji.detach().item()
    
    # 给每一个损失添加权重
    if self.autobalance:
        self.balance = [x / self.balance[self.ssi] for x in self.balance]
    lbox *= self.hyp['box']
    lobj *= self.hyp['obj']
    lcls *= self.hyp['cls']
    bs = tobj.shape[0]  # batch size
    
    return (lbox + lobj + lcls) * bs, torch.cat((lbox, lobj, lcls)).detach()
    
    

    (2) 第一次循环
      P3层的网络输出为【1,3,80,80,85】,而该层对应的正样本数量为【0】。此时不需要计算分类损失和定位损失,但是需要计算置信度损失。此时的二元交叉熵损失函数的标签是提前初始化为全零的矩阵【tobj】,其大小是与该层输出特征图的大小是一致的,每一个grid预测【3】个预测框,所以形状为 [1, 3, 80, 80]。

    # 即使没有正样本,仍需计算置信度损失,
    obji = self.BCEobj(pi[..., 4], tobj)
    # tobj 的形状为:torch.Size([1, 3, 80, 80])
    # pi[..., 4]的形状为:torch.Size([1, 3, 80, 80])
    
    # 为了改善不同尺寸目标的识别难度,每一层的置信度权重系数是不一样的【4,1,0.4】
    lobj += obji * self.balance[i]  # obj loss 	
    

      上述代码使用的是二元交叉熵损失函数计算,并且合并了sigmoid函数。针对不同的层,置信度损失分别赋予不同的权重,计算的结果如下,
    在这里插入图片描述

    (3) 第二次循环
      首先,取出提前保存的索引值。具体的值可以参考【扩增正样本,(6) 数据的存储结构】小节中第二次循环的打印结果。此时会再次初始化【tobj】为全零矩阵,形状为 torch.Size([1, 3, 40, 40]),与当前层输出特征图大小保持一致。

     b, a, gj, gi = indices[i]  # image, anchor, gridy, gridx
     tobj = torch.zeros(pi.shape[:4], dtype=pi.dtype, device=self.device)  # target obj 	
    

      其次,从预测的数据中取出有效数据。此时正样本数量【n=6】,然后从预测结果【pi】中取出有效的数据,所谓有效的数据,表示这个位置有正样本,a,b,gj,gi 就是正样本所在位置的索引。

    if n:
      # pxy, pwh, _, pcls = pi[b, a, gj, gi].tensor_split((2, 4, 5), dim=1)  # faster, requires torch 1.8.0
      pxy, pwh, _, pcls = pi[b, a, gj, gi].split((2, 2, 1, self.nc), 1)  # target-subset of predictions 	
    

      那么,如何从高维Tensor中取数据?如下图所示,依据【b】可以定位到batch的哪一个大的块,【a】可以定位到哪一个anchor块,【gj,gi】定位具体grid,
    在这里插入图片描述

      然后,计算损失
      定位损失:pxy与pwh的计算方式与YOLO-V3不一样,边框损失使用CIOU,

    # Regression 	 
    pxy = pxy.sigmoid() * 2 - 0.5 	 
    pwh =(pwh.sigmoid() * 2) ** 2 * anchors[i] 	 
    pbox = torch.cat((pxy, pwh),1)  # predicted box 	 
    iou = bbox_iou(pbox, tbox[i], CIoU=True).squeeze()  # iou(prediction, target) 	 
    lbox += (1.0 - iou).mean()  # iou loss 	
    

      置信度损失:这里用IOU值给【tobj】赋予置信度值,也是后面计算置信度损失的标签值,L7行代码如下,

    iou.detach().clamp(0).type(tobj.dtype) 	 
    if self.sort_obj_iou:
    	j = iou.argsort()
    	b, a, gj, gi, iou = b[j], a[j], gj[j], gi[j], iou[j] 	 
    if self.gr < 1:
    	iou = (1.0 - self.gr) + self.gr * iou 	 
    
    tobj[b, a, gj, gi] = iou  # iou ratio 	
    

      分类损失:用self.cn=0初始化t,形状为 torch.Size([6, 80]),代码L5行将类别id作为索引值,给【t】指定位置赋值为【1】,相当于制作【one-hot】标签。L6行代码调用二元交叉熵函数计算分类损失,

    # Classification 	
    if self.nc > 1:  # cls loss (only if multiple classes)
    	# 初始化0矩阵
    	t = torch.full_like(pcls, self.cn, device=self.device)  # targets
    	t[range(n), tcls[i]] = self.cp
    	lcls += self.BCEcls(pcls, t)  # BCE 
    

      上述过程完成了【正样本】的损失计算。下面继续计算置信度损失,【tobj】既有正样本又有负样本,正样本标签为【iou】值,负样本标签为【0】,

    obji = self.BCEobj(pi[..., 4], tobj) 	 
    lobj += obji * self.balance[i]  # obj loss 	
    

    (4) 第三次循环
      与第二次类似,略。

    (5) 最终结果
      三部分损失的权重是不太一样的,box=0.05,obj=1,cls=0.5. 最终的损失是三部分之和,并且乘以batch_size,代码如下

    lbox *= self.hyp['box'] 
    lobj *= self.hyp['obj'] 
    lcls *= self.hyp['cls'] 
    bs = tobj.shape[0]  # batch size
    
    return (lbox + lobj + lcls) * bs, torch.cat((lbox, lobj,lcls)).detach() 
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/126771.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Go-学生教务管理系统【无界面 / 离线版】(一)

【Go】学生教务管理系统&#xff08;无界面 / 离线版&#xff09;&#xff08;一&#xff09;Ⅰ. 程序说明一、博客日期二、引言Ⅱ. 版权声明Ⅲ. 开发环境一、开发配置二、开发工具Ⅳ. 效果演示一、工程结构&#xff08;一&#xff09;目录结构&#xff08;二&#xff09;目录说…

【Ctfer训练计划】——(六)

作者名&#xff1a;Demo不是emo 主页面链接&#xff1a;主页传送门 创作初心&#xff1a;舞台再大&#xff0c;你不上台&#xff0c;永远是观众&#xff0c;没人会关心你努不努力&#xff0c;摔的痛不痛&#xff0c;他们只会看你最后站在什么位置&#xff0c;然后羡慕或鄙夷座…

Springboot启动之自定义run方法

前言 之前分析的Springboot启动过程的源码分析中给自己留了一个扩展作业&#xff1a;执行自定义的run方法&#xff0c;此方法在B.7、调用运行器简单分析过&#xff0c;今天咱们就自定义一个Run方法试试。 一、实现自定义的run方法 由于java中的接口可以多实现&#xff0c;所以…

IOS----TangramKit 布局框架

文章目录系统结构CocoaPods安装举例下面一个应用场景:布局线性布局TGLinearLayout相对布局TGRelativeLayout框架布局TGFrameLayout表格布局TGTableLayout流式布局TGFlowLayout浮动布局TGFloatLayout路径布局MyPathLayoutgithub: https://github.com/youngsoft/TangramKit/blob…

day32【代码随想录】回溯之N皇后、N皇后||、解数独、有效的数独

文章目录前言一、N皇后&#xff08;力扣51&#xff09;二、N皇后||&#xff08;力扣52&#xff09;三、解数独&#xff08;力扣37&#xff09;四、有效的数独&#xff08;力扣36&#xff09;前言 1、N皇后、 2、N皇后||、 3、解数独、 4、有效的数独 一、N皇后&#xff08;力扣…

材料表征仪器:慢正电子束谱仪最全知识讲解

1 引言 21世纪科学的发展将是微观与宏观的相互渗透与密切结合。凝聚态物理、材料科学等的研究&#xff0c;将由现在的宏观统计方法&#xff08;包括宏观量子统计&#xff09;深入发展到物质的原子层次物性研究&#xff0c;微观粒子&#xff08;颗粒、孔隙&#xff09;的量子效…

求最大公约数,求阶乘,求n个n相乘的末两位数(Python)

问题 AN: 41.求最大公约数 题目描述 对于求两个正整数m&#xff0c;n的最大公约数可以用do-while实现 输入 输入两个正整数m&#xff0c;n 输出 最大公约数 样例输入 1 2 样例输出 1 a,b map(int,input().split()) if a<b:a,b b,a#python很方便的交换操作 #适应判断不带…

MySQL复制技术方案——异步复制配置

为MySQL服务器配置复制非常简单。但由于场景不同&#xff0c;基本的步骤还是有所差异。最基本的场景是新安装的主库和备库&#xff0c;总的来说分为以下几步∶ 1. 配置复制 为MySQL服务器配置复制非常简单。但由于场景不同&#xff0c;基本的步骤还是有所差异。最基本的场景是…

HTML5 Canvas

文章目录HTML5 Canvas概述Canvas元素使用绘制直线画2条直线用直线画三角形用直线画矩形绘制矩形描边矩形填充矩形混合使用rect()清空矩形区域清空画布绘制多边形画箭头画正三角形绘制圆形HTML5 Canvas 概述 HTML5新增了一个Canvas元素&#xff0c;我们常说的Canvas技术&#…

【LeetCode】填充每个节点的下一个右侧节点指针 [M](二叉树遍历)

116. 填充每个节点的下一个右侧节点指针 - 力扣&#xff08;LeetCode&#xff09; 一、题目 给定一个 完美二叉树 &#xff0c;其所有叶子节点都在同一层&#xff0c;每个父节点都有两个子节点。二叉树定义如下&#xff1a; struct Node { int val; Node *left; Node *…

通过webpack解决浏览器兼容问题

前言 很多面试时都会问到关于浏览器兼容问题&#xff0c;正好最近在看webpack打包&#xff0c;那就在这里记录一下我们如何通过webpack来实现兼容。 需求 要知道我们到底需要兼容那些浏览器&#xff0c;在这里就需要用到browserlist来配置需要兼容的浏览器版本并告诉webpack…

Jenkins自动部署项目

目录 1.安装插件 2.配置 本文只讲解通过插件来自动部署项目&#xff0c;Jenkins的安装可以看博主的另一篇文章&#xff0c;绝对保姆级&#xff0c;简洁丝滑的安装教程&#xff1a; jenkins下载安装__BugMan的博客-CSDN博客 1.安装插件 目前业内常用的解决方法是使用publish…

程序员必备十大网站

窝窝整理了十大程序猿必备网站&#xff0c;涵盖了开源平台、搜索引擎、免费的精品课程&#xff0c;包括让你头疼的BUG、算法等。偷偷告诉你&#xff0c;还有帮你拿到心仪的 offer&#xff01; 一&#xff0c;海量的资源平台 十大网站榜首 &#xff1a;GitHub — 开发者极其重…

图查询语言 nGQL 简明教程 vol.01 快速入门

本文旨在让新手快速了解 nGQL&#xff0c;掌握方向&#xff0c;之后可以脚踩在地上借助文档写出任何心中的 NebulaGraph 图查询。 视频 本教程的视频版在B站这里。 准备工作 在正式开始 nGQL 实操之前&#xff0c;记得先看过文档「快速入门流程」&#xff0c;部署、连接过 …

《小强升职记》读后感

为什么平庸&#xff1f; 成功的人每天都在忙碌&#xff0c;平庸的人每天也在忙碌&#xff0c;而时间对每个人来说是绝对公平的&#xff0c;那么&#xff0c;两者之间的差距到底是如何产生的呢? 第一&#xff0c;在这个人生的关键时期&#xff0c;我们被迫完成角色的转变。 …

C语言 字符函数和字符串函数及模拟实现

上图注意内容 代码解释如下 int main() {if (my_strlen("abc") - my_strlen("abcdef") > 0){printf(">\n");}else{printf("<\n");}return 0; }my_strlen int my_strlen(const char* str) {int count 0;//计数器assert(str…

Springboot AOP切面

文章目录SpringBoot Aop切面(Aop)一、什么是切面二、切面的用途三、AOP切面常用注解四、详细内容1、切面&#xff08;Aspect&#xff09;2、连接点&#xff08;Joinpoint&#xff09;3、通知&#xff08;Advice&#xff09;4.切入点&#xff08;Pointcut&#xff09;五、代码操…

PMP是什么?PMP证书有什么用?(含PMP资料)

PMP介绍 PMP的英文全称是Project Management Professional&#xff0c;中文全称叫做项目管理专业人士资格认证。 它是由美国项目管理协会(PMI)在全球范围内推出的针对项目经理的资格认证体系&#xff0c;严格评估项目管理人员知识技能是否具有高品质的资格认证考试&#xff0…

学习:如何使用Axure制作网站、app结构图

​“老师&#xff0c;axure里面可以制作网站的结构图吗&#xff1f;” “结构图只能在脑图工具中才能画吗&#xff1f;能不能直接在axure里面直接画&#xff1f;” “结构图到底需不需要做&#xff1f;对我来说好像没什么用呢” 在同学们的学习当中&#xff0c;有部分同学对…

CIO你好,现在是时候我们来谈一下“去”中台的问题了

去中台是个什么鬼 去中台&#xff1f;要不我去XXXXXXX 首先&#xff0c;你看到这个标题有没有懵圈&#xff1f;是不是有一种。。。 的感觉&#xff1f; 不过我现在告诉你&#xff0c;你没有看错。这篇文章是正儿八经的来谈“去”中台的。只不过这个“去”字&#xff0c;打着…