为什么模型拟合/评估时的binary_crossentropy损失与手动计算结果不一致?
问题原因及解决方案
你之前认为单样本binary_crossentropy最大值不超过15.5的结论是正确的,这个数值是预测值裁剪到1e-7左右时的理论上限,只有损失计算逻辑错误、被额外求和/加权时才会超过该值。二者损失差异极大的常见原因按优先级排序如下:
1. from_logits参数配置不匹配
这是最常见的触发原因:
- 如果第二个输出层没有加
sigmoid激活,输出的是未归一化的logits,但你使用默认的字符串形式binary_crossentropy(默认from_logits=False),会直接将logits当成0~1的概率计算,损失值会异常偏高。 - 反之如果输出层已经加了
sigmoid,但损失函数手动设置了from_logits=True,框架会对已经是概率的输出再做一次sigmoid变换,计算结果也会和手动计算差异极大。
你可以通过以下代码验证:
# 加上from_logits=True再计算,看结果是否接近217 K.mean(tf.losses.binary_crossentropy(test_out2, pred[1], from_logits=True))
2. 损失归约(Reduction)配置错误
tf.keras损失函数默认归约方式是SUM_OVER_BATCH_SIZE,即对batch内所有样本的损失求和后除以batch大小得到平均损失。如果归约方式被修改为SUM(仅求和不除以batch大小)、或者对损失维度做了额外的求和操作,损失会被成比例放大。可检查compile时传入的是否为自定义损失实例:
# 错误示例:归约为求和会让损失放大为batch_size倍 loss=tf.keras.losses.BinaryCrossentropy(reduction=tf.keras.losses.Reduction.SUM)
3. 额外损失项配置
以下配置只会在模型训练/评估的损失中生效,不会影响手动计算的结果:
- 标签平滑:compile时如果设置了
label_smoothing参数,框架会对标签做平滑处理,计算出的损失会比手动用原始标签计算的更高。 - 类别/样本权重:如果设置了
class_weight或者evaluate时传入了sample_weight,少数类的损失会被放大,最终平均损失会偏高。 - 输出维度不匹配:如果第二个输出层的神经元数量大于1,而你传入的标签是
(1000,1),框架会自动广播计算,损失会被重复计算多次导致数值偏高。
4. 数值裁剪阈值差异
为了避免出现log(0)的无穷值,交叉熵计算默认会对预测值做裁剪:
- 手动调用
tf.losses.binary_crossentropy默认会将预测值裁剪到[epsilon, 1-epsilon]区间(epsilon通常为1e-7),避免出现无穷大损失。 - 如果你使用了自定义损失函数没有加裁剪,或者旧版本框架的裁剪阈值不同,当存在预测值为1.0(对应标签为0)的样本时,损失就会异常偏高。
内容的提问来源于stack exchange,提问作者Mastiff
相关产品推荐
相关产品推荐

