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

or

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

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


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

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


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

binary_crossentropy = len(class_id_index) * categorical_crossentropy

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

At the beginning the most frequent class is dominating the loss - so network is learning to predict mostly this class for every example. After it learnt the most frequent pattern it starts discriminating among less frequent classes. But when you are using adam - the learning rate has a much smaller value than it had at the beginning of training (it's because of the nature of this optimizer). It makes training slower and prevents your network from e.g. leaving a poor local minimum less possible.

这就是为什么这个常量因子在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方法计算的精度很简单 当使用超过2个标签的binary_crossentropy时错误

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

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

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

是有效的,第二个

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

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

Why is that? If you check the metrics source code, Keras does not define a single accuracy metric, but several different ones, among them binary_accuracy and categorical_accuracy. What happens under the hood is that, since you have selected binary cross entropy as your loss function and have not specified a particular accuracy metric, Keras (wrongly...) infers that you are interested in the binary_accuracy, and this is what it returns - while in fact you are interested in the 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

更新:在我的帖子发布后,我发现这个问题已经在这个答案中被确定了。


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


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

二元分类(两个目标类), 多类分类(两个以上专属目标), 多标签分类(两个以上的非排他目标),其中多个目标类别可以同时打开。

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

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

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

二元交叉熵定义为

分类交叉熵定义为

其中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()的源代码中,nn。实际上使用了tensorflow的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, predicted =0,而且也惩罚了label =0, predicted = 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。

多标签的情况下,期望= [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,0,1,0,0,0.....990 0]。预测结果可能是这样的:[。8, 1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1, 1.....990年0.1的)

在本例中,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)

You can see how the -ve classes are beginning to create a nuisance value when calculating the loss. The voice of the +ve samples (which may be all that we care about) is getting drowned out. What do we do? We can't use categorical CE (the version where only +ve samples are considered in calculation). This is because, we are forced to break up the probability distributions into multiple binary probability distributions because otherwise it would not be a probability distribution in the first place. Once we break it into multiple binary probability distributions, we have no choice but to use binary CE and this of course gives weightage to -ve classes.

一种选择是用一个倍增器淹没-ve类的声音。所以我们将所有-ve损失乘以一个值,其中gamma < 1。在上面的例子中,gamma可以是。0001。现在损失是:

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

The nuisance value has come down. 2 years back Facebook did that and much more in a paper they came up with where they also multiplied the -ve losses by p to the power of x. 'p' is the probability of the output being a +ve and x is a constant>1. This penalized -ve losses even further especially the ones where the model is pretty confident (where 1-p is close to 1). This combined effect of punishing negative class losses combined with harsher punishment for the easily classified cases (which accounted for majority of the -ve cases) worked beautifully for Facebook and they called it focal loss.

所以在回答OP关于二进制CE在他的情况下是否有任何意义的问题时,答案是——这要看情况。在99%的情况下,传统的拇指规则是有效的,但有时这些规则可能会被弯曲甚至破坏,以适应手头的问题。

要了解更深入的治疗方法,请访问:https://towardsdatascience.com/cross-entropy-classification-losses-no-math-few-stories-lots-of-intuition-d56f8c7f06b0