Gradient Tensorflow zwraca nan lub Inf

Aug 28 2020

Próbuję wdrożyć model WGAN-GP z wykorzystaniem tensorflow i keras (dla danych o oszustwach kart kredytowych od kaggle ).

Przeważnie śledziłem przykładowy kod, który znajduje się na stronie internetowej keras i kilka innych przykładowych kodów w Internecie (ale zmieniłem je z obrazu na moje dane) i jest to całkiem proste.

Ale kiedy chcę zaktualizować krytyka, nanpo kilku partiach gradient strat względem wagi krytyka staje się wszystkim . I to powoduje, że wagi krytyka stają się, nana potem wagi generatora stają się nan... Więc wszystko się staje nan!

Użyłem tf.debugging.enable_check_numericsi stwierdziłem, że problem pojawia się, ponieważ -Infpo kilku iteracjach pojawia się w gradiencie.

Jest to bezpośrednio związane z określeniem gradientu kary w utracie, ponieważ kiedy usuwam problem, problem znika.

Zwróć uwagę, że gpsam w sobie nie jest nan, ale kiedy otrzymam gradient wag krytyka straty do ( c_gradsw poniższym kodzie), który zawiera, -Infa następnie w jakiś sposób staje się wszystkim nan.

Sprawdziłem matematykę i architekturę sieci pod kątem możliwych błędów (takich jak prawdopodobieństwo zniknięcia gradientu itp.), A także sprawdzałem mój kod pod kątem możliwych błędów przez wiele godzin. Ale utknąłem.

Byłbym bardzo wdzięczny, gdyby ktoś mógł znaleźć źródło problemu

Uwaga: pamiętaj, że funkcja wyjścia i straty krytyka różni się nieco od oryginalnego artykułu (ponieważ próbuję uczynić to warunkowym), ale nie ma to nic wspólnego z problemem, ponieważ jak powiedziałem wcześniej, cały problem znika kiedy po prostu usuwam warunek kary gradientu

To jest mój krytyk:

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

To jest moja funkcja kary gradientu:

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

A to jest kod aktualizacji krytyka

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

Uwaga: Jak widać, nie używam żadnej krzyżowej entropii ani innych funkcji napisanych przez siebie z ryzykiem dzielenia przez zero.

Odpowiedzi

1 Amir Aug 28 2020 at 07:26

Więc po dużo dokładniejszym zagłębianiu się w internet okazuje się, że dzieje się tak z powodu niestabilności numerycznej tf.norm(i niektórych innych funkcji).

W przypadku normfunkcji problem polega na tym, że przy obliczaniu jej gradientu jej wartość pojawia się w mianowniku. Więc d(norm(x))/dxw x = 0stanie się 0 / 0(to jest tajemnica, division-by-zeroktórej szukałem!)

Problem polega na tym, że wykres obliczeniowy czasami kończy się takimi rzeczami, jak a / agdzie a = 0numerycznie jest niezdefiniowany, ale istnieje granica. A ze względu na sposób działania tensorflow (który oblicza gradienty za pomocą reguły łańcucha) daje to nans lub +/-Infs.

Najlepszym sposobem byłoby prawdopodobnie wykrycie tych wzorców przez tensorflow i zastąpienie ich ich uproszczonym analitycznie odpowiednikiem. Ale dopóki tego nie zrobią, mamy inny sposób, a mianowicie użycie czegoś wywołanego tf.custom_gradientdo zdefiniowania naszej funkcji niestandardowej za pomocą naszego niestandardowego gradientu ( powiązany problem na ich githubie )

Chociaż w moim przypadku było tak naprawdę jeszcze prostsze rozwiązanie (chociaż nie było to proste, gdy nie wiedziałem, że tf.normjest winowajcą):

Więc zamiast:

tf.norm(x)

Możesz użyć:

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

Uwaga: Uważaj na wymiary (jeśli x jest macierzą lub tensorem i musisz obliczyć normy dla wierszy lub kolumn)! to jest tylko przykładowy kod, aby zademonstrować koncepcję

Mam nadzieję, że to komuś pomoże