为什么 Keras/tensorflow 的 sigmoid 和交叉熵精度低?
Why does sigmoid & crossentropy of Keras/tensorflow have low precision?
我有以下简单的神经网络(只有 1 个神经元)来测试 Keras 的 sigmoid
激活和 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
) 在 [=18] 时达到 1.09e-7
的下限=] 是大约。 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 设置以提高其精度吗?还是我在某处弄错了?我会很感激 suggestions/comments/answers.
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
损失的输出也是有界的。
现在,这里的 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 的计算稳定性。
我认为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果然遇到了numeric overflow。
我有以下简单的神经网络(只有 1 个神经元)来测试 Keras 的 sigmoid
激活和 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
) 在 [=18] 时达到 1.09e-7
的下限=] 是大约。 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 设置以提高其精度吗?还是我在某处弄错了?我会很感激 suggestions/comments/answers.
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
损失的输出也是有界的。
现在,这里的 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 的计算稳定性。
我认为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果然遇到了numeric overflow。