交叉熵损失函数

发布时间 2023-11-03 09:19:37作者: 饮一杯天上水

交叉熵损失函数

1.基础知识

import torch
import numpy as np

# 自然对数底数e
print("自然对数底数e",np.e)
# 无穷大
print('无穷大',np.inf)
# 无穷小
print('负无穷大',-np.inf)

输出:

# 自然对数底数e 2.718281828459045
# 无穷大 inf
# 负无穷大 -inf
data = torch.tensor([-np.inf,-5,0,1,5,np.inf])
print(data)
# tensor([-inf, -5.,  0.,  1.,  5., inf])
# 指数运算,以e为底
print(torch.exp(data))
# tensor([0.0000e+00, 6.7379e-03, 1.0000e+00, 2.7183e+00, 1.4841e+02,        inf])

2、softmax

定义

\[\operatorname{Softmax}\left(z_i\right)=\frac{e^{z_i}}{\sum_{c=1}^C e^{z_c}} \]

其中\(z_i\) 为第\(i\)个节点的输出值,\(C\)为输出节点的个数, 即分类的类别个数。

通过\(Softmax\)函数就可以将多分类的输出值转换为范围在\([0,1]\)和为\(1\)的概率分布。

计算过程

代码

import torch

# 定义一个一维张量
t_data = torch.tensor([0,1,2,3,4])
# 将张量指数化,以e为底
exp_data = torch.exp(t_data) # tensor([ 1.0000,  2.7183,  7.3891, 20.0855, 54.5981])
# 对张量求和
sum = torch.sum(exp_data) # tensor(85.7910)
# 计算softmax概率
softmax = exp_data/sum 
print(softmax) # tensor([0.0117, 0.0317, 0.0861, 0.2341, 0.6364])
# 计算softmax之和
print(softmax.sum()) # tensor(1.)
import torch

# 定义一个一维张量
t_data = torch.randn(4,4)
print('t_data',t_data)
# 将张量指数化,以e为底
exp_data = torch.exp(t_data)
print('exp_data',exp_data)
# 对张量求和
sum = torch.sum(exp_data,dim=1).reshape(-1,1)
print(sum)
# 计算softmax概率
softmax = exp_data/sum
print('softmax',softmax)
print('softmax.sum',softmax.sum())
# 计算softmax概率
softmax = exp_data/sum
print('softmax',softmax)
# 打印softmax张量总和
print('softmax.sum',softmax.sum())
# 计算softmax张量在行方向上每行之和
torch.sum(softmax,dim=1) # tensor([1.0000, 1.0000, 1.0000, 1.0000])

优缺点

优点:

  1. 输出是一个概率分布,所有元素的和为1,可以直接用于多类别分类问题。
  2. 可以将输入的实数值映射到0到1之间,使得输出更易于解释和理解。
  3. 具有平滑性,对输入的小变化相对较不敏感

缺点:

  1. 对于输入中的大值,Softmax函数的输出会趋向于1,而对于小值,输出会趋向于0,这可能导致梯度消失或梯度爆炸的问题。
  2. Softmax函数的计算相对复杂,当输入向量较大时,计算开销较大

2.log_softmax

定义

\[\mathrm{LogSoftmax}(x_i)=\log\left(\frac{\exp(x_i)}{\sum_j\exp(x_j)}\right) \]

LogSoftmax函数是Softmax函数的一种变体,它将Softmax函数的输出取对数,以解决Softmax函数在数值计算上的一些问题。LogSoftmax函数常用于多类别分类问题中的损失函数计算。

代码

import torch
import torch.nn.functional as F

test = torch.tensor([[0,1,2],
              [1,4,5],
             [2,4,4]],dtype=float)
# 使用softmax激活
softmax = F.softmax(test,dim=1)
#tensor([[0.0900, 0.2447, 0.6652],
#         [0.0132, 0.2654, 0.7214],
#         [0.0634, 0.4683, 0.4683]], dtype=torch.float64)

# 对softmax输出做log
torch.log(softmax)
# tensor([[-2.4076, -1.4076, -0.4076],
#         [-4.3266, -1.3266, -0.3266],
#         [-2.7586, -0.7586, -0.7586]], dtype=torch.float64)

# 直接对数据集做log_softmax
F.log_softmax(test,dim=1)
# tensor([[-2.4076, -1.4076, -0.4076],
#         [-4.3266, -1.3266, -0.3266],
#         [-2.7586, -0.7586, -0.7586]], dtype=torch.float64)

如上结果发现:F.log_softmax(test,dim=1)结果与先F.softmax(test,dim=1)后torch.log结果一致。说明log_softmax就是对softmax结果再log化。

优缺点

优点:

  1. 避免了数值计算上的不稳定性:在Softmax函数中,当输入值较大时,指数运算可能导致数值溢出或计算不稳定。而LogSoftmax函数通过取对数的方式,将指数运算转换为加法运算,减少了数值计算上的不稳定性。
  2. 保留了相对概率关系:LogSoftmax函数的输出仍然保留了输入之间的相对概率关系,因为对数运算不会改变相对大小关系。

缺点:

  1. 输出不再是概率分布:与Softmax函数不同,LogSoftmax函数的输出不再是概率分布,因为取对数后的值不再满足概率的非负性和和为1的性质。因此,LogSoftmax函数的输出不能直接解释为概率。

总结来说,LogSoftmax函数通过取对数的方式解决了Softmax函数在数值计算上的不稳定性问题,但它的输出不再是概率分布。在实际应用中,LogSoftmax函数常用于损失函数的计算,而Softmax函数常用于预测和分类的输出。

3、CrossEntropyLoss与NLLLoss

\(CrossEntropyLoss=NLLLoss+softmax+log\)
\(NLLLoss\)\(N\)\(egative\) \(L\)\(og\) \(L\)\(ikelihood\) \(L\)\(oss\),负对数似然损失函数
CrossEntropyLoss: 交叉熵损失函数。

信息量

​ 信息熵(information entropy)是信息论的基本概念。描述信息源各可能事件发生的不确定性。20世纪40年代,香农(C.E.Shannon)借鉴了热力学的概念,把信息中排除了冗余后的平均信息量称为“信息熵”,并给出了计算信息熵的数学表达式。 [1] 信息熵的提出解决了对信息的量化度量问题。

它是用来衡量一个事件的不确定性的;一个事件发生的概率越大,不确定性越小,则它所携带的信息量就越小。通常,一个信源发送出什么符号是不确定的,衡量它可以根据其出现的概率来度量。概率大,出现机会多,不确定性小;反之不确定性就大。

​ 不确定性函数\(h\)是概率\(P\)减函数;两个独立符号所产生的不确定性应等于各自不确定性之和,即\(h(x,y) = h(x) + h(y)\),这称为可加性;\(x,y\)是俩个不相关的事件,那么满足\(p(x,y) = p(x)*p(y)\).同时满足这三个条件的函数\(h\)是对数函数,即

\[h(x)=-\log _2 p(x) \]

  1. 不确定性函数\(h\)是概率\(P\)的减函数
  2. 可加性:两个独立符号所产生的不确定性应等于各自不确定性之和,即\(h(x,y) = h(x) + h(y)\)
  3. 不相关事件的概率独立:\(p(x,y) = p(x)*p(y)\)

为什么有一个负号?

其中,负号是为了确保信息一定是正数或者是0!概率\(P(x)\)属于\([0,1]\),对应\(\log _2 p(x)\)的值域为\([-inf,0]\),加上负号则为\([0,inf]\)

为什么底数为2?

这是因为,我们只需要信息量满足低概率事件x对应于高的信息量。那么对数的选择是任意的。我们只是遵循信息论的普遍传统,使用2作为对数的底!

​ 假设 \(X\) 是一个离散型随机变量,其取值集合为 \(X\) ,概率分布函数为 \(p(x)=P(X=x), x \in X\) ,我们定义事件 \(X=x_0\) 的信息量为: \(I(x 0)=-\log \left(p\left(x_0\right)\right)\)\(p\left(x_0\right)=1\) 时,摘将等于 0 ,也就是说该事件的发生不会导致任何信息量的增加。

信息熵

信息量度量的是一个具体事件发生了所带来的信息,而熵则是在结果出来之前对可能产生的信息量的期望——考虑该随机变量的所有可能取值,即所有可能发生事件所带来的信息量的期望

​ 在信源中,考虑的不是某一单个符号发生的不确定性,而是要考虑这个信源所有可能发生情况的平均不确定性

​ 在信源中,考虑的不是某一单个符号发生的不确定性,而是要考虑这个信源所有可能发生情况的平均不确定性。若信源符号 有n种取值: \(U_1 \ldots U_i \ldots U_n\) ,对应概率为: \(P_1 \ldots P_1 \ldots P_n\) ,且各种符号的出现彼此独立。这时,信源的平均不确定性应当为单个符号 不确定性- \(\log \mathrm{P}\) 的统计平均值 \((\mathrm{E})\) ,可称为信息熵,即

\[H(U)=E\left[-\log p_i\right]=-\sum_{i=1}^n p_i \log p_i \]

式中对数一般取 2 为底,单 位为比特。但是,也可以取其它对数底,采用其它相应的单位,它们间可用换底公式换算。

​ 它是用来衡量一个系统的混乱程度的,代表一个系统中信息量的总和;信息量总和越大,表明这个系统不确定性就越大。

​ 举个例子:假如小明和小王去打靶,那么打靶结果其实是一个0-1分布, \(X\) 的取值有 \(\{0\) :打 中, 1: 打不中 \(\}\) 。在打靶之前我们知道小明和小王打中的先验概率为 \(10 \% , 99.9 \%\) 。根据上面的信 息量的介绍,我们可以分别得到小明和小王打靶打中的信息量。但是如果我们想进一步度量小明打 靶结果的不确定度,这就需要用到熵的概念了。那么如何度量呢,那就要采用期望了。我们对所有 可能事件所带来的信息量求期望,其结果就能衡量小明打靶的不确定度:

\[H_A(x)=-\left[p\left(x_A\right) \log \left(p\left(x_A\right)\right)+\left(1-p\left(x_A\right)\right) \log \left(1-p\left(x_A\right)\right)\right]=0.4690 \]

与之对应的,小王的熵(打靶的不确定度)为:

\[H_B(x)=-\left[p\left(x_B\right) \log \left(p\left(x_B\right)\right)+\left(1-p\left(x_B\right)\right) \log \left(1-p\left(x_B\right)\right)\right]=0.0114 \]

​ 虽然小明打靶结果的不确定度较低,毕竟十次有9次都脱靶;但是小王打靶结果的不确定度更低,1000次射击只有1次脱靶,结果相当的确定。

交叉熵

它主要刻画的是实际输出(概率)与期望输出(概率)的距离,也就是交叉熵的值越小,两个概率分布就越接近。

假设现在有一个样本集中两个概率分布p,q,其中p为真实分布,q为非真实分布。假如,按照真实分布p来衡量识别一个样本所需要的编码长度的期望为:

\[H(p)=E\left[-\log p_i\right]=-\sum_{i=1}^n p_i \log p_i \]

上述公式其实就是信息熵公式

但是,如果采用错误的分布q来表示来自真实分布p的平均编码长度(p为期望输出,概率分布q为实际输出),则叫做交叉熵:

\[H(p,q)=E\left[-\log p_i\right]=-\sum_{i=1}^n p_i \log q_i \]

举个例子,假设 \(\mathrm{N}=3\) ,期望输出为 \(p=(1,0,0)\) ,实际输出 \(q 1=(0.5,0.2,0.3) , q 2=(0.8,0.1,0.1)\) ,那么:

\[\begin{aligned} & \left.H\left(p, q_1\right)=-(1 \log 0.5+0 \log 0.2+0 \log 0.3+0 \log 0.5+1 \log 0.8+1 \log 0.7)\right)=0.55 \\ & \left.H\left(p, q_2\right)=-(1 \log 0.8+0 \log 0.1+0 \log 0.1+0 \log 0.2+1 \log 0.9+1 \log 0.9)\right)=0.19 \end{aligned} \]

通过上面可以看出,q2与p更为接近,它的交叉熵也更小。

3、KL散度(相对熵)

信息熵和交叉熵的不同点,由公式可以看出,信息熵使用真实分布p计算信息量:\(\log p_i\),而交叉熵用拟合分布q计算信息量:\(\log q_i\),直观上,用p来描述样本是最完美的,用q描述样本就不那么完美,根据吉布斯不等式\(H(p, q) \geq H(p)\)恒成立,当q为真实分布时取等,我们将由q得到的平均编码长度比由p得到的平均编码长度多出的bit数称为相对熵,也叫KL散度

\[D(p \| q)=H(p, q)-H(p)=\sum_{i=1}^C p\left(x_i\right) \log \left(\frac{p\left(x_i\right)}{q\left(x_i\right)}\right) \]

​ 在机器学习的分类问题中,我们希望缩小模型预测和标签之间的差距,即KL散度越小越好,在这里由于KL散度中的 \(H(p)\)项不变(在其他问题中未必),故在优化过程中只需要关注交叉熵就可以了,因此一般使用交叉熵作为损失函数。

4、机器学习中多分类任务中的交叉熵损失函数

公式如下:

\[\text { Loss }=-\sum_{i=0}^{C-1} y_i \log \left(p_i\right)=-\log \left(p_c\right) \]

\(y=\left[y_0, \ldots, y_{C-1}\right]\) 是样本标签的onehot表示,\(y_i\)表示样本标签中第i位置上的值;\(p=\left[p_0, \ldots, p_{C-1}\right]\)是一个概率分布,\(p_i\)表示预测标签第i位置上的值,每个元素\(p_i\)表示样本属于第i类的概率。C表示样本标签的长度(类别数量)。一般的,onehot表示为\([0,0,0,...1,...,0,0,0]\)样式,可以观察到一个onehot表示除了正确类别位置为1,其余位置都为0,这就简化了交叉熵损失的表示。

5、Pytorch nll_loss:负对数似然

概率是已知参数,推数据。似然是已知数据,推参数。

\[\text { NLL Loss }=-\frac{1}{N} \sum_{i=1}^N \log p\left(y_i \mid x_i\right) \]

整个数据集的负对数似然损失就是对所有样本的损失进行求和或平均。N表示样本数量。使用概率模型来预测每个样本的类别概率分布,表示为\(p\left(y_i \mid x_i\right)\),表示在给定输入条件下,样本 \(x_i\)属于类别 \(y_i\) 的概率.\(\sum_{i=1}^N \log p\left(y_i \mid x_i\right)\)表示对所有样本的负对数似然损失求和,如果用平均损失,可以除以N。负对数似然损失的目标是最小化\(p\left(y_i \mid x_i\right)\)的负对数,既\(-\log p\left(y_i \mid x_i\right)\).

但是在pytorch中的F.nll_loss实现,是与上述公式不一样的!!!并没有取\(log\):

\[\text { Pytorch NLL Loss }=-\frac{1}{N} \sum_{i=1}^N p\left(y_i \mid x_i\right) \]

代码

import torch

def nll_loss(output, target):
    batch_size = output.size(0)
    return -output[range(batch_size), target].mean()

# 示例
output = torch.tensor([[0.2, 0.3, 0.5], [0.1, 0.6, 0.3]])
target = torch.tensor([2, 0])
loss = nll_loss(torch.log(output), target)
print(loss) # tensor(1.4979)

F.nll_loss(torch.log(output),target) # # tensor(1.4979)

上述代码中可以看到,自定义的nll_loss函数,模拟公式写出,计算结果和torch自带的nll_loss计算结果一致

6、Pytorch CrossEntropyLoss:交叉熵

交叉熵定义公式:

\[H(p,q)=E\left[-\log p_i\right]=-\sum_{i=1}^n p_i \log q_i \]

其中\(p_i\)为真实标签概率,\(q_i\)为预测概率。但是一般的机器学习中\(p_i\)使用onehot编码,类似\([0,0,1,0]\)的表示方法,计算一个样本的交叉熵时候除却onehot中1的位置可以计算,其他部位都是0,假设一条样本经过softmax后得到的概率表示为:\([0.2,0.1,0.5,0.2]\),则计算过程为:

\[\left.H\left(p, q_1\right)=-(0 \log 0.2+0 \log 0.1+1 \log 0.5+0 \log 0.2\right)=0.6931 \]

所以交叉熵损失函数可以直接简化为:

\[H(p,q)=E\left[-\log p_i\right]=-\sum_{i=1}^n p_i \log q_i = -\log q_i \]

代码

\(H(p, q)=-\sum_xp(x) \log q(x)\)公式,其\(q(x)\)为softmax激活后的概率表示;\(p(x)\)为onehot编码的表示。

import torch
import torch.nn as nn

input = torch.tensor([[ 0.8082,  1.3686, -0.6107],
                      [ 1.2787,  0.1579,  0.6178],
                      [-0.6033, -1.1306,  0.0672],
                      [-0.7814,  0.1185, -0.2945]])
# softmax激活,转化为概率
softmax_output = nn.Softmax(dim=1)(input)
print(softmax_output)
#tensor([[0.3341, 0.5851, 0.0808],
#        [0.5428, 0.1770, 0.2803],
#        [0.2821, 0.1665, 0.5515],
#        [0.1966, 0.4835, 0.3199]])

# 标签采用onehot编码,y_target中的元素表示onehot中为1的元素位置,比如y_target = [1,0,2,1]表示的onehot为[[0,1,0],[1,0,0],[0,0,1],[0,1,0]]
y_target = [1,0,2,1]

计算\(\log q(x)\),\(q(x)\)为经过softmax计算后的值,需要再log化

log_softmax_output = torch.log(softmax_output)
log_softmax_output
#tensor([[-1.0964, -0.5360, -2.5153],
#        [-0.6111, -1.7319, -1.2720],
#        [-1.2657, -1.7930, -0.5952],
#        [-1.6266, -0.7267, -1.1397]])

手动从log_softmax_output输出中按照y_target所表示的onehot元素1位置,找到对应的预测概率,手动做

loss =-(-0.5360-0.6111-0.5952-0.7267) / 4
print(loss) # 0.61725

使用torch自带的CrossEntropyLoss计算:

import torch
import torch.nn as nn

input = torch.tensor([[ 0.8082,  1.3686, -0.6107],
        [ 1.2787,  0.1579,  0.6178],
        [-0.6033, -1.1306,  0.0672],
        [-0.7814,  0.1185, -0.2945]],requires_grad=True)
target = torch.tensor([1,0,2,1])

loss = nn.CrossEntropyLoss()
output = loss(input, target)
output.backward()
print(output)# 0.61725

发现两者结果一致

7、pytorch nll_loss 与 CrossEntropyLoss的关系

负对数似然函数公式:

\[\text { NLL Loss }=-\frac{1}{N} \sum_{i=1}^N \log p\left(y_i \mid x_i\right)\\ \text { Pytorch NLL Loss }=-\frac{1}{N} \sum_{i=1}^N p\left(y_i \mid x_i\right) \]

N表示样本数量,\(p(y_i \mid x_i)\)表示预测概率。

交叉熵损失函数公式:

\[H(p,q)=E\left[-\log p_i\right]=-\sum_{i=1}^n p_i \log q_i = -\log q_i \]

上述为一个样本公式,如果是N个样本,则表示形式为如下:

\[\text { Cross Entropy Loss }=-\frac{1}{N} \sum_{i=1}^N \log q_i \]

在onehot标签编码形势下,由于只计算onehot编码中不为0的项,简化后的交叉熵编码和nll_loss公式是一致的。我们可以发现,Nll_loss的完整形式和交叉熵公式其实是一致的,但是在pytorch中的实现,并没有对输入的概率取\(log\),所以在他两者计算时候,差别就在一个输入是否取\(log\)上。

总的来说,onehot的标签编码形式,导致了这种统一性,一切建立在onehot标签编码基础之上。

代码

import torch
import torch.nn.functional as F

# 创建随机二维(3,3)结构的矩阵张量
x_input=torch.randn(3,3)
# 定义一个目标张量
y_target=torch.tensor([1,2,0])
print('x_input:\n',x_input)
#tensor([[ 0.6821, -1.3179, -1.0447],
#       [-0.2849, -0.4089,  1.2510],
#      [ 0.2702,  1.3541,  1.1645]])


# 直接使用交叉熵损失函数计算损失
crossentropyloss_output=F.cross_entropy(x_input,y_target)
print(crossentropyloss_output) # tensor(1.4898)

F.cross_entropy计算结果为tensor(1.4898)


# 使用经过log_softmax之后的输入用nll_loss计算损失
log_softmax_output = F.log_softmax(x_input,dim=1)
log_softmax_to_nlloss_output = F.nll_loss(log_softmax_output,y_target)
print(log_softmax_to_nlloss_output) #tensor(1.4898)

输入经过og_softmax后的F.nll_loss计算结果为tensor(1.4898)

# 直接使用原来输入与目标张量计算nll_loss损失
nll_loss_output = F.nll_loss(x_input,y_target)
print(nll_loss_output) # tensor(-0.0678)

直接使用F.nll_loss计算结果为tensor(-0.0678)

总结:通过上述实验代码可以看出,pytorch中自带的两种交叉熵分类损失函数,不同点和区别就在于,F.nll_loss直接使用交叉熵运算,而F.cross_entropy则是比F.nll_loss在输入张量上多了一个预处理:输入张量要首先经过log_softmax进行数据变换,然后再调用F.nll_loss。