DCGAN understanding generator update step
Here is some DCGAN example in Pytorch:
https://pytorch.org/tutorials/beginner/dcgan_faces_tutorial.html#training
I wonder why we don't zero discriminator gradients before we update generator? (I added line in original code # netD.zero_grad() # Q: why we don't do this?)
Is it because gradients accumulate in some separate variable and don't affect gradients calculation in generator and only can affect update of weights, but on generator update step we only update generator weights so it's fine?
Here is code section from training loop:
############################
# (1) Update D network: maximize log(D(x)) + log(1 - D(G(z)))
###########################
## Train with all-real batch
netD.zero_grad()
# Format batch
real_cpu = data[0].to(device)
b_size = real_cpu.size(0)
label = torch.full((b_size,), real_label, dtype=torch.float, device=device)
# Forward pass real batch through D
output = netD(real_cpu).view(-1)
# Calculate loss on all-real batch
errD_real = criterion(output, label)
# Calculate gradients for D in backward pass
errD_real.backward()
D_x = output.mean().item()

## Train with all-fake batch
# Generate batch of latent vectors
noise = torch.randn(b_size, nz, 1, 1, device=device)
# Generate fake image batch with G
fake = netG(noise)
label.fill_(fake_label)
# Classify all fake batch with D
output = netD(fake.detach()).view(-1)
# Calculate D's loss on the all-fake batch
errD_fake = criterion(output, label)
# Calculate the gradients for this batch, accumulated (summed) with previous gradients
errD_fake.backward()
D_G_z1 = output.mean().item()
# Compute error of D as sum over the fake and the real batches
errD = errD_real + errD_fake
# Update D
optimizerD.step()

############################
# (2) Update G network: maximize log(D(G(z)))
###########################
netG.zero_grad()
# netD.zero_grad() # Q: why we don't do this?
label.fill_(real_label)  # fake labels are real for generator cost
# Since we just updated D, perform another forward pass of all-fake batch through D
output = netD(fake).view(-1)
# Calculate G's loss based on this output
errG = criterion(output, label)
# Calculate gradients for G
errG.backward()
D_G_z2 = output.mean().item()
# Update G
optimizerG.step()



.. gradients accumulate in some separate variable ..

Yes. That is correct. They are two storages (read &quot;variables&quot;) - one for parameters and another for its gradients.

.. don't affect gradients calculation in generator ..

Also correct. Let's look at the computation graph for the relevant part
(x) -----&gt; netG ----&gt; (fake) -&gt; netD -&gt; (output)
             ^                   ^
(G_params) --|                   |
                                 |
                (D_params) ------|

Here, things inside () are tensors and rest are &quot;pure computation units&quot;. I have explicitly depicted the parameters as inputs to their own computation units. When you hit .backward(), the following computations take place (read backwards)
(GRAD_x) &lt;----- netG &lt;---- (GRAD_fake) &lt;- netD &lt;- (GRAD_output)
                  |                         ^
(GRAD_G_params)&lt;--|                         |
                                            |
                       (D_params) ----------|

I have replaced each variable with their gradients (i.e. GRAD_*) in the backward pass.
You see, there is no GRAD_D_params because its just not required to go further into netG since netG has no role in producing D_params (they are standalone entities associated only with netD).
Simply stated, just because the flow is &quot;going through&quot; netD does not mean it is required to compute/use gradient w.r.t its parameters. Computation of GRAD_fake does not require GRAD_D_params (however it requires D_params), which is why there is no reason to clear up the old GRAD_D_params. But technically, there is no harm either in clearing up GRAD_D_params.

Question in comment: How GRAD_fake is obtained with GRAD_output and D_params ?
Its simple chain rule. Let's rename the variable
fake -&gt; x
netD -&gt; f()
output -&gt; y
D_params -&gt; p
GRAD_output -&gt; del_L/del_y

Then the forward computation simplifies to y = f(x, p). It doesn't matter what the form of f() is, computing gradient of x w.r.t some loss L

would require two things, 1. , which is basically GRAD_output; 2.  or equivalently , which is basically a complicated function involving (x, p) (not their derivatives).
