Почему сигмоид и кроссцентропия Кераса / тензорного потока имеют низкую точность?
У меня есть следующая простая нейронная сеть (только с 1 нейроном), чтобы проверить точность вычислений 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, а затем оценил модель с помощью двухточечного тренировочного набора {(-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_ce
) рассчитывается Keras/Tensorflow достичь пола 1.09e-7
когда a
составляет ок. 16, как показано ниже (синяя линия). Он не уменьшается дальше, так как "а" продолжает расти. Это почему?
Эта нейронная сеть имеет только 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
увеличивается, как показано оранжевым цветом ("мой") выше. Могу ли я изменить некоторые настройки Keras/Tensorflow/Python, чтобы повысить их точность? Или я где-то ошибаюсь? Буду признателен за любые предложения / комментарии / ответы.
2 ответа
TL; версия DR: значения вероятности (то есть выходы сигмоидальной функции) ограничены из-за численной стабильности при вычислении функции потерь.
Если вы проверите исходный код, вы обнаружите, что с помощью binary_crossentropy
как потеря приведет к звонку binary_crossentropy
функция в файле lost.py:
def binary_crossentropy(y_true, y_pred):
return K.mean(K.binary_crossentropy(y_true, y_pred), axis=-1)
который, в свою очередь, как вы можете видеть, вызывает эквивалентную внутреннюю функцию. В случае использования Tensorflow в качестве бэкэнда это приведет к вызову binary_crossentropy
Функция в файле tenorflow_backend.py:
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 оценивается как true, и в результате значения в выходных данных обрезаются до диапазона [epsilon, 1-epislon]
, Поэтому, независимо от того, насколько мала или велика вероятность, она не может быть меньше epsilon
и больше чем 1-epsilon
, И это объясняет, почему вывод binary_crossentropy
потеря также ограничена.
Теперь, что это за эпсилон здесь? Это очень маленькая константа, которая используется для числовой стабильности (например, предотвратить деление на ноль или неопределенное поведение и т. Д.). Чтобы узнать его значение, вы можете дополнительно проверить исходный код и найти его в файле 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
Если по какой-либо причине вам нужна более высокая точность, вы можете в качестве альтернативы установить значение epsilon на меньшую константу, используя set_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
Однако имейте в виду, что установка эпсилона на крайне низкое положительное значение или ноль может нарушить стабильность вычислений на всем протяжении Keras.
Я думаю что keras
учитывать числовую стабильность, давайте отслеживать, как keras
caculate
Первый,
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
используется для численной стабильности
Давайте сравним керас binary_crossentropy
, тензорный поток tf.nn.sigmoid_cross_entropy_with_logits
и пользовательская функция потерь (Elementate 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()
мы можем видеть, что тензор потока такой же, как и при ручном вычислении, но кера с пользовательскими потерями сталкиваются с переполнением чисел, как и ожидалось.