SSD目标检测损失函数multibox loss pytorch源码详解 - 样本不均衡问题解决

补充一些更详细的说明:

数据集是大样本5000,小样本500,但实际上每个样本会包含多个groud truth,因此首先对数据做拆分和清洗

统计出准确的大小样本比例约为5:1,然后从损失函数公式入手:

对于数量级较小的样本,考虑增大其在计算损失时负样本的损失,即,可作如下理解:假设大小样本比例为5:1,在训练时,如果发生小样本被误判为大样本的情况,计算损失时将这些负样本的损失扩大5倍,主要修改如下红框部分,全文参考下方注释。

-------------------------------------------------------------------------------分割线---------------------------------------------------------------------------

最近做大作业,用到了目标检测算法,要解决样本不均衡的问题。

样本不均衡除了数据扩增外,在损失函数上,容易想到,对于数目较少的类别,当检测类别错误时将惩罚加倍。这里从multibox loss下手,关于它的计算公式不再赘述,已有许多原理解析,写的相当不错。要来解析的是它的源码。

其实已有许多自称源码解析,但是对于重要的部分讲解并不是很详细。。甚至翻译了一下注释就略过了。。。囧

以下注解集合了各路神仙的帮助,以及一些个人理解,在此感谢某知乎作者(忘记叫啥了 和另一位CSDN博主。

以下代码包含了一小段解决样本不均衡问题的部分,因为本人其实是写java的。。所以python写的可能比较丑,另外直接取了第一个标签,因为不知道多标签的时候应该怎么样的。。。所以对一张图有多个标注的可能还需要另外处理。

多说几句,Tensor的很多语法一开始真的是难住了我这个python 0基础,比如一个Tensor 方括号中包含了另一个Tensor 和0比较的结果,之类的,一些很迷的操作,可能会有一些阻碍,解决方法就是多试试,然后就会发现,哦,原来这个操作是这样子的。

各位,共勉。

    def forward(self, predictions, targets):
        """Multibox Loss
        Args:
            predictions (tuple): A tuple containing loc preds, conf preds,
            and prior boxes from SSD net.
                conf shape: torch.size(batch_size,num_priors,num_classes)
                loc shape: torch.size(batch_size,num_priors,4)
                priors shape: torch.size(num_priors,4)

            targets (tensor): Ground truth boxes and labels for a batch,
                shape: [batch_size,num_objs,5] (last idx is the label).
        """
        loc_data, conf_data, priors = predictions
        num = loc_data.size(0) # num here=batch size
        priors = priors[: loc_data.size(1), :] # priors = priors[:8732, :]
        num_priors = priors.size(0) # num_priors=8732
        num_classes = self.num_classes # num_classes = 3

        # match priors (default boxes) and ground truth boxes
        loc_t = torch.Tensor(num, num_priors, 4)# 定义目标定位值的tensor维度,size:(batch size, 8732, 4)
        conf_t = torch.LongTensor(num, num_priors)# 定义目标置信度的tensor维度,size:(batch size, 8732)
        for idx in range(num):
            truths = targets[idx][:, :-1].data # targets[0]中第一个到倒数第二个数值
            labels = targets[idx][:, -1].data # targets[0]中最后一个数值为label
            defaults = priors.data
            #在训练时,groundtruth boxes 与 default boxes(就是prior boxes) 按照如下方式进行配对:
            # 首先,寻找与每一个ground truth box有最大的jaccard overlap的default box,这样就能保证每一个groundtruth box与唯一的一个default box对应起来(所谓的jaccard overlap就是IoU)。
            # SSD之后又将剩余还没有配对的default box与任意一个groundtruth box尝试配对,只要两者之间的jaccard overlap大于阈值,就认为match(SSD 300 阈值为0.5)。
            # 显然配对到GT的default box就是positive,没有配对到GT的default box就是negative。
            match(
                self.threshold,
                truths,
                defaults,
                self.variance,
                labels,
                loc_t,
                conf_t,
                idx,
            )
        if self.use_gpu: # cpu到gpu的数据转换
            loc_t = loc_t.cuda()
            conf_t = conf_t.cuda()
        # wrap targets
        loc_t = Variable(loc_t, requires_grad=False) 
        conf_t = Variable(conf_t, requires_grad=False)

        pos = conf_t > 0 # pos.shape: torch.Size([batch size, 8732]),conf_t中所有大于0的地方,pos在该位置为1
        num_pos = pos.sum(dim=1, keepdim=True) # num_pos.shape: torch.Size([batch size, 1]) #每张图可匹配默认框数量

        # Localization Loss (Smooth L1)
        # Shape: [batch,num_priors,4]
        pos_idx = pos.unsqueeze(pos.dim()).expand_as(loc_data) # 增一维度size扩展为(32,8732,1) 再扩增为loc_data(32,8732,4)的维度
        loc_p = loc_data[pos_idx].view(-1, 4) # 此时loc_data和pos_idx维度一样,选择出positive的loc
        loc_t = loc_t[pos_idx].view(-1, 4) # 选出一样数量的目标定位值
        loss_l = F.smooth_l1_loss(loc_p, loc_t, size_average=False) # 用smooth_l1_los求损失

        # Compute max conf across batch for hard negative mining
        batch_conf = conf_data.view(-1, self.num_classes)
        loss_c = log_sum_exp(batch_conf) - batch_conf.gather(1, conf_t.view(-1, 1))

        # Hard Negative Mining
        loss_c = loss_c.view(num, -1)
        loss_c[pos] = 0  # filter out pos boxes for now
        loss_c = loss_c.view(num, -1)
        _, loss_idx = loss_c.sort(1, descending=True)
        _, idx_rank = loss_idx.sort(1) # 对于每个GT, idx_rank是该位置的priors的loss得分排名
        num_pos = pos.long().sum(1, keepdim=True)
        #clamp:将输入input张量每个元素的夹紧到区间 [min,max]
        #由于负样本数目远大于正样本数,因此将正负比例控制在1:3 -> negpos_ratio=3
        num_neg = torch.clamp(self.negpos_ratio * num_pos, max=pos.size(1) - 1)
        # 从所有idx_rank中挑选出在负样本范围内的样本id
        neg = idx_rank < num_neg.expand_as(idx_rank)

        # Confidence Loss Including Positive and Negative Examples
        pos_idx = pos.unsqueeze(2).expand_as(conf_data)
        neg_idx = neg.unsqueeze(2).expand_as(conf_data)
            
        # conf_data shape [batch,num_priors,num_classes]
        # 下面这部分是我解决样本不均衡的部分 不需要的可以略过
        ########################## 开始分割 #########################
        for idx in range(num):
            labels = targets[idx][:, -1].data
            if labels[0] == 0:
                conf_data[idx][:][1] *= 5
        ########################## 结束分割 #########################
        conf_p = conf_data[(pos_idx+neg_idx).gt(0)].view(-1, self.num_classes)
        targets_weighted = conf_t[(pos + neg).gt(0)]
        loss_c = F.cross_entropy(conf_p, targets_weighted, size_average=False)
        
        # Sum of losses: L(x,c,l,g) = (Lconf(x, c) + αLloc(x,l,g)) / N

        N = num_pos.data.sum().double() # N为batch size个图像中检测到的positive框总数
        loss_l = loss_l.double() / N
        loss_c = loss_c.double() / N
        return loss_l, loss_c

 

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值