实现二元交叉熵损失给出了与 Tensorflow 不同的答案

Posted

技术标签:

【中文标题】实现二元交叉熵损失给出了与 Tensorflow 不同的答案【英文标题】:Implementing Binary Cross Entropy loss gives different answer than Tensorflow's 【发布时间】:2021-08-09 09:26:08 【问题描述】:

我正在使用 Raw python 实现 Binary Cross-Entropy 损失函数,但它给了我与 Tensorflow 截然不同的答案。 这是我从 Tensorflow 得到的答案:-

import numpy as np
from tensorflow.keras.losses import BinaryCrossentropy

y_true = np.array([1., 1., 1.])
y_pred = np.array([1., 1., 0.])
bce = BinaryCrossentropy()
loss = bce(y_true, y_pred)
print(loss.numpy())

输出:

>>> 5.1416497230529785

据我所知,二元交叉熵的公式是这样的:

我用原始 python 实现了相同的功能,如下所示:

def BinaryCrossEntropy(y_true, y_pred):
    m = y_true.shape[1]
    y_pred = np.clip(y_pred, 1e-7, 1 - 1e-7)
    # Calculating loss
    loss = -1/m * (np.dot(y_true.T, np.log(y_pred)) + np.dot((1 - y_true).T, np.log(1 - y_pred)))

    return loss

print(BinaryCrossEntropy(np.array([1, 1, 1]).reshape(-1, 1), np.array([1, 1, 0]).reshape(-1, 1)))

但是从这个函数中我得到的损失值是:

>>> [[16.11809585]]

我怎样才能得到正确的答案?

【问题讨论】:

【参考方案1】:

tf.keras.losses.BinaryCrossentropy()的构造函数中,你会注意到,

tf.keras.losses.BinaryCrossentropy(
    from_logits=False, label_smoothing=0, reduction=losses_utils.ReductionV2.AUTO,
    name='binary_crossentropy'
)

默认参数reduction 很可能具有Reduction.SUM_OVER_BATCH_SIZE 的值,如here 所述。假设我们的模型输出的形状是[ 1 , 3 ]。意思是,我们的批量大小为 1,输出 dims 为 3(这并不意味着有 3 个类)。我们需要计算第 0 个轴上的平均值,即批量维度。

我会用代码说清楚,

import tensorflow as tf
import numpy as np

y_true = np.array( [1., 1., 1.] ).reshape( 1 , 3 )
y_pred = np.array( [1., 1., 0.] ).reshape( 1 , 3 )

bce = tf.keras.losses.BinaryCrossentropy( from_logits=False , reduction=tf.keras.losses.Reduction.SUM_OVER_BATCH_SIZE )
loss = bce( y_true, y_pred )

print(loss.numpy())

输出是,

5.1416497230529785

二元交叉熵的表达式与问题中提到的相同。 N 指的是批量大小。

我们现在自行实施 BCE。首先,我们裁剪模型的输出,将max 设置为tf.keras.backend.epsilon(),将min 设置为1 - tf.keras.backend.epsilon()tf.keras.backend.epsilon() 的值为 1e-7。

y_pred = np.clip( y_pred , tf.keras.backend.epsilon() , 1 - tf.keras.backend.epsilon() )

使用 BCE 的表达式,

p1 = y_true * np.log( y_pred + tf.keras.backend.epsilon() )
p2 = ( 1 - y_true ) * np.log( 1 - y_pred + tf.keras.backend.epsilon() )

print( p1 )
print( p2 )

输出,

[[  0.           0.         -15.42494847]]
[[-0. -0.  0.]]

请注意,形状仍然保留。 np.dot 会将它们变成一个包含两个元素的数组,即形状为 [ 1 , 2 ] (与您的实现一样)。

最后,我们将它们相加并使用np.mean() 在批处理维度上计算它们的平均值,

o  = -np.mean( p1 + p2 )
print( o )

输出是,

5.141649490132791

您可以通过打印每个条款的shape 来检查实施中的问题。

【讨论】:

【参考方案2】:

您的实施存在一些问题。正确的是numpy

def BinaryCrossEntropy(y_true, y_pred):
    y_pred = np.clip(y_pred, 1e-7, 1 - 1e-7)
    term_0 = (1-y_true) * np.log(1-y_pred + 1e-7)
    term_1 = y_true * np.log(y_pred + 1e-7)
    return -np.mean(term_0+term_1, axis=0)

print(BinaryCrossEntropy(np.array([1, 1, 1]).reshape(-1, 1), 
                         np.array([1, 1, 0]).reshape(-1, 1)))
[5.14164949]

注意,在tf. keras 模型训练期间,最好使用keras 后端功能。您可以使用keras 后端实用程序以同样的方式实现它。

def BinaryCrossEntropy(y_true, y_pred): 
    y_pred = K.clip(y_pred, K.epsilon(), 1 - K.epsilon())
    term_0 = (1 - y_true) * K.log(1 - y_pred + K.epsilon())  
    term_1 = y_true * K.log(y_pred + K.epsilon())
    return -K.mean(term_0 + term_1, axis=0)

print(BinaryCrossEntropy(
    np.array([1., 1., 1.]).reshape(-1, 1), 
    np.array([1., 1., 0.]).reshape(-1, 1)
    ).numpy())
[5.14164949]

【讨论】:

以上是关于实现二元交叉熵损失给出了与 Tensorflow 不同的答案的主要内容,如果未能解决你的问题,请参考以下文章

为啥tf模型训练时的二元交叉熵损失与sklearn计算的不同?

Keras 和 TensorFlow 中所有这些交叉熵损失之间有啥区别?

Sigmoid 与二元交叉熵损失

损失函数tensorflow2实现——Python实战

sklearn基于make_scorer函数为Logistic模型构建自定义损失函数+代码实战(二元交叉熵损失 binary cross-entropy loss)

二元交叉熵损失如何在自动编码器上起作用?