Le gradient Tensorflow renvoie nan ou Inf

Aug 28 2020

J'essaie de mettre en œuvre un modèle WGAN-GP en utilisant tensorflow et keras (pour les données de fraude par carte de crédit de kaggle ).

J'ai principalement suivi l'exemple de code fourni sur le site Web de keras et plusieurs autres exemples de codes sur Internet (mais je les ai modifiés de l'image à mes données), et c'est assez simple.

Mais quand je veux mettre à jour le critique, le gradient de la perte par rapport au poids du critique devient tout nanaprès quelques lots. Et cela fait que les poids du critique deviennent nanet après cela les poids du générateur deviennent nan, ... Alors tout devient nan!

J'ai utilisé tf.debugging.enable_check_numericset constaté que le problème se pose car un -Infapparaît dans le dégradé après quelques itérations.

Ceci est directement lié au terme de pénalité de gradient dans la perte, car lorsque je supprime cela, le problème disparaît.

Veuillez noter que le gplui-même ne l'est pas nan, mais quand j'obtiens le gradient de la perte par rapport aux poids du critique ( c_gradsdans le code ci-dessous), il contient -Infet devient en quelque sorte tout nan.

J'ai vérifié les mathématiques et l'architecture du réseau pour d'éventuelles erreurs (comme la probabilité de disparition du gradient, etc.), et j'ai vérifié mon code pour d'éventuels bogues pendant des heures et des heures. Mais je suis coincé.

J'apprécierais beaucoup si quelqu'un pouvait trouver la racine du problème

Remarque: gardez à l'esprit que la fonction de sortie et de perte du critique est légèrement différente de l'article original (car j'essaie de le rendre conditionnel) mais cela n'a rien à voir avec le problème car, comme je l'ai déjà dit, tout le problème disparaît. quand je viens de supprimer le terme de pénalité de gradient

Voici mon critique:

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

Voici ma fonction de pénalité de gradient:

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

Et c'est le code de mise à jour du critique

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

Remarque: comme vous pouvez le voir, je n'utilise aucune entropie croisée ou d'autres fonctions auto-écrites avec le risque de division par zéro.

Réponses

1 Amir Aug 28 2020 at 07:26

Donc, après avoir beaucoup plus fouillé sur Internet, il s'avère que c'est à cause de l'instabilité numérique de tf.norm(et de certaines autres fonctions également).

Dans le cas de la normfonction, le problème est que lors du calcul de son gradient, sa valeur apparaît dans le dénominateur. Alors d(norm(x))/dxà x = 0deviendrait 0 / 0(c'est le mystérieux que division-by-zeroje cherchais!)

Le problème est que le graphe de calcul aboutit parfois à des choses comme a / aa = 0ce qui n'est pas défini numériquement mais la limite existe. Et à cause du fonctionnement du tensorflow (qui calcule les gradients en utilisant la règle de la chaîne), il en résulte nans ou +/-Infs.

Le meilleur moyen serait probablement que tensorflow détecte ces modèles et les remplace par leur équivalent analytiquement simplifié. Mais jusqu'à ce qu'ils le fassent, nous avons un autre moyen, et cela utilise quelque chose appelé tf.custom_gradientpour définir notre fonction personnalisée avec notre dégradé personnalisé ( problème lié sur leur github )

Bien que dans mon cas, il y avait en fait une solution encore plus simple (même si ce n'était pas simple quand je ne savais pas que tf.normc'était le coupable):

Donc au lieu de:

tf.norm(x)

Vous pouvez utiliser:

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

Remarque: faites attention aux dimensions (si x est une matrice ou un tenseur et que vous devez calculer des normes en ligne ou en colonne)! ceci est juste un exemple de code pour illustrer le concept

J'espère que ça aide quelqu'un