我正在尝试训练 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
。如果您有两个类,它们将在二进制标签中表示为 0, 1
,在分类标签格式中表示为 10, 01
。
Dense(1, activation='softmax')
用于二元分类是完全错误的。请记住,softmax 输出是一个总和为 1 的概率分布。如果您希望只有一个具有二进制分类的输出神经元,请使用具有二进制交叉熵的 sigmoid。
分类和分类之间出现这种明显性能差异的原因二进制交叉熵是用户 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_accuracy
和 categorical_accuracy
。 under 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 中发现了。
这完全取决于您正在处理的分类问题的类型。主要分为三大类
二元分类(两个目标类),
多类分类(两个以上的排他目标),
多标签分类(两个以上非独占目标),其中可以同时开启多个目标类。
在第一种情况下,应该使用二进制交叉熵,并且应该将目标编码为单热向量。
在第二种情况下,应该使用分类交叉熵,并且应该将目标编码为 one-hot 向量。
在最后一种情况下,应该使用二进制交叉熵,并且应该将目标编码为 one-hot 向量。每个输出神经元(或单元)被认为是一个单独的随机二元变量,整个输出向量的损失是单个二元变量损失的乘积。因此,它是每个单个输出单元的二元交叉熵的乘积。
二元交叉熵定义为
https://i.stack.imgur.com/Qb9x9.png
分类交叉熵定义为
https://i.stack.imgur.com/g0b6Y.png
其中 c
是在类数 C
上运行的索引。
我遇到了一个“倒置”的问题——我使用 categorical_crossentropy(有 2 个类)得到了很好的结果,而使用 binary_crossentropy 却很差。似乎问题出在错误的激活函数上。正确的设置是:
对于 binary_crossentropy:sigmoid 激活,标量目标
对于 categorical_crossentropy:softmax 激活,one-hot 编码目标
这真是一个有趣的案例。实际上,在您的设置中,以下陈述是正确的:
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
。这是这种奇怪行为背后的实际原因,因为在多分类的情况下,这种标准化会损害训练。
在评论了@Marcin 的答案后,我更仔细地检查了我的一个学生代码,我发现了同样的奇怪行为,即使只有 2 个 epochs ! (所以@Marcin的解释在我看来不太可能)。
而且我发现答案实际上非常简单:使用带有 2 个以上标签的 binary_crossentropy 时,使用 Keras 方法 evaluate
计算的准确度完全是错误的。您可以通过自己重新计算准确度来检查(首先调用 Keras 方法“预测”,然后计算预测返回的正确答案的数量):您会得到真正的准确度,它远低于 Keras“评估”的准确度。
一个多类设置下的简单例子来说明
假设你有 4 个类(onehot 编码),下面只是一个预测
true_label = [0,1,0,0] 预测标签 = [0,0,1,0]
使用 categorical_crossentropy 时,准确率仅为 0 ,它只关心您是否正确获取了相关类。
但是,当使用 binary_crossentropy 时,会计算所有类的准确度,这个预测的准确率是 50%。最终结果将是两种情况下各个精度的平均值。
建议对多类(类互斥)问题使用 categorical_crossentropy,对多标签问题使用 binary_crossentropy。
由于它是一个多类问题,您必须使用 categorical_crossentropy,二进制交叉熵会产生虚假结果,很可能只会评估前两个类。
多类问题的 50% 可能相当不错,具体取决于类的数量。如果你有 n 个类,那么 100/n 是通过输出一个随机类可以获得的最低性能。
您在使用损失 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'])
使用 categorical_crossentropy
损失时,您的目标应该是分类格式(例如,如果您有 10 个类,则每个样本的目标应该是一个全零的 10 维向量,除了对应于样本类别)。
看一下方程你会发现binary cross entropy不仅惩罚那些标签= 1,预测= 0,而且标签= 0,预测= 1。
然而 categorical cross entropy 只惩罚那些标签 = 1 但预测 = 1。这就是为什么我们假设只有一个标签是正面的。
主旨被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% 的情况下,传统的拇指规则有效,但有时这些规则可能会被弯曲甚至破坏以适应手头的问题。
binary_crossentropy(y_target, y_predict)
不需要应用于二元分类问题。
binary_crossentropy()的源码中,实际用到了tensorflow的nn.sigmoid_cross_entropy_with_logits(labels=target, logits=output)
。
而且,在 documentation 中,它说:
测量离散分类任务中的概率误差,其中每个类都是独立的,而不是互斥的。例如,可以执行多标签分类,其中一张图片可以同时包含大象和狗。
不定期副业成功案例分享
c
索引在二进制交叉熵公式中是多余的,它不需要存在(因为只有 2 个类并且每个类的概率都嵌入在y(x)
中。否则那些公式应该是正确的,但请注意那些不是损失,那些是可能性。如果你想要损失,你必须采取其中的log
。C
、c
和所有其他符号的含义。 (是的,我熟悉日志技巧)。此外,在所有情况下,您都说目标应该是单热编码的,但是您对每种情况都这么说,而不是说“对于所有情况,目标都需要热编码”。也许你应该用文字来解释你的解释。C
和c
sigmoid
激活。只是在每个项目符号中重复一次热编码要求确实是多余的,而且不是好的做法。