基础定义

我们要让神经网络往正确的方向拟合,就要让神经网络知道预测值和真实值之间的差异,那么就需要一个函数来描述该差异/损失,因此我们从文章中一步步地解释如何用数学方法描述出预测值和真实值的损失。

多分类任务

机器学习和深度学习大致分为两类,一类是多分类任务,一类是二分类任务,本文重点介绍二分类任务,由于二分类任务中有些地方容易与多分类任务混淆,这里简单介绍一下多分类任务,区别如下:

区别 多分类 二分类
归一化函数 softmax() sigmod()
输出神经元个数 多个 1个(值为0或1)
归一化函数输出 向量所有元素值相加为1 输出每个元素在[0,1]范围内

下面介绍多分类任务的损失函数计算过程

one-hot编码

首先我们需要理解一个概念为独热(one-hot)编码,这个编码就是为了定义每一个类别,有些从数学的角度解释这个编码有点过于复杂了,数学角度简单来说就是让每个编码表示的向量空间上互相垂直,不会产生别的空间分量,因此也就互相独立,能代表独立的个体。从神经网络解释,该编码描述的就是概率分布

简单理解,例如有三个类别,人,猫,狗。可以用一个数组表示,例如 [ 1 , 0 , 0 ] [1,0,0] [1,0,0]就表示人的概率为1,这个编码代表人。此编码也可以理解为概率分布

类别
1 0 0
0 1 0
0 0 1

softmax归一化

那么我们需要计算损失函数,就要将输出与独热编码作比较,因此我们将输出定义为以下形式:
假设有n个类别定义输出为: [ x 1 , x 2 , x 3 , . . . x n ] [x_1,x_2,x_3,...x_n] [x1,x2,x3,...xn]

在输出时每个值代表一个分数,用logits表示, 例如 x 2 x_2 x2就表示第2个类的分数。(这个定义也是大多数神经网络的输出结果)。但是此时由于所有的x的值加起来不等于1所以还不能通过x的值表示类别的概率,那么我们需要定义一个函数将分数转化为概率分布深度学习中最常用的就是softmax函数。如下所示:

p i = softmax ( x i ) = e x i ∑ j = 1 n e x j p_i = \text{softmax}(x_i) = \frac{e^{x_i}}{\sum_{j=1}^{n} e^{x_j}} pi=softmax(xi)=j=1nexjexi

通过softmax函数后,我们的输出就映射到了[0,1]区间上,那么我们就将分数转化为概率分布, 每个值也就代表了该类别的概率。

KL散度

KL散度是度量两个概率分布的相似性,由上面可知,通过one-hot编码,得到了真实值的概率分布,再通过softmax函数得到预测值的概率分布,因此我们现在就可以计算损失了。KL散度的离散分布的公式如下(log表示任意底的对数,可以采用10也可以采用e)
D K L ( P ∥ Q ) = ∑ i = 1 n P ( x i ) l o g P ( x i ) Q ( x i ) \mathbb{D}_{KL}(P \parallel Q) = \sum_{i =1}^{n}P(x_i)log\frac{P(x_i)}{Q(x_i)} DKL(PQ)=i=1nP(xi)logQ(xi)P(xi)
KL散度在连续概率分布下的公式: D K L ( P ∥ Q ) = ∫ − ∞ ∞ P ( x ) l o g P ( x ) Q ( x ) \mathbb{D}_{KL}(P \parallel Q) = \int_{-\infty}^{\infty}P(x)log\frac{P(x)}{Q(x)} DKL(PQ)=P(x)logQ(x)P(x)
其中 P ( x ) P(x) P(x)表示目标概率分布, Q ( x ) Q(x) Q(x)为预测概率分布

交叉熵损失函数:

当我们对上面的KL散度公式进行化简,如下所示: D K L ( P ∥ Q ) = ∑ i = 1 n P ( x i ) l o g P ( x i ) Q ( x i ) = ∑ i = 1 n P ( x i ) l o g P ( x i ) − ∑ i = 1 n P ( x i ) l o g Q ( x i ) = − H ( P ) + H ( P , Q ) \mathbb{D}_{KL}(P \parallel Q) = \sum_{i =1}^{n}P(x_i)log\frac{P(x_i)}{Q(x_i)}=\sum_{i =1}^{n}P(x_i)logP(x_i)-\sum_{i =1}^{n}P(x_i)logQ(x_i)=-H(P)+H(P,Q) DKL(PQ)=i=1nP(xi)logQ(xi)P(xi)=i=1nP(xi)logP(xi)i=1nP(xi)logQ(xi)=H(P)+H(P,Q)
由于 P ( x ) P(x) P(x)是目标概率分布,因此 H ( P ) H(P) H(P)为常数项,对函数的单调性无影响,因此我们取 H ( P , Q ) H(P,Q) H(P,Q)代表该函数,那么我们可以得到交叉熵的公式为: H ( P , Q ) = − ∑ i = 1 n P ( x i ) l o g Q ( x i ) H(P, Q) = -\sum_{i =1}^{n}P(x_i)logQ(x_i) H(P,Q)=i=1nP(xi)logQ(xi)

  • 例: P = [ 1 , 0 , 0 ] P=[1,0,0] P=[1,0,0], Q = [ 0.8 , 0.1 , 0.1 ] Q=[0.8,0.1,0.1] Q=[0.8,0.1,0.1]交叉熵为(log采用以10为底) H ( P , Q ) = − ∑ i = 1 n P ( x i ) l o g Q ( x i ) = − ( 1 ∗ l o g 0.8 + 0 ∗ l o g 0.1 + 0 ∗ l o g 0.1 ) = 0.097 H(P, Q) = -\sum_{i =1}^{n}P(x_i)logQ(x_i)=-(1*log0.8+0*log0.1+0*log0.1)=0.097 H(P,Q)=i=1nP(xi)logQ(xi)=(1log0.8+0log0.1+0log0.1)=0.097

二分类任务

当我们需要一次二分类问题时,我们就不需要使用one-hot编码和softmax代替的是0,1标签和sigmod归一化,且损失函数的形式也要改变,改变如下:
令标签 P = 1 P=1 P=1就代表 P = 0 P=0 P=0就代表是,由于一个概率可以通过1减去另一个概率得到,那么上述的交叉熵损失函数的公式就变为如下形式,因此我们就得到了经典的二分类交叉熵损失函数,也叫二元交叉熵损失函数:
单个二分类任务损失函数如下 L = − [ y ∗ l o g y ^ + ( 1 − y ) ∗ l o g ( 1 − y ^ ) ] L = -[y*log\hat{y}+(1-y)*log(1-\hat{y})] L=[ylogy^+(1y)log(1y^)]多个的二分类任务损失函数公式如下(多用于图像分割): L = − 1 N ∑ i = 1 N [ y i ∗ l o g y i ^ + ( 1 − y i ) ∗ l o g ( 1 − y i ^ ) ] L = -\frac{1}{N}\sum_{i=1}^{N}[ y_i*log\hat{y_i}+(1-y_i)*log(1-\hat{y_i})] L=N1i=1N[yilogyi^+(1yi)log(1yi^)]

多分类和二分类标签区别:

  • 一次多分类:例如有一个3分类任务,那么一次分类的P为元素个数为3的one-hot向量,标签只能为[1,0,0],[0,1,0],[0,0,1]
  • 一次二分类:假如有一个二分类任务,那么一次分类都只用一个数进行计算,而不是向量
  • 三次二分类:例如有三个二分类任务,标签可以为[1,1,1],[1,0,1]···任意元素都是独立的都可以为1或0,1个元素就代表一个二分类任务

python的BCEWithLogitsLoss

在python中,经典的二分类交叉熵损失函数内置在了nn.BCEWithLogitsLoss函数中,该函数默认实现sigmod函数,sigmod函数公式如下:
S = 1 1 + e − x \mathbb{S} = \frac{1}{1+e^{-x}} S=1+ex1对于每一个输出,sigmod都可以将它压缩为[0,1]区间

  • 例如三个二分类任务如下:
P = torch.tensor([1, 0, 1], dtype=torch.float32)
Q = torch.tensor([1.2, -1, 1.8],dtype=torch.float32)
sigmodQ = torch.sigmoid(Q)
criterion = nn.BCEWithLogitsLoss()
print(f"sigmodQ: {sigmodQ}")
loss = criterion(Q, P)
print(f"loss: {loss}")

该代码输出如下:

sigmodQ: tensor([0.7685, 0.2689, 0.8581])
loss: 0.24317391216754913
  • 计算过程:使用二元交叉熵损失函数公式 L = − 1 N ∑ i = 1 N [ y i ∗ l o g y i ^ + ( 1 − y i ) ∗ l o g ( 1 − y i ^ ) ] L = -\frac{1}{N}\sum_{i=1}^{N}[ y_i*log\hat{y_i}+(1-y_i)*log(1-\hat{y_i})] L=N1i=1N[yilogyi^+(1yi)log(1yi^)]python中默认log以e为底,因此计算过程为(此处省略了0作为乘数的步骤): l o s s = − 1 3 [ 1 ∗ l n ( 0.7685 ) + 1 ∗ l n ( 1 − 0.2689 ) + 1 ∗ l n ( 0.8581 ) ] = 0.243174 loss = -\frac{1}{3}[1*ln(0.7685)+1*ln(1-0.2689)+1*ln(0.8581)]=0.243174 loss=31[1ln(0.7685)+1ln(10.2689)+1ln(0.8581)]=0.243174

平衡交叉熵损失函数

在二分类问题中我们经常能遇到正负样本不均衡的情况,例如在图像分割中,我们可能只从一个大面积的图像中分割出一小部分,如果负样本过多,模型可能会更倾向于预测负样本,因此为了让模型均匀地学习正负样本的特征,就出现了平衡交叉熵损失函数。公式如下:
L = − 1 N ∑ i = 1 N [ α ∗ y i ∗ l o g y i ^ + ( 1 − α ) ( 1 − y i ) ∗ l o g ( 1 − y i ^ ) ] L = -\frac{1}{N}\sum_{i=1}^{N}[\alpha* y_i*log\hat{y_i}+(1-\alpha )(1-y_i)*log(1-\hat{y_i})] L=N1i=1N[αyilogyi^+(1α)(1yi)log(1yi^)]
式中, α \alpha α代表系数,一般使用负样本占总样本的比重 N n e g N s u m \frac{N_{neg}}{N_{sum}} NsumNneg的值,以此平衡正负样本的损失。代码如下:(神经网络最后一层未经过sigmod激活需要在代码中先计算输出logits的sigmod的值)

class BalancedBCELoss(nn.Module):
    def __init__(self, alpha=0.5, reduction='mean'):
        # """
        # Args:
        #     alpha (float): 正样本的权重,应在0到1之间。通常设置为负样本比例:num_neg / (num_pos + num_neg)
        #     reduction (str): 损失缩减方式,可选 'mean' 或 'sum'
        # """
        super(BalancedBCELoss, self).__init__()
        self.alpha = alpha
        self.reduction = reduction

    def forward(self, input, target):
        # 计算sigmoid概率(假设input为logits)
        prob = torch.sigmoid(input)
        #prob = input
        # 计算平衡交叉熵损失
        loss = -2*(self.alpha * target * torch.log(prob + 1e-8) + (1 - self.alpha) * (1 - target) * torch.log(1 - prob + 1e-8))

        if self.reduction == 'mean':
            return loss.mean()
        elif self.reduction == 'sum':
            return loss.sum()

        else:
            return loss
  • 例分割一个图像的标签为为3*3的像素矩阵标签如下
    [ 0 0 0 0 1 0 0 0 0 ] \begin{bmatrix} 0 & 0& 0\\ 0& 1& 0\\ 0& 0& 0 \end{bmatrix} 000010000
  • 使用python代码计算,代码如下
if __name__ == "__main__":
    P = torch.tensor([
        [0,0,0],
        [0,1,0],
        [0,0,0]
    ], dtype=torch.float32)
    Q = torch.tensor([
        [-2,-2,-2],
        [-2,-3,-2],
        [-2,-2,-2]
    ], dtype=torch.float32)
    # 初始化损失函数
    criterion = BalancedBCELoss(alpha=8/9)
    criterion_BCE = nn.BCEWithLogitsLoss()
    # 计算损失
    loss = criterion(Q, P)
    loss_BCE = criterion_BCE(Q, P)
    print(f"Balanced BCE Loss: {loss}")
    print(f"BCE Loss: {loss_BCE}")
  • 运行结果如下:
Balanced BCE Loss: 0.6272623538970947
BCE Loss: 0.45155683159828186

可以看出在正样本少于负样本的损失函数中,平衡交叉熵损失函数对正样本的偏离较为敏感

Dice+交叉熵损失函数

Dice是图像分类中的一个指标,表示俩个图像的相似度。

  • 公式如下
    D i c e = 2 X ⋂ Y ∣ X ∣ + ∣ Y ∣ Dice = 2\frac{X\bigcap Y}{\mid X\mid+\mid Y\mid} Dice=2X+YXY
    公式能看出Dice的值为 [ 0 , 1 ] [0,1] [0,1]范围内,Dice为1表示两个图像/集合重合
  • 因此混合损失函数公式为
    L = B C E W i t h L o g i t s L o s s + α ( 1 − D i c e ) L = BCEWithLogitsLoss + \alpha(1-Dice) L=BCEWithLogitsLoss+α(1Dice)
  • 代码如下:
def dice_coefficient(pred, target):
    pred = torch.sigmoid(pred)
    pred = (pred > 0.5).float()
    smooth = 1e-8  # 用于避免除以零的小常数
    intersection = (pred * target).sum()
    dice = (2. * intersection + smooth) / (pred.sum() + target.sum() + smooth)
    return dice

实例根据公式自行融合运行

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐