初学GAN(WGAN-gp)生成直线的小实验,以及一个可能很有用的小想法——加校正的WGAN-gp

一直以来对各种利用GAN来生成各种有趣的图像的应用很感兴趣,也想学习一下。前两天终于动手实践了一下,终于学会了GAN基本的方法。然后我又东改改西改改,又发现了两个很有趣的小方法,感觉挺实用的。我这个人看论文比较少,也许别人已经提出来过,但是我没有看到过。反正不管有没有人提出过,我都写下来吧。我这个人很懒,隔几年才写个博客,不过我写下来的,都是我觉得比较有趣的想法,这次这个我觉得还是挺有意思的,值得花点时间记下来。

GAN网络的基本知识我就不多说了,资料太多了,我也说不好,等会直接上代码。

先说说这两个想法:

一个是一次前向和反向把判别器D和生成器G同时都训练了,可以节省大于1/5的时间,以及少写几行代码^_^。

另一个则是由于生成的效果总是不让我很满意,然后我想出了一个办法,新增了一个我称之为“校正"(Correct)网络,然后用这个校正网络去校正一下生成器的输出,再用校正后的输出作为期望输出来计算一个校正损失作为损失的一部分。加入这个校正输出,以及其它的一些方法后,效果明显。具体方法后面再详说。

我用了最原始的GAN和据说很好的WGAN-gp两种方法来实现。我选的例子很简单,教生成器生成9个数的等差数列,画出来就是一条9个点的直线。之所以选这个例子,一是作为我的第一个GAN代码,以掌握方法为主,不想搞太复杂,二是一条直线,很容易观察生成质量的好坏,而如果是个很复杂的图形的话,一来需要的时间长,代码复杂,二来质量评估不是很直观。而直线的质量好不好,一眼就看出来了,很细微的弯曲都是很明显的。事实上,也正是由于直线的弯曲太显眼了,所以让我对WGAN-gp最后输出的结果还是不太满意(虽然比原始GAN好多了),然后才想了各种各样的方法,最后通过加上校正器C,才最终改善了质量,生成了笔直的直线。

先看看效果。我每个batch用400条直线。图例中的数字是这条直线被辨别器D打的分,越高越好。原始GAN是加了sigmoid的,分值是0到1,WGAN-gp分值是没有限制的,但由于梯度惩罚项的存在,数值也不会太大。

这是原始GAN网络训练40000次的效果,质量嘛……就不说了。

这是WGAN-gp训练10000次的效果,可以看出要好得多,但还是有些弯曲

 这是WGAN-gp训练10000次,但采用了同时训练D和G的效果,可以看出效果与分别训练的差不多,但时间上可以节省大约1/4。

 好了,这是加了校正器C,训练5000次的效果。

 可以看出质量好太多了,训练次数少一半,肉眼就已经看不出明显的弯曲了。虽然由于要多训练一个校正器,总时间上与不加校正的差不多

 这是我觉得上面打分出来的数值的绝对值太大了,由于打分输出的均值就是loss,太大了感觉不太好,所以我在训练判别器时又加了个打分的二次均方惩罚项,这样输出的打分值的绝对值就小一些了。

 

首先说下同时训练判别器D和生成器G的方法。我照着网上的教程和代码写代码的时候,发现在训练判别器D时,先要用生成网络产生一些负样本,然后在计算判别器的loss时,要把这个负样本输入到判别器,然后backward一下,教程视频上老师这个时候还专门说明了这个时候要把这个负样本detach一下,避免反向计算梯度时计算生成器的梯度。然而呢,在后面训练生成器的时候,又要把这个过程做一遍,只是这个时候需要把梯度计算传递到生成器上,并且此时生成器需要的loss与判别器是反的。大致的过程是这个样子的:

1 用真实数据dataTrue给判别器D,得到输出outTrue

2 用随机数据给生成器,生成假数据dataFake , 并且把dataFake.detach()一下,再把dataFack给判别器D,得到outFake

3 计算D的lossD = -E[ log(outTrue] + E[ log (1-outFake)]

4 反向传递 lossD , 然后用优化器更新 判别器D

5 接下来要训练生成器G了,还是先用随机数据给生成器,生成假数据dataFake,这时不能detach了,然后把dataFack给判别器D,得到outFake

6 G的损失 lossG就是 -E[ log(1-outFake)]

7 反向传递lossG,然后用优化器更新生成器

这里我们发现,在训练D和G的时候,都要生成一个dataFake,然后输入给D产生一个outFake,然后loss中都有一个E[ log(1-outFake)],只不过一个是负的,一个是正的,并且在训练D的时候,不需要把梯度计算到生成器G中,但是呢,如果不detach一下,实际上也是会计算下去的,这个计算时间就浪费了,所以视频上才强调要detach一下。

可是我却发现是不是可以反其道而行之,干脆一次把训练生成器G的梯度也算了,反正只差一个负号,把lossD反向完了以后,把存在G网络参数里的梯度取个反就行了。实现代码也就几行

        #直接把生成器的梯度取反进行训练,可节省一次正向推演
        for name,para in gNet.named_parameters():
            if '.weight' in name or '.bias' in name:
                para.grad*=-1
        optimG.step()

抱着试试看的想法试了一下,发现生成器G还真的能更新,效果和单独训练差不多,而时间上节省了大约1/5,简直是白赚。特别是还能少写几行代码。

接下来先上原始GAN的代码。里面为了在线显示效果,加了个画图线程,每1000个batch从生成的直线中抽前10条出来显示。

import numpy as np
import torch
import torch.nn as nn
import os
import matplotlib
import matplotlib.pyplot as plt
import threading as thread
import time

'''测试GAN对抗生成网络
生成一个中项±1之间 ,项差±1之间的 n个数的等差数列 ,即构成一条直线 '''

savePathD='GAN_Dnet.pkl'
savePathG='GAN_Gnet.pkl'
pNum=9  #9个点
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

#判断网络
class DNet(nn.Module):
    def __init__(self):
        super(DNet,self).__init__()
        self.fc1=nn.Linear(pNum,64)
        self.fc2=nn.Linear(64,64)
        self.fout=nn.Linear(64,1)
    def forward(self,input):
        out=self.fc1(input.view(-1,pNum))
        out=torch.tanh(out)
        out=self.fc2(out)
        out=torch.celu(out)
        out=self.fout(out)

        return torch.sigmoid(out)

#生成网络
class GNet(nn.Module):
    def __init__(self):
        super(GNet,self).__init__()
        self.fc1=nn.Linear(1,16)
        self.fc2=nn.Linear(16,64)
        self.fc3=nn.Linear(64,32)
        self.fout=nn.Linear(32,pNum)
    def forward(self,input):
        out=self.fc1(input.view(-1,1))
        out=torch.celu(out)
        out=self.fc2(out)
        out=torch.celu(out)
        out=self.fc3(out)
        out=torch.celu(out)
        out=self.fout(out)
        return out

#生成数据函数
def CreateData(num,pointnum):
    mu=np.random.random([num,1])*2-1
    
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值