关于GAN训练过程中的报错:one of the variables needed for gradient computation has been modified by an inplace

首先说明,按照我目前的查询,这可能是全网唯一公开的正确解决方法,所以一定要看下去

1、错误发生情景

在github和百度上搜索gan示例代码的时候,通常会得到下面这种代码:先更新辨别器,再更新生成器。

            netD.zero_grad()
            #optimizerD.step()
            real_out = netD(real_img).mean()
            fake_out = netD(fake_img).mean()
            d_loss = 1 - real_out + fake_out
            d_loss.backward(retain_graph=True)
            #d_loss.backward()
            optimizerD.step()
    

            netG.zero_grad()
            g_loss = generator_criterion(fake_out, fake_img, real_img)
            g_loss.backward()
            optimizerG.step()

            fake_img = netG(z)
            fake_out = netD(fake_img).mean()
            #this code is applied to the pytorch1.4 version and before.
            #because the optimizer.step() actually modifies the weights of the model inplace while the original value of these weights is needed to compute the loss.backward() !!!!

首先说明代码是没有问题的,但前提是你使用是老版本的pytorch,但是如果你是新一点的版本,你就会得到以下错误:

one of the variables needed for gradient computation has been modified by an inplace operation: [torch.cuda.FloatTensor [3, 6, 3, 3]] is at 
version 3; expected version 2 instead. Hint: enable anomaly detection to find the operation that failed to compute its gradient, with torch.autograd.set_detect_anomaly(True).

2、典型错误解决方法

当你在网上搜索解决方法的时候,你大概率会得到以下答案:更新梯度的步骤调后放在一起。

            real_out = netD(real_img).mean()
            fake_out = netD(fake_img).mean()
            d_loss = 1 - real_out + fake_out
            netD.zero_grad()
            g_loss = generator_criterion(fake_out, fake_img, real_img)
            netG.zero_grad()

            d_loss.backward(retain_graph=True)
            g_loss.backward()

            optimizerD.step()
            optimizerG.step()

            fake_img = netG(z)
            fake_out = netD(fake_img).mean()

但是我想说 你可真是害人不浅啊!!!!!!
虽然这样解决了报错,但是这样你将永远得不到收敛成功的模型!
原因如下:在d_loss.backward(retain_graph=True)这步中返回梯度的时候,由于d_loss是由生成器的输出和辨别器的输出结合得出的结果,所以梯度会同时反馈到生成器和辨别器中,在第一节中先更新辨别器,再更新生成器,之间会有netG.zero_grad()这一步将生成器的到的错误梯度归零。所以不会对训练有影响。
但是!!!!!!如果采用刚刚说的解决方法, 在optimizerG.step()求步长的时候,就会同时使用到辨别器损失d_loss.backward(retain_graph=True),生成器损失g_loss.backward()反馈的梯度,这样导致的结果就是:辨别器为了正确分出正负样本,”不是让辨别器自己变得更优秀,而是让生成器变得更垃圾,这样你将永远得不到正确收敛的模型。

3、解决方法

问题报错的原因是:辨别器损失d_loss和生成器损失g_loss,都会包含由生成器的输出fake_out和错误标签fake_label得出的fake_loss, 辨别器为了区分正负样本目标是让fake_loss越小越好,生成器为了混淆辨别器会让fake_loss越大越好。这样原本没有问题,但是在d_loss.backward(retain_graph=True)更新这一步,新版本pytorch梯度反馈机制,将会对fake_loss做出一些改变,导致在 g_loss.backward()反馈使用fake_loss的时候,就会报错。(我对pytorch研究不是太深,也不清楚究竟改变了什么,希望评论区有大佬告知)
既然知道了原因,就可以很简单的找到解决办法,只需要在d_loss.backward(retain_graph=True)之后,重新出计算一个fake_loss就可以,详细代码如下:

    self.G_optimizer.zero_grad()
    G_loss2 = G_loss - fake_loss*0.01                                              
    G_loss2.backward(retain_graph=True)
    self.G_optimizer.step()

    self.D_optimizer.zero_grad()
    fake_pred = self.D_net(outputs.detach())             #训练辨别器不需要将梯度传回
    fake_loss = self.D_loss(fake_pred, fake_label)
    D_loss = (real_loss + fake_loss )*0.5
    D_loss.backward()
    self.D_optimizer.step()

你可能感兴趣的