目录
pytorch中的torch.nn.CrossEntropyLoss()
关于报错提示0D or 1D target tensor expected, multi-target not supported
关于语义分割中的torch.nn.CrossEntropyLoss()(简单提一下)
在语义分割中的torch.nn.CrossEntropyLoss()
关于torch.nn.CrossEntropyLoss()
数学原理
该函数的名字是交叉熵损失函数,交叉熵其实就是熵的“交叉”,或者说,每个特征的熵之间的灵活应用。
那么熵是什么?
关于熵
熵这一次最早来自于信息论,信息论中熵的概念首次被香农提出,目的是寻找一种高效/无损地编码信息的方法:以编码后数据的平均长度来衡量高效性,平均长度越小越高效;同时还需满足“无损”的条件,即编码后不能有原始信息的丢失。这样,香农提出了熵的定义:无损编码事件信息的最小平均编码长度。
直接看信息论的东西可能看不明白。
我们只需要明白如何应用就好,至于信息论中的如何,可以看知乎的这篇博客:一文搞懂熵(Entropy),交叉熵(Cross-Entropy) - 知乎 (zhihu.com)
数学公式
交叉熵公式的形式其实和熵的公式是一样的,这里直接给出交叉熵的公式:
熵的公式也是如此,为什么我们的叫交叉熵,我的理解是,我们是不同的特征所得到的不同的熵,所以叫做“交叉熵”。
一般而言,我们用熵来表示“混乱程度”或者说“不确定性” ,用交叉熵函数用在损失函数上是可以的,可以表示为:我们预测的结果预测为标签的不确定程度。我们的目的就是让这种不确定程度到最小。这样应该可以好理解一些。
它可以看作为,所有样本的熵的和,那样本的熵的公式可以表示为:-样本的真实值*log样本是该真实值的概率。
但是放到实际问题中亦有所不同,在实际问题中,尤其是多分类问题,我们最后得到的往往是多个神经元,每个神经元中的都是一个值,或者说概率值(也不一定)。所以这就需要一些操作来通过样本的标签来找到对应的神经元,这一点可以让计算更方便合理。
我们常见的分类问题都可以使用交叉熵损失函数,在实际使用中,二分类或者多分类都可以使用该交叉熵损失函数,不过有一些小小的区别。下文会详细说明,他们都是上面公式的灵活应用。
pytorch中的torch.nn.CrossEntropyLoss()
pythorch中的torch.nn.CrossEntropyLoss()函数并非只是简单的是实现他的数学原理,还有一些要注意的地方。
torch.nn.CrossEntropyLoss()
下面是官方给出的参数用法的汉语直译:
参数
weight (Tensor, 可选):
一个形状为 ( C ) (这里的C可以解释为常数)的张量,表示每个类别的权重。如果提供了这个参数,损失函数会根据类别的权重来调整各类别的损失,适用于类别不平衡的问题。默认值是 None。
size_average (bool, 可选):
已弃用。如果 reduction 不是 'none',则默认情况下损失是取平均(True);否则,是求和(False)。默认值是 None。
ignore_index (int, 可选):
如果指定了这个参数,则该类别的索引会被忽略,不会对损失和梯度产生影响。默认值是 -100。
reduce (bool, 可选):
已弃用。请使用 reduction 参数。默认值是 None。
reduction (str, 可选):
指定应用于输出的归约方式。可选值为 'none'、'mean'、'sum'。'none' 表示不进行归约,'mean' 表示对所有样本的损失求平均,'sum' 表示对所有样本的损失求和。默认值是 'mean'。
label_smoothing (float, 可选):
标签平滑值,范围在 [0.0, 1.0] 之间。默认值是 0.0。标签平滑是一种正则化技术,通过在真实标签上添加一定程度的平滑来避免过拟合。
上面的参数一般而言都是采用默认值,对此我们并不深入解释,如果想了解可以看这篇博客。PyTorch nn.CrossEntropyLoss() 交叉熵损失函数详解和要点提醒_torch.nn.crossentropyloss-CSDN博客
我们只需要关注的参数可能就是reduction该函数对应的是损失值的处理,默认的损失值其实是平均损失值,也就是所有样本的交叉熵的值的和/样本的个数。
如果该参数的值是’sum‘的话就不会进行平均操作,而是所有样本的损失和,我们知道损失值的处理方式对参数的更新存在直接的影响,但是这种影响一般不会很大。
torch.nn中的CrossEntropyLoss() 本质上是一个类,从源码可以看出来,但是看我下面的解释就好,因为原码没什么好看的。
我们确定好要使用的这个类的参数后,如果要使用它的话,我们要创建该类的对象,这样才能使用,这一点是我曾经小有疑惑的点。
交叉熵函数的使用
在使用交叉熵损失函数时,我们创建了它的对象:
import torch
import torch.nn as nn
loss = nn.CrossEntropyLoss()
一般而言,我们求损失值时的用法如下:
output = loss(input, target)
其中input是我们前向传播得到的值,target是我们对应的标签。
在多分类(包括二分类)问题中 ,官方给出的标签的形式有两种:
类别索引
类别的整数索引,而不是 one-hot 编码。范围在 [ 0 , C )之间,其中 C 是类别数,是一个整数。如果指定了 ignore_index,则该类别索引也会被接受(即便可能不在类别范围内),这句话的意思是我们传入的标签是一个一维张量,里面的每个数都是整数,对应的是每个样本,值是样本的真实值也就是标签。该值表示的是一个下标,它的意思是下标为该值的神经元对应的标签也就是真实值是1,其余是0,常用于多分类问题和二分类问题。
代码示例
import torch
import torch.nn as nn
loss = nn.CrossEntropyLoss()
input = torch.randn(2, 2, requires_grad=True)
print('输入:',input)
print('*-----------使用类别下标------------------*')
target=torch.tensor([1,0],dtype=torch.long)
output = loss(input, target)
print('output:',output)
output.backward()
结果
输入: tensor([[ 0.1123, -0.8732],
[ 0.2770, 0.4324]], requires_grad=True)
*-----------使用类别下标------------------*
output: tensor(1.0383, grad_fn=<NllLossBackward0>)
关于ignore_index
在PyTorch的torch.nn.CrossEntropyLoss
函数中,ignore_index
参数用于指定一个目标类别索引,该索引对应的损失会被忽略,不计入最终的平均损失计算中。这通常用于处理那些不应影响模型训练的错误标签或特殊类别标签(如填充或掩码标记)。
例如,当ignore_index=-100
时,所有标签为-100的样本在损失计算中都会被忽略。
类别概率(独热编码属于此类)
类别的概率分布,适用于需要每个批次项有多个类别标签的情况,如标签平滑等。 这句话的内容可能不清晰,它的意思就是说,在我们传入标签的时候,传入的是和我们输出的神经元的尺寸大小一模一样的张量,该张量内的数在位置上和样本输出的神经元是对应关系,里面的值皆是概率,至于具体解释不太好解释,因为不同的场景有不同的说法。
在我们常用的多分类问题表较常见的就是独热编码,独热编码很好解释。比如如果有三个类别,第一类,第二类,第三类对应的下标分别时0,1,2,那么【1,0,0】表示的是第一类,【0,1,0】表示的是第二类,【0,0,1】表示的是第三类,就是是哪个类别哪个类别对应的值为1,其余为0即可,这就是所谓“独热”。
其实独热编码就是一种类别概率的常用形式。
代码示例
import torch
import torch.nn as nn
loss = nn.CrossEntropyLoss()
input = torch.randn(2, 2, requires_grad=True)
print('输入:',input)
target = torch.tensor([[0,1],[1,0]],dtype=torch.float32)
print('标签:',target)
print('*---------------直接应用CrossEntropyLoss------------*')
print('*--------------使用类别概率,Onehot类型也在其中---------------*')
output = loss(input, target)
print('output:',output)
output.backward()
结果
输入: tensor([[-0.1621, -1.0876],
[-0.0420, 1.7635]], requires_grad=True)
标签: tensor([[0., 1.],
[1., 0.]])
*---------------直接应用CrossEntropyLoss------------*
*--------------使用类别概率,Onehot类型也在其中---------------*
output: tensor(1.6085, grad_fn=<DivBackward1>)
和数学公式之间的关系
我们知道了交叉熵的数学公式,那么我们可不可以直接用数学公式来进行求解呢,但答案是可以的。
代码展示
import torch
import torch.nn as nn
loss = nn.CrossEntropyLoss()
input = torch.randn(2, 2, requires_grad=True)
print('输入:',input)
target = torch.tensor([[0,1],[1,0]],dtype=torch.float32)
print('标签:',target)
print('*----------------数学公式计算----------------*')
soft=torch.softmax(input,dim=1)
print('soft:',soft)
log=torch.log(soft)
print('log:',log)
H=-target*log
print('H(x):',H)
print('mean_H(x):',torch.sum(H)/len(H))
print('*---------------直接应用CrossEntropyLoss------------*')
print('*--------------使用类别概率,Onehot类型也在其中---------------*')
output = loss(input, target)
print('output:',output)
output.backward()
print('*-----------使用类别下标------------------*')
target=torch.tensor([1,0],dtype=torch.long)
output = loss(input, target)
print('output:',output)
output.backward()
结果
输入: tensor([[ 1.4340, -1.3645],
[ 1.8894, 0.0173]], requires_grad=True)
标签: tensor([[0., 1.],
[1., 0.]])
*----------------数学公式计算----------------*
soft: tensor([[0.9426, 0.0574],
[0.8667, 0.1333]], grad_fn=<SoftmaxBackward0>)
log: tensor([[-0.0591, -2.8575],
[-0.1431, -2.0151]], grad_fn=<LogBackward0>)
H(x): tensor([[0.0000, 2.8575],
[0.1431, 0.0000]], grad_fn=<MulBackward0>)
mean_H(x): tensor(1.5003, grad_fn=<DivBackward0>)
*---------------直接应用CrossEntropyLoss------------*
*--------------使用类别概率,Onehot类型也在其中---------------*
output: tensor(1.5003, grad_fn=<DivBackward1>)
*-----------使用类别下标------------------*
output: tensor(1.5003, grad_fn=<NllLossBackward0>)
进程已结束,退出代码0
可以看到,数学公式和我们分别采用封装好的类对象,结果并没什么问题。
关于报错提示0D or 1D target tensor expected, multi-target not supported
在PyTorch中,当你使用某些损失函数(如nn.CrossEntropyLoss
、nn.NLLLoss
等)时,这些函数通常期望目标标签张量是0维(标量)或1维(向量)。如果你的目标张量是更高维度的,比如2维或更高,就会出现这个错误。
意思就是你的标签维度太高了。在我们使用torch.nn.CrossEntropyLoss()中的类别索引作为标签时就容易出现这种情况。
例如
import torch
import torch.nn as nn
loss = nn.CrossEntropyLoss()
input = torch.randn(2, 2, requires_grad=True)
print('输入:',input)
target = torch.tensor([[0,1],[1,0]],dtype=torch.float32)
print('标签:',target)
print('*----------------数学公式计算----------------*')
soft=torch.softmax(input,dim=1)
print('soft:',soft)
log=torch.log(soft)
print('log:',log)
H=-target*log
print('H(x):',H)
print('mean_H(x):',torch.sum(H)/len(H))
print('*---------------直接应用CrossEntropyLoss------------*')
print('*--------------使用类别概率,Onehot类型也在其中---------------*')
output = loss(input, target)
print('output:',output)
output.backward()
print('*-----------使用类别下标------------------*')
target=torch.tensor([[1],[0]],dtype=torch.long)
output = loss(input, target)
print('output:',output)
output.backward()
结果
输入: tensor([[ 0.2257, 1.0807],
[-0.5871, 0.2197]], requires_grad=True)
标签: tensor([[0., 1.],
[1., 0.]])
*----------------数学公式计算----------------*
soft: tensor([[0.2984, 0.7016],
[0.3086, 0.6914]], grad_fn=<SoftmaxBackward0>)
log: tensor([[-1.2094, -0.3544],
[-1.1758, -0.3690]], grad_fn=<LogBackward0>)
H(x): tensor([[0.0000, 0.3544],
[1.1758, 0.0000]], grad_fn=<MulBackward0>)
mean_H(x): tensor(0.7651, grad_fn=<DivBackward0>)
*---------------直接应用CrossEntropyLoss------------*
*--------------使用类别概率,Onehot类型也在其中---------------*
output: tensor(0.7651, grad_fn=<DivBackward1>)
*-----------使用类别下标------------------*
Traceback (most recent call last):
File "D:\论文\识别每个图片.py", line 32, in <module>
output = loss(input, target)
File "D:\Anaconda3\envs\pytorch\lib\site-packages\torch\nn\modules\module.py", line 1511, in _wrapped_call_impl
return self._call_impl(*args, **kwargs)
File "D:\Anaconda3\envs\pytorch\lib\site-packages\torch\nn\modules\module.py", line 1520, in _call_impl
return forward_call(*args, **kwargs)
File "D:\Anaconda3\envs\pytorch\lib\site-packages\torch\nn\modules\loss.py", line 1179, in forward
return F.cross_entropy(input, target, weight=self.weight,
File "D:\Anaconda3\envs\pytorch\lib\site-packages\torch\nn\functional.py", line 3059, in cross_entropy
return torch._C._nn.cross_entropy_loss(input, target, weight, _Reduction.get_enum(reduction), ignore_index, label_smoothing)
RuntimeError: 0D or 1D target tensor expected, multi-target not supported
进程已结束,退出代码1
可以看到,如果采用类别索引的方式,那么我们的标签应当是一维张量,张量里面的每个值都应当是一个整数,他表示的是一个索引值或者说下标(神经元的)。
softmax中的dim
在pytorch封装的softmax中,dim是一个关键参数,对于我们的数据集(不含标签)来说,它通常是二维的,softmax可以将一行或者一列的值作为一个整体,然后将他们进行计算,使得他们成为0-1之间的数,并且他们这一行或者一列的值总和为1,dim就决定了时沿行计算还是沿列计算。
dim=0,沿行计算
竖着来,竖着的总和为1
dim=1,沿列计算
横着来,横着的值总和为1.
代码
# Example of target with class indices
import torch
import torch.nn as nn
loss = nn.CrossEntropyLoss()
input = torch.randn(2, 2, requires_grad=True)
print('输入:',input)
target = torch.tensor([[0,1],[1,0]],dtype=torch.float32)
print('标签:',target)
print('*----------------数学公式计算----------------*')
soft=torch.softmax(input,dim=1)
print('soft:',soft)
soft=torch.softmax(input,dim=0)
print('soft:',soft)
结果
输入: tensor([[ 0.3605, 0.5830],
[-0.9064, 0.1092]], requires_grad=True)
标签: tensor([[0., 1.],
[1., 0.]])
*----------------数学公式计算----------------*
soft: tensor([[0.4446, 0.5554],
[0.2659, 0.7341]], grad_fn=<SoftmaxBackward0>)
soft: tensor([[0.7802, 0.6163],
[0.2198, 0.3837]], grad_fn=<SoftmaxBackward0>)
关于语义分割中的torch.nn.CrossEntropyLoss()(简单提一下)
关于语义分割
语义分割其实就是在将图像中的每个像素分配给一个预定义的类别标签 ,实际上这只是一个笼统的说法,更接地气的说法可以理解为:把原图像通过卷积、反卷积,打碎重揉成一个”新图“,然后对这个新图的每个像素点进行和真实值的比较,得到损失值,然后更新参数,使得我们输入一个图片后可以得到该图片分好的类别图(结果和初始图象的尺寸有可能不同)。
所以才会说时给每个像素分配一个类。
在语义分割中的torch.nn.CrossEntropyLoss()
语义分割最后的结果时一张图,或者说时可以构成一张图的像素点(神经元)
在语义分割任务中,网络的输出通常是一个形状为 [batch_size, num_classes, height, width]
的张量,其中 num_classes
是类别数(包括背景),height
和 width
是输出特征图的高度和宽度。然而,CrossEntropyLoss
期望的输入是一个形状为 [batch_size, num_classes, H*W]
的张量,其中 H*W
是图像中像素的总数。因此,在将输出传递给 CrossEntropyLoss
之前,你需要将 [batch_size, num_classes, height, width]
的输出重新塑形为 [batch_size, num_classes, H*W]
,并对最后一个维度进行转置,使其变为 [batch_size, H*W, num_classes]
,因为 CrossEntropyLoss
期望每个样本的类别预测都在最后一个维度上。
但是,在 PyTorch 的最新版本中,CrossEntropyLoss
可以直接处理 [batch_size, num_classes, height, width]
形状的输入,只要你将 reduce
参数(在较新版本的 PyTorch 中已被弃用,现在使用 reduction
)设置为 'none'
并确保 ignore_index
(如果有的话)被正确设置,以便在计算损失时忽略某些类别(如背景或未标记的类别)。然而,对于大多数语义分割任务,你仍然需要将输出重新塑形为 [batch_size, H*W, num_classes]
,以便更直接地应用损失函数。
其他内容正在研究中。