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


159

我正在尝试训练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'])

从直觉上讲,为什么我要使用分类交叉熵是合理的,我不明白为什么使用二进制得到好的结果,而使用分类不好的原因。


10
如果是多类问题,则必须使用categorical_crossentropy。标签也需要转换为分类格式。请参阅to_categorical执行此操作。另见分类和二进制crossentropies的定义在这里
自治

我的标签是分类的,使用to_categorical创建(每个类一个热向量)。这是否意味着二进制交叉熵的〜80%准确度只是一个假数字?
Daniel Messias

我认同。如果您使用分类标签(即一种热向量),则需要categorical_crossentropy。如果有两个类,它们将以0, 1二进制标签和10, 01分类标签格式表示。
自治

1
我认为他只是将其与向量中的第一个数字进行比较,而忽略其余部分。
Thomas Pinetz '17

2
@NilavBaranGhosh对于涉及两个类的分类分类,表示形式为[[1,0],[0,1]](不是您提到的[[0,0],[0,1]])。Dense(1, activation='softmax')对于二进制分类是完全错误的。请记住,softmax输出是一个总和为1的概率分布。如果只希望具有一个具有二进制分类的输出神经元,请使用具有二进制交叉熵的Sigmoid。
自治

Answers:


204

用户xtof54在下面的答案中已经报告了类别和二进制交叉熵之间明显的性能差异的原因,即:

evaluate当使用带有2个以上标签的binary_crossentropy时,用Keras方法计算出的精度是完全错误的

我想对此进行详细说明,说明实际的根本问题,加以解释,并提供补救措施。

这不是错误。根本原因是当您仅将其包括在模型编辑中时,Keras如何根据所选择的损失函数实际猜测要使用哪种精度,这是一个相当微妙且未记录的问题metrics=['accuracy']。换句话说,当您的第一个编译选项

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中的MNIST 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

更新:发布后,我发现此答案中已发现此问题


1
loss='categorical_crossentropy', metrics=['categorical_accuracy']用于多类分类有什么问题吗?这就是我的直觉
NeStack

2
@NeStack不仅没有错,而且这是名义上的组合。
desertnaut

1
根据您所说的,只要我使用loss ='binary_crossentropy',就不会得到相同的收益,而我会使用metrics ='binary_accuracy'或metrics ='accuracy'吗?
BioCoder

2
@BioCoder确切地
-desertnaut

54

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

  • 二进制分类(两个目标类别),
  • 多类别分类(两个以上的排他目标),
  • 多标签分类(两个以上的非排他目标),其中可以同时启用多个目标类别。

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

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

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

二进制交叉熵定义为

在此处输入图片说明

类别交叉熵定义为

在此处输入图片说明

c类数上运行的索引在哪里


在我看来,您的答案非常正确,但是...我尝试遵循@desertnaut答案并进行了测试:使用binary_crossentropy损失函数和对categorical_accurency的度量,与使用categorical_crossentropy损失函数和准确性度量的精度相比,我有更高的精确度-并且我无法解释那...
Metal3d

@ Metal3d:您的问题的表述是:多标签还是单标签?
Whynote'18

单一标签,现在我意识到了为什么效果更好:)
Metal3d

您确定此答案的公式中定义了二元和分类交叉熵吗?
nbro

@nbro,实际上,c索引在二进制交叉熵公式中是多余的,它不需要在那里(因为只有2个类,并且每个类的概率都嵌入其中y(x)。否则这些公式应该是正确的,但是注意到这些都不是损失,这些都是可能性。如果你想要的损失,你必须采取的。log这些。
Whynote

40

我遇到了一个“倒置”问题-categorical_crossentropy(具有2个类)获得了良好的结果,binary_crossentropy获得了较差的结果。似乎问题在于激活功能错误。正确的设置是:

  • 用于binary_crossentropy:乙状结肠激活,标量目标
  • 用于categorical_crossentropy:softmax激活,一键编码目标

4
您确定关于binary_crossentropy的标量目标吗?看来您应该使用“很多”编码目标(例如[0 1 0 0 1 1])。
德米特里(Dmitry)

5
当然。请参阅keras.io/losses/#usage-of-loss-functions,其中说:“使用categorical_crossentropy损失时,您的目标应采用分类格式(例如,如果您有10个类别,则每个样本的目标应为10全零的三维矢量在对应于样本类别的索引处期望为1)”
Alexander Svetkin

1
但是我们谈论的是binary_crossentropy,而不是categorical_crossentropy。
德米特里(Dmitry)

这个答案似乎与stackoverflow.com/a/49175655/3924118不一致,作者在其中说目标应该是单热编码的,而在您的答案中,您建议目标应该是标量。您应该澄清这一点。
nbro

@AlexanderSvetkin,目标应该是一个热无处不在编码,用分类交叉熵不只是当
Whynote

28

这是一个非常有趣的案例。实际上,在您的设置中,以下语句是正确的:

binary_crossentropy = len(class_id_index) * categorical_crossentropy

这意味着,直到恒定的乘数,您的损失才是相等的。您在训练阶段观察到的怪异行为可能是以下现象的一个示例:

  1. 在开始时,最频繁的课程是损失的主要控制者-因此网络正在学习每个示例的主要预测方法。
  2. 在学习了最频繁的模式后,便开始在较不频繁的班级之间进行区分。但是,当您使用时adam-学习率的值比训练开始时的值要小得多(这是由于此优化器的性质所致)。这会使训练变慢,并防止您的网络减少例如降低较差的本地最小值的可能性。

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

class_weight = 1 / class_frequency

这使得在训练开始时以及在优化过程的另一部分中,频率较低的班级失去了平衡优势班级损失的影响。

编辑:

实际上-即使在数学情况下,我也进行了检查:

binary_crossentropy = len(class_id_index) * categorical_crossentropy

如果keras不正确,则应保留- 因为keras会自动将所有输出标准化为1。这是这种奇怪行为背后的实际原因,因为在多分类的情况下,这种归一化会损害训练。


我的回答对您有帮助吗?
马辛Możejko

1
这是一个非常合理的解释。但是我不确定这真的是主要原因。因为我还观察到在几个我的学生申请二元-X-ENT,而不是猫-X-ENT(这是一个错误的)工作时,这种怪异的行为。即使仅训练2个纪元,也是如此!将class_weight与反向类优先级结合使用并没有帮助。严格调整学习率可​​能会有所帮助,但默认值似乎偏爱bin-X-ent。我认为这个问题值得更多调查...
xtof54

1
等等,对不起,我没有得到您的更新:softmax总是使输出总和为1,所以我们不在乎吗?只要我们每个示例只有一个正确的黄金课程,为什么还要进行这种危害训练?
xtof54

20

在评论@Marcin答案之后,我更仔细地检查了我的一个学生代码,即使在2个纪元之后,我仍然发现相同的奇怪行为!(因此,就我而言,@ Marcin的解释不太可能)。

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


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

10

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

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

true_label = [0,1,0,0]预测_label = [0,0,1,0]

当使用categorical_crossentropy时,精度仅为0,它只在乎您是否正确设置了相关的类。

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

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


8

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

多类问题的50%可能很好,具体取决于类的数量。如果您有n个类别,则通过输出随机类别可以获得100 / n的最低性能。


2

当使用categorical_crossentropy损失时,您的目标应采用分类格式(例如,如果您有10个类别,则每个样本的目标应为10维向量,该向量为全零,但对应于该类别的索引处的索引为1。样品)。


3
这究竟如何回答这个问题?
desertnaut

2

查看方程式,您会发现二元互熵不仅惩罚那些标签= 1,预测= 0,而且还惩罚那些标签= 0,预测= 1。

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


1

您正在传递形状为(x-dim,y-dim)的目标数组,同时将其用作loss categorical_crossentropycategorical_crossentropy期望目标是形状(样本,类)的二进制矩阵(1s和0s)。如果目标是整数类,则可以通过以下方式将它们转换为预期的格式:

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'])

0

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

binary_crossentropy()的源代码中,nn.sigmoid_cross_entropy_with_logits(labels=target, logits=output)实际使用了TensorFlow函数。并且,在文档中,它说:

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.