1. focal loss


class FocalLoss(nn.Module):
    def __init__(self, alpha=1, gamma=2, logits=False, reduce=True):
        super(FocalLoss, self).__init__()
        self.alpha = alpha
        self.gamma = gamma
        self.logits = logits
        self.reduce = reduce

    def forward(self, inputs, targets):
        if self.logits:
            BCE_loss = F.binary_cross_entropy_with_logits(inputs, targets, reduce=False)
        else:
            BCE_loss = F.binary_cross_entropy(inputs, targets, reduce=False)
        pt = torch.exp(-BCE_loss)
        F_loss = self.alpha * (1-pt)**self.gamma * BCE_loss

        if self.reduce:
            return torch.mean(F_loss)
        else:
            return F_loss

2 语义分割单通道和多通道输出交叉熵损失函数的计算问题


交叉熵损失函数

交叉熵损失函数的原理和推导过程,可以参考这篇博文,交叉熵的计算公式如下:

CE(p,q)=−p∗log(q)CE(p,q)=−p∗log(q)

其中 qq 为预测的概率,q∈[0,1]q∈[0,1], pp 为标签,p∈{0,1}p∈{0,1}。

而交叉熵损失函数则是利用上式计算每一个分类的交叉熵之和。对于概率,所有分类的概率 qq 之和满足相加等于1,而对于标签,则需要进行one-hot编码,使得有且只有一个分类的 pp 为1,其余的分类为0。

单通道输出时的交叉熵损失计算

pytorch 计算 fid pytorch 计算分割F1_损失函数

单通道输出交叉熵损失计算示意图

首先,假设我们研究的是一个二分类语义分割问题。

网络的输入是一个 2×2 的图像,设置 batch_size 为 2,网络输出单通道特征图。网络的标签也是一个 2 ×2 的二进制掩模图(即只有0和1的单通道图像)。

我们在 pytorch 中将其定义:

python

import torch

# 假设输出一个 [batch_size=2, channel=1, height=2, width=2] 格式的张量 x1
x1 = torch.tensor(
    [[[[ 0.43, -0.25],
        [-0.32, 0.69]]],

        [[[-0.29, 0.37],
          [0.54,  -0.72]]]])

# 假设标签图像为与 x1 同型的张量 y1
y1 = torch.tensor(
    [[[[0., 0.],
        [0., 1.]]],

        [[[0., 0.],
          [1.,  1.]]]])

在进行交叉熵前,首先需要做一个 sigmoid 操作,将数值压缩到0到1之间:

python

# 根据二进制交叉熵的计算过程
# 首先进行sigmoid计算,然后与标签图像进行二进制交叉熵计算,最后取平均值,即为损失值

# 1. sigmoid
s1 = torch.sigmoid(x1)
s1

'''
out:
tensor([[[[0.6059, 0.4378],
          [0.4207, 0.6660]]],


        [[[0.4280, 0.5915],
          [0.6318, 0.3274]]]]
'''

然后进行交叉熵计算,由于计算的是每个像素的损失值,所以要取个平均值:

python

# 2.交叉熵计算
loss_cal = -1*(y1*torch.log(s1)+(1-y1)*torch.log(1-s1)) # 此处相当于一个one-hot编码
loss_cal_mean = torch.mean()
loss_cal_mean

'''
out:
tensor(0.6861)
'''

为了验证结果,我们使用 pytorch 自带的二进制交叉熵损失函数计算:

python

# 使用torch自带的二进制交叉熵计算
loss_bce = torch.nn.BCELoss()(s1,y1)
loss_bce

'''
out:
tensor(0.6861)
'''

当计算损失值前没有进行 sigmoid 操作时,pytorch 还提供了包含这个操作的二进制交叉熵损失函数:

python

# 使用带sigmoid的二进制交叉熵计算
loss_bce2 = torch.nn.BCEWithLogitsLoss()(x1,y1)
loss_bce2

'''
out:
tensor(0.6861)
'''

可以看到,我们使用了三种方式,计算了交叉熵损失,结果一致。

多通道输出时的交叉熵损失计算

pytorch 计算 fid pytorch 计算分割F1_损失函数_02

多通道输出交叉熵损失计算示意图

首先,假设我们研究的是一个二分类语义分割问题。

网络的输入是一个 2×2 的图像,设置 batch_size 为 2,网络输出多(二)通道特征图。网络的标签也是一个 2 ×2 的二进制掩模图(即只有0和1的单通道图像)。

我们在 pytorch 中将其定义:

python

# 假设输出一个[batch_size=2, channel=2, height=2, width=2]格式的张量 x1
x1 = torch.tensor([[[[ 0.3164, -0.1922],
          [ 0.4326, -1.2193]],

         [[ 0.6873,  0.6838],
          [ 0.2244,  0.5615]]],


        [[[-0.2516, -0.8875],
          [-0.6289, -0.1796]],

         [[ 0.0411, -1.7851],
          [-0.3069, -1.0379]]]])

# 假设标签图像为与x1同型,然后去掉channel的张量 y1 (注意两点,channel没了,格式为LongTensor)
y1 = torch.LongTensor([[[0., 1.],
         [1., 0.]],

        [[1., 1.],
         [0., 1.]]])

在进行交叉熵前,首先需要做一个 softmax 操作,将数值压缩到0到1之间,且使得各通道之间的数值之和为1:

python

# 1.softmax
s1 = torch.softmax(x1,dim=1)
s1

'''
out:
tensor([[[[0.4083, 0.2940],
          [0.5519, 0.1442]],

         [[0.5917, 0.7060],
          [0.4481, 0.8558]]],


        [[[0.4273, 0.7105],
          [0.4202, 0.7023]],

         [[0.5727, 0.2895],
          [0.5798, 0.2977]]]])
'''

对于标签图,由于其张量的形状与网络输出张量不一样,因此需要做一个one-hot转换,什么是one-hot?请看这篇博文

python

# 2.one-hot
y1_one_hot = torch.zeros_like(x1).scatter_(dim=1,index=y1.unsqueeze(dim=1),src=torch.ones_like(x1))
y1_one_hot

'''
out:
tensor([[[[1., 0.],
          [0., 1.]],

         [[0., 1.],
          [1., 0.]]],


        [[[0., 0.],
          [1., 0.]],

         [[1., 1.],
          [0., 1.]]]])
'''

这里需要重点理解这个scatter_函数,他起到的作用十分关键,one-hot 转换时,其实可以理解为将一个同型的全1矩阵中的元素,有选择性的复制到全0矩阵中的过程,这里的选择依据就是我们的标签图,它决定了哪个位置和通道上的元素取值为 1 。在scatter_ 函数中,dim 决定了用于确定我们在哪个维度上开始定位要建立联系的元素,index是我们选择的依据。

按照交叉熵定义,继续计算:

python

# 交叉熵计算
loss_cal = -1 *(y1_one_hot * torch.log(s1)) 
loss_cal_mean = loss_cal.sum(dim=1).mean() # 在batch维度下计算每个样本的交叉熵
loss_cal_mean

'''
out:
tensor(0.9823)
'''

我们也可以使用 pytorch 自带的交叉熵损失函数计算:

python

loss_ce = torch.nn.CrossEntropyLoss()(x1,y1)
loss_ce

'''
tensor(0.9823)
'''

可以看到,两种方式结果一样。

结论

  • 交叉熵本质上将一群对象择其一进行研究,自然就变成一个二进制问题,即是这个对象或不是这个对象,然后将标签与概率融进公式中,计算损失值。对于每一个对象都可以计算一个损失值,求个平均值就是最后这个群体的损失值了。
  • 不论是sigmoid或者softmax,我们都是在有目的将数据规整到0到1之间,从而形成一个概率值,sigmoid针对的是二分类问题,因此算出一个概率,另一个用一减去就到了。多分类问题,由于最后会输出对应数量的值,softmax 能够将这些值转换到0到1,并满足加起来等于1,这样的话,当我们只研究其中一个类的概率时,其他类的概率自然就是用1减去这个类的概率了,又回到了二分类问题。
  • 对于二分类语义分割问题,其实采用上述两种方式都是可以的。