编写对每个类别/实例加权的自定义loss

最近需要一种自定义loss,可以对每个实例的loss进行不同的加权。在网上找到的代码,没有我想要的,因此首先对torch的loss进行了研究。

torch的loss有包装在nn.Module之中的,有在nn.functional之中的,两种的区别就是前者需要torch对参数进行维护,如果没有parameter不用算梯度的话,就是多占了几个参数的区别而已。torch本身的nn.BCEloss就是调用了一个functional叫binary_cross_entropy,代码非常简单。

class BCELoss(_WeightedLoss):
    __constants__ = ['reduction', 'weight']

    def __init__(self, weight=None, size_average=None, reduce=None, reduction='mean'):
        super(BCELoss, self).__init__(weight, size_average, reduce, reduction)

    def forward(self, input, target):
        return F.binary_cross_entropy(input, target, weight=self.weight, reduction=self.reduction)

而torch之中是有两个loss的大类的,分别是_Loss和_WeightedLoss

class _Loss(Module):
    def __init__(self, size_average=None, reduce=None, reduction='mean'):
        super(_Loss, self).__init__()
        if size_average is not None or reduce is not None:
            self.reduction = _Reduction.legacy_get_string(size_average, reduce)
        else:
            self.reduction = reduction


class _WeightedLoss(_Loss):
    def __init__(self, weight=None, size_average=None, reduce=None, reduction='mean'):
        super(_WeightedLoss, self).__init__(size_average, reduce, reduction)
        self.register_buffer('weight', weight)

其中的区别仅仅在于一个weight,这个weight挺鸡肋的,因为他是不同batch之间进行加权。引用torch官方的说明:

在计算loss的时候,对于不同的batch进行了加权。但是一般人谁用得到这个功能……

图中的yn和xn就是每个batch的数据,在我这里,由于我做多标签分类(10标签),就是每个2维的图片(0-1之间的数值),大小是[157,10]。

由于torch使用了矩阵运算,因此就靠一个式子就能计算一张图片的loss。得到的结果是一个[batch,157,10]的结果,然后使用reduction参数,如果是mean就求均值,使用sum就求和,默认求均值。这里的均值不是每一个图片的loss均值,而是每一个点的loss均值,这解释了为什么loss都那么小。

因此,我只需要在求均值之前,不让其求均值,直接得到所有点的loss,然后乘以我的weight矩阵,然后自己再手动求均值就可以了。代码和测试结果如下:

import torch.nn as nn
import torch
import torch.nn.functional as F

class InstanceWeightedBCELoss(nn.Module):
    def __init__(self):

        super().__init__()

    def forward(self, input, target, instance_weight):
        unweightedloss = F.binary_cross_entropy(input, target,  reduction="none")
        weightedloss = unweightedloss * instance_weight
        loss = torch.mean(weightedloss)
        return loss

# test code
Loss = InstanceWeightedBCELoss()
a = torch.rand((1,157,10))
b = torch.rand((1,157,10))
weight = torch.rand(1,157,10)/5+0.8
print("weight",weight)
loss = Loss(a,b,weight)
print("loss",loss)

#result:
"""
weight tensor([[[0.8668, 0.8081, 0.9909,  ..., 0.9227, 0.9832, 0.8670],
         [0.8636, 0.8764, 0.9606,  ..., 0.8045, 0.9127, 0.8106],
         [0.8062, 0.8840, 0.8495,  ..., 0.9784, 0.8501, 0.9222],
         ...,
         [0.9620, 0.8217, 0.9606,  ..., 0.9674, 0.9211, 0.8560],
         [0.9315, 0.8104, 0.9081,  ..., 0.9596, 0.9536, 0.9976],
         [0.9907, 0.8936, 0.9285,  ..., 0.8556, 0.9208, 0.8183]]])
loss tensor(0.9109)
"""

有个实例加权的loss,那么class加权的loss就很容易了。由于我这个class在最后一维,因此由于tensor的广播机制,直接放入10维的tensor即可,代码和结果如下:

import torch.nn as nn
import torch
import torch.nn.functional as F

class ClassWeightedBCELoss(nn.Module):
    def __init__(self):

        super().__init__()

    def forward(self, input, target, class_weight):
        unweightedloss = F.binary_cross_entropy(input, target,  reduction="none")
        weightedloss = unweightedloss * class_weight
        loss = torch.mean(weightedloss)
        return loss


# test code
Loss = ClassWeightedBCELoss()
a = torch.rand((1,157,10))
b = torch.rand((1,157,10))
weight = torch.rand(10)/5+0.8
print("weight",weight)
loss = Loss(a,b,weight)
print("loss",loss)

#result:
"""
weight tensor([0.8591, 0.9359, 0.8241, 0.9032, 0.9452, 0.8897, 0.9429, 0.9727, 0.9250,
        0.8846])
loss tensor(0.9067)
"""

代码其实很好懂,但是我很纳闷为什么torch官方没有这个自带功能,网上也没有找到很好的代码。

谢谢你的阅读,如果有什么问题和想要讨论的欢迎留言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值