Tensorflow gradient возвращает nan или Inf

Я пытаюсь реализовать модель WGAN-GP, используя тензорный поток и keras (для данные о мошенничестве с кредитными картами из kaggle).

Я в основном следовал образцу кода, представленному на веб-сайте keras, и нескольким другим образцам кодов на сайте Интернет (но изменил их с изображения на мои данные), и это довольно просто.

Но когда я хочу обновить критика, градиент потерь по отношению к весам критика становится равным nan после нескольких пакетов. И это приводит к тому, что веса критика становятся nan, а после этого веса генератора становятся _3 _, ... Итак, все становится nan!

введите описание изображения здесь

Я использовал tf.debugging.enable_check_numerics и обнаружил, что проблема возникает из-за того, что -Inf появляется в градиенте после некоторых итераций.

Это напрямую связано со штрафом за градиент в потерях, потому что, когда я удаляю это, проблема исчезает.

Обратите внимание, что gp сам по себе не nan, но когда я получаю градиент потерь относительно весов критиков (c_grads в приведенном ниже коде), он содержит -Inf, а затем каким-то образом становится все nan.

Я проверил математику и сетевую архитектуру на возможные ошибки (например, вероятность исчезновения градиента и т. Д.), И я часами проверял свой код на возможные ошибки. Но я застрял.

Я был бы очень признателен, если бы кто-нибудь мог найти корень проблемы

Примечание. Имейте в виду, что функция результатов и потерь критика немного отличается от исходной статьи (потому что я пытаюсь сделать ее условной), но это не имеет ничего общего с проблемой, потому что, как я сказал раньше вся проблема исчезает, когда я просто удаляю термин "штраф за градиент"

Это мой критик:

critic = keras.Sequential([
        keras.layers.Input(shape=(x_dim,), name='c-input'),
        keras.layers.Dense(64, kernel_initializer=keras.initializers.he_normal(), name='c-hidden-1'),
        keras.layers.LeakyReLU(alpha=0.25, name='c-activation-1'),
        keras.layers.Dense(32, kernel_initializer=keras.initializers.he_normal(), name='c-hidden-2'),
        keras.layers.LeakyReLU(alpha=0.25, name='c-activation-2'),
        keras.layers.Dense(2, activation='tanh', name='c-output')
    ], name='critic')

Это моя функция градиентного штрафа:

def gradient_penalty(self, batch_size, x_real, x_fake):
    # get the random linear interpolation of real and fake data (x hat)
    alpha = tf.random.uniform([batch_size, 1], 0.0, 1.0)
    x_interpolated = x_real + alpha * (x_fake - x_real)
    with tf.GradientTape() as gp_tape:
        gp_tape.watch(x_interpolated)
        # Get the critic score for this interpolated data
        scores = 0.5 * (self.critic(x_interpolated, training=True) + 1.0)
    # Calculate the gradients w.r.t to this interpolated data
    grads = gp_tape.gradient(scores, x_interpolated)
    # Calculate the norm of the gradients
    # Gradient penalty enforces the gradient to stay close to 1.0 (1-Lipschitz constraint)
    gp = tf.reduce_mean(tf.square(tf.norm(grads, axis=-1) - 1.0))
    return gp

А это код обновления критика

# Get random samples from latent space
z = GAN.random_samples((batch_size, self.latent_dim))

# Augment random samples with the class label (1 for class "fraud") for conditioning
z_conditioned = tf.concat([z, tf.ones((batch_size, 1))], axis=1)
# Generate fake data using random samples
x_fake = self.generator(z_conditioned, training=True)

# Calculate the loss and back-propagate
with tf.GradientTape() as c_tape:
    c_tape.watch(x_fake)
    c_tape.watch(x_real)

    # Get the scores for the fake data
    output_fake = 0.5 * (self.critic(x_fake) + 1.0)
    score_fake = tf.reduce_mean(tf.reduce_sum(output_fake, axis=1))
    # Get the scores for the real data
    output_real = 0.5 * (self.critic(x_real, training=True) + 1.0)
    score_real = tf.reduce_mean((1.0 - 2.0 * y_real) * (output_real[:, 0] - output_real[:, 1]))

# Calculate the gradient penalty
gp = self.gp_coeff * self.gradient_penalty(batch_size, x_real, x_fake)
# Calculate critic's loss (added 1.0 so its ideal value becomes zero)
c_loss = 1.0 + score_fake - score_real + gp
# Calculate the gradients
c_grads = c_tape.gradient(c_loss, self.critic.trainable_weights)
# back-propagate the loss
self.c_optimizer.apply_gradients(zip(c_grads, self.critic.trainable_weights))

Также примечание: Как видите, я не использую перекрестную энтропию или другие самописные функции с риском деления на ноль.


person Amir    schedule 27.08.2020    source источник


Ответы (1)


Итак, после долгого изучения Интернета выяснилось, что это из-за числовой нестабильности tf.norm (а также некоторых других функций).

В случае функции norm проблема в том, что при вычислении ее градиента ее значение появляется в знаменателе. Итак, d(norm(x))/dx в x = 0 превратится в 0 / 0 (это таинственный division-by-zero, который я искал!)

Проблема в том, что вычислительный граф иногда заканчивается такими вещами, как a / a, где a = 0 численно не определено, но предел существует. И из-за того, как работает тензорный поток (который вычисляет градиенты с использованием правила цепочки), он приводит к nans или +/-Infs.

Лучшим способом, вероятно, было бы для tenorflow обнаруживать эти шаблоны и заменять их их аналитически упрощенным эквивалентом. Но пока они этого не сделают, у нас есть другой способ, и он использует что-то под названием _11 _, чтобы определить нашу настраиваемую функцию с помощью нашего настраиваемого градиента (связанная проблема на их github)

Хотя в моем случае на самом деле было еще более простое решение (хотя это было непросто, когда я не знал, что виноват tf.norm):

Так что вместо:

tf.norm(x)

Вы можете использовать:

tf.sqrt(tf.reduce_sum(tf.square(x)) + 1.0e-12)

Примечание. Будьте осторожны с размерами (если x - матрица или тензор, и вам нужно вычислить нормы по строкам или столбцам)! это всего лишь пример кода для демонстрации концепции

Надеюсь, это кому-то поможет

person Amir    schedule 28.08.2020