YOLOv1学习:(二)损失函数理解和实现

jupiter
2021-01-12 / 0 评论 / 1,176 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2021年11月25日,已超过933天没有更新,若内容或图片失效,请留言反馈。

YOLOv1学习:(二)损失函数理解和实现

损失函数形式

损失函数理解1

  1. 预测框的中心点(x, y) 造成的损失(即对损失函数有贡献)是图中的第一行。其中$||_{ij}^{obj}$为控制函数,在标签中包含物体的那些格点处,该值为1;若格点不含有物体,该值为 0。也就是只对那些有真实物体所属的格点进行损失计算若该格点不包含物体则不进行此项损失计算,因此预测数值不对此项损失函数造成影响(因为这个预测数值根本不带入此项损失函数计算)
  2. 预测框的高度(w, h)造成的损失(即对损失函数有贡献)是图中的第二行。其中 $||_{ij}^{obj}$为控制函数,含义与预测中心一样。1、2项就是边框回归
  3. 第三行与第四行,都是预测框的置信度C。当该格点不含有物体时,该置信度的标签为0;若含有物体时,该置信度的标签为预测框与真实物体框的IOU数值(IOU计算公式为:两个框交集的面积除以并集的面积)。其中第三行函数的$||_{ij}^{obj}$依然为控制函数,在标签中包含物体的那些格点处,该值为1;若格点不含有物体,该值为 0。也就是只对那些有真实物体所属的格点进行损失计算若该格点不包含物体则不进行此项损失计算,因此预测数值不对此项损失函数造成影响(因为这个预测数值根本不带入此项损失函数计算)。第四行的$||_{ij}^{obj}$也控制函数,只是含义与第三项的相反,在标签中不含物体的那些格点处,该值为1;若格点含有物体,该值为 0。也就是只对那些没有真实物体所属的格点进行损失计算若该格点包含物体(包含物体置信度损失已经在第三项计算了)则不进行此项损失计算,因此预测数值不对此项损失函数造成影响(因为这个预测数值根本不带入此项损失函数计算)
  4. 第五行为物体类别概率P,对应的类别位置,该标签数值为1,其余位置为0,与分类网络相同。其中此项$||_{ij}^{obj}$也为控制函数,在标签中包含物体的那些格点处,该值为1;若格点不含有物体,该值为 0。也就是只对那些有真实物体所属的格点进行物体类别损失计算若该格点不包含物体则不进行此项损失计算,因此预测数值不对此项损失函数造成影响(因为这个预测数值根本不带入此项损失函数计算)

此时再来看${\lambda}_{coord}$ 与${\lambda}_{noobj}$ ,Yolo面临的物体检测问题,是一个典型的类别数目不均衡的问题。其中49个格点,含有物体的格点往往只有3、4个,其余全是不含有物体的格点。此时如果不采取点措施,那么物体检测的mAP不会太高,因为模型更倾向于不含有物体的格点。因此${\lambda}_{coord}$ 与 ${\lambda}_{noobj}$的作用,就是让含有物体的格点在损失函数中的权重更大让模型更加“重视”含有物体的格点所造成的损失。在论文中, ${\lambda}_{coord}$ 与 ${\lambda}_{noobj}$ 的取值分别为5与0.5。

损失函数理解2-损失函数分为三个部分

$$ ||_{ij}^{obj}表示cell中是否含有真实物体的中心,含有则1,否则取0 $$

① 坐标误差

为什么宽和高要带根号???

对不同大小的bbox预测中,相比于大bbox预测偏一点,小box预测偏一点更不能忍受。作者用了一个比较取巧的办法,就是将box的width和height取平方根代替原本的height和width

(主要为了平衡小目标检测预测的偏移)

② IOU误差

这里的$\hat{C_i}$分别表示 1 和 0 $,C_i=Pr(Object)*IOU_{pred}^{truth}$

③ 分类误差

这个很容易理解(激活函数的输出)。

损失函数代码实现

实现

"""
+ input
    + pred: (batch_size,30,7,7)的网络输出数据
    + labels: (batch_size,30,7,7)的样本标签数据
+ output
    + 当前批次样本的平均损失
"""
"""
+ YOLOv1 的损失分为3部分
    + 坐标预测损失
    + 置信度预测损失
        + 含object的box的confidence预测损失
        + 不含object的box的confidence预测损失
    + 类别预测损失
"""
class Loss_YOLOv1(nn.Module):
    def __init__(self,batch_size=1):
        super(Loss_YOLOv1,self).__init__()
        self.batch_size = batch_size
        
    """
    box格式转换
    + input
        + src_box : [box_x_lefttop,box_y_lefttop,box_w,box_h]
    + output
        + dst_box : [box_x1,box_y1,box_x2,box_y2]
    """
    def convert_box_type(self,src_box):
        x,y,w,h = tuple(src_box)
        x1,y1 = x,y
        x2,y2 = x+w,y+w
        return [x1,y1,x2,y2]
    
    """
    iou计算
    """
    def cal_iou(self,box1,box2):
        # 求相交区域左上角的坐标和右下角的坐标
        box_intersect_x1 = max(box1[0], box2[0])
        box_intersect_y1 = max(box1[1], box2[1])
        box_intersect_x2 = min(box1[2], box2[2])
        box_intersect_y2 = min(box1[3], box2[3])

        # 求二者相交的面积
        area_intersect = (box_intersect_y2 - box_intersect_y1) * (box_intersect_x2 - box_intersect_x1)

        # 求box1,box2的面积
        area_box1 = (box1[2] - box1[0]) * (box1[3] - box1[1])
        area_box2 = (box2[2] - box2[0]) * (box2[3] - box2[1])

        # 求二者相并的面积
        area_union = area_box1 + area_box2 - area_intersect

        # 计算iou(交并比)
        iou = area_intersect / area_union
        return iou
    
    def forward(self,pred,target):
        lambda_noobj = 0.5 # lambda_noobj参数
        lambda_coord = 5 # lambda_coord参数

        site_pred_loss = 0 # 坐标预测损失
        obj_confidence_pred_loss = 0 # 含object的box的confidence预测损失
        noobj_confidence_pred_loss = 0 #不含object的box的confidence预测损失
        class_pred_loss = 0 # 类别预测损失
        
        for batch_size_index in range(self.batch_size):  # batchsize循环
                for x_index in range(7):  # x方向网格循环
                    for y_index in range(7):  # y方向网格循环
                        # 获取单个网格的预测数据和真实数据
                        pred_data = pred[batch_size_index,:,x_index,y_index] # [x,y,w,h,confidence,x,y,w,h,confidence,cls*20]
                        true_data = target[batch_size_index,:,x_index,y_index] #[x,y,w,h,confidence,x,y,w,h,confidence,cls*20]
                        if true_data[4]==1:# 如果包含物体
                            # 解析预测数据和真实数据
                            pred_box_confidence_1 = pred_data[0:5] # [x,y,w,h,confidence1]
                            pred_box_confidence_2 = pred_data[5:10] # [x,y,w,h,confidence2]
                            true_box_confidence = true_data[0:5] # [x,y,w,h,confidence]
                            

                            # 获取两个预测box并计算与真实box的iou
                            iou1 = self.cal_iou(self.convert_box_type(pred_box_confidence_1[0:4]),self.convert_box_type(true_box_confidence[0:4]))
                            iou2 = self.cal_iou(self.convert_box_type(pred_box_confidence_2[0:4]),self.convert_box_type(true_box_confidence[0:4]))

                            # 在两个box中选择iou大的box负责预测物体
                            if iou1 >= iou2:
                                better_box_confidence,bad_box_confidence = pred_box_confidence_1,pred_box_confidence_2
                                better_iou,bad_iou = iou1,iou2
                            else:
                                better_box_confidence,bad_box_confidence = pred_box_confidence_2,pred_box_confidence_1
                                better_iou,bad_iou = iou2,iou1

                            # 计算坐标预测损失
                            site_pred_loss += lambda_coord * torch.sum((better_box_confidence[0:2]- true_box_confidence[0:2])**2) # x,y的预测损失
                            site_pred_loss += lambda_coord * torch.sum((better_box_confidence[2:4].sqrt()-true_box_confidence[2:4].sqrt())**2) # w,h的预测损失

                            # 计算含object的box的confidence预测损失
                            obj_confidence_pred_loss += (better_box_confidence[4] - better_iou)**2

                            # iou比较小的bbox不负责预测物体,因此confidence loss算在noobj中
                            # 因此还需计算不含object的box的confidence预测损失
                            noobj_confidence_pred_loss += lambda_noobj * (bad_box_confidence[4] - bad_iou)**2

                            # 计算类别损失
                            class_pred_loss += torch.sum((pred_data[10:] - true_data[10:])**2) 

                        else:  # 如果不包含物体,则只有置信度损失--noobj_confidence_pred_loss
                            # [4,9]代表取两个预测框的confidence
                            noobj_confidence_pred_loss += lambda_noobj * torch.sum(pred[batch_size_index,(4,9),x_index,y_index]**2)
        loss = site_pred_loss + obj_confidence_pred_loss + noobj_confidence_pred_loss + class_pred_loss

        return loss/self.batch_size

调用测试

label1 = torch.rand([1,30,7,7])
label2 = torch.rand([1,30,7,7])
print(label1.shape,label2.shape)
print(loss(label1,label2))
torch.Size([1, 30, 7, 7]) torch.Size([1, 30, 7, 7])
tensor(14.6910)

参考资料

  1. YOLO V1损失函数理解:http://www.likecs.com/show-65912.html
  2. YOLOv1算法理解:https://www.cnblogs.com/ywheunji/p/10808989.html
  3. 【目标检测系列】yolov1的损失函数详解(结合pytorch代码):https://blog.csdn.net/gbz3300255/article/details/109179751
  4. yolo-yolo v1损失函数理解:https://blog.csdn.net/qq_38236744/article/details/106724596
0

评论 (0)

打卡
取消