Почему сигмоид и кроссцентропия Кераса / тензорного потока имеют низкую точность? - PullRequest
0 голосов
/ 01 сентября 2018

У меня есть следующая простая нейронная сеть (только с одним нейроном) для проверки точности вычислений 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, как показано ниже (синяя линия). Он не уменьшается дальше, так как «а» продолжает расти. Это почему?

enter image description here

Эта нейронная сеть имеет только 1 нейрон, вес которого установлен на 1, а смещение равно 0. С двухточечным обучающим набором {(-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 ]

0 голосов
/ 01 сентября 2018

Я думаю, что 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 и пользовательскую функцию потерь (отсечение элементарных долей)

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()

мы видим, что тензор потока такой же, как и при ручном вычислении, но кера с пользовательскими потерями сталкиваются с переполнением чисел, как и ожидалось. enter image description here

0 голосов
/ 01 сентября 2018

TL; версия DR: значения вероятности (то есть выходы сигмоидальной функции) ограничены из-за численной стабильности при вычислении функции потерь.


Если вы проверите исходный код, вы обнаружите, что использование binary_crossentropy в качестве потери приведет к вызову функции binary_crossentropy в loss.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 file:

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.

...