GAN,CGAN,DCGAN

GAN对抗生成网络

训练流程在这里插入图片描述

图片以及训练过程来源
训练这样的两个模型的大方法就是单独交替迭代训练

  • 我们人为的定义真假样本集的标签,因为我们希望真样本集的输出尽可能为1,假样本集为0,我们就已经默认真样本集所有的类标签都为1,而假样本集的所有类标签都为0。这样单就判别网络来说,此时问题就变成了一个再简单不过的有监督的二分类问题了。

  • 如果我们把刚才的判别网络串接在生成网络的后面,这样我们就知道真假了,也就有了误差了,所以对于生成网络的训练其实是对生成-判别网络串接的训练。我们要把这些假样本的标签都设置为1,也就是认为这些假样本在生成网络训练的时候是真样本,这样才能起到迷惑判别器的目的,也才能使得生成的假样本逐渐逼近为正样本。现在对于生成网络的训练,我们有了样本集(只有假样本集,没有真样本集),有了对应的label(全为1),是不是就可以训练了?有人会问,这样只有一类样本,训练啥呀?谁说一类样本就不能训练了?只要有误差就行。

  • 训练这个串接的网络的时候,一个很重要的操作就是不要判别网络的参数发生变化,也就是不让它参数发生更新,只是把误差一直传,传到生成网络那块后更新生成网络的参数。这样就完成了生成网络的训练了。

  • 在完成生成网络训练后,我们可以根据目前新的生成网络再对先前的那些噪声Z生成新的假样本了,并且训练后的假样本更真了。然后又有了新的真假样本集(其实是新的假样本集),这样又可以重复上述过程了。

代码实现

github—GAN源码
代码分析参考csdn博客

os.makedirs("images", exist_ok=True)

创建子文件夹images,exist_ok取值为Ture时,如果已存在该文件夹,也不会报错。

# 初始化参数,如rpoch次数,batch_size的大小等,sample_interval表示后续间隔保存CGAN图片的保存间隔。
parser = argparse.ArgumentParser() # 声明一个parser
parser.add_argument("--n_epochs", type=int, default=200, help="number of epochs of training") # 添加如下参数,并设定默认值
parser.add_argument("--batch_size", type=int, default=64, help="size of the batches") # 后面的help是添加的描述
parser.add_argument("--lr", type=float, default=0.0002, help="adam: learning rate")
parser.add_argument("--b1", type=float, default=0.5, help="adam: decay of first order momentum of gradient")
parser.add_argument("--b2", type=float, default=0.999, help="adam: decay of first order momentum of gradient")
parser.add_argument("--n_cpu", type=int, default=8, help="number of cpu threads to use during batch generation")
parser.add_argument("--latent_dim", type=int, default=100, help="dimensionality of the latent space")
parser.add_argument("--img_size", type=int, default=28, help="size of each image dimension")
parser.add_argument("--channels", type=int, default=1, help="number of image channels")
parser.add_argument("--sample_interval", type=int, default=400, help="interval betwen image samples")
opt = parser.parse_args() # 读取命令行参数
print(opt) # 调用这些参数
img_shape = (opt.channels, opt.img_size, opt.img_size)

这些参数opt.channels, opt.img_size是需要去上一部分设定的参数的位置去找的, 图像的通道数为1,尺寸大小为28*28

class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()

        def block(in_feat, out_feat, normalize=True):
            layers = [nn.Linear(in_feat, out_feat)]
            if normalize:
                layers.append(nn.BatchNorm1d(out_feat, 0.8))
            layers.append(nn.LeakyReLU(0.2, inplace=True))
            return layers

        self.model = nn.Sequential(
            *block(opt.latent_dim, 128, normalize=False),
            *block(128, 256),
            *block(256, 512),
            *block(512, 1024),
            nn.Linear(1024, int(np.prod(img_shape))),
            nn.Tanh()
        )

    def forward(self, z):
        img = self.model(z)
        img = img.view(img.size(0), *img_shape)
        return img

搭建生成器神经网络

inplace=True的意思是进行原地操作,例如x=x+5对x就是一个原地操作,虽然y=x+5,x=y完成了同样的功能但不是原地操作。

forward中的z是在程序后面的定义的高斯噪声信号,形状为64*100

img.size(0)为64,也就是一批次训练的数目batch_size的值。

class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
 
        self.model = nn.Sequential(
            nn.Linear(int(np.prod(img_shape)), 512),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(256, 1),
            nn.Sigmoid()
        )
 
    def forward(self, img):
        img_flat = img.view(img.size(0), -1)
        validity = self.model(img_flat)
 
        return validity
adversarial_loss = torch.nn.BCELoss()

定义了损失函数nn.BCELoss(),输入(X,Y), X 需要经过sigmoid, Y元素的值只能是0或1float

dataloader = torch.utils.data.DataLoader(
    datasets.MNIST('../../data/mnist', train=True, download=True,
                   transform=transforms.Compose([
                       transforms.ToTensor(),
                      ])),
    batch_size=opt.batch_size, shuffle=True)

PyTorch中数据读取的一个重要接口是torch.utils.data.DataLoader,该接口定义在dataloader.py脚本中,该接口主要用来将自定义的数据读取接口的输出或者PyTorch已有的数据读取接口的输入按照batch size封装成Tensor,后续只需要再包装成Variable即可作为模型的输入,因此该接口有点承上启下的作用。简单来说就是你训练的数据集不是一股脑的全部丢进来,而是分成了一批一批的,这个接口函数就是将数据集分批并转化成可以处理的Tensor类型。

transforms.Compose([transforms.ToTensor(),transforms.Normalize(std=(0.5,0.5,0.5),mean=(0.5,0.5,0.5))])
其作用就是先将输入归一化到(0,1),再使用公式”(x-mean)/std”,将每个元素分布到(-1,1)

for i, (imgs, _) in enumerate(dataloader):

        # Adversarial ground truths
        valid = Variable(Tensor(imgs.size(0), 1).fill_(1.0), requires_grad=False)
        fake = Variable(Tensor(imgs.size(0), 1).fill_(0.0), requires_grad=False)

        # Configure input  将真实的图片转化为神经网络可以处理的变量。
        real_imgs = Variable(imgs.type(Tensor))

这部分定义的相当于是一个标准,vaild可以看成是64行1列的向量,为了在后面计算损失时和1比较;fake也是一样是全为0的向量,用法和1的用法相同。

        # -----------------
        #  Train Generator
        # -----------------
        
        optimizer_G.zero_grad()
        
        # Sample noise as generator input
        z = Variable(Tensor(np.random.normal(0, 1, (imgs.shape[0], opt.latent_dim))))

        # Generate a batch of images
        gen_imgs = generator(z)

        # Loss measures generator's ability to fool the discriminator
        g_loss = adversarial_loss(discriminator(gen_imgs), valid)

        g_loss.backward()
        optimizer_G.step()

np.random.normal(0, 1, (imgs.shape[0], opt.latent_dim)的意思就是输入从0到1之间,形状为imgs.shape[0], opt.latent_dim的随机高斯数据。

        # ---------------------
        #  Train Discriminator
        # ---------------------

        optimizer_D.zero_grad()

        # Measure discriminator's ability to classify real from generated samples
        real_loss = adversarial_loss(discriminator(real_imgs), valid)
        fake_loss = adversarial_loss(discriminator(gen_imgs.detach()), fake)
        d_loss = (real_loss + fake_loss) / 2

        d_loss.backward()
        optimizer_D.step()

GAN交替训练的过程:

  • 重新计算假样本(假样本每次是需要更新的,产生越来越像的样本)
  • 训练D网络,一个二分类的神经网络
  • 训练G网络,一个串联起来的长网络,也是一个二分类的神经网络(不过只有假样本来训练),同时D部分参数在下一次的时候不能变了。

CGAN

  • CGAN目的就是能指定生成什么样的数据。核心就是通过给原始的GAN生成器G和判别器D添加额外的条件信息,实现条件生成模型。额外信息可以是类别标签或者是其他的辅助信息,最直接的就是使用类别标签信息y
  • 原始的GAN生成器的输入信息是一固定长度的噪声信息,那么CGAN中则是将噪声信息结合标签信息组合起来
  • 作为输入,标签信息一般是采用one-hot编码构成。
  • 原始的GAN判别器输入是图像数据(真实的训练样本和生成器生成的数据),在CGAN中则是将类别标签和图像数据进行组合作为判别器的输入。

代码实现CGAN

class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        
        ## n_classes是dataset的类别数
    
        self.label_emb = nn.Embedding(opt.n_classes, opt.n_classes)

        def block(in_feat, out_feat, normalize=True):
            layers = [nn.Linear(in_feat, out_feat)]
            if normalize:
                layers.append(nn.BatchNorm1d(out_feat, 0.8))
            layers.append(nn.LeakyReLU(0.2, inplace=True))
            return layers

        self.model = nn.Sequential(
            *block(opt.latent_dim + opt.n_classes, 128, normalize=False),
            *block(128, 256),
            *block(256, 512),
            *block(512, 1024),
            nn.Linear(1024, int(np.prod(img_shape))),
            nn.Tanh()
        )

    def forward(self, noise, labels):
        # Concatenate label embedding and image to produce input
        gen_input = torch.cat((self.label_emb(labels), noise), -1)
        img = self.model(gen_input)
        img = img.view(img.size(0), *img_shape)
        return img
class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()

        self.label_embedding = nn.Embedding(opt.n_classes, opt.n_classes)

        self.model = nn.Sequential(
            nn.Linear(opt.n_classes + int(np.prod(img_shape)), 512),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(512, 512),
            nn.Dropout(0.4),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(512, 512),
            nn.Dropout(0.4),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(512, 1),
        )

    def forward(self, img, labels):
        # Concatenate label embedding and image to produce input
        d_in = torch.cat((img.view(img.size(0), -1), self.label_embedding(labels)), -1)
        validity = self.model(d_in)
        return validity

def sample_image(n_row, batches_done):
    """Saves a grid of generated digits ranging from 0 to n_classes"""
    # Sample noise
    z = Variable(FloatTensor(np.random.normal(0, 1, (n_row ** 2, opt.latent_dim))))
    
    # Get labels ranging from 0 to n_classes for n rows
    labels = np.array([num for _ in range(n_row) for num in range(n_row)])
    labels = Variable(LongTensor(labels))
    
    gen_imgs = generator(z, labels)
    save_image(gen_imgs.data, "images02/%d.png" % batches_done, nrow=n_row, normalize=True)

DCGAN

参考博客

  • DCGAN是将CNN 与 GAN结合,只是将G和D换成两个卷积神经网络(CNN),DCGAN将CNN做了一些改变,为提高样本质量和收敛速度。

  • strided convolution 替代确定性的pooling(从而可以让网络自己学习downsampling(下采样)

    • G网络中使用微步幅度卷积(fractionally strided convolution)代替 pooling 层
    • D网络中使用步幅卷积(strided convolution)代替 pooling 层。
  • 在 D 和 G 中均使用 batch normalization批量归一化 ,去掉 FC 层,使网络变为全卷积网络 ,G 网络中使用ReLU 激活函数,最后一层使用tanh激活函数 ,D 网络中所有层都使用 LeakyReLU 作为激活函数

class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()

        self.init_size = opt.img_size // 4
        self.l1 = nn.Sequential(nn.Linear(opt.latent_dim, 128 * self.init_size ** 2)) #l1函数进行Linear变换。线性变换的两个参数是变换前的维度,和变换之后的维度

        self.conv_blocks = nn.Sequential(           #nn.sequential{}是一个组成模型的壳子,用来容纳不同的操作
            nn.BatchNorm2d(128),                    # BatchNorm2d的目的是使我们的一批(batch)feature map 满足均值0方差1,就是改变数据的量纲
            nn.Upsample(scale_factor=2),            #上采样,将图片放大两倍(这就是为啥class最先开始将图片的长宽除了4,下面还有一次放大2倍)
            nn.Conv2d(128, 128, 3, stride=1, padding=1), #二维卷积函数,(输入数据channel,输出的channel,步长,卷积核大小,padding的大小)
            nn.BatchNorm2d(128, 0.8),
            nn.LeakyReLU(0.2, inplace=True),        #relu激活函数
            nn.Upsample(scale_factor=2),            #上采样
            nn.Conv2d(128, 64, 3, stride=1, padding=1),#二维卷积
            nn.BatchNorm2d(64, 0.8),                #BN
            nn.LeakyReLU(0.2, inplace=True),
            nn.Conv2d(64, opt.channels, 3, stride=1, padding=1),
            nn.Tanh(),                              #Tanh激活函数
        )

    def forward(self, z):
        out = self.l1(z)              #l1函数进行的是Linear变换 (第50行定义了)
        out = out.view(out.shape[0], 128, self.init_size, self.init_size)#view是维度变换函数,可以看到out数据变成了四维数据,第一个是batch_size(通过整个的代码,可明白),第二个是channel,第三,四是单张图片的长宽
        img = self.conv_blocks(out)
        return img
class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()

        def discriminator_block(in_filters, out_filters, bn=True):
            block = [nn.Conv2d(in_filters, out_filters, 3, 2, 1), nn.LeakyReLU(0.2, inplace=True), nn.Dropout2d(0.25)]#Conv卷积,Relu激活,Dropout将部分神经元失活,进而防止过拟合
            if bn:
                block.append(nn.BatchNorm2d(out_filters, 0.8))    #如果bn这个参数为True,那么就需要在block块里面添加上BatchNorm的归一化函数
            return block

        self.model = nn.Sequential(
            *discriminator_block(opt.channels, 16, bn=False),
            *discriminator_block(16, 32),
            *discriminator_block(32, 64),
            *discriminator_block(64, 128),
        )

        # The height and width of downsampled image
        ds_size = opt.img_size // 2 ** 4
        self.adv_layer = nn.Sequential(nn.Linear(128 * ds_size ** 2, 1), nn.Sigmoid()) #先进行线性变换,再进行激活函数激活
                          #上一句中 128是指model中最后一个判别模块的最后一个参数决定的,ds_size由model模块对单张图片的卷积效果决定的,而2次方是整个模型是选取的长宽一致的图片
    def forward(self, img):
        out = self.model(img)
        out = out.view(out.shape[0], -1)    #将处理之后的数据维度变成batch * N的维度形式
        validity = self.adv_layer(out)      #第92行定义
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值