Der Tensorflow-Gradient gibt nan oder Inf zurück
Ich versuche, ein WGAN-GP-Modell mit Tensorflow und Keras (für Kreditkartenbetrugsdaten von kaggle ) zu implementieren .
Ich habe meistens den Beispielcode befolgt, der auf der Keras-Website und mehreren anderen Beispielcodes im Internet bereitgestellt wird (aber sie wurden von Bild zu meinen Daten geändert), und es ist ziemlich einfach.
Aber wenn ich den Kritiker aktualisieren möchte, wird der Gradient des Verlusts für die Gewichte des Kritikers nannach ein paar Chargen alle. Und dies führt dazu, dass die Gewichte des Kritikers werden nanund danach die Gewichte des Generators nan... Also wird alles nan!
Ich habe verwendet tf.debugging.enable_check_numericsund festgestellt, dass das Problem -Infauftritt , weil a nach einigen Iterationen im Gradienten erscheint.
Dies hängt direkt mit dem Gradienten-Straf-Term im Verlust zusammen, denn wenn ich das entferne, verschwindet das Problem.
Bitte beachten Sie, dass das gpselbst nicht nander Fall ist , aber wenn ich den Gradienten der Verluste der Kritikergewichte ( c_gradsim Code unten) erhalte , enthält es -Infund wird dann irgendwie alles nan.
Ich überprüfte die Mathematik- und Netzwerkarchitektur auf mögliche Fehler (wie die Wahrscheinlichkeit des Verschwindens des Gradienten usw.) und überprüfte meinen Code stundenlang auf mögliche Fehler. Aber ich stecke fest.
Ich würde es sehr begrüßen, wenn jemand die Wurzel des Problems finden kann
Hinweis: Beachten Sie , dass sich die Ausgabe- und Verlustfunktion des Kritikers geringfügig von der des Originalpapiers unterscheidet (weil ich versuche, sie bedingt zu machen), aber das hat nichts mit dem Problem zu tun, da, wie ich bereits sagte, das gesamte Problem verschwindet wenn ich nur den Gradientenstrafenbegriff entferne
Das ist mein Kritiker:
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')
Dies ist meine Gradientenstraffunktion:
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
Und das ist der Update-Code des Kritikers
# 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))
Hinweis: Wie Sie sehen, verwende ich keine Kreuzentropie oder andere selbstgeschriebene Funktionen mit dem Risiko einer Division durch Null.
Antworten
Nachdem wir uns viel mehr mit dem Internet befasst haben, stellt sich heraus, dass dies auf die numerische Instabilität von tf.norm(und einige andere Funktionen) zurückzuführen ist.
Bei der normFunktion besteht das Problem darin, dass bei der Berechnung des Gradienten der Wert im Nenner erscheint. So d(norm(x))/dxbei x = 0würde 0 / 0(dies ist die mysteriös division-by-zeroich suche!)
Das Problem ist, dass der Berechnungsgraph manchmal zu Dingen führt, a / abei a = 0denen numerisch undefiniert ist, aber die Grenze existiert. Und aufgrund der Funktionsweise des Tensorflusses (der die Gradienten mithilfe der Kettenregel berechnet) ergibt sich nans oder +/-Infs.
Der beste Weg wäre wahrscheinlich, dass Tensorflow diese Muster erkennt und durch ihr analytisch vereinfachtes Äquivalent ersetzt. Aber bis sie dies tun, haben wir einen anderen Weg, und das ist etwas, das aufgerufen wird tf.custom_gradient, um unsere benutzerdefinierte Funktion mit unserem benutzerdefinierten Gradienten zu definieren ( verwandtes Problem auf ihrem Github ).
Obwohl es in meinem Fall tatsächlich eine noch einfachere Lösung gab (obwohl es nicht einfach war, als ich nicht wusste, dass dies tf.normder Schuldige war):
Also statt:
tf.norm(x)
Sie können verwenden:
tf.sqrt(tf.reduce_sum(tf.square(x)) + 1.0e-12)
Hinweis: Achten Sie auf die Abmessungen (wenn x eine Matrix oder ein Tensor ist und Sie zeilen- oder spaltenweise Normen berechnen müssen)! Dies ist nur ein Beispielcode, um das Konzept zu demonstrieren
Hoffe es hilft jemandem