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

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


当前回答

您正在传递一个形状的目标数组(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'])

其他回答

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

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

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

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

分类交叉熵和二元交叉熵之间这种明显的性能差异的原因是用户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

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

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

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。这就是这种奇怪行为背后的实际原因,因为在多分类的情况下,这种规范化会损害训练。

您正在传递一个形状的目标数组(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'])