实现二元交叉熵损失函数的结果与TensorFlow不同

9

我正在使用原始的Python实现二元交叉熵损失函数,但与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

据我所知,二元交叉熵的公式如下:

enter image description here

我使用原生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]]

如何得到正确的答案?

2个回答

9

你的实现存在一些问题。这里是正确的实现,使用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]

np.clip 是不必要的。 - Maxim Egorushkin
是的,但如果您看到问题,提问者确实这样做了。答案是针对他的。 - Innat

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,如此处所述。假设我们模型输出的形状为[1, 3],这意味着我们的批处理大小为1,输出维度为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来检查实现中的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接