ChatGPT解决这个技术问题 Extra ChatGPT

为什么 binary_crossentropy 和 categorical_crossentropy 对同一问题给出不同的性能?

我正在尝试训练 CNN 按主题对文本进行分类。当我使用二元交叉熵时,我得到约 80% 的准确度,而使用分类交叉熵时,我得到约 50% 的准确度。

我不明白为什么会这样。这是一个多类问题,这是否意味着我必须使用分类交叉熵并且二进制交叉熵的结果没有意义?

model.add(embedding_layer)
model.add(Dropout(0.25))
# convolution layers
model.add(Conv1D(nb_filter=32,
                    filter_length=4,
                    border_mode='valid',
                    activation='relu'))
model.add(MaxPooling1D(pool_length=2))
# dense layers
model.add(Flatten())
model.add(Dense(256))
model.add(Dropout(0.25))
model.add(Activation('relu'))
# output layer
model.add(Dense(len(class_id_index)))
model.add(Activation('softmax'))

然后我使用 categorical_crossentropy 作为损失函数来编译它:

model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

或者

model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

直观地说,为什么我要使用分类交叉熵是有道理的,我不明白为什么我用二进制得到好的结果,而用分类的结果却很差。

如果是多类问题,则必须使用 categorical_crossentropy。标签也需要转换为分类格式。请参阅 to_categorical 来执行此操作。另请参阅分类和二元交叉熵的定义 here
我的标签是分类的,使用 to_categorical(每个类一个热向量)创建。这是否意味着二元交叉熵的 80% 准确度只是一个虚假数字?
我认同。如果您使用分类标签,即一个热向量,那么您需要 categorical_crossentropy。如果您有两个类,它们将在二进制标签中表示为 0, 1,在分类标签格式中表示为 10, 01
我认为他只是比较向量中的第一个数字而忽略其余部分。
@NilavBaranGhosh 对于涉及两个类的分类分类,表示将是 [[1, 0], [0, 1]] (不是你提到的 [[0, 0], [0, 1]] )。 Dense(1, activation='softmax') 用于二元分类是完全错误的。请记住,softmax 输出是一个总和为 1 的概率分布。如果您希望只有一个具有二进制分类的输出神经元,请使用具有二进制交叉熵的 sigmoid。

d
desertnaut

分类和分类之间出现这种明显性能差异的原因二进制交叉熵是用户 xtof54 在 his answer below 中已经报告的,即:

当使用带有超过 2 个标签的 binary_crossentropy 时,使用 Keras 方法评估计算的准确度是完全错误的

我想详细说明这一点,展示实际的潜在问题,解释它,并提供补救措施。

这种行为不是错误;根本原因是一个相当微妙的&当您在模型编译中仅包含 metrics=['accuracy'] 时,Keras 如何实际猜测使用哪种准确度的未记录问题,具体取决于您选择的损失函数。换句话说,当你的第一个编译选项

model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

是有效的,你的第二个:

model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

不会产生您期望的结果,但原因不是使用二元交叉熵(至少在原则上,这是一个绝对有效的损失函数)。

这是为什么?如果您选中 metrics source code,Keras 并没有定义单一的准确度指标,而是定义了几个不同的指标,其中包括 binary_accuracycategorical_accuracyunder the hood 发生的情况是,由于您选择二元交叉熵作为损失函数并且没有指定特定的准确度指标,Keras(错误地......)推断您对 binary_accuracy 感兴趣,这就是它返回 - 而实际上您对 categorical_accuracy 感兴趣。

让我们使用 Keras 中的 MNIST CNN example 验证是否是这种情况,并进行以下修改:

model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])  # WRONG way

model.fit(x_train, y_train,
          batch_size=batch_size,
          epochs=2,  # only 2 epochs, for demonstration purposes
          verbose=1,
          validation_data=(x_test, y_test))

# Keras reported accuracy:
score = model.evaluate(x_test, y_test, verbose=0) 
score[1]
# 0.9975801164627075

# Actual accuracy calculated manually:
import numpy as np
y_pred = model.predict(x_test)
acc = sum([np.argmax(y_test[i])==np.argmax(y_pred[i]) for i in range(10000)])/10000
acc
# 0.98780000000000001

score[1]==acc
# False    

为了解决这个问题,即使用二元交叉熵作为您的损失函数(正如我所说,这没有错,至少在原则上),同时仍然获得手头问题所需的分类准确性,您应该在模型编译中明确要求 categorical_accuracy,如下所示:

from keras.metrics import categorical_accuracy
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=[categorical_accuracy])

在 MNIST 示例中,在如上所示训练、评分和预测测试集之后,这两个指标现在是相同的,它们应该是:

# Keras reported accuracy:
score = model.evaluate(x_test, y_test, verbose=0) 
score[1]
# 0.98580000000000001

# Actual accuracy calculated manually:
y_pred = model.predict(x_test)
acc = sum([np.argmax(y_test[i])==np.argmax(y_pred[i]) for i in range(10000)])/10000
acc
# 0.98580000000000001

score[1]==acc
# True    

系统设置:

Python version 3.5.3
Tensorflow version 1.2.1
Keras version 2.0.4

更新:在我发帖后,我发现这个问题已经在 this answer 中发现了。


d
desertnaut

这完全取决于您正在处理的分类问题的类型。主要分为三大类

二元分类(两个目标类),

多类分类(两个以上的排他目标),

多标签分类(两个以上非独占目标),其中可以同时开启多个目标类。

在第一种情况下,应该使用二进制交叉熵,并且应该将目标编码为单热向量。

在第二种情况下,应该使用分类交叉熵,并且应该将目标编码为 one-hot 向量。

在最后一种情况下,应该使用二进制交叉熵,并且应该将目标编码为 one-hot 向量。每个输出神经元(或单元)被认为是一个单独的随机二元变量,整个输出向量的损失是单个二元变量损失的乘积。因此,它是每个单个输出单元的二元交叉熵的乘积。

二元交叉熵定义为

https://i.stack.imgur.com/Qb9x9.png

分类交叉熵定义为

https://i.stack.imgur.com/g0b6Y.png

其中 c 是在类数 C 上运行的索引。


您确定二进制和分类交叉熵的定义与此答案中的公式相同吗?
@nbro,实际上,c 索引在二进制交叉熵公式中是多余的,它不需要存在(因为只有 2 个类并且每个类的概率都嵌入在 y(x) 中。否则那些公式应该是正确的,但请注意那些不是损失,那些是可能性。如果你想要损失,你必须采取其中的 log
@Whynote您应该解释为什么分类交叉熵的公式显然比二元交叉熵的公式看起来更简单。您还应该解释 Cc 和所有其他符号的含义。 (是的,我熟悉日志技巧)。此外,在所有情况下,您都说目标应该是单热编码的,但是您对每种情况都这么说,而不是说“对于所有情况,目标都需要热编码”。也许你应该用文字来解释你的解释。
@nbro 我为什么要解释为什么一个公式看起来比另一个更简单?知道这一点对理解答案有何帮助?为什么我重复目标应该是单热编码会是一个问题?这不是对文章或论文的评论。我不知道你为什么关心措辞。只要解释有道理。我将解释 Cc
由于您决定就相关 ML 概念给出一般教程类型的答案,而不是按要求解决特定的编码问题,因此指出在二进制情况下存在选项 not< 可能是有用的/i> 进行一次热编码,但将标签保持为单个数字并在最后一层使用 sigmoid 激活。只是在每个项目符号中重复一次热编码要求确实是多余的,而且不是好的做法。
A
Alex Svetkin

我遇到了一个“倒置”的问题——我使用 categorical_crossentropy(有 2 个类)得到了很好的结果,而使用 binary_crossentropy 却很差。似乎问题出在错误的激活函数上。正确的设置是:

对于 binary_crossentropy:sigmoid 激活,标量目标

对于 categorical_crossentropy:softmax 激活,one-hot 编码目标


您确定 binary_crossentropy 的标量目标吗?看起来您应该使用“多热”编码目标(例如 [0 1 0 0 1 1])。
当然。请参阅 keras.io/losses/#usage-of-loss-functions,它说:“当使用 categorical_crossentropy 损失时,您的目标应该是分类格式(例如,如果您有 10 个类,则每个样本的目标应该是一个 10 维向量,该向量期望1在与样本类别对应的索引处)"
但我们谈论的是 binary_crossentropy - 而不是 categorical_crossentropy。
这个答案似乎与 stackoverflow.com/a/49175655/3924118 不一致,其中作者说目标应该是一次性编码的,而在你的回答中,你建议它们应该是标量。你应该澄清这一点。
@AlexanderSvetkin,目标应该在任何地方进行单热编码,而不仅仅是在使用分类交叉熵时
d
desertnaut

这真是一个有趣的案例。实际上,在您的设置中,以下陈述是正确的:

binary_crossentropy = len(class_id_index) * categorical_crossentropy

这意味着在乘数不变的情况下,您的损失是等价的。您在训练阶段观察到的奇怪行为可能是以下现象的一个示例:

一开始,最常见的类占主导地位 - 因此网络正在学习为每个示例预测大部分此类。在它学会了最频繁的模式之后,它开始区分不太频繁的类。但是当您使用 adam 时 - 学习率的值比训练开始时的值要小得多(这是因为这个优化器的性质)。它使训练速度变慢,并防止您的网络例如使较差的局部最小值变得不太可能。

这就是为什么这个常数因素在 binary_crossentropy 的情况下可能会有所帮助。在许多 epoch 之后 - 学习率值大于 categorical_crossentropy 的情况。当我注意到这种行为或/和使用以下模式调整班级权重时,我通常会重新开始训练(和学习阶段)几次:

class_weight = 1 / class_frequency

这使得来自不太频繁的类的损失在训练开始和优化过程的进一步部分平衡了主要类损失的影响。

编辑:

实际上 - 即使在数学的情况下,我也检查过:

binary_crossentropy = len(class_id_index) * categorical_crossentropy

应该成立 - 在 keras 的情况下它不正确,因为 keras 会自动将所有输出归一化以总计为 1。这是这种奇怪行为背后的实际原因,因为在多分类的情况下,这种标准化会损害训练。


这是一个非常合理的解释。但我不确定这真的是主要原因。因为我还观察到我的几个学生在应用 binary-X-ent 而不是 cat-X-ent 时会出现这种奇怪的行为(这是一个错误)。即使只训练 2 个 epoch 也是如此!使用带有逆类先验的 class_weight 并没有帮助。可能对学习率进行严格调整会有所帮助,但默认值似乎有利于 bin-X-ent。我认为这个问题值得更多的调查......
等等,不抱歉,我没有得到你的更新:softmax 总是使输出总和为 1,所以我们不在乎吗?只要我们每个示例只有一个正确的黄金课程,为什么这会损害培训?
d
desertnaut

在评论了@Marcin 的答案后,我更仔细地检查了我的一个学生代码,我发现了同样的奇怪行为,即使只有 2 个 epochs ! (所以@Marcin的解释在我看来不太可能)。

而且我发现答案实际上非常简单:使用带有 2 个以上标签的 binary_crossentropy 时,使用 Keras 方法 evaluate 计算的准确度完全是错误的。您可以通过自己重新计算准确度来检查(首先调用 Keras 方法“预测”,然后计算预测返回的正确答案的数量):您会得到真正的准确度,它远低于 Keras“评估”的准确度。


我在第一次迭代中也看到了类似的行为。
b
bazinga

一个多类设置下的简单例子来说明

假设你有 4 个类(onehot 编码),下面只是一个预测

true_label = [0,1,0,0] 预测标签 = [0,0,1,0]

使用 categorical_crossentropy 时,准确率仅为 0 ,它只关心您是否正确获取了相关类。

但是,当使用 binary_crossentropy 时,会计算所有类的准确度,这个预测的准确率是 50%。最终结果将是两种情况下各个精度的平均值。

建议对多类(类互斥)问题使用 categorical_crossentropy,对多标签问题使用 binary_crossentropy。


D
Dr. Snoopy

由于它是一个多类问题,您必须使用 categorical_crossentropy,二进制交叉熵会产生虚假结果,很可能只会评估前两个类。

多类问题的 50% 可能相当不错,具体取决于类的数量。如果你有 n 个类,那么 100/n 是通过输出一个随机类可以获得的最低性能。


s
susan097

您在使用损失 categorical_crossentropy 时传递了一个形状为 (x-dim, y-dim) 的目标数组。 categorical_crossentropy 期望目标是形状(样本、类)的二进制矩阵(1 和 0)。如果您的目标是整数类,您可以通过以下方式将它们转换为预期格式:

from keras.utils import to_categorical
y_binary = to_categorical(y_int)

或者,您可以改用损失函数 sparse_categorical_crossentropy,它确实需要整数目标。

model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

P
Priyansh

使用 categorical_crossentropy 损失时,您的目标应该是分类格式(例如,如果您有 10 个类,则每个样本的目标应该是一个全零的 10 维向量,除了对应于样本类别)。


这究竟是如何回答这个问题的?
K
Kuang Yan

看一下方程你会发现binary cross entropy不仅惩罚那些标签= 1,预测= 0,而且标签= 0,预测= 1。

然而 categorical cross entropy 只惩罚那些标签 = 1 但预测 = 1。这就是为什么我们假设只有一个标签是正面的。


A
Allohvk

主旨被desernaut精彩的侦探片圆满地回答了。然而,在某些情况下,BCE(二元交叉熵)可能会产生与 CCE(分类交叉熵)不同的结果,并且可能是首选。虽然上面共享的拇指规则(选择哪个损失)适用于 99% 的情况,但我想在这个讨论中添加一些新的维度。

OP 有一个 softmax 激活,这会抛出一个概率分布作为预测值。这是一个多类问题。首选损失是分类 CE。本质上,这归结为 -ln(p),其中“p”是样本中唯一正类的预测概率。这意味着负面预测在计算 CE 中没有作用。这是故意的。

在极少数情况下,可能需要将 -ve 声部计算在内。这可以通过将上述样本视为一系列二进制预测来完成。因此,如果预期为 [1 0 0 0 0] 并且预测为 [0.1 0.5 0.1 0.1 0.2],则进一步细分为:

expected = [1,0], [0,1], [0,1], [0,1], [0,1]
predicted = [0.1, 0.9], [.5, .5], [.1, .9], [.1, .9], [.2, .8]

现在我们继续计算 5 个不同的交叉熵 - 一个用于上述 5 个预期/预测组合中的每一个并将它们相加。然后:

CE = -[ ln(.1) + ln(0.5) + ln(0.9) + ln(0.9) + ln(0.8)]

CE 具有不同的尺度,但仍然是对预期值和预测值之间差异的度量。唯一的区别是,在这个方案中,-ve 值也与 +ve 值一起受到惩罚/奖励。如果您的问题是您要使用输出概率(+ve 和 -ves)而不是使用 max() 来预测 1 +ve 标签,那么您可能需要考虑这个版本的 CE。

预期 = [1 0 0 0 1] 的多标签情况如何?传统方法是每个输出神经元使用一个 sigmoid,而不是整体 softmax。这确保了输出概率彼此独立。所以我们得到类似的东西:

expected = [1 0 0 0 1]
predicted is = [0.1 0.5 0.1 0.1 0.9]

根据定义,CE 测量 2 个概率分布之间的差异。但以上两个列表都不是概率分布。概率分布的总和应始终为 1。因此,传统的解决方案是使用与以前相同的损失方法 - 将预期值和预测值分解为 5 个单独的概率分布,继续计算 5 个交叉熵并将它们相加。然后:

CE = -[ ln(.1) + ln(0.5) + ln(0.9) + ln(0.9) + ln(0.9)] = 3.3

当类的数量可能非常多时就会出现挑战——比如 1000 个,并且每个样本中可能只有几个。所以预期是这样的:[1,0,0,0,0,0,1,0,0,0.....990 个零]。预测可能类似于:[.8, .1, .1, .1, .1, .1, .8, .1, .1, .1.....990 0.1's]

在这种情况下,CE =

- [ ln(.8) + ln(.8) for the 2 +ve classes and 998 * ln(0.9) for the 998 -ve classes]

= 0.44 (for the +ve classes) +  105 (for the negative classes)

您可以看到 -ve 类在计算损失时如何开始产生令人讨厌的值。 +ve 样本的声音(这可能是我们所关心的全部)正在被淹没。我们做什么?我们不能使用分类 CE(在计算中只考虑 +ve 样本的版本)。这是因为,我们被迫将概率分布分解为多个二元概率分布,否则它一开始就不是概率分布。一旦我们将其分解为多个二进制概率分布,我们别无选择,只能使用二进制 CE,这当然会给 -ve 类赋予权重。

一种选择是通过乘数淹没 -ve 类的声音。因此,我们将所有 -ve 损失乘以 gamma < 1 的值 gamma。假设在上述情况下,gamma 可以是 0.0001。现在损失来了:

= 0.44 (for the +ve classes) +  0.105 (for the negative classes)

讨厌的价值已经下降。 2 年前,Facebook 在他们提出的一篇论文中做到了这一点,并且在论文中他们还将 -ve 损失乘以 p 的 x 次方。 'p' 是输出为 a +ve 且 x 是一个常数 > 1 的概率。这种惩罚 -ve 损失更大,尤其是模型非常自信的那些(其中 1-p 接近 1)。这种惩罚负类损失以及对容易分类的案例(占 -ve 案例的大部分)进行更严厉惩罚的综合效果对 Facebook 来说效果很好,他们称之为焦点损失。

因此,针对 OP 关于二进制 CE 在他的情况下是否有意义的问题,答案是 - 这取决于。在 99% 的情况下,传统的拇指规则有效,但有时这些规则可能会被弯曲甚至破坏以适应手头的问题。

更深入的处理可以参考:https://towardsdatascience.com/cross-entropy-classification-losses-no-math-few-stories-lots-of-intuition-d56f8c7f06b0


d
desertnaut

binary_crossentropy(y_target, y_predict) 不需要应用于二元分类问题。

binary_crossentropy()的源码中,实际用到了tensorflow的nn.sigmoid_cross_entropy_with_logits(labels=target, logits=output)

而且,在 documentation 中,它说:

测量离散分类任务中的概率误差,其中每个类都是独立的,而不是互斥的。例如,可以执行多标签分类,其中一张图片可以同时包含大象和狗。


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅