Pytorch中的C类NLLLoss损失函数是用来做什么的?

8
我想了解有关NLLLoss损失函数的C类问题。
文档说明如下:
负对数似然损失。它对于训练具有C个类别的分类问题非常有用。
基本上,在那之后的一切都取决于您知道什么是C类,我认为我知道什么是C类,但文档对我来说没有多大意义。特别是当它描述(N, C) where C = number of classes的预期输入时,我感到困惑,因为我认为C类仅指输出。我的理解是,C类是一个独热向量,用于分类。我经常在教程中发现NLLLoss通常与LogSoftmax配对以解决分类问题。
我希望在以下示例中使用NLLLoss
# Some random training data
input = torch.randn(5, requires_grad=True)
print(input)  # tensor([-1.3533, -1.3074, -1.7906,  0.3113,  0.7982], requires_grad=True)
# Build my NN (here it's just a LogSoftmax)
m = nn.LogSoftmax(dim=0)
# Train my NN with the data
output = m(input)
print(output)  # tensor([-2.8079, -2.7619, -3.2451, -1.1432, -0.6564], grad_fn=<LogSoftmaxBackward>)
loss = nn.NLLLoss()
print(loss(output, torch.tensor([1, 0, 0])))

上述代码最后一行会产生以下错误:
ValueError: 预期有 2 个或更多维度 (实际只有 1 个)
我们可以忽略这个错误,因为显然我不理解自己在做什么。下面我将解释一下我写上述源代码的意图。
input = torch.randn(5, requires_grad=True)

我需要将一个随机的一维数组与一个独热向量 [1, 0, 0] 进行配对,用于训练。 我正在尝试将二进制位转换为十进制数的独热向量。

m = nn.LogSoftmax(dim=0)
< p > LogSoftmax 的文档中说输出的形状将与输入相同,但我只看到过使用 LogSoftmax(dim=1) 的示例,因此我一直在尝试使其运行,但找不到相关的示例。< /p >
print(loss(output, torch.tensor([1, 0, 0])))

现在我已经得到了神经网络的输出,我想知道从我的分类[1, 0, 0]中得到的损失。在这个例子中,任何数据都不是很重要。我只是想得到一个代表分类的one-hot向量的损失。
此时,我遇到了无法解决与期望输出和输入结构相关的损失函数错误。我尝试对输出和输入使用view(...)来修复形状,但这只会引起其他错误。
因此,这又回到了我的最初的问题,我将展示文档中的示例来解释我的困惑:
m = nn.LogSoftmax(dim=1)
loss = nn.NLLLoss()
input = torch.randn(3, 5, requires_grad=True)
train = torch.tensor([1, 0, 4])
print('input', input)  # input tensor([[...],[...],[...]], requires_grad=True)
output = m(input)
print('train', output, train)  # tensor([[...],[...],[...]],grad_fn=<LogSoftmaxBackward>) tensor([1, 0, 4])
x = loss(output, train)

再次提到,我们在LogSoftmax上有一个dim=1,这让我感到困惑,因为看一下输入数据。它是一个3x5的张量,我被搞糊涂了。

这是NLLLoss函数第一个输入的文档:

输入:(N, C),其中 C = 类别数量

这些输入是按类别数量进行分组的?

所以输入张量的每一与训练张量的每个元素相关联?

如果我更改输入张量的第二个维度,那么没有任何错误发生,我不明白发生了什么。

input = torch.randn(3, 100, requires_grad=True)
# 3 x 100 still works?

所以我不明白这里的C类是什么意思,我认为C类是一个分类(就像一个标签),只有在NN的输出上才有意义。

希望你能理解我的困惑,因为NN的输入形状不应该与用于分类的one hot向量的形状无关吗?

代码示例和文档都表明,输入的形状由分类数量定义,我真的不太理解为什么会这样。

我已经尝试学习文档和教程来理解我所缺少的内容,但几天过去了,我还是无法跨越这一点,所以我决定问这个问题。这让我感到谦卑,因为我原以为这将是比较容易学习的东西之一。

2个回答

8

基本上,您缺少“批次”概念。

简而言之,每个输入到损失函数(以及通过网络传递的输入)都需要“批次”维度(即使用多少个样本)。

逐步分解:

文档示例与您的示例

每个步骤都将进行比较,以使其更清晰(文档在上,您的示例在下)。

输入

input = torch.randn(3, 5, requires_grad=True)
input = torch.randn(5, requires_grad=True)

在第一种情况(docs)中,创建了具有5个特征的输入,并使用3个样本。在您的情况下,只有批次维度(5个样本),没有所需的特征。如果你想要一个具有5个特征的样本,应该这样做:
input = torch.randn(5, requires_grad=True)

对数Softmax

对数Softmax是在特征维度上完成的,而你正在批处理上完成它。

m = nn.LogSoftmax(dim=1) # 应用于特征 m = nn.LogSoftmax(dim=0) # 应用于批处理

通常情况下,这个操作没有意义,因为样本相互独立。

目标

由于这是多类分类,每个向量元素代表一个样本,可以传递任意数量的数字(只要它小于特征数,在文档示例中为5,因此[0-4]是可以的)。

train = torch.tensor([1, 0, 4])
train = torch.tensor([1, 0, 0])

我假设你也想将一个one-hot向量作为目标传递。但是PyTorch不支持这种方式,因为这样会导致内存效率低下(当你可以准确地指定类别时,为什么要将所有内容都存储为one-hot编码呢?在您的情况下,它将是0)。

只有神经网络的输出才是one-hot编码的,以便通过所有输出节点进行误差反向传播,对于目标则不需要。

结论

你不应该在这个任务中使用torch.nn.LogSoftmax,只需使用torch.nn.Linear作为最后一层,并使用torch.nn.CrossEntropyLoss与你的目标。


5
我同意您的观点,关于nn.NLLLoss()文档的说明还不够理想,但是我认为我们可以通过澄清在机器学习上下文中"类别"的意思常常被用来作为"分类"的同义词的方式来澄清你的问题。
因此,当PyTorch谈论C类时,实际上是指您试图训练网络的不同类别的数量。 因此,在一个传统的分类神经网络尝试将"猫"和"狗"区分开来的例子中,C = 2,因为它只能是猫或狗。
特别是对于这个分类问题,还有一点很重要,我们仅有一个真值覆盖我们类别数组(一张图片不能同时描绘出猫和狗,而总是只有其中一种),这就是为什么我们可以方便地通过其索引表示对应的图像类别(假设0表示猫,1表示狗)。现在,我们可以简单地将网络输出与我们所需的类别进行比较。
但是,为了使其有效,我们还需要清楚这些损失值所指向的内容(在我们的网络输出中),因为我们的网络通常会通过对不同输出神经元进行softmax来进行预测,这意味着我们通常有多个输出值。幸运的是,PyTorch的nn.NLLLoss会自动为您执行此操作。
您上面提到的LogSoftmax示例实际上只生成了单个输出值,这是此示例的关键情况。 这样,您基本上只能判断某个事物是否存在/不存在,但在分类示例中使用它没有多大意义,更何况在回归案例中(但这将需要完全不同的损失函数)。
最后但并非最不重要的是,您还应考虑到我们通常具有2D张量作为输入,因为批处理(同时计算多个样本)通常被认为是匹配性能所必需的步骤。即使您选择一个批次大小为1,这仍然需要您的输入具有尺寸(batch_size, input_dimensions),因此您的输出张量具有形状(batch_size, number_of_categories)
这就解释了为什么您在网上找到的大多数示例都是在dim=1上执行LogSoftmax(),因为这是"分布轴"而不是批处理轴(这将是dim=0)。
如果您只想解决您的问题,最简单的方法是通过添加一个额外的维度来扩展您的随机张量(torch.randn([1, 5], requires_grad=True)),然后仅比较输出张量中的一个值(print(loss(output, torch.tensor([1]))))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接