yolov4 LOSS代码详解【附代码】上

简介: 笔记

本文章是从代码层面可以更好的了解YOLOv4的损失函数,从实践过程中去了解这部分的处理过程。


这里先大致说一下这一实现过程:


1)获得target形式【就是我们标注的目标真实信息】


2)batch_target【获取1)中target映射到特征层上box信息类别】


3)计算batch_target中的box和缩放后anchor的IOU,获得anchor和gt最大iou,得到anchor的索引【表示这些anchor内是ground truth】


4)步骤3可以获得由哪些anchor来表示gt,或者说知道了由哪些anchor来预测。但还不知道目标落在了哪个cell内,可对步骤2中的中心点取整来判断目标落在哪个cell.


5)获得y_true和noobj_mask【记录cell中有无目标以及类别信息,根据步骤4即可获得】


6)获得预测框位于cell网格坐标信息【比如x+grid_x,y_grid_y】


7)将步骤6中的预测框和步骤5中y_true中box计算loc loss.


8)将网络输出的类别置信度和步骤5中y_truth计算分类loss


9)将网络有无目标置信度和步骤5中y_true有无目标置信度计算conf loss


这里先将LOSS中的初始化参数列出来。

class YOLOLoss(nn.Module):
    def __init__(self, anchors, num_classes, input_shape, cuda, anchors_mask = [[6,7,8], [3,4,5], [0,1,2]], label_smoothing = 0, focal_loss = False, alpha = 0.25, gamma = 2):
        super(YOLOLoss, self).__init__()
        #-----------------------------------------------------------#
        #   13x13的特征层对应的anchor是[142, 110],[192, 243],[459, 401]
        #   26x26的特征层对应的anchor是[36, 75],[76, 55],[72, 146]
        #   52x52的特征层对应的anchor是[12, 16],[19, 36],[40, 28]
        #-----------------------------------------------------------#
        self.anchors        = anchors  # 先验框
        self.num_classes    = num_classes  # 类的数量
        self.bbox_attrs     = 5 + num_classes  # bbox参数 5:x,y,w,h,p
        self.input_shape    = input_shape  # 网络输入大小
        self.anchors_mask   = anchors_mask
        self.label_smoothing = label_smoothing  # 标签平滑
        self.balance        = [0.4, 1.0, 4]
        self.box_ratio      = 0.05
        self.obj_ratio      = 5 * (input_shape[0] * input_shape[1]) / (416 ** 2)
        self.cls_ratio      = 1 * (num_classes / 80)
        self.focal_loss     = focal_loss
        self.alpha          = alpha
        self.gamma          = gamma
        self.ignore_threshold = 0.5
        self.cuda           = cuda

在训练代码中outputs是我们的三个预测特征层,outputs[l]就是分别遍历这三个特征层,如果输入大小是416 * 416,那么三个特征层就是13 * 13,26 * 26, 52 *52,如果是608的就是19 * 19, 38 * 38,76 * 76,可以看到yolo_loss,传入了三个参数,l是预测层的索引,outputs[l]是对应第几个层,targets就是我们的真实值:


for l in range(len(outputs)):
       loss_item = yolo_loss(l, outputs[l], targets)

当我们第一次遍历的时候,此刻l=0,outputs[0]的shape为【batch_size,3*(5+num_classe),19 * 19,5指的是box的参数(x,y,w,h,conf)】,我这里网络输入大小为608的,只有一个类,batch_size=4,所以我这里的大小是【4,18,19,19】。


target形式


target是我们的真实值,大小【batch_size,5】,target的形式是用list进行存储的,由于我这里有4个batch,所以列表长度为4,每个元素又是5列【x,y,w,h,class】。我们再仔细看一下target的具体内容,前面说了列表的长度为4即代表了4个batch【4张图】,但我们可以看到第一个target[0]的长度又为4,target[1]、target[2]、target[3]的长度又变成了1,这里是怎么回事呢?target[0]的长度为4这是因为在这张图里,我标注了4个目标即存在4个真实值,其他的三张样本均标注了一个目标【这个标注的目标就是你用标注工具标注的】。前面的4列代表了x,y,w,h【我们一般标注的box是左上和右下坐标,这里转成了中心点坐标和宽与高了】,最后一列全是0,即代表了类,我这里只有一个类。


[


tensor([


       [0.6028, 0.2599, 0.1398, 0.2664, 0.0000],

       [0.4391, 0.2870, 0.3026, 0.4194, 0.0000],

       [0.3775, 0.8988, 0.1595, 0.2023, 0.0000],

       [0.9194, 0.7442, 0.1612, 0.2944, 0.0000],

       [0.8092, 0.3158, 0.2171, 0.3520, 0.0000]], device='cuda:0'),


tensor([[0.4359, 0.5592, 0.2467, 0.7566, 0.0000]], device='cuda:0'),


tensor([[0.7401, 0.4868, 0.2105, 0.2171, 0.0000]], device='cuda:0'),


tensor([[0.3873, 0.5518, 0.7747, 0.8964, 0.0000]], device='cuda:0')


]


forward部分


然后我们直接看loss函数的forward()。这里有三个值,l是对应0,1,2【特征层索引】,input就是前面说的model产生的outputs,target是前面提到的真实值。


def forward(self, l, input, targets=None):

这里input的shape为【4,3*(5+1),19,19】=【4,18,19,19】。


从input获得batch_size,特征层的尺寸h,w.


 

#--------------------------------#
        #   获得图片数量,特征层的高和宽
        #--------------------------------#
        bs      = input.size(0)  # batch_size  input.size(1)=3*(5+num_classes)
        in_h    = input.size(2)
        in_w    = input.size(3)

bs=4


in_h=19


in_w=19


计算步长


这里的步长实际就是指的缩放比,比如我的输入大小为608,此刻的特征层为19 * 19,那么608缩小了32倍。相当于此刻特征层的一个特征点【像素点】对应我原图中的32个像素点。


   

#-----------------------------------------------------------------------#
        #   计算步长
        #   每一个特征点对应原来的图片上多少个像素点
        #   
        #   如果特征层为13x13的话,一个特征点就对应原来的图片上的32个像素点 实际就是原图缩小了多少倍 32倍
        #   如果特征层为26x26的话,一个特征点就对应原来的图片上的16个像素点 16倍
        #   如果特征层为52x52的话,一个特征点就对应原来的图片上的8个像素点  8倍
        #   stride_h = stride_w = 32、16、8
        #             a_h,  a_w
        #   (anchor([[ 12.,  16.],  # 从这开始是52*52的
        #        [ 19.,  36.],
        #        [ 40.,  28.],
        #        [ 36.,  75.],   # 这开始是26*26的
        #        [ 76.,  55.],
        #        [ 72., 146.],
        #        [142., 110.],   # 从这往下是13*13的
        #        [192., 243.],
        #        [459., 401.]]), 9)
        #-----------------------------------------------------------------------#
        stride_h = self.input_shape[0] / in_h
        stride_w = self.input_shape[1] / in_w


stride_h = 32.0


stride_w = 32.0


同理的,我们的anchor也需要进行一个缩放。


我们原来的anchor大小为,分别对应我们三个特征层的大中小设置的anchor:


[[ 12.  16.],


[ 19.  36.],


[ 40.  28.],------------->大特征层


[ 36.  75.],


[ 76.  55.],


[ 72. 146.],------------>中特征层


[142. 110.],


[192. 243.],


[459. 401.]] ----------->小特征层


anchor的缩放


scaled_anchors  = [(a_w / stride_w, a_h / stride_h) for a_w, a_h in self.anchors]

此刻的步长是32,得到缩放后的anchor大小为:


[(0.375, 0.5),


(0.59375, 1.125),


(1.25, 0.875),


(1.125, 2.34375),


(2.375, 1.71875),


(2.25, 4.5625),


(4.4375, 3.4375),


(6.0, 7.59375),


(14.34375, 12.53125)]


然后我们对input【也就是网络的output】进行一个reshape,我们原来的shape是【4,3*(5+1),19,19】,通过view变为【4,3,5+1,19,19】=[4,3,6,19,19],再通过permute对维度进行转化变为【4,3,19,19,6】.


prediction = input.view(bs, len(self.anchors_mask[l]), self.bbox_attrs, in_h, in_w).permute(0, 1, 3, 4, 2).contiguous()

通过上面的操作,我们把(5+num_classes)这个维度放在了最后,这里再说一下5值哪些【center_x,center_y,w,h,conf】。


获得先验框的中心位置:

#-----------------------------------------------#
        #   先验框的中心位置的调整参数
        # prediction[...,0]=prediction[:,:,:,:,0]
        # x,y是经过sigmoid后的0到1之间的数
        #-----------------------------------------------#
        x = torch.sigmoid(prediction[..., 0])
        y = torch.sigmoid(prediction[..., 1])

获得先验框的宽和高:


 

#-----------------------------------------------#
        #   先验框的宽高调整参数
        #-----------------------------------------------#
        w = prediction[..., 2]
        h = prediction[..., 3]

获得置信度,是否有物体:


表示每个cell内的每个anchor有物体的概率.shape为【batch_size,3,19,19】


   

#-----------------------------------------------#
        #   获得置信度,是否有物体
        #-----------------------------------------------#
        conf = torch.sigmoid(prediction[..., 4])

获得类的置信度:


表示每个cell分类概率,shape为【batch_size,3,19,19,num_classes】


   

#-----------------------------------------------#
        #   种类置信度 prediction[..., 5:]取的是类别,shape(batchsize,3,13,13,num_classes)
        #-----------------------------------------------#
        pred_cls = torch.sigmoid(prediction[..., 5:])



目录
相关文章
|
6月前
|
Python
【论文复现】针对yoloV5-L部分的YoloBody部分重构(Slim-neck by GSConv)
【论文复现】针对yoloV5-L部分的YoloBody部分重构(Slim-neck by GSConv)
173 0
【论文复现】针对yoloV5-L部分的YoloBody部分重构(Slim-neck by GSConv)
|
机器学习/深度学习 编解码 算法
yolo原理系列——yolov1--yolov5详细解释
yolo原理系列——yolov1--yolov5详细解释
1224 0
yolo原理系列——yolov1--yolov5详细解释
|
6月前
|
算法 文件存储 计算机视觉
【YOLOv8改进】MobileNetV3替换Backbone (论文笔记+引入代码)
YOLO目标检测专栏探讨了MobileNetV3的创新改进,该模型通过硬件感知的NAS和NetAdapt算法优化,适用于手机CPU。引入的新架构包括反转残差结构和线性瓶颈层,提出高效分割解码器LR-ASPP,提升了移动设备上的分类、检测和分割任务性能。MobileNetV3-Large在ImageNet上准确率提升3.2%,延迟降低20%,COCO检测速度增快25%。MobileNetV3-Small则在保持相近延迟下,准确率提高6.6%。此外,还展示了MobileNetV3_InvertedResidual模块的代码实现。
|
机器学习/深度学习 计算机视觉 异构计算
Darknet53详细原理(含torch版源码)
Darknet53详细原理(含torch版源码)—— cifar10
438 0
Darknet53详细原理(含torch版源码)
|
机器学习/深度学习 编解码
MobileNetV1详细原理(含torch源码)
MobilenetV1(含torch源码)—— cifar10
363 0
MobileNetV1详细原理(含torch源码)
|
机器学习/深度学习 存储 编解码
MobileNetV3详细原理(含torch源码)
MobilneNetV3详细原理(含torch源码)—— cifar10
728 0
MobileNetV3详细原理(含torch源码)
|
机器学习/深度学习 计算机视觉 异构计算
MobileNetV2详细原理(含torch源码)
MobileNetV2详细原理(含torch源码)—— cifar10
484 0
MobileNetV2详细原理(含torch源码)
|
PyTorch 算法框架/工具 机器学习/深度学习
GoogLeNet InceptionV3代码复现+超详细注释(PyTorch)
GoogLeNet InceptionV3代码复现+超详细注释(PyTorch)
391 0
|
PyTorch 算法框架/工具
GoogLeNet InceptionV1代码复现+超详细注释(PyTorch)
GoogLeNet InceptionV1代码复现+超详细注释(PyTorch)
332 0
|
人工智能 自动驾驶 安全
YOLO v8!| 附教程+代码 以及 vs YOLOv6 v3.0
YOLO v8!| 附教程+代码 以及 vs YOLOv6 v3.0