Does the generator's optimizer also train the discriminator?

Viewed 58

While learning about GANs, I noticed that the code examples exhibit this pattern:

The discriminator is trained like this:

d_optim.zero_grad()

real_pred = d(real_batch)
d_loss = d_loss_fn(real_pred, torch.ones(real_batch_size, 1))
d_loss.backward()

fake_pred = d(g(noise_batch).detach())
d_loss = d_loss_fn(fake_pred, torch.zeros(noise_batch_size, 1))
d_loss.backward()

d_optim.step()

The generator is trained like this:

g_optim.zero_grad()

fake_pred = d(g(noise_batch))
g_loss = g_loss_fn(fake_pred, torch.ones(noise_batch_size, 1))
g_loss.backward()

g_optim.step()

It is mentioned that d(g(noise_batch).detach()) is written for the discriminator instead of d(g(noise_batch)) to prevent d_optim.step() from training g, but nothing is said about d(g(noise_batch)) for the generator; would g_optim.step() also train d?

In fact, why do we d(g(noise_batch).detach()) if, for example, d_optim = torch.optim.SGD(d.parameters(), lr=0.001)? Does this not specify that d.parameters() and not also g.parameters() are to be updated?

1 Answers

TLDR: optimizer will update only the parameters specified to it, whereas backward() call computes the gradients for all variables in the computation graph. So, it is useful to detach() the variables for which gradient computation is not required at that instant.

I believe the answer lies in the way things are implemented within PyTorch.

  • tensor.detach() creates a tensor that shares storage with tensor that does not require grad. So, effectively, you cut off the computation graph. That is, doing fake_pred = d(g(noise_batch).detach()) will detach (cut off) the computation graph of the generator.
  • When you call backward() on the loss, gradients are calculated for the entire computation graph (irrespective of whether optimizer uses it or not). Thus, cutting off the generator part will avoid the gradient computations for the generator weights (since they are not required).
  • Also, only the parameters passed to particular optimizer are updated when optimizer.step() is called. So, the g_optim will only optimize the parameters passed to it (You don't explicitly mention which parameters are passed to g_optim). Similarly, d_optim will only update d.parameters() since you explicitly specify that.
Related