为什么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'])

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

232444 次浏览

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

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

这是一个很有趣的案例。实际上,在你的设置中,下面的语句是正确的:

binary_crossentropy = len(class_id_index) * categorical_crossentropy

这意味着在一个常数乘法因子之前,你的损失是相等的。你在训练阶段观察到的奇怪行为可能是以下现象的一个例子:

  1. 一开始,最频繁的类是占主导地位的损失-所以网络正在学习预测大多数这类的每一个例子。
  2. 在它学会了最常见的模式后,它开始区分不太常见的类别。但是当你使用adam时,学习率的值比训练开始时小得多(这是因为这个优化器的性质)。它使训练速度变慢,并防止你的网络,例如,不太可能留下一个糟糕的局部最小值。

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

class_weight = 1 / class_frequency

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

编辑:

事实上,我检查了,即使在数学方面

binary_crossentropy = len(class_id_index) * categorical_crossentropy

应该成立——如果是keras,则不成立,因为keras会自动将所有输出归一化为1。这就是这种奇怪行为背后的实际原因,因为在多分类的情况下,这种规范化会损害训练。

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

我发现答案其实很简单:当使用超过2个标签的binary_crossentropy时,用Keras方法evaluate计算的精度完全错误。你可以自己重新计算准确率(首先调用Keras方法“predict”,然后计算由predict返回的正确答案的数量):你得到了真正的准确率,这比Keras的“evaluate”要低得多。

我遇到了一个“倒置”的问题-我用categorical_crossentropy(2个类)得到了很好的结果,而用binary_crossentropy得到了很差的结果。看来问题出在激活函数错了。正确的设置是:

  • 对于binary_crossentropy: sigmoid激活,标量目标
  • 对于categorical_crossentropy: softmax激活,单热编码目标

这一明显的性能差异的原因在分类& &;二进制交叉熵是用户xtof54已经在他的回答如下中报告的,即:

用Keras方法evaluate计算的精度很简单 错误时使用binary_crossentropy超过2个标签

我想对此进行更详细的阐述,展示实际的潜在问题,解释它,并提供补救措施。

这种行为不是bug;潜在的原因是相当微妙的&当你在你的模型编译中简单地包含metrics=['accuracy']时,Keras实际上如何猜测使用哪个精度,这取决于你所选择的损失函数。换句话说,当您的第一个编译选项

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

是有效的,第二个

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

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

为什么呢?如果你检查度量标准源代码, Keras并没有定义一个精度度量,而是定义了几个不同的度量,其中包括binary_accuracycategorical_accuracy。发生在在引擎盖下的事情是,因为你选择了二进制交叉熵作为你的损失函数,并且没有指定一个特定的精度度量,Keras(错误地…)推断你对binary_accuracy感兴趣,这就是它返回的结果——而实际上你对categorical_accuracy感兴趣。

让我们用Keras中的CNN例子来验证这种情况,并进行以下修改:

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

更新:在我发表文章后,我发现这个问题已经在这个答案中被确定。

当使用categorical_crossentropy损失时,你的目标应该是分类格式的(例如,如果你有10个类,每个样本的目标应该是一个10维向量,除了对应于样本类的索引处的1外,它都是零)。

这完全取决于你要处理的分类问题的类型。主要有三个类别

  • 二进制分类(两个目标类),
  • 分类(超过两个独家目标),
  • 多标记分类(超过两个非独家目标),其中多个目标类别可以同时打开。

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

在第二种情况下,应使用分类交叉熵,并将目标编码为单热向量。

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

二元交叉熵定义为

enter image description here

分类交叉熵定义为

enter image description here

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

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

假设您有4个类(其中一个是编码的),下面只有一个预测

true_label = [0,1,0,0] Predicted_label = [0,0,1,0]

当使用categorical_crossentropy时,准确率仅为0,它只关心你是否得到了相关的类。

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

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

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

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

并且,在文档中,它说:

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

你正在传递一个形状(x-dim, y-dim)的目标数组,同时使用categorical_crossentropy作为损失。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'])

看看这个方程,你会发现二元交叉熵不仅惩罚那些label = 1, predict =0,而且label =0, predict = 1。

然而分类交叉熵只惩罚那些标签= 1但预测= 1的人。这就是为什么我们假设只有一个标签是正的。

这本书的主要观点得到了出色的侦探作品《沙漠》(desernaut)的圆满回答。然而,在某些情况下,BCE(二元交叉熵)可能会产生与CCE(分类交叉熵)不同的结果,并且可能是首选。虽然上面分享的经验法则(选择哪种损失)适用于99%的情况,但我想在这个讨论中添加一些新的维度。

OP有一个软最大激活,这将抛出一个概率分布作为预测值。这是一个多阶层的问题。首选的损失是分类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。

多标记情况如何,其中expected = [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度量两个概率分布之间的差值。但上面两个列表不是概率分布。概率分布的总和应该总是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年0]。预测可以是类似于[。8, .1, .1, .1, .1, .1, .8, .1, .1..... __abc3]

在本例中,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损失乘以一个值,其中<1. 在上面的例子中,gamma可以是。0001。现在损失是:

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

妨害价值下降了。两年前,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