AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

Generative Adversarial Networks: The Minimax Game

📚 Generative AI⏱️ 21 min read🎓 Grade 11
✍️ AI Computer Institute Editorial Team Updated: September 2026 CBSE-aligned · Peer-reviewed · 21 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

India's currency presses — the Currency Note Press at Nashik, the Bank Note Press at Dewas, and the two Bharatiya Reserve Bank Note Mudran plants at Mysuru and Salboni — exist because counterfeiting has always been an arms race. A press adds a security thread, a colour-shifting ink patch, a latent image visible only when the note is tilted. Forgers study the new note and adapt. The press responds with the next generation of security features. Over decades, this back-and-forth has produced notes that are extraordinarily hard to fake, without either side ever reaching a final, permanent win.

Now compress that decades-long institutional race into a single piece of software, replayed thousands of times in a few hours of GPU time, and you have a Generative Adversarial Network. A GAN pits two neural networks against each other in exactly this structure: a Generator that starts from random noise and tries to paint counterfeit data, and a Discriminator that starts knowing nothing and learns, example by example, what genuine data looks like. Neither network is ever told the "correct answer" in the usual supervised-learning sense. Each one only ever sees the other's output and a single scalar scorecard. This chapter builds that scorecard — the minimax objective — from first principles, solves it exactly with calculus, traces one real gradient update by hand, and names the single most common misunderstanding students carry out of a first reading of the GAN paper.

Two networks, one shared scoreboard

Every deep learning model you have studied so far — classifiers, autoencoders, even language models — is trained to minimize a single loss function that is entirely its own. A GAN breaks that pattern. It has two networks with two separate parameter sets, θg and θd, but they read off the same scalar function. The Generator G(z; θg) maps a noise vector z, sampled from something simple like a standard normal distribution, into the data space — an image, a transaction record, a currency-note feature vector. The Discriminator D(x; θd) maps any point x in that data space to a single number in (0, 1): its estimate of the probability that x came from the real dataset rather than from G. Training alternates: the Discriminator adjusts θd to push its score up on real data and down on the Generator's output; the Generator adjusts θg to push the Discriminator's score on its fakes back up. Both are climbing the same scoreboard in opposite directions — that shared coupling, not merely "two networks," is what makes this a game rather than two independent optimization problems.

GAN architecture: generator, discriminator, and the adversarial feedback loop Noise vector z z ~ N(0, I) Generator G params θg — the forger maps noise → fake data Fake sample G(z) a counterfeit note Real data x ~ p_data genuine ₹500 notes Discriminator D params θd — the note-checker scores real vs fake D(x) = P(real) 0 = fake … 1 = real ascend log D(x) + log(1−D(G(z))) → update θd ascend log D(G(z)) → update θg (backprop through D) Both players update from the SAME scalar D(·) — that shared scoreboard is what makes the game adversarial, not two independent losses.

The minimax objective

Goodfellow et al. (2014) wrote the whole game as one expression:

min_G  max_D  V(D, G)  =  E[x ~ p_data] [ log D(x) ]  +  E[z ~ p_z] [ log(1 − D(G(z))) ]

Read it term by term. The first expectation is taken over real data: D is rewarded (V goes up) whenever it assigns a real sample a probability close to 1, since log(1) = 0 is the best that term can do and log D(x) → −∞ as D(x) → 0. The second expectation is taken over the Generator's fakes: D is rewarded whenever it assigns a fake sample a probability close to 0, so that 1 − D(G(z)) → 1 and its log approaches 0. D therefore wants to maximize V — both terms simultaneously large means D is discriminating correctly in both directions. G only appears inside the second term, and only through D's judgment of its output, so G wants to minimize V — specifically, it wants to push D(G(z)) up toward 1 so that log(1 − D(G(z))) becomes very negative, dragging V down. This is a two-player zero-sum game with a single payoff function that one player maximizes and the other minimizes — a genuine minimax game, in the same formal sense as the game trees you study in adversarial search, except here the "moves" are gradient steps in continuous parameter space rather than discrete choices on a board.

Training in practice: alternating gradient steps

Nobody solves the minimax problem in closed form during training — p_data is unknown, only samples from it are available. Instead, training alternates finite steps of gradient ascent on D and gradient descent on G, using mini-batches of real and fake samples:

for each training iteration:
    for k steps:
        sample a minibatch of m noise vectors {z(1), ..., z(m)} from p_z
        sample a minibatch of m real examples {x(1), ..., x(m)} from p_data
        update θd by ascending:
            (1/m) * sum_i [ log D(x(i)) + log(1 − D(G(z(i)))) ]

    sample a minibatch of m noise vectors {z(1), ..., z(m)} from p_z
    update θg by ascending:
        (1/m) * sum_i [ log D(G(z(i))) ]

Notice the last line does not literally match the min_G term from the objective. The original formulation has G minimize log(1 − D(G(z))). In early training, when G's fakes are poor, D rejects them confidently — D(G(z)) is close to 0 — and the gradient of log(1 − D(G(z))) with respect to D's pre-sigmoid input collapses toward zero exactly when G needs the strongest correction. Practitioners instead have G maximize log D(G(z)): both objectives push G in the same direction (increase D(G(z))), but as the worked derivative below shows, the second one supplies a large gradient precisely when the Generator is failing badly. This is the "non-saturating" generator loss, and it is what the code below implements.

A complete worked update, by hand and in code

Take the smallest possible GAN that still exhibits every moving part: a 1-dimensional feature space, a Discriminator that is a single logistic unit D(x) = σ(w·x + b), and a Generator that is a single linear unit G(z) = θ·z. Say the "real" feature is a note's measured optical reflectance under UV light, and two genuine notes give readings x₁ = 5 and x₂ = 7. The Discriminator currently has w = 0.1, b = −0.6. The Generator currently has θ = 2, and for a fixed noise draw z = 1 it outputs G(1) = 2 — a fake reflectance far too low compared to the real notes.

Two derivative identities do all the work here, so derive them once. Let σ(t) = 1/(1 + e−t). Since log σ(t) = −log(1 + e−t), differentiating gives d/dt log σ(t) = e−t/(1 + e−t) = 1 − σ(t). And since log(1 − σ(t)) = −log(1 + et), differentiating gives d/dt log(1 − σ(t)) = −σ(t). Both facts are used below through the chain rule, since t is always a linear function of the parameter being updated.

Forward pass first. For x₁ = 5: t = 0.1(5) − 0.6 = −0.1, so D(x₁) = σ(−0.1) = 1/(1 + e0.1) = 0.475021. For x₂ = 7: t = 0.1(7) − 0.6 = 0.1, so D(x₂) = σ(0.1) = 0.524979 (note these two sum to exactly 1, a coincidence of the symmetric t-values chosen here). For the fake, t = 0.1(2) − 0.6 = −0.4, so D(G(z)) = σ(−0.4) = 0.401312 — the Discriminator is already leaning correctly toward "fake," as it should given the fake's low reflectance.

import numpy as np

def sigmoid(t):
    return 1 / (1 + np.exp(-t))

w, b = 0.1, -0.6                 # discriminator weight, bias
x_real = np.array([5.0, 7.0])    # genuine-note reflectance readings

theta, z = 2.0, 1.0               # generator weight, noise draw
g_z = theta * z                   # G(1) = 2.0

d_real = sigmoid(w * x_real + b)  # [0.475021, 0.524979]
d_fake = sigmoid(w * g_z + b)     # 0.401312

grad_w = np.sum(x_real * (1 - d_real)) - d_fake * g_z   # 5.147418
grad_b = np.sum(1 - d_real) - d_fake                     # 0.598688

lr = 0.01
w += lr * grad_w   # 0.151474
b += lr * grad_b   # -0.594013

d_fake_old = sigmoid(0.1 * g_z + (-0.6))          # 0.401312, using pre-update D
grad_theta = (1 - d_fake_old) * 0.1 * z            # 0.059869
theta += lr * grad_theta                           # 2.000599

Trace the Discriminator's gradient with respect to w: for each real sample, the contribution to d(log D(x))/dw is x·(1 − D(x)), from the chain rule dt/dw = x combined with the identity above. That gives 5(0.524979) + 7(0.475021) = 2.624895 + 3.325147 = 5.950042. For the fake term, d(log(1 − D(G(z))))/dw = −D(G(z))·G(z) = −0.401312 × 2 = −0.802625, using dt/dw = G(z) here. Summing: 5.950042 − 0.802625 = 5.147417, matching the code. After one ascent step at learning rate 0.01, w moves from 0.1 to 0.151474 and b from −0.6 to −0.594013 — the Discriminator becomes more sensitive to reflectance, correctly, since real notes score higher on this feature than the current fake.

For the Generator, held against the Discriminator's pre-update weights (alternating optimization freezes one player while the other moves), the gradient of log D(G(z)) with respect to θ is (1 − D(G(z)))·w·z = 0.598688 × 0.1 × 1 = 0.059869. θ nudges from 2 to 2.000599 — a tiny step, and that smallness is the point: real GAN training takes thousands of such alternating steps, each one only slightly reshaping both networks, because a large single step in either direction would overshoot and destabilize the other player's next move.

Solving for the optimal discriminator

The alternating procedure above is what actually runs on hardware, but the minimax formulation has an exact analytical answer for what the Discriminator's best response looks like for any fixed Generator — and that answer is what reveals what the game is really optimizing toward. Write V as an integral over x:

V(D, G) = ∫ p_data(x) log D(x) dx  +  ∫ p_g(x) log(1 − D(x)) dx

where p_g is the density induced by G. Both integrals combine into one integral over x, and for each fixed x the integrand is a·log(y) + b·log(1 − y) with a = p_data(x), b = p_g(x), and y = D(x) the only free variable at that point. Differentiate with respect to y and set to zero: a/y − b/(1 − y) = 0, so a(1 − y) = by, so y(a + b) = a, giving

D*_G(x) = p_data(x) / (p_data(x) + p_g(x))

This is the optimal Discriminator for a fixed Generator. It has an immediate, testable consequence: if the Generator has become perfect, meaning p_g = p_data everywhere, then D*(x) = p_data(x) / (2·p_data(x)) = 1/2 for every x. The best possible Discriminator, facing a perfect Generator, cannot do better than a coin flip — not because it has become a worse classifier, but because there is genuinely no signal left to distinguish real from fake.

Substituting D* back into V gives the value of the game at the Discriminator's optimum, written C(G) = max_D V(D, G). Using m(x) = (p_data(x) + p_g(x))/2, each term p_data log(p_data/(p_data+p_g)) can be rewritten as p_data·[log(1/2) + log(p_data/m)], and integrating gives −log 2 + KL(p_data ‖ m); the fake term gives −log 2 + KL(p_g ‖ m) by the same steps. Summing:

C(G) = −log 4 + KL(p_data ‖ m) + KL(p_g ‖ m) = −log 4 + 2·JSD(p_data ‖ p_g)

where JSD is the Jensen–Shannon divergence, defined as the average of the two KL divergences to the midpoint distribution m. JSD is always ≥ 0 and equals 0 exactly when p_data = p_g. So C(G) is minimized — meaning the Generator has won as completely as it can — precisely at p_g = p_data, where C(G) = −log 4 ≈ −1.386. This is the global equilibrium of the game: the point where the Discriminator's best possible performance is a coin flip, because the Generator's output distribution is indistinguishable from the real one.

The misconception: "a stronger discriminator means a better GAN"

The natural instinct, carried over from every other classifier you have trained, is to treat Discriminator accuracy as the health metric of the system — climbing accuracy means training is working, and the finished model should have a Discriminator that reliably catches fakes. The derivation above shows this is backwards. D*(x) = 1/2 everywhere is the mathematical signature of success, not failure: it is the unique condition under which p_g = p_data. A Discriminator that ends training at 95% accuracy is not a sign of a well-trained GAN — it is direct evidence that the Generator's distribution still differs measurably from the real one, because if it did not, no classifier (however well-trained) could separate them better than chance. This is also why GAN training does not look like an ordinary loss curve monotonically decreasing to zero: both losses fluctuate as the two networks chase a moving target, and the correct read of "training is converging" is that the Discriminator's accuracy on held-out fakes drifts down toward 50%, not up toward 100%.

Where the game actually breaks: mode collapse and vanishing gradients

Two failure modes follow directly from the mechanics above. The vanishing-gradient problem was already visible in the worked example's design choice: using log(1 − D(G(z))) for the Generator's loss means its gradient with respect to the Discriminator's logit scales with −D(G(z)), which shrinks toward 0 exactly when the Discriminator is confidently correct — early training, when the Generator most needs a strong correction signal. The non-saturating swap to maximizing log D(G(z)) fixes this because its gradient scales with (1 − D(G(z))), which is largest precisely when the Discriminator is confidently rejecting the Generator's output.

Mode collapse is a different failure, arising from the alternation itself rather than from any single gradient. If the Generator, at some point in training, finds one single output that reliably fools the current Discriminator, gradient descent has no built-in incentive to keep exploring — that one point is a local minimum of G's loss for the current D. G collapses to producing that one output (or a small cluster of them) regardless of the noise input z, ignoring most of p_data's actual diversity. The Discriminator then learns to reject that specific mode, the Generator jumps to a different single mode to fool the updated Discriminator, and the two can cycle between a handful of modes indefinitely without ever converging to the full data distribution — a failure the −log4/JSD analysis above does not predict, because that analysis assumes D is solved to its exact optimum at every step, which finite-step SGD never actually achieves.

Active recall

Attempt each question before reading its answer.

  1. Write the GAN minimax objective V(D, G) and state which player maximizes it and which minimizes it.
  2. Derive the optimal Discriminator D*_G(x) for a fixed Generator, starting from V written as an integral over x.
  3. At the global optimum where p_g = p_data, what is the value of max_D V(D, G)? Express it using log 4 and justify why it cannot go lower.
  4. A Discriminator has weights w = 0.2, b = −1.0. Compute D(x) for a real sample x = 6.
  5. A friend says: "Our GAN is done training — the Discriminator now catches 97% of the Generator's fakes, better than ever." Explain, using the D* result, why this is evidence of failure rather than success.
  6. Define mode collapse and explain why it can occur even though the theoretical JSD analysis says the only global minimum is p_g = p_data.

Answers.

1. V(D, G) = Ex~p_data[log D(x)] + Ez~p_z[log(1 − D(G(z)))]. D maximizes V (min_G max_D V(D,G)); G minimizes it.

2. V = ∫ p_data(x) log D(x) dx + ∫ p_g(x) log(1 − D(x)) dx. Pointwise, maximize a·log y + b·log(1−y) over y, with a = p_data(x), b = p_g(x). Setting the derivative a/y − b/(1−y) = 0 gives y = a/(a+b), so D*_G(x) = p_data(x)/(p_data(x) + p_g(x)).

3. C(G) = −log 4 ≈ −1.386, since C(G) = −log 4 + 2·JSD(p_data ‖ p_g) and Jensen–Shannon divergence is bounded below by 0, with equality only when p_data = p_g. No Generator distribution can push C(G) below −log 4.

4. t = 0.2(6) − 1.0 = 1.2 − 1.0 = 0.2. D(x) = σ(0.2) = 1/(1 + e−0.2) = 1/(1 + 0.818731) = 0.549834.

5. D*(x) = p_data(x)/(p_data(x)+p_g(x)) equals 1/2 everywhere exactly when p_g = p_data — a perfect Generator makes the best possible Discriminator no better than a coin flip. So a Discriminator that catches 97% of fakes is proof that p_g still diverges substantially from p_data; rising Discriminator accuracy late in training signals the Generator has stalled or regressed, not that the system is "done."

6. Mode collapse is when the Generator maps most or all noise inputs z to one output (or a small cluster of outputs) that currently fools the Discriminator, rather than covering the full diversity of p_data. It can happen despite the JSD analysis because that analysis assumes the Discriminator is solved to its exact global optimum at every step of training; real training only takes a few finite SGD steps on D per G update, so G can exploit a locally-good-enough D by collapsing onto whatever single mode currently maximizes its own gradient, without ever being forced to explore the rest of the data distribution.

Think About It

Think about this: How would you explain generative adversarial networks: the minimax game to a friend who has never seen a computer? What real-world analogy would you use? Imagine you had to build a system using these concepts — what would be your first step? Try this: before moving on, write down three things you learned and one question you still have.

Key Takeaways — Summary and Recap

Let us recap what we covered: the core ideas behind generative adversarial networks: the minimax game, how they connect to real-world applications, and why they matter for your journey in computer science. Remember these key points as you move forward. For competitive exam preparation (CBSE, JEE, BITSAT), focus on understanding the WHY behind each concept, not just the WHAT.

← U-Net: Medical Image SegmentationDCGAN: Deep Convolutional GANs →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn