CrossEntropyLoss(交叉熵损失函数)和LabelSmoothing(标签平滑)推导和代码

CrossEntropyLoss

公式

J l o s s = − ∑ i = 1 N ∑ k = 1 K y i k ∗ l o g ( h θ ( x i ) k ) J_{loss} = -\sum_{i = 1}^{N} \sum_{k = 1}^{K} y_{ik}*log(h_{\theta}(x_{i})_{k}) Jloss=i=1Nk=1Kyiklog(hθ(xi)k)

h θ ( x i ) j = e x p ( x i ) j ∑ k = 1 K e x p ( x i ) k h_{\theta}(x_{i})_{j} = \frac{exp(x_{i})_{j}}{\sum_{k = 1}^{K}exp(x_{i})_{k}} hθ(xi)j=k=1Kexp(xi)kexp(xi)j

其中

  • N N N : 样本点的数量
  • K K K : 类别的数量
  • y i c y_{ic} yic : 样本目标的 o n e − h o t one-hot onehot 编码。如果样本 x i x_{i} xi 的真实类别等于 k k k 取1,否则取0。
  • h θ ( x i ) k h_{\theta}(x_{i})_{k} hθ(xi)k : 观测样本 x i x_{i} xi 属于类别 k k k 的预测概率。

样例

下面以一个图片分类问题为例,理解上面的公式。这个例子,根据图片中动物的轮廓、颜色等特征,来预测动物的类别(猫、狗、马)。

类别名称类别编号one-hot
0[1, 0, 0]
1[0, 1, 0]
2[0, 0, 1]

有一张马的图片,它的真实类别编号为 2, one-hot 编码为 [ 0 , 0 , 1 ] [0, 0, 1] [0,0,1] 。假定模型的预测概率为 [ 0.1 , 0.12 , 0.78 ] [0.1, 0.12, 0.78] [0.1,0.12,0.78],如下为计算交叉熵的过程。

类别名称预测概率类别的one-hot符号函数值 yicyic​单项交叉熵计算过程单项交叉熵结果
0.1[1, 0, 0]0-0 * log(0.1)0
0.12[0, 1, 0]0-0 * log(0.12)0
0.78[0, 0, 1]1-1 * log(0.78)0.2485

总的交叉熵为0.2485。

Pytorch 中的CrossEntropy的形式

pytorch中torch.nn模块和torch.nn.functional中均有对应的交叉熵的模块,但是两者是相通的。不过torch.nn.functional中可以理解为是一个接口,而torch.nn是具体类的定义。

  1. torch.nn.functional.cross_entropy
def cross_entropy(input, target, weight=None, size_average=None, ignore_index=-100,
                  reduce=None, reduction='mean'):
    # type: (Tensor, Tensor, Optional[Tensor], Optional[bool], int, Optional[bool], str) -> Tensor

    if size_average is not None or reduce is not None:
        reduction = _Reduction.legacy_get_string(size_average, reduce)
    return nll_loss(log_softmax(input, 1), target, weight, None, ignore_index, None, reduction)

看上面代码也能知道input和target是必选项,并且是Tensor类型的。最后一行说明functional.cross_entropy实际计算过程就是先计算Tensor的log_softmax,然后再计算nll_loss。

那么问题来了,log_softmax是怎么计算的,干了些什么,用上面表格的数据来举例来说:

Tensor的log_softmax函数和functional的函数作用一样,都是先对数据进行softmax,然后进行log函数,这里的log以e为底,即ln。log_softmax和softmax中的数字表示按照什么维度计算。0代表按列计算,softmax函数计算后的数据按列加起来为1;1代表按行计算,softmax函数计算后的数据按行加起来为1。

import torch.nn.functional as F
import torch
truth = torch.tensor([[1, 0, 0]], dtype=torch.float)
predicted1 = torch.tensor([[0.5, 0.4, 0.1]], dtype=torch.float)
print(truth.softmax(0))
print(truth.softmax(1))
print(F.log_softmax(predicted1, 1))
print(truth.log_softmax(-1))

上面代码的输出结果为:

tensor([[1., 1., 1.]])
tensor([[0.5017, 0.2491, 0.2491]])
tensor([[-0.9459, -1.0459, -1.3459]])
tensor([[-0.6897, -1.3897, -1.3897]])

第一行的输出结果可以理解,按照列计算只有一个数据,所以计算后每列的只有1个1。
第二行的输出结果可以通过计算得到:

e 1 e 1 + e 0 + e 0 = e e + 2 ​ = 0.5017 \frac{e^{1}}{e^{1}+e^{0}+e^{0}}=\frac{e}{e+2}​=0.5017 e1+e0+e0e1=e+2e=0.5017

e 0 e 1 + e 0 + e 0 = 1 e + 2 ​ = 0.2491 \frac{e^{0}}{e^{1}+e^{0}+e^{0}}=\frac{1}{e+2}​=0.2491 e1+e0+e0e0=e+21=0.2491

e 0 e 1 + e 0 + e 0 = 1 e + 2 ​ = 0.2491 \frac{e^{0}}{e^{1}+e^{0}+e^{0}}=\frac{1}{e+2}​=0.2491 e1+e0+e0e0=e+21=0.2491

第四行的输出结果通过计算得到:

l n ( 0.5017 ) = − 0.68975 ln(0.5017)=−0.68975 ln(0.5017)=0.68975
l n ( 0.2491 ) = − 1.38990 ln(0.2491)=−1.38990 ln(0.2491)=1.38990
l n ( 0.2491 ) = − 1.38990 ln(0.2491)=−1.38990 ln(0.2491)=1.38990

知道了log_softmax是干嘛的,现在来了解nll_loss是干嘛的。

truth = torch.tensor([0], dtype=torch.int64)
predicted1 = torch.tensor([[0.5, 0.4, 0.1]], dtype=torch.float)

loss = nn.NLLLoss()

print(F.log_softmax(predicted1, 1))
print(loss(F.log_softmax(predicted1, 1), truth))
print(F.cross_entropy(predicted1, truth))

上面的输出结果为:

tensor([[-0.9459, -1.0459, -1.3459]])
tensor(0.9459)
tensor(0.9459)

可以看到truth中的值就是log_softmax结果后的数组的idx,即truth0对应-0.9459的位置,将-0.9459取出后取负数便为NLLLoss的结果。同样地,若truth的值为1,那么第二行和第三行的输出结果为1.0459

Label Smoothing

Label Smoothing也称之为标签平滑,其实是一种防止过拟合的正则化方法。传统的分类loss采用softmax loss,先对全连接层的输出计算 s o f t m a x softmax softmax,视为各类别的置信度概率,再利用交叉熵计算损失。

S o f t m a x Softmax Softmax函数,或称归一化指数函数,该函数的形式通常按下面的式子给出:

σ ( z ) j = e z j ∑ k = 1 K e z k , j ∈ [ 1 , K ] \sigma(z)_{j} = \frac{e^{z_{j}}}{\sum_{k = 1}^{K}e^{z_{k}}},j \in [1,K] σ(z)j=k=1Kezkezj,j[1,K]

在这个过程中尽可能使得各样本在正确类别上的输出概率为1,这要使得对应的 z j z_{j} zj值为 + ∞ +\infty +,这拉大了其与其他类别间的距离。

例如:
对于表情分类,假设 h a p p y happy happy o n e − h o t one-hot onehot 编码是 [ 1 , 0 , 0 , 0 , 0 ] [1,0,0,0,0] [1,0,0,0,0] ,若要预测的结果完全正确,则预测 z j z_{j} zj的值应该为 [ + ∞ , − ∞ , − ∞ , − ∞ , − ∞ ] [+\infty , -\infty , -\infty , -\infty , -\infty] [+,,,,] 。这拉大了不同类别之间的距离,并且有时候类与类之间的并不是毫无关联,如果鼓励输出的概率间相差过大,会导致一定程度上的过拟合。

因此Label Smoothing的想法是让目标不再是one-hot编码,而是变为如下形式:

q i = { 1 − ε , if  i = y ε / ( K − 1 ) , o t h e r w i s e q_{i} = \begin{cases} 1 - \varepsilon, & \text{if } i = y \\ \varepsilon/(K - 1), & otherwise \end{cases} qi={1ε,ε/(K1),if i=yotherwise

其中 ε \varepsilon ε为一个较小的常数, K K K 是类别数,这使得softmax损失中的概率优目标不再为1和0,同时z值的最优解也不再是正无穷大,而是一个具体的数值。

σ ( z ) j = e z j ∑ k = 1 K e z k → ( 1 − ε ) \sigma(z)_{j} = \frac{e^{z_{j}}}{\sum_{k = 1}^{K}e^{z_{k}}} \rightarrow (1 - \varepsilon) σ(z)j=k=1Kezkezj(1ε)

假设 ε = 0.1 \varepsilon = 0.1 ε=0.1 h a p p y happy happy 的编码就是 [ 0.9 , 0.025 , 0.025 , 0.025 , 0.025 ] [0.9,0.025,0.025,0.025,0.025] [0.9,0.025,0.025,0.025,0.025] ,预测 z j z_{j} zj 的值
[ 3.06679091 − 0.51669774 − 0.51669774 − 0.51669774 − 0.51669774 ] [ 3.06679091 -0.51669774 -0.51669774 -0.51669774 -0.51669774] [3.066790910.516697740.516697740.516697740.51669774] , 较少了不同类别之间的距离,这在一定程度上避免了过拟合,也缓解了错误标签带来的影响。

timm 库中的实现

pytorch-image-models/timm/loss/cross_entropy.py at main · huggingface/pytorch-image-models (github.com)

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

class LabelSmoothingCrossEntropy(nn.Module):
    """ 
    NLL loss with label smoothing.
    """
    def __init__(self, smoothing=0.1):
        super(LabelSmoothingCrossEntropy, self).__init__()
        assert smoothing < 1.0
        self.smoothing = smoothing
        self.confidence = 1. - smoothing

    def forward(self, x: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
        logprobs = F.log_softmax(x, dim=-1)
        nll_loss = -logprobs.gather(dim=-1, index=target.unsqueeze(1))
        nll_loss = nll_loss.squeeze(1)
        smooth_loss = -logprobs.mean(dim=-1)
        loss = self.confidence * nll_loss + self.smoothing * smooth_loss
        return loss.mean()

if __name__ == '__main__':
    # Example of using LabelSmoothingCrossEntropy
    criterion = LabelSmoothingCrossEntropy(smoothing=0.1)
    input = torch.Tensor([[-2.2280, -1.2998, -1.7275, -1.2504, -1.8609],
        [-1.9066, -2.8856, -1.1829, -1.3866, -1.4301],
        [-1.0275, -1.8822, -2.0455, -1.1622, -3.0423]])
    target = torch.Tensor([1, 4, 3]).long()
    loss = criterion(input, target)
    loss.backward()
    print(loss.item())

解释:

    def forward(self, x: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
        """
        logprobs
        tensor([[-2.2280, -1.2998, -1.7275, -1.2504, -1.8609],
        [-1.9066, -2.8856, -1.1829, -1.3866, -1.4301],
        [-1.0275, -1.8822, -2.0455, -1.1622, -3.0423]])
        """
        logprobs = F.log_softmax(x, dim=-1)
        """
        index
        tensor([[1],
        [4],
        [3]])
        """
        index=target.unsqueeze(1)
        """ 
        -logprobs.gather(dim=-1, index = index)
        tensor([[1.2998],
        [1.4301],
        [1.1622]])
        """
        nll_loss = -logprobs.gather(dim=-1, index = index)
        """
        nll_loss
        tensor([1.2998,1.4301,1.1622])
        """
        nll_loss = nll_loss.squeeze(1)
        """
        smooth_loss
        tensor([1.6733, 1.7584, 1.8319])
        """
        smooth_loss = -logprobs.mean(dim=-1)
        loss = self.confidence * nll_loss + self.smoothing * smooth_loss
        return loss.mean()

其中:

        logprobs = F.log_softmax(x, dim=-1)
        nll_loss = -logprobs.gather(dim=-1, index=target.unsqueeze(1))
        nll_loss = nll_loss.squeeze(1)

这三行计算原始的交叉熵损失。

smooth_loss = -logprobs.mean(dim=-1)

计算预测的值进行 l o g _ s o f t m a x log\_softmax log_softmax 之后的平均值。

loss = self.confidence * nll_loss + self.smoothing * smooth_loss

计算LabelSmoothing 之后的损失。(这里想不明白看接下来的推导)

推导

J l s _ l o s s = ( 1 − ε ) ∗ ( ∑ k = 1 K y k ∗ − l o g ( h k ) ) ) + ε ∗ ( ∑ k = 1 K − l o g ( h ( x k ) ) K ) = ( 1 − ε ) ∗ ( ∑ k = 1 K y k ∗ − l o g ( h k ) ) ) + ε K ∗ ( ∑ k = 1 K − l o g ( h ( x k ) ) ) = ( 1 − ε ) ∗ ( − l o g ( h k ) ) ) , k = i + ε K ∗ ( ∑ k = 1 K − l o g ( h ( x k ) ) ) = ( 1 − ε + ε K ) ∗ ( − l o g ( h k ) ) ) , k = i + ε K ∗ ( ∑ k = 1 K − l o g ( h ( x k ) ) ) , k ≠ i \begin{align} J_{ls\_loss} & = (1 - \varepsilon)*( \sum_{k = 1}^{K}y_{k}*-log(h_{k}))) + \varepsilon * (\sum_{k =1}^{K}\frac{-log(h(x_{k}))}{K}) \\ & = (1 - \varepsilon)*( \sum_{k = 1}^{K}y_{k}*-log(h_{k}))) + \frac{\varepsilon}{K} * (\sum_{k =1}^{K}-log(h(x_{k}))) \\ & = (1 - \varepsilon)*( -log(h_{k}))) , k = i \\ & + \frac{\varepsilon}{K} * (\sum_{k =1}^{K}-log(h(x_{k})))\\ & = (1 - \varepsilon + \frac{\varepsilon}{K})*( -log(h_{k}))) , k = i \\ & + \frac{\varepsilon}{K} * (\sum_{k =1}^{K}-log(h(x_{k}))),k \neq i \\ \end{align} Jls_loss=(1ε)(k=1Kyklog(hk)))+ε(k=1KKlog(h(xk)))=(1ε)(k=1Kyklog(hk)))+Kε(k=1Klog(h(xk)))=(1ε)(log(hk))),k=i+Kε(k=1Klog(h(xk)))=(1ε+Kε)(log(hk))),k=i+Kε(k=1Klog(h(xk))),k=i

可以看出和原来的公式有所不同,这里

q i = { 1 − ε + ε K , if  i = y ε / K , o t h e r w i s e q_{i} = \begin{cases} 1 - \varepsilon + \frac{\varepsilon}{K}, & \text{if } i = y \\ \varepsilon/K, & otherwise \end{cases} qi={1ε+Kε,ε/K,if i=yotherwise

要使 L o s s Loss Loss 最小,就要使

σ ( z j ) = e z j ∑ k = 1 K e z k → ( 1 − ε + ε K ) , j = i σ ( z j ) = e z j ∑ k = 1 K e z k → ( ε K ) , j ≠ i \begin{align} & \sigma(z_{j}) = \frac{e^{z_{j}}}{\sum_{k = 1}^{K}e^{z_{k}}} \rightarrow (1 - \varepsilon + \frac{\varepsilon}{K} ) ,& j = i \\ & \sigma(z_{j}) = \frac{e^{z_{j}}}{\sum_{k = 1}^{K}e^{z_{k}}} \rightarrow (\frac{\varepsilon}{K} ) ,& j \neq i \\ \end{align} σ(zj)=k=1Kezkezj(1ε+Kε),σ(zj)=k=1Kezkezj(Kε),j=ij=i

举例

假设有二分类问题, ε = 0.2 \varepsilon = 0.2 ε=0.2 ,happy 和 sad 。损失函数由上文可知如下:

J l s _ l o s s = − ( 0.1 ∗ l n ( 1 − x ) ) + 0.9 ∗ l n ( x ) J_{ls\_loss} = -(0.1*ln(1-x)) + 0.9*ln(x) Jls_loss=(0.1ln(1x))+0.9ln(x)

可以得到如下图像:

image.png

极值点的 x x x 坐标为 0.9。

Reference

Pytorch:交叉熵损失(CrossEntropyLoss)以及标签平滑(LabelSmoothing)的实现_labelsmoothingcrossentropy-CSDN博客 \

Softmax函数 - 维基百科,自由的百科全书 (wikipedia.org)

交叉熵损失函数(cross-entropy loss function)原理及Pytorch代码简介_损失函数 dkl-CSDN博客

交叉熵损失函数(Cross Entropy Loss):图示+公式+代码 (vectorexplore.com)

CS 152 NN—8: Regularization—Label Smoothing (youtube.com)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我是路人贾啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值