C.W.K.
Stream
Lesson 06 of 06 · published

Practical: Custom GAN Training Loop

~8 min · custom-train

Level 0Keras Apprentice
0 XP0/97 lessons0/20 achievements
0/120 XP to next level120 XP to go0% complete

The capstone: build it the hard way once

You already saw the GAN as a clean train_step() override. This lesson does the same model as a fully manual loop — no fit(), no train_step() — for one reason: to feel the weight of everything fit() was carrying. When you write the epoch loop, the two tapes, the metric printing, and the checkpoint logic by hand, the value of the higher rungs stops being an abstract claim and becomes a line count.

What the manual GAN loop actually does

The mechanics don't change — only who's responsible for them. Each step still trains the discriminator on a mixed real+fake batch, then trains the generator to fool it, with a separate optimizer per network. The skeleton below shows the two-tape core; in a real run you'd wrap it with your own progress reporting, periodic sample-image dumps, and checkpointing — all the things the override version inherited for free.

  • Train the discriminator on real + fake images together
  • Train the generator by trying to fool the discriminator
  • Use a separate optimizer for each network
  • Monitor both losses — healthy training has them oscillating in tension, not collapsing to zero

Code

Manual GAN step (TensorFlow backend)·python
import tensorflow as tf

for epoch in range(epochs):
    for real_images in dataset:
        batch_size = tf.shape(real_images)[0]
        noise = keras.random.normal((batch_size, latent_dim))

        # --- Discriminator step ---
        fake_images = generator(noise)
        combined = tf.concat([real_images, fake_images], axis=0)
        labels = tf.concat([
            tf.ones((batch_size, 1)),
            tf.zeros((batch_size, 1)),
        ], axis=0)
        with tf.GradientTape() as tape:
            preds = discriminator(combined)
            d_loss = loss_fn(labels, preds)
        d_grads = tape.gradient(d_loss, discriminator.trainable_variables)
        d_opt.apply_gradients(zip(d_grads, discriminator.trainable_variables))

        # --- Generator step (fool the discriminator) ---
        noise = keras.random.normal((batch_size, latent_dim))
        misleading = tf.ones((batch_size, 1))
        with tf.GradientTape() as tape:
            preds = discriminator(generator(noise))
            g_loss = loss_fn(misleading, preds)
        g_grads = tape.gradient(g_loss, generator.trainable_variables)
        g_opt.apply_gradients(zip(g_grads, generator.trainable_variables))

    print(f"epoch {epoch}: d_loss={float(d_loss):.3f} g_loss={float(g_loss):.3f}")

External links

Exercise

Re-implement the DCGAN from lesson 4 as a fully manual training loop (no fit, no train_step). Then count, side by side, the lines you spent on logging, checkpointing, and progress reporting versus the override version. Write one sentence on what fit() gave you for free that you most missed.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.