目标检测 YOLOv5 – 损失函数的改进

目标检测 YOLOv5 – 损失函数的改进

flyfish

完整代码下载地址
该改进源码完全兼容原版的YOLOv5:v5版本,同时backbone支持mobilenetv3,shufflenetv2,原有的backbone全部支持等等

类别有包含关系的例如一个目标可以是人,男人,也有互斥关系的,一个类别例如人,猫,狗。在数据集的类别是互斥关系下尝试损失函数的改进

类别是包含关系的

BCEWithLogitsLoss 可以用于多标签分类的,一个目标可以属于一个或者多个类别,例如一个目标可以是人,男人,儿童,类别存在一种包括关系。
因为BCEWithLogitsLoss = Sigmoid + BCELoss,BCEWithLogitsLoss将Sigmoid加入了损失函数中。Sigmoid概率和不需要是1。
例如sigmoid的计算结果取出一行看示例代码中的输出[0.5100, 0.6713, 0.5025]这个数累加起来不是1,如果定义阈值大于等于0.50。那么这个目标同时属于三个类,结果如果要求只属于一个类,可以取最大的那个。

类别是互斥关系的

如果检测的类别,类别是互斥关系,例如人,猫,狗这种互斥关系,如何改造呢?
CrossEntropyLoss = LogSoftmax + NLLLoss
Softmax概率和是1或者说接近1。Softmax 大值比其他值具有更大的概率。Sigmoid数值大则概率大,但概率不会比另一个数值的概率更大。
看示例代码中的输出[0.2543, 0.4990, 0.2467]这三个数加起来和是1。

Sigmoid和Softmax 示例代码

import torch
import torch.nn as nn

input = torch.Tensor([[0.0402, 0.7142,0.01],
        [0.2214, 0.4781,0.01]])

net1 = nn.Sigmoid()
output1 = net1(input)
print(output1)
# tensor([[0.5100, 0.6713, 0.5025],
#         [0.5551, 0.6173, 0.5025]])
net2 = nn.Softmax(dim=-1)
output2 = net2(input)
print(output2)
# tensor([[0.2543, 0.4990, 0.2467],
#         [0.3224, 0.4167, 0.2609]])

Softmax是互斥关系,那么是尝试使用交叉熵损失改造下看看。

更改代码如下或者直接到这里YOLOv5-ShuffleNetV2-CrossEntropyLoss下载全部代码

训练阶段

utils/loss.py

class ComputeLoss:
    # Compute losses
    def __init__(self, model, autobalance=False):
        super(ComputeLoss, self).__init__()
        device = next(model.parameters()).device  # get model device
        h = model.hyp  # hyperparameters

        # Define criteria
       

        #changed by Sisyphus

        BCEcls = nn.CrossEntropyLoss()
        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
        print("self.cp, self.cn:",self.cp,":", self.cn)

        # Focal loss
        g = h['fl_gamma']  # focal loss gamma
        if g > 0:
            BCEcls, BCEobj = FocalLoss(BCEcls, g), FocalLoss(BCEobj, g)

        det = model.module.model[-1] if is_parallel(model) else model.model[-1]  # Detect() module
        self.balance = {3: [4.0, 1.0, 0.4]}.get(det.nl, [4.0, 1.0, 0.25, 0.06, .02])  # P3-P7
        self.ssi = list(det.stride).index(16) if autobalance else 0  # stride 16 index
        self.BCEcls, self.BCEobj, self.gr, self.hyp, self.autobalance = BCEcls, BCEobj, model.gr, h, autobalance
        for k in 'na', 'nc', 'nl', 'anchors':
            setattr(self, k, getattr(det, k))

    def __call__(self, p, targets):  # predictions, targets, model
        device = targets.device
        lcls, lbox, lobj = torch.zeros(1, device=device), torch.zeros(1, device=device), torch.zeros(1, device=device)
        tcls, tbox, indices, anchors = self.build_targets(p, targets)  # targets

        # Losses
        for i, pi in enumerate(p):  # layer index, layer predictions
            b, a, gj, gi = indices[i]  # image, anchor, gridy, gridx
            print("indices[i] :",indices[i].shape )
            tobj = torch.zeros_like(pi[..., 0], device=device)  # target obj

            n = b.shape[0]  # number of targets
            if n:
                ps = pi[b, a, gj, gi]  # prediction subset corresponding to targets

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

                # Objectness
                tobj[b, a, gj, gi] = (1.0 - self.gr) + self.gr * iou.detach().clamp(0).type(tobj.dtype)  # iou ratio

                # Classification
                if self.nc > 1:  # cls loss (only if multiple classes)
                    t = torch.full_like(ps[:, 5:], self.cn, device=device)  # targets
                    t[range(n), tcls[i]] = self.cp
                    #lcls += self.BCEcls(ps[:, 5:], t)  # BCE
                    #changed by Sisyphus 20210914
                    lcls += self.BCEcls(ps[:, 5:], tcls[i].clone().detach()) 

                # Append targets to text file
                # with open('targets.txt', 'a') as file:
                #     [file.write('%11.5g ' * 4 % tuple(x) + '\n') for x in torch.cat((txy[i], twh[i]), 1)]

            obji = self.BCEobj(pi[..., 4], tobj)
            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

        loss = lbox + lobj + lcls
        return loss * bs, torch.cat((lbox, lobj, lcls, loss)).detach()

推理阶段

models/yolo.py

class Detect(nn.Module):
    stride = None  # strides computed during build
    export = False  # onnx export

    def __init__(self, nc=80, anchors=(), ch=()):  # detection layer
        super(Detect, self).__init__()
        self.nc = nc  # number of classes
        self.no = nc + 5  # number of outputs per anchor
        self.nl = len(anchors)  # number of detection layers
        self.na = len(anchors[0]) // 2  # number of anchors
        self.grid = [torch.zeros(1)] * self.nl  # init grid
        a = torch.tensor(anchors).float().view(self.nl, -1, 2)
        self.register_buffer('anchors', a)  # shape(nl,na,2)
        self.register_buffer('anchor_grid', a.clone().view(self.nl, 1, -1, 1, 1, 2))  # shape(nl,1,na,1,1,2)
        self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch)  # output conv

    def forward(self, x):
        # x = x.copy()  # for profiling
        z = []  # inference output
        self.training |= self.export
        for i in range(self.nl):
            x[i] = self.m[i](x[i])  # conv
            bs, _, ny, nx = x[i].shape  # x(bs,255,20,20) to x(bs,3,20,20,85)
            x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()

            if not self.training:  # inference
                if self.grid[i].shape[2:4] != x[i].shape[2:4]:
                    self.grid[i] = self._make_grid(nx, ny).to(x[i].device)

                y = x[i].sigmoid()
                tmp = x[i][...,5:]# add by Sisyphus 
                tmp = tmp.softmax(dim=-1)
                y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i]) * self.stride[i]  # xy
                y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i]  # wh
                y[...,5:] = tmp
                z.append(y.view(bs, -1, self.no))

        return x if self.training else (torch.cat(z, 1), x)

    @staticmethod
    def _make_grid(nx=20, ny=20):
        yv, xv = torch.meshgrid([torch.arange(ny), torch.arange(nx)])
        return torch.stack((xv, yv), 2).view((1, 1, ny, nx, 2)).float()

可以换上自己的数据集尝试一下

阅读此文章的人还阅读了以下内容
目标检测 YOLOv5 – 早停机制(Early Stopping)
目标检测 YOLOv5 – 卷积层和BN层的融合
目标检测 YOLOv5 – Sample Assignment
目标检测 YOLOv5 – 数据增强
目标检测 YOLOv5 – 学习率
目标检测 YOLOv5 – 多机多卡训练
目标检测 YOLOv5 – 浮点取模
目标检测 YOLOv5 – 在多类别中应用NMS(非极大值抑制)
目标检测 YOLOv5 – loss for objectness and classification
目标检测 YOLOv5 – loss for bounding box regression
目标检测 YOLOv5 – 指标计算
目标检测 YOLOv5 – anchor设置
目标检测 YOLOv5 – SPP模块
目标检测 YOLOv5 – 边框预测(bounding box prediction)
目标检测 YOLOv5 – 自定义网络结构(YOLOv5-ShuffleNetV2)
目标检测 YOLOv5 – 常见的边框(bounding box )坐标表示方法
目标检测 YOLOv5 – 图像大小与loss权重的关系
目标检测 YOLOv5 – 根据配置改变网络的深度和宽度
目标检测 YOLOv5 – 转ncnn移动端部署
目标检测 YOLOv5 – Backbone中的Focus
目标检测 YOLOv5 – 模型训练、推理、导出命令
目标检测 YOLOv5 – 人脸数据集widerface转YOLOv5格式
目标检测 YOLOv5 – 使用的数据集格式
目标检测 YOLOv5 – 中使用COCO数据集
目标检测 YOLOv5 – CrowdHuman数据集格式转YOLOv5格式

来源:西西弗Sisyphus

物联沃分享整理
物联沃-IOTWORD物联网 » 目标检测 YOLOv5 – 损失函数的改进

发表评论