当目标不是单热时,如何计算 Pytorch 中 2 个张量之间的正确交叉熵?

Posted

技术标签:

【中文标题】当目标不是单热时,如何计算 Pytorch 中 2 个张量之间的正确交叉熵?【英文标题】:How to calculate correct Cross Entropy between 2 tensors in Pytorch when target is not one-hot? 【发布时间】:2021-10-07 02:06:59 【问题描述】:

我对 Pytorch 中交叉熵的计算感到困惑。如果我想计算 2 个张量之间的交叉熵并且目标张量不是 one-hot 标签,我应该使用哪个损失?计算2个概率分布而不是预测结果和确定的one-hot标签之间的交叉熵是很常见的。

基本损失函数CrossEntropyLoss 强制目标为索引整数,在这种情况下不符合条件。 BCELoss 似乎有效,但它给出了意想不到的结果。计算交叉熵的预期公式为

但是BCELoss计算每个维度的BCE,表示为

-yi*log(pi)-(1-yi)*log(1-pi)

与第一个等式相比,不应涉及术语-(1-yi)*log(1-pi)。这是一个使用BCELoss 的示例,我们可以看到第二项涉及到每个维度的结果。这使得结果与正确的结果不同。

import torch.nn as nn
import torch
from math import log

a = torch.Tensor([0.1,0.2,0.7])
y = torch.Tensor([0.2,0.2,0.6])
L = nn.BCELoss(reduction='none')
y1 = -0.2 * log(0.1) - 0.8 * log(0.9)
print(L(a, y))
print(y1)

结果是

tensor([0.5448, 0.5004, 0.6956])
0.5448054311250702

如果我们将所有维度的结果相加,最终的交叉熵与预期的不对应。因为这些维度中的每一个都涉及-(1-yi)*log(1-pi) 术语。相比之下,Tensorflow 可以用CategoricalCrossentropy 计算出正确的交叉熵值。这是相同设置的示例,我们可以看到交叉熵的计算方式与第一个公式相同。

import tensorflow as tf
from math import log
L = tf.losses.CategoricalCrossentropy()
a = tf.convert_to_tensor([0.1,0.2,0.7])
y = tf.convert_to_tensor([0.2,0.2,0.6])
y_ = -0.2* log(0.1) - 0.2 * log(0.2) - 0.6 * log(0.7)

print(L(y,a), y_)
tf.Tensor(0.9964096, shape=(), dtype=float32) 0.9964095674488687

是否有任何函数可以在 Pytorch 中计算正确的交叉熵,使用第一个公式,就像 Tensorflow 中的CategoricalCrossentropy

【问题讨论】:

第一个公式是哪一个?您要查找的结果是第二个示例中的0.996,对吗? 【参考方案1】:

根本问题是您错误地使用了BCELoss 函数。

交叉熵损失是你想要的。它用于计算两个任意概率分布之间的损失。事实上,它的定义正是您提供的等式:

其中p 是目标分布,q 是您的预测分布。请参阅this *** post 了解更多信息。

在您提供该行的示例中

y = tf.convert_to_tensor([0.2, 0.2, 0.6])

您正在对多类分类问题进行隐式建模,其中目标类可以是三个类之一(该张量的长度)。更具体地说,该行表示对于这个数据实例,0 类的概率为 0.2,1 类的概率为 0.2,2 类的概率为 0.6。

您遇到的问题是 PyTorch 的 BCELoss 计算 二进制 交叉熵损失,其公式不同。二元交叉熵损失计算目标类只能为 0 或 1 的分类问题的交叉熵。

在二元交叉熵中,您只需要一个概率,例如0.2,表示实例为 1 类的概率为 0.2。相应地,第 0 类的概率为 0.8。

如果您将相同的张量[0.2, 0.2, 0.6] 提供给 BCELoss,则您正在对存在三个数据实例的情况进行建模,其中数据实例 0 属于 1 类的概率为 0.2,数据实例 1 属于 1 类的概率为 0.2,数据实例 2 属于 1 类的概率为 0.6。

现在,回答你原来的问题:

如果我想计算 2 个张量之间的交叉熵,并且目标张量不是 one-hot 标签,我应该使用哪个损失?

不幸的是,PyTorch 没有接受两个概率分布的交叉熵函数。看到这个问题: https://discuss.pytorch.org/t/how-should-i-implement-cross-entropy-loss-with-continuous-target-outputs/10720

建议使用其方程定义来实现您自己的函数。这是有效的代码:

def cross_entropy(input, target):
    return torch.mean(-torch.sum(target * torch.log(input), 1))


y = torch.Tensor([[0.2, 0.2, 0.6]])
yhat = torch.Tensor([[0.1, 0.2, 0.7]])
cross_entropy(yhat, y)
# tensor(0.9964)

它提供了您想要的答案。

【讨论】:

多么棒的答案!太感谢了。顺便说一句,Pytorch 没有为这种情况提供官方 API 真的让我感到惊讶。 @***user2010 “PyTorch 没有接受两个概率分布的交叉熵函数”是不正确的。看看pytorch.org/docs/stable/generated/…(另见user10517719的回答。)【参考方案2】:

更新:从 1.10 版本开始,Pytorch 支持 CrossEntropyLoss 中的类概率目标,因此您现在可以简单地使用:

criterion = torch.nn.CrossEntropyLoss()
loss = criterion(x, y)

x 是输入,y 是目标。当yx 具有相同的形状时,它将被视为类概率。请注意,x 预计将包含每个类别的原始、非标准化分数,而y 预计将包含每个类别的概率(通常是 softmax 层的输出)。您可以在docs中找到详细信息。

【讨论】:

【参考方案3】:

也许你应该试试torch.nn.CrossEntropyLoss 函数

【讨论】:

OP 明确表示CrossEntropyLoss 不是他/她想要的,因为它要求目标是类索引,而不是分布。 是的,你是对的。我没注意到。很抱歉。

以上是关于当目标不是单热时,如何计算 Pytorch 中 2 个张量之间的正确交叉熵?的主要内容,如果未能解决你的问题,请参考以下文章

pytorch中序数多分类的损失函数

如何在单热编码中修复“索引 3 超出轴 1 大小为 3 的范围”? [复制]

如何在4d张量中为k个最大元素创建单热张量?

WiX 在运行热时使子目录成为组件组

如何在pytorch中计算BCEWithLogitsLoss的不平衡权重

Pandas Dataframe:如何将一列拆分为多个单热编码列[重复]