Yolov1 源码讲解 loss.py

这段代码详细展示了YOLO(YouOnlyLookOnce)目标检测模型中的损失函数计算过程,包括有目标和无目标的像素处理,以及预测框与真实框的IoU计算,用于优化网络训练。主要涉及坐标计算、交并比、平方差损失(MSE)以及损失加权等概念。

结构

1.lt rb我觉得不是很合适 正确来说是lb rt 因为比较出来的都是左下和右上坐标

比如前两个,都是max出来的 选两个box左下坐标中最大的,  后两个则是右上坐标中最小的 那也就形成了交集面积   

但是代码中仍然是lt rb我也就直接这样说

而算出lt和rb之后 算他们差可以算出高宽,只要没有交集 w或者h必定为负,可以画图验证一下

后面就是普通的iou算法

    def compute_iou(self, bbox1, bbox2):
        """ Compute the IoU (Intersection over Union) of two set of bboxes, each bbox format: [x1, y1, x2, y2].
        Args:
            bbox1: (Tensor) bounding bboxes, sized [N, 4].
            bbox2: (Tensor) bounding bboxes, sized [M, 4].
        Returns:
            (Tensor) IoU, sized [N, M].
        """
        N = bbox1.size(0)
        M = bbox2.size(0)

        # Compute left-top coordinate of the intersections
        lt = torch.max(
            bbox1[:, :2].unsqueeze(1).expand(N, M, 2), # [N, 2] -> [N, 1, 2] -> [N, M, 2]
            bbox2[:, :2].unsqueeze(0).expand(N, M, 2)  # [M, 2] -> [1, M, 2] -> [N, M, 2]
        )
        # Conpute right-bottom coordinate of the intersections
        rb = torch.min(
            bbox1[:, 2:].unsqueeze(1).expand(N, M, 2), # [N, 2] -> [N, 1, 2] -> [N, M, 2]
            bbox2[:, 2:].unsqueeze(0).expand(N, M, 2)  # [M, 2] -> [1, M, 2] -> [N, M, 2]
        )
        # Compute area of the intersections from the coordinates
        wh = rb - lt   # width and height of the intersection, [N, M, 2]
        wh[wh < 0] = 0 # clip at 0
        inter = wh[:, :, 0] * wh[:, :, 1] # [N, M]

        # Compute area of the bboxes
        area1 = (bbox1[:, 2] - bbox1[:, 0]) * (bbox1[:, 3] - bbox1[:, 1]) # [N, ]
        area2 = (bbox2[:, 2] - bbox2[:, 0]) * (bbox2[:, 3] - bbox2[:, 1]) # [M, ]
        area1 = area1.unsqueeze(1).expand_as(inter) # [N, ] -> [N, 1] -> [N, M]
        area2 = area2.unsqueeze(0).expand_as(inter) # [M, ] -> [1, M] -> [N, M]

        # Compute IoU from the areas
        union = area1 + area2 - inter # [N, M, 2]
        iou = inter / union           # [N, M, 2]

        return iou

2.比较难的部分也就是重头戏

        coord_mask = target_tensor[..., 4] > 0 #三个点自动判断维度 自动找到最后一维 用4找出第五个 也就是置信度,为什么30维 第二个框是怎么样的 等下再看
        #没有目标的张量[n_batch, S, S]
        noobj_mask = target_tensor[..., 4] == 0 
        #扩展维度的布尔值相同,[n_batch, S, S] -> [n_batch, S, S, N]
        coord_mask = coord_mask.unsqueeze(-1).expand_as(target_tensor)  
        noobj_mask = noobj_mask.unsqueeze(-1).expand_as(target_tensor)  

target中具有真实的置信度 有就是1没有就是0   而不是训练时的train值 train值是从0-1模糊的与网络输出值     

而这里的置信度赋值是从voc的encode方法中赋值的

这里筛选出存在物体与不存在物体的部分分别为coord_mask和noobj_mask,大小也分别为(batch_size,S,S) 意思是这个batch批次中,这个像素是否存在物体, 值为True False

扩充维数,使之对应(batch_size,S,S,30)  30维度

而负责的话这30维全为true或者不负责全为false

下一部分

        #预测值里含有目标的张量取出来,[n_coord, N] view类似于reshape 这里可以当作reshape看 就是变形
        coord_pred = pred_tensor[coord_mask].view(-1, N)        
        
        #提取bbox和C,[n_coord x B, 5=len([x, y, w, h, conf])]
        bbox_pred = coord_pred[:, :5*B].contiguous().view(-1, 5)   #防止内存不连续报错
        # 预测值的分类信息[n_coord, C]
        class_pred = coord_pred[:, 5*B:]                            

        #含有目标的标签张量,[n_coord, N]
        coord_target = target_tensor[coord_mask].view(-1, N)        
        
        #提取标签bbox和C,[n_coord x B, 5=len([x, y, w, h, conf])]
        bbox_target = coord_target[:, :5*B].contiguous().view(-1, 5) 
        #标签的分类信息
        class_target = coord_target[:, 5*B:]                        

 从网络中输出的预测张量中,这里叫他预测值(但这只是训练网络输出的预测值,而不是detect的预测值),我们在target中所有的含有物体的像素的地方,从预测中取出来,叫coord_pred

coord_pred也就是对应着真实存在的像素的张量,下面把他分割出bbox和class出来。

target也分割出bbox和class出来用于待会比较 切成10和20长度

ps:coord_pred的是取出结果后将前面三个维度拉平了, 只留最后一个维度N也就是30,

以整体来看pred_tensor.view(-1, N) 形状是(batch_size*S*S,N)  coord_pred就是取出来之后的类比于这个形状的张量 (所有batch中各自对应的图片中所有含有物体的像素的cell,N),人话来说就是所有batch中真实框个数总和

#没有目标的处理
        #找到预测值里没有目标的网格张量[n_noobj, N],n_noobj=SxS-n_coord
        noobj_pred = pred_tensor[noobj_mask].view(-1, N)         
        #标签的没有目标的网格张量 [n_noobj, N]                                                     
        noobj_target = target_tensor[noobj_mask].view(-1, N)            
        
        noobj_conf_mask = torch.cuda.BoolTensor(noobj_pred.size()).fill_(0) # [n_noobj, N]
        for b in range(B):
            noobj_conf_mask[:, 4 + b*5] = 1 # 没有目标置信度置1,noobj_conf_mask[:, 4] = 1; noobj_conf_mask[:, 9] = 1 目标是下面把置信度拿出来再并排
        

        noobj_pred_conf = noobj_pred[noobj_conf_mask]       # [n_noobj x 2=len([conf1, conf2])] 这里目标是
        noobj_target_conf = noobj_target[noobj_conf_mask]   # [n_noobj x 2=len([conf1, conf2])]
        #计算没有目标的置信度损失 加法》? #如果 reduction 参数未指定,默认值为 'mean',表示对所有元素的误差求平均值。
        #loss_noobj=F.mse_loss(noobj_pred_conf, noobj_target_conf,)*len(noobj_pred_conf)
        loss_noobj = F.mse_loss(noobj_pred_conf, noobj_target_conf, reduction='sum')

这里是取出所有不含物体的预测张量的部分,那个for循环是提前把位置赋予1标出来 后面用来提取找出这部分。

找出对应两个部分置信度之后做mse,平方差损失得到 不负责物体的像素置信度的损失,实际上是(0-预测出来的置信度)^2 论文中还要带个权重, 原因是不负责的像素太多了为了公平性,基于比较低的权重

        coord_response_mask = torch.cuda.BoolTensor(bbox_target.size()).fill_(0)    # [n_coord x B, 5]
        coord_not_response_mask = torch.cuda.BoolTensor(bbox_target.size()).fill_(1)# [n_coord x B, 5]
        bbox_target_iou = torch.zeros(bbox_target.size()).cuda()                    # [n_coord x B, 5], only the last 1=(conf,) is used

初始化下面循环需要用到的变量

        for i in range(0, bbox_target.size(0), B):
            pred = bbox_pred[i:i+B] # predicted bboxes at i-th cell, [B, 5=len([x, y, w, h, conf])]
            pred_xyxy = Variable(torch.FloatTensor(pred.size())) # [B, 5=len([x1, y1, x2, y2, conf])]
            # Because (center_x,center_y)=pred[:, 2] and (w,h)=pred[:,2:4] are normalized for cell-size and image-size respectively,
            # rescale (center_x,center_y) for the image-size to compute IoU correctly.
            pred_xyxy[:,  :2] = pred[:, :2]/float(S) - 0.5 * pred[:, 2:4]
            pred_xyxy[:, 2:4] = pred[:, :2]/float(S) + 0.5 * pred[:, 2:4]

            target = bbox_target[i] # target bbox at i-th cell. Because target boxes contained by each cell are identical in current implementation, enough to extract the first one.
            target = bbox_target[i].view(-1, 5) # target bbox at i-th cell, [1, 5=len([x, y, w, h, conf])]
            target_xyxy = Variable(torch.FloatTensor(target.size())) # [1, 5=len([x1, y1, x2, y2, conf])]
            # Because (center_x,center_y)=target[:, 2] and (w,h)=target[:,2:4] are normalized for cell-size and image-size respectively,
            # rescale (center_x,center_y) for the image-size to compute IoU correctly.
            target_xyxy[:,  :2] = target[:, :2]/float(S) - 0.5 * target[:, 2:4]
            target_xyxy[:, 2:4] = target[:, :2]/float(S) + 0.5 * target[:, 2:4]

            iou = self.compute_iou(pred_xyxy[:, :4], target_xyxy[:, :4]) # [B, 1]
            max_iou, max_index = iou.max(0)
            max_index = max_index.data.cuda()

            coord_response_mask[i+max_index] = 1
            coord_not_response_mask[i+max_index] = 0

            # "we want the confidence score to equal the intersection over union (IOU) between the predicted box and the ground truth"
            # from the original paper of YOLO.
            bbox_target_iou[i+max_index, torch.LongTensor([4]).cuda()] = (max_iou).data.cuda()

训练是进行预测框中每两个循环一次的比较,

预测值两个框是独立不一样的,而真实值target中在voc我们给予两个框一样的值。

pred_xyxy[:,  :2] = pred[:, :2]/float(S) - 0.5 * pred[:, 2:4]
pred_xyxy[:, 2:4] = pred[:, :2]/float(S) + 0.5 * pred[:, 2:4]但是这里除以S,我也没搞懂 我就先跳了

这里preds和target数量是一一对应的

而pred和target中坐标分别为 中心x和y还有w和h,target这四个都是相对图片归一化的 可以从voc解析中知道,而pred则是网络预测的随机值

iou需要传入的参数是左上,右下坐标 这里需要计算 

于是在某次循环中,得出最大iou,将那个iou的索引,也就是iou最大是第几个框拿出来在coord_response_mask 和coord_notresponse_mask,这两个变量形状完全与bbox相同

bbox_target_iou在gpu存下结果,用于下面计算损失,循环结束

下面是循环外内容

        bbox_target_iou = Variable(bbox_target_iou).cuda()

        # BBox location/size and objectness loss for the response bboxes.
        bbox_pred_response = bbox_pred[coord_response_mask].view(-1, 5)      # [n_response, 5]
        bbox_target_response = bbox_target[coord_response_mask].view(-1, 5)  # [n_response, 5], only the first 4=(x, y, w, h) are used
        target_iou = bbox_target_iou[coord_response_mask].view(-1, 5)        # [n_response, 5], only the last 1=(conf,) is used
        loss_xy = F.mse_loss(bbox_pred_response[:, :2], bbox_target_response[:, :2], reduction='sum')
        loss_wh = F.mse_loss(torch.sqrt(bbox_pred_response[:, 2:4]), torch.sqrt(bbox_target_response[:, 2:4]), reduction='sum')
        loss_obj = F.mse_loss(bbox_pred_response[:, 4], target_iou[:, 4], reduction='sum')

        ################################################################################


        # Class probability loss for the cells which contain objects.
        loss_class = F.mse_loss(class_pred, class_target, reduction='sum')

        # Total loss
        loss = self.lambda_coord * (loss_xy + loss_wh) + loss_obj + self.lambda_noobj * loss_noobj + loss_class
        loss = loss / float(batch_size)

负责物体部分的损失 

xy坐标求损失,因为含物体的像素少,论文中给予权重5加大损失倾向,

wh求损失,因为大框和小框之间差会非常大,容易造成损失,所以论文中加入根号,使之差不会过于太大,(大框差的值加在小框上是好几倍甚至几十倍这是无法容忍的),这里也因为像素少,给予权重5

置信度损失,此处网络输出出来的预测值是随机的,论文的想法是应该逼近她所应该位于的值,也就是真实的置信度。他位置在哪就应该给予在哪的置信度。这里体现在与真实框的iou交并比。但是yolov3中将这个与真实框的iou交并比的标签值换成了1,不懂这里也不用纠结。

这里是单纯计算损失  计算方法保持论文中提到的要求,mse_loss也是默认参数不给的话会自动求平均,加个参数sum单纯求平方差的和

返回的loss最终是除以batch_size的一个平均值

    def forward(self, pred_tensor, target_tensor):#target_tensor[2,0,0,:]
        """ Compute loss for YOLO training. #
        Args:
            pred_tensor: (Tensor) predictions, sized [n_batch, S, S, Bx5+C], 5=len([x, y, w, h, conf]).
            target_tensor: (Tensor) targets, sized [n_batch, S, S, Bx5+C].
        Returns:
            (Tensor): loss, sized [1, ].
        """
        # TODO: Romove redundant dimensions for some Tensors.
        #获取网格参数S=7,每个网格预测的边框数目B=2,和分类数C=20
        S, B, C = self.S, self.B, self.C
        N = 5 * B + C    # 5=len([x, y, w, h, conf],N=30

        #批的大小
        batch_size = pred_tensor.size(0)
        #有目标的张量[n_batch, S, S]
        coord_mask = target_tensor[..., 4] > 0 #三个点自动判断维度 自动找到最后一维 用4找出第五个 也就是置信度,为什么30维 第二个框是怎么样的 等下再看
        #没有目标的张量[n_batch, S, S]
        noobj_mask = target_tensor[..., 4] == 0 
        #扩展维度的布尔值相同,[n_batch, S, S] -> [n_batch, S, S, N]
        coord_mask = coord_mask.unsqueeze(-1).expand_as(target_tensor)  
        noobj_mask = noobj_mask.unsqueeze(-1).expand_as(target_tensor)  

        #int8-->bool
        noobj_mask = noobj_mask.bool()  #不是已经bool了?
        coord_mask = coord_mask.bool()  

        ##################################################
        #预测值里含有目标的张量取出来,[n_coord, N]
        coord_pred = pred_tensor[coord_mask].view(-1, N)        
        
        #提取bbox和C,[n_coord x B, 5=len([x, y, w, h, conf])]
        bbox_pred = coord_pred[:, :5*B].contiguous().view(-1, 5)   #防止内存不连续报错
        # 预测值的分类信息[n_coord, C]
        class_pred = coord_pred[:, 5*B:]                            

        #含有目标的标签张量,[n_coord, N]
        coord_target = target_tensor[coord_mask].view(-1, N)        
        
        #提取标签bbox和C,[n_coord x B, 5=len([x, y, w, h, conf])]
        bbox_target = coord_target[:, :5*B].contiguous().view(-1, 5) 
        #标签的分类信息
        class_target = coord_target[:, 5*B:]                         
        ######################################################

        # ##################################################
        #没有目标的处理
        #找到预测值里没有目标的网格张量[n_noobj, N],n_noobj=SxS-n_coord
        noobj_pred = pred_tensor[noobj_mask].view(-1, N)         
        #标签的没有目标的网格张量 [n_noobj, N]                                                     
        noobj_target = target_tensor[noobj_mask].view(-1, N)            
        
        noobj_conf_mask = torch.cuda.BoolTensor(noobj_pred.size()).fill_(0) # [n_noobj, N]
        for b in range(B):
            noobj_conf_mask[:, 4 + b*5] = 1 # 没有目标置信度置1,noobj_conf_mask[:, 4] = 1; noobj_conf_mask[:, 9] = 1 目标是下面把置信度拿出来再并排
        

        noobj_pred_conf = noobj_pred[noobj_conf_mask]       # [n_noobj x 2=len([conf1, conf2])] 这里目标是
        noobj_target_conf = noobj_target[noobj_conf_mask]   # [n_noobj x 2=len([conf1, conf2])]
        #计算没有目标的置信度损失 加法》? #如果 reduction 参数未指定,默认值为 'mean',表示对所有元素的误差求平均值。
        #loss_noobj=F.mse_loss(noobj_pred_conf, noobj_target_conf,)*len(noobj_pred_conf)
        loss_noobj = F.mse_loss(noobj_pred_conf, noobj_target_conf, reduction='sum')
        #################################################################################

        #################################################################################
        # Compute loss for the cells with objects.
        coord_response_mask = torch.cuda.BoolTensor(bbox_target.size()).fill_(0)    # [n_coord x B, 5]
        coord_not_response_mask = torch.cuda.BoolTensor(bbox_target.size()).fill_(1)# [n_coord x B, 5]
        bbox_target_iou = torch.zeros(bbox_target.size()).cuda()                    # [n_coord x B, 5], only the last 1=(conf,) is used

        # Choose the predicted bbox having the highest IoU for each target bbox.
        for i in range(0, bbox_target.size(0), B):
            pred = bbox_pred[i:i+B] # predicted bboxes at i-th cell, [B, 5=len([x, y, w, h, conf])]
            pred_xyxy = Variable(torch.FloatTensor(pred.size())) # [B, 5=len([x1, y1, x2, y2, conf])]
            # Because (center_x,center_y)=pred[:, 2] and (w,h)=pred[:,2:4] are normalized for cell-size and image-size respectively,
            # rescale (center_x,center_y) for the image-size to compute IoU correctly.
            pred_xyxy[:,  :2] = pred[:, :2]/float(S) - 0.5 * pred[:, 2:4]
            pred_xyxy[:, 2:4] = pred[:, :2]/float(S) + 0.5 * pred[:, 2:4]

            target = bbox_target[i] # target bbox at i-th cell. Because target boxes contained by each cell are identical in current implementation, enough to extract the first one.
            target = bbox_target[i].view(-1, 5) # target bbox at i-th cell, [1, 5=len([x, y, w, h, conf])]
            target_xyxy = Variable(torch.FloatTensor(target.size())) # [1, 5=len([x1, y1, x2, y2, conf])]
            # Because (center_x,center_y)=target[:, 2] and (w,h)=target[:,2:4] are normalized for cell-size and image-size respectively,
            # rescale (center_x,center_y) for the image-size to compute IoU correctly.
            target_xyxy[:,  :2] = target[:, :2]/float(S) - 0.5 * target[:, 2:4]
            target_xyxy[:, 2:4] = target[:, :2]/float(S) + 0.5 * target[:, 2:4]

            iou = self.compute_iou(pred_xyxy[:, :4], target_xyxy[:, :4]) # [B, 1]
            max_iou, max_index = iou.max(0)
            max_index = max_index.data.cuda()

            coord_response_mask[i+max_index] = 1
            coord_not_response_mask[i+max_index] = 0

            # "we want the confidence score to equal the intersection over union (IOU) between the predicted box and the ground truth"
            # from the original paper of YOLO.
            bbox_target_iou[i+max_index, torch.LongTensor([4]).cuda()] = (max_iou).data.cuda()
        bbox_target_iou = Variable(bbox_target_iou).cuda()

        # BBox location/size and objectness loss for the response bboxes.
        bbox_pred_response = bbox_pred[coord_response_mask].view(-1, 5)      # [n_response, 5]
        bbox_target_response = bbox_target[coord_response_mask].view(-1, 5)  # [n_response, 5], only the first 4=(x, y, w, h) are used
        target_iou = bbox_target_iou[coord_response_mask].view(-1, 5)        # [n_response, 5], only the last 1=(conf,) is used
        loss_xy = F.mse_loss(bbox_pred_response[:, :2], bbox_target_response[:, :2], reduction='sum')
        loss_wh = F.mse_loss(torch.sqrt(bbox_pred_response[:, 2:4]), torch.sqrt(bbox_target_response[:, 2:4]), reduction='sum')
        loss_obj = F.mse_loss(bbox_pred_response[:, 4], target_iou[:, 4], reduction='sum')

        ################################################################################


        # Class probability loss for the cells which contain objects.
        loss_class = F.mse_loss(class_pred, class_target, reduction='sum')

        # Total loss
        loss = self.lambda_coord * (loss_xy + loss_wh) + loss_obj + self.lambda_noobj * loss_noobj + loss_class
        loss = loss / float(batch_size)

        return loss

 

总的

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable


class Loss(nn.Module):

    def __init__(self, feature_size=7, num_bboxes=2, num_classes=20, lambda_coord=5.0, lambda_noobj=0.5):
        """ Constructor.
        Args:
            feature_size: (int) size of input feature map.
            num_bboxes: (int) number of bboxes per each cell.
            num_classes: (int) number of the object classes.
            lambda_coord: (float) weight for bbox location/size losses.
            lambda_noobj: (float) weight for no-objectness loss.
        """
        super(Loss, self).__init__()

        self.S = feature_size
        self.B = num_bboxes
        self.C = num_classes
        self.lambda_coord = lambda_coord
        self.lambda_noobj = lambda_noobj


    def compute_iou(self, bbox1, bbox2):
        """ Compute the IoU (Intersection over Union) of two set of bboxes, each bbox format: [x1, y1, x2, y2].
        Args:
            bbox1: (Tensor) bounding bboxes, sized [N, 4].
            bbox2: (Tensor) bounding bboxes, sized [M, 4].
        Returns:
            (Tensor) IoU, sized [N, M].
        """
        N = bbox1.size(0)
        M = bbox2.size(0)

        # Compute left-top coordinate of the intersections
        lt = torch.max(
            bbox1[:, :2].unsqueeze(1).expand(N, M, 2), # [N, 2] -> [N, 1, 2] -> [N, M, 2]
            bbox2[:, :2].unsqueeze(0).expand(N, M, 2)  # [M, 2] -> [1, M, 2] -> [N, M, 2]
        )
        # Conpute right-bottom coordinate of the intersections
        rb = torch.min(
            bbox1[:, 2:].unsqueeze(1).expand(N, M, 2), # [N, 2] -> [N, 1, 2] -> [N, M, 2]
            bbox2[:, 2:].unsqueeze(0).expand(N, M, 2)  # [M, 2] -> [1, M, 2] -> [N, M, 2]
        )
        # Compute area of the intersections from the coordinates
        wh = rb - lt   # width and height of the intersection, [N, M, 2]
        wh[wh < 0] = 0 # clip at 0
        inter = wh[:, :, 0] * wh[:, :, 1] # [N, M]

        # Compute area of the bboxes
        area1 = (bbox1[:, 2] - bbox1[:, 0]) * (bbox1[:, 3] - bbox1[:, 1]) # [N, ]
        area2 = (bbox2[:, 2] - bbox2[:, 0]) * (bbox2[:, 3] - bbox2[:, 1]) # [M, ]
        area1 = area1.unsqueeze(1).expand_as(inter) # [N, ] -> [N, 1] -> [N, M]
        area2 = area2.unsqueeze(0).expand_as(inter) # [M, ] -> [1, M] -> [N, M]

        # Compute IoU from the areas
        union = area1 + area2 - inter # [N, M, 2]
        iou = inter / union           # [N, M, 2]

        return iou

    def forward(self, pred_tensor, target_tensor):#target_tensor[2,0,0,:]
        """ Compute loss for YOLO training. #
        Args:
            pred_tensor: (Tensor) predictions, sized [n_batch, S, S, Bx5+C], 5=len([x, y, w, h, conf]).
            target_tensor: (Tensor) targets, sized [n_batch, S, S, Bx5+C].
        Returns:
            (Tensor): loss, sized [1, ].
        """
        # TODO: Romove redundant dimensions for some Tensors.
        #获取网格参数S=7,每个网格预测的边框数目B=2,和分类数C=20
        S, B, C = self.S, self.B, self.C
        N = 5 * B + C    # 5=len([x, y, w, h, conf],N=30

        #批的大小
        batch_size = pred_tensor.size(0)
        #有目标的张量[n_batch, S, S]
        coord_mask = target_tensor[..., 4] > 0 #三个点自动判断维度 自动找到最后一维 用4找出第五个 也就是置信度,为什么30维 第二个框是怎么样的 等下再看
        #没有目标的张量[n_batch, S, S]
        noobj_mask = target_tensor[..., 4] == 0 
        #扩展维度的布尔值相同,[n_batch, S, S] -> [n_batch, S, S, N]
        coord_mask = coord_mask.unsqueeze(-1).expand_as(target_tensor)  
        noobj_mask = noobj_mask.unsqueeze(-1).expand_as(target_tensor)  

        #int8-->bool
        noobj_mask = noobj_mask.bool()  #不是已经bool了?
        coord_mask = coord_mask.bool()  

        ##################################################
        #预测值里含有目标的张量取出来,[n_coord, N]
        coord_pred = pred_tensor[coord_mask].view(-1, N)        
        
        #提取bbox和C,[n_coord x B, 5=len([x, y, w, h, conf])]
        bbox_pred = coord_pred[:, :5*B].contiguous().view(-1, 5)   #防止内存不连续报错
        # 预测值的分类信息[n_coord, C]
        class_pred = coord_pred[:, 5*B:]                            

        #含有目标的标签张量,[n_coord, N]
        coord_target = target_tensor[coord_mask].view(-1, N)        
        
        #提取标签bbox和C,[n_coord x B, 5=len([x, y, w, h, conf])]
        bbox_target = coord_target[:, :5*B].contiguous().view(-1, 5) 
        #标签的分类信息
        class_target = coord_target[:, 5*B:]                         
        ######################################################

        # ##################################################
        #没有目标的处理
        #找到预测值里没有目标的网格张量[n_noobj, N],n_noobj=SxS-n_coord
        noobj_pred = pred_tensor[noobj_mask].view(-1, N)         
        #标签的没有目标的网格张量 [n_noobj, N]                                                     
        noobj_target = target_tensor[noobj_mask].view(-1, N)            
        
        noobj_conf_mask = torch.cuda.BoolTensor(noobj_pred.size()).fill_(0) # [n_noobj, N]
        for b in range(B):
            noobj_conf_mask[:, 4 + b*5] = 1 # 没有目标置信度置1,noobj_conf_mask[:, 4] = 1; noobj_conf_mask[:, 9] = 1 目标是下面把置信度拿出来再并排
        

        noobj_pred_conf = noobj_pred[noobj_conf_mask]       # [n_noobj x 2=len([conf1, conf2])] 这里目标是
        noobj_target_conf = noobj_target[noobj_conf_mask]   # [n_noobj x 2=len([conf1, conf2])]
        #计算没有目标的置信度损失 加法》? #如果 reduction 参数未指定,默认值为 'mean',表示对所有元素的误差求平均值。
        #loss_noobj=F.mse_loss(noobj_pred_conf, noobj_target_conf,)*len(noobj_pred_conf)
        loss_noobj = F.mse_loss(noobj_pred_conf, noobj_target_conf, reduction='sum')
        #################################################################################

        #################################################################################
        # Compute loss for the cells with objects.
        coord_response_mask = torch.cuda.BoolTensor(bbox_target.size()).fill_(0)    # [n_coord x B, 5]
        coord_not_response_mask = torch.cuda.BoolTensor(bbox_target.size()).fill_(1)# [n_coord x B, 5]
        bbox_target_iou = torch.zeros(bbox_target.size()).cuda()                    # [n_coord x B, 5], only the last 1=(conf,) is used

        # Choose the predicted bbox having the highest IoU for each target bbox.
        for i in range(0, bbox_target.size(0), B):
            pred = bbox_pred[i:i+B] # predicted bboxes at i-th cell, [B, 5=len([x, y, w, h, conf])]
            pred_xyxy = Variable(torch.FloatTensor(pred.size())) # [B, 5=len([x1, y1, x2, y2, conf])]
            # Because (center_x,center_y)=pred[:, 2] and (w,h)=pred[:,2:4] are normalized for cell-size and image-size respectively,
            # rescale (center_x,center_y) for the image-size to compute IoU correctly.
            pred_xyxy[:,  :2] = pred[:, :2]/float(S) - 0.5 * pred[:, 2:4]
            pred_xyxy[:, 2:4] = pred[:, :2]/float(S) + 0.5 * pred[:, 2:4]

            target = bbox_target[i] # target bbox at i-th cell. Because target boxes contained by each cell are identical in current implementation, enough to extract the first one.
            target = bbox_target[i].view(-1, 5) # target bbox at i-th cell, [1, 5=len([x, y, w, h, conf])]
            target_xyxy = Variable(torch.FloatTensor(target.size())) # [1, 5=len([x1, y1, x2, y2, conf])]
            # Because (center_x,center_y)=target[:, 2] and (w,h)=target[:,2:4] are normalized for cell-size and image-size respectively,
            # rescale (center_x,center_y) for the image-size to compute IoU correctly.
            target_xyxy[:,  :2] = target[:, :2]/float(S) - 0.5 * target[:, 2:4]
            target_xyxy[:, 2:4] = target[:, :2]/float(S) + 0.5 * target[:, 2:4]

            iou = self.compute_iou(pred_xyxy[:, :4], target_xyxy[:, :4]) # [B, 1]
            max_iou, max_index = iou.max(0)
            max_index = max_index.data.cuda()

            coord_response_mask[i+max_index] = 1
            coord_not_response_mask[i+max_index] = 0

            # "we want the confidence score to equal the intersection over union (IOU) between the predicted box and the ground truth"
            # from the original paper of YOLO.
            bbox_target_iou[i+max_index, torch.LongTensor([4]).cuda()] = (max_iou).data.cuda()
        bbox_target_iou = Variable(bbox_target_iou).cuda()

        # BBox location/size and objectness loss for the response bboxes.
        bbox_pred_response = bbox_pred[coord_response_mask].view(-1, 5)      # [n_response, 5]
        bbox_target_response = bbox_target[coord_response_mask].view(-1, 5)  # [n_response, 5], only the first 4=(x, y, w, h) are used
        target_iou = bbox_target_iou[coord_response_mask].view(-1, 5)        # [n_response, 5], only the last 1=(conf,) is used
        loss_xy = F.mse_loss(bbox_pred_response[:, :2], bbox_target_response[:, :2], reduction='sum')
        loss_wh = F.mse_loss(torch.sqrt(bbox_pred_response[:, 2:4]), torch.sqrt(bbox_target_response[:, 2:4]), reduction='sum')
        loss_obj = F.mse_loss(bbox_pred_response[:, 4], target_iou[:, 4], reduction='sum')

        ################################################################################


        # Class probability loss for the cells which contain objects.
        loss_class = F.mse_loss(class_pred, class_target, reduction='sum')

        # Total loss
        loss = self.lambda_coord * (loss_xy + loss_wh) + loss_obj + self.lambda_noobj * loss_noobj + loss_class
        loss = loss / float(batch_size)

        return loss

<think>好的,用户想了解YOLOv模型中loss.py文件的实现细节。首先,我需要回忆一下YOLO系列的损失函数设计,尤其是YOLOv5的版本。根据之前的引用资料,loss.py主要负责计算目标检测中的三个损失:分类损失、定位损失和目标置信度损失。 用户可能已经对YOLO的网络结构有一定了解,现在需要深入损失函数的具体实现。我需要先概述整个损失函数的组成部分,再逐一拆解每个部分的具体计算方式。比如分类损失通常使用交叉熵,定位损失可能用CIoU或者GIoU,目标置信度损失可能有不同的处理方式。 接下来,根据引用中的信息,特别是引用4提到的build_targets函数,这个函数负责生成正样本的匹配和扩展。这里需要详细说明正样本是如何通过anchor匹配和网格偏移来扩充的,以及这些步骤如何影响损失计算。比如,每个目标框会被分配到多个anchor,并根据宽高比筛选,同时考虑中心点偏移,选择邻近的网格作为正样本,从而增加正样本数量。 然后,代码结构部分需要分模块讲解,比如初始化部分涉及的参数和损失函数类型,build_targets的作用,以及计算每个损失的具体步骤。需要结合引用中的源码分析,如Focal Loss的应用、平衡系数的设置等。 用户可能对源码的具体实现感兴趣,比如如何将预测结果与真实标签匹配,如何处理不同尺度的检测层,以及损失计算中的权重分配。这时候需要引用具体的代码段落,并解释关键参数的作用,如anchor_t超参数控制anchor匹配的阈值,以及不同损失组件的权重balance。 此外,优化策略如正样本扩充和多尺度训练也是重点。引用4提到的两次扩充正样本的过程需要详细说明,这有助于提高模型对小目标和密集目标的检测能力。同时,平衡不同损失项的重要性,避免某些损失主导训练过程,这也是设计损失函数时的关键考虑。 最后,需要确保回答结构清晰,分点阐述,并引用提供的参考资料。用户可能还想知道实现中的注意事项或常见问题,比如超参数调整对模型性能的影响,或者如何处理负样本的不平衡问题。因此,在相关问题部分可以引导用户进一步探索这些方面。 总结来说,我需要将YOLOv5的loss.py分解成各个功能模块,结合源码和引用资料,详细解释每个部分的实现细节,并强调其中的关键技术和优化方法,确保用户能够全面理解损失函数的设计和实现。</think>### YOLOv5 loss.py 实现详解 YOLOv5的`loss.py`文件实现了目标检测任务中的**多任务损失函数**,包含分类损失、定位损失(bounding box回归)和置信度损失三部分[^2][^3]。其核心逻辑通过`ComputeLoss`类实现,以下是关键实现细节的拆解: --- #### 一、损失函数组成 1. **定位损失(Box Loss)** 采用$CIoU\ Loss$,结合中心点距离、宽高比和IoU的综合指标: $$L_{box} = 1 - IoU + \frac{\rho^2(b_{pred},b_{gt})}{c^2} + \alpha v$$ 其中$\alpha$控制宽高比惩罚项,$v$衡量宽高比一致性[^3]。 2. **分类损失(Cls Loss)** 使用带标签平滑的**二元交叉熵**(BCEWithLogitsLoss): $$L_{cls} = -\sum [y_{true} \cdot \log(\sigma(y_{pred})) + (1-y_{true}) \cdot \log(1-\sigma(y_{pred}))]$$ 其中$\sigma$为sigmoid函数。 3. **置信度损失(Obj Loss)** 同样使用二元交叉熵,衡量预测框是否包含目标: $$L_{obj} = -\sum [IoU_{gt} \cdot \log(\sigma(y_{obj})) + (1-IoU_{gt}) \cdot \log(1-\sigma(y_{obj}))]$$ --- #### 二、代码结构解析 ```python class ComputeLoss: def __init__(self, model, autobalance=False): # 初始化超参数 self.sort_obj_iou = False # 是否对obj iou排序 self.autobalance = autobalance # 自动平衡损失权重 self.box, self.obj, self.cls = 0.05, 1.0, 0.5 # 初始损失权重 self.anchor_t = 4.0 # anchor匹配阈值 def __call__(self, p, targets): # 主要计算流程 lcls = torch.zeros(1, device=self.device) # 分类损失 lbox = torch.zeros(1, device=self.device) # 定位损失 lobj = torch.zeros(1, device=self.device) # 置信度损失 # 1. 生成正样本 tcls, tbox, indices, anchors = self.build_targets(p, targets) # 2. 计算各层损失 for i, pi in enumerate(p): b, a, gj, gi = indices[i] # 第i层的正样本索引 tobj = torch.zeros_like(pi[..., 0], device=self.device) # 目标obj张量 # 定位损失计算 pxy = pi[b, a, gj, gi].sigmoid() * 2 - 0.5 pwh = (pi[b, a, gj, gi].sigmoid() * 2) ** 2 * anchors[i] pbox = torch.cat((pxy, pwh), 1) # 预测框坐标 iou = bbox_iou(pbox.T, tbox[i], CIoU=True).squeeze() lbox += (1.0 - iou).mean() # 置信度损失计算 iou = iou.detach().clamp(0).type(tobj.dtype) tobj[b, a, gj, gi] = iou # 用IoU作为监督信号 lobj += BCEobj(pi[..., 4], tobj) * self.balance[i] # 分类损失计算 if self.nc > 1: t = torch.full_like(pi[b, a, gj, gi, 5:], self.cn, device=self.device) t[range(n), tcls[i]] = self.cp lcls += BCEcls(pi[b, a, gj, gi, 5:], t) # 3. 损失权重平衡 lbox *= self.hyp['box'] lobj *= self.hyp['obj'] lcls *= self.hyp['cls'] return (lbox + lobj + lcls) * bs, torch.cat((lbox, lobj, lcls)).detach() ``` --- #### 三、关键实现细节 1. **正样本扩充策略** - 通过`build_targets`函数将每个目标框匹配到**3个不同尺度的anchor**(对应3个检测层) - 对于每个匹配的anchor,额外选择**相邻两个网格**作为正样本(共3×2=6倍扩展)[^4] - 示例:若原始20个目标框,最终可能扩展为20×3×2=120个正样本 2. **动态损失权重** 当`autobalance=True`时,自动调整各损失项权重: ```python if self.autobalance: self.balance = [0.4, 1.0, 4.0] # P3-5层的权重系数 self.box *= 3 / (self.k * (1 + 4)) # 平衡定位损失 ``` 3. **标签分配策略** - 使用**宽高比阈值**(默认4.0)过滤不匹配的anchor: $$\max(\frac{w_{pred}}{w_{gt}}, \frac{w_{gt}}{w_{pred}}) < \text{anchor_t}$$ - 采用**跨网格预测**策略,允许目标中心点偏移到相邻网格 --- #### 四、优化策略 1. **Focal Loss应用** 在分类损失中可选使用Focal Loss缓解类别不平衡: $$FL(p_t) = -\alpha_t (1-p_t)^\gamma \log(p_t)$$ 2. **损失平衡系数** 默认使用经验权重: ```yaml loss: box: 0.05 # 定位损失权重 obj: 1.0 # 置信度损失权重 cls: 0.5 # 分类损失权重 ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值