【问题标题】:Why does sigmoid & crossentropy of Keras/tensorflow have low precision?为什么 Keras/tensorflow 的 sigmoid 和交叉熵精度低?
【发布时间】:2019-02-07 02:30:20
【问题描述】:

我有以下简单的神经网络(只有 1 个神经元)来测试 Keras 的sigmoidactivation 和binary_crossentropy 的计算精度:

model = Sequential()
model.add(Dense(1, input_dim=1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

为了简化测试,我手动将唯一权重设置为1,偏差设置为0,然后用2点训练集{(-a, 0), (a, 1)}评估模型,即

y = numpy.array([0, 1])
for a in range(40):
    x = numpy.array([-a, a])
    keras_ce[a] = model.evaluate(x, y)[0] # cross-entropy computed by keras/tensorflow
    my_ce[a] = np.log(1+exp(-a)) # My own computation

我的问题:我发现 Keras/Tensorflow 计算的二元交叉熵 (keras_ce) 在 a 约为16,如下图所示(蓝线)。随着'a'不断增长,它不会进一步减少。这是为什么?

这个神经网络只有 1 个神经元,其权重设置为 1,偏差为 0。使用 2 点训练集 {(-a, 0), (a, 1)}binary_crossentropy 只是

-1/2 [ log(1 - 1/(1+exp(a)) ) + log( 1/(1+exp(-a)) ) ] = log(1+exp(-a))

所以交叉熵应该随着a 的增加而减少,如上面橙色('my')所示。我可以更改一些 Keras/Tensorflow/Python 设置以提高其精度吗?还是我在某个地方弄错了?如有任何建议/cmets/answers,我将不胜感激。

【问题讨论】:

    标签: python tensorflow keras classification cross-entropy


    【解决方案1】:

    TL;DR 版本:在计算损失函数时,由于数值稳定性,概率值(即 sigmoid 函数的输出)被裁剪。


    如果您检查源代码,您会发现使用binary_crossentropy 作为损失会导致在losses.py 文件中调用binary_crossentropy 函数:

    def binary_crossentropy(y_true, y_pred):
        return K.mean(K.binary_crossentropy(y_true, y_pred), axis=-1)
    

    如您所见,它又调用等效的后端函数。如果使用 Tensorflow 作为后端,这将导致在 tensorflow_backend.py 文件中调用 binary_crossentropy 函数:

    def binary_crossentropy(target, output, from_logits=False):
        """ Docstring ..."""
    
        # Note: tf.nn.sigmoid_cross_entropy_with_logits
        # expects logits, Keras expects probabilities.
        if not from_logits:
            # transform back to logits
            _epsilon = _to_tensor(epsilon(), output.dtype.base_dtype)
            output = tf.clip_by_value(output, _epsilon, 1 - _epsilon)
            output = tf.log(output / (1 - output))
    
        return tf.nn.sigmoid_cross_entropy_with_logits(labels=target,
                                                       logits=output)
    

    如您所见,from_logits 参数默认设置为False。因此,if 条件的计算结果为真,因此输出中的值被裁剪到[epsilon, 1-epislon] 范围内。这就是为什么无论概率大小,它都不能小于epsilon,也不能大于1-epsilon。这就解释了为什么binary_crossentropy loss 的输出也是有界的。

    现在,这里的 epsilon 是什么?这是一个非常小的常数,用于数值稳定性(例如,防止被零除或未定义的行为等)。要找出它的价值,您可以进一步检查源代码,您可以在 common.py 文件中找到它:

    _EPSILON = 1e-7
    
    def epsilon():
        """Returns the value of the fuzz factor used in numeric expressions.
        # Returns
            A float.
        # Example
        ```python
            >>> keras.backend.epsilon()
            1e-07
        ```
        """
        return _EPSILON
    

    如果出于任何原因,您希望获得更高的精度,您也可以使用后端的 set_epsilon 函数将 epsilon 值设置为更小的常数:

    def set_epsilon(e):
        """Sets the value of the fuzz factor used in numeric expressions.
        # Arguments
            e: float. New value of epsilon.
        # Example
        ```python
            >>> from keras import backend as K
            >>> K.epsilon()
            1e-07
            >>> K.set_epsilon(1e-05)
            >>> K.epsilon()
            1e-05
        ```
        """
        global _EPSILON
        _EPSILON = e
    

    但是,请注意,将 epsilon 设置为极低的正值或零,可能会破坏整个 Keras 计算的稳定性。

    【讨论】:

    • 这个答案很有帮助。我很感激。
    【解决方案2】:

    我认为keras考虑到数值稳定性, 让我们跟踪keras 是如何计算的

    首先,

    def binary_crossentropy(y_true, y_pred):
        return K.mean(K.binary_crossentropy(y_true, y_pred), axis=-1)
    

    那么,

    def binary_crossentropy(target, output, from_logits=False):
        """Binary crossentropy between an output tensor and a target tensor.
    
        # Arguments
            target: A tensor with the same shape as `output`.
            output: A tensor.
            from_logits: Whether `output` is expected to be a logits tensor.
                By default, we consider that `output`
                encodes a probability distribution.
    
        # Returns
            A tensor.
        """
        # Note: tf.nn.sigmoid_cross_entropy_with_logits
        # expects logits, Keras expects probabilities.
        if not from_logits:
            # transform back to logits
            _epsilon = _to_tensor(epsilon(), output.dtype.base_dtype)
            output = tf.clip_by_value(output, _epsilon, 1 - _epsilon)
            output = tf.log(output / (1 - output))
    
    
        return tf.nn.sigmoid_cross_entropy_with_logits(labels=target,
                                                       logits=output)
    

    注意tf.clip_by_value 用于数值稳定性

    让我们比较一下 keras binary_crossentropy、tensorflow tf.nn.sigmoid_cross_entropy_with_logits 和自定义损失函数(eleminate vale clipping)

    import numpy as np
    import matplotlib.pyplot as plt
    import tensorflow as tf
    from keras.models import Sequential
    from keras.layers import Dense
    import keras
    
    # keras
    model = Sequential()
    model.add(Dense(units=1, activation='sigmoid', input_shape=(
        1,), weights=[np.ones((1, 1)), np.zeros(1)]))
    # print(model.get_weights())
    model.compile(loss='binary_crossentropy',
                  optimizer='adam', metrics=['accuracy'])
    
    # tensorflow
    G = tf.Graph()
    with G.as_default():
        x_holder = tf.placeholder(dtype=tf.float32, shape=(2,))
        y_holder = tf.placeholder(dtype=tf.float32, shape=(2,))
        entropy = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(
            logits=x_holder, labels=y_holder))
    sess = tf.Session(graph=G)
    
    
    # keras with custom loss function
    def customLoss(target, output):
        # if not from_logits:
        #     # transform back to logits
        #     _epsilon = _to_tensor(epsilon(), output.dtype.base_dtype)
        #     output = tf.clip_by_value(output, _epsilon, 1 - _epsilon)
        #     output = tf.log(output / (1 - output))
        output = tf.log(output / (1 - output))
        return tf.nn.sigmoid_cross_entropy_with_logits(labels=target,
                                                       logits=output)
    model_m = Sequential()
    model_m.add(Dense(units=1, activation='sigmoid', input_shape=(
        1,), weights=[np.ones((1, 1)), np.zeros(1)]))
    # print(model.get_weights())
    model_m.compile(loss=customLoss,
                    optimizer='adam', metrics=['accuracy'])
    
    
    N = 100
    xaxis = np.linspace(10, 20, N)
    keras_ce = np.zeros(N)
    tf_ce = np.zeros(N)
    my_ce = np.zeros(N)
    keras_custom = np.zeros(N)
    
    y = np.array([0, 1])
    for i, a in enumerate(xaxis):
        x = np.array([-a, a])
        # cross-entropy computed by keras/tensorflow
        keras_ce[i] = model.evaluate(x, y)[0]
        my_ce[i] = np.log(1+np.exp(-a))  # My own computation
        tf_ce[i] = sess.run(entropy, feed_dict={x_holder: x, y_holder: y})
        keras_custom[i] = model_m.evaluate(x, y)[0]
    # print(model.get_weights())
    
    plt.plot(xaxis, keras_ce, label='keras')
    plt.plot(xaxis, my_ce, 'b',  label='my_ce')
    plt.plot(xaxis, tf_ce, 'r:', linewidth=5, label='tensorflow')
    plt.plot(xaxis, keras_custom, '--', label='custom loss')
    plt.xlabel('a')
    plt.ylabel('xentropy')
    plt.yscale('log')
    plt.legend()
    plt.savefig('compare.jpg')
    plt.show()
    

    我们可以看到 tensorflow 与手动计算是一样的,但是自定义 loss 的 keras 会遇到预期的数字溢出。

    【讨论】:

    • 非常感谢“自定义损失”和“张量流”比较!它更清楚地说明了这个问题。
    猜你喜欢
    • 2018-02-27
    • 2018-05-31
    • 2016-08-01
    • 2022-10-04
    • 2019-06-08
    • 2018-01-31
    • 1970-01-01
    • 1970-01-01
    • 2018-03-12
    相关资源
    最近更新 更多