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

GAN Training Pattern

~10 min · custom-train

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

Why a GAN breaks the single-loss mold

A GAN is two networks locked in a minimax game. The discriminator learns to tell real images from generated ones; the generator learns to produce fakes the discriminator can't catch. They have opposing objectives, separate weights, and separate optimizers — so the single loss, single optimizer shape that fit() assumes simply can't express them. Each batch needs two distinct update steps in sequence.

Why train_step() still fits

It's tempting to reach straight for a manual loop here, but the alternating pattern lives perfectly inside a train_step() override. You hold both sub-models on the GAN object, and inside one step you train the discriminator on a real+fake batch, then train the generator against frozen-discriminator feedback, and return both losses. Because you stayed inside fit(), the whole GAN inherits callbacks, checkpointing, and distribution for free. The skeleton below keeps the two updates in named helper methods (_train_discriminator / _train_generator) so the step reads as the game it models; each helper holds its own gradient tape and optimizer.

Code

GAN as a train_step() override·python
class GAN(keras.Model):
    def __init__(self, generator, discriminator, latent_dim):
        super().__init__()
        self.generator = generator
        self.discriminator = discriminator
        self.latent_dim = latent_dim

    def train_step(self, real_images):
        batch_size = keras.ops.shape(real_images)[0]
        noise = keras.random.normal(
            shape=(batch_size, self.latent_dim)
        )

        # Train discriminator on real + fake
        fake_images = self.generator(noise)
        combined = keras.ops.concatenate([real_images, fake_images])
        labels = keras.ops.concatenate([
            keras.ops.ones((batch_size, 1)),
            keras.ops.zeros((batch_size, 1)),
        ])
        d_loss = self._train_discriminator(combined, labels)

        # Train generator to fool the discriminator
        noise = keras.random.normal(shape=(batch_size, self.latent_dim))
        misleading_labels = keras.ops.ones((batch_size, 1))
        g_loss = self._train_generator(noise, misleading_labels)

        return {"d_loss": d_loss, "g_loss": g_loss}

External links

Exercise

Implement a tiny DCGAN for MNIST: small generator (Dense → reshape → ConvTranspose), small discriminator (Conv → flatten → Dense). Use train_step override. Train 5 epochs, verify g_loss decreases and samples improve qualitatively.

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.