第G1周:生成对抗网络(GAN)入门

1.什么是GAN ️

GAN并不指代某一个具体的神经网络,而是指一类基于博弈思想而设计的神经网络。
GAN由两个分别被称为生成器(Generator)和判别器(Discriminator)的神经网络组成。其中,生成器从某种噪声分布中随机采样作为输入,输出与训练集中真实样本非常相似的人工样本;判别器的输入则为真实样本或人工样本,其目的是将人工样本与真实样本尽可能地区分出来。生成器和判别器交替运行,相互博弈,各自的能力都得到升。理想情况下,经过足够次数的博弈之后,判别器无法判断给定样本的真实性,即对于所有样本都输出50%真,50%假的判断。此时,生成器输出的人工样本已经逼真到使判别器无法分辨真假,停止博弈。这样就可以得到一个具有“伪造”真实样本能力的生成器。

1.1 生成器

        GANs中,生成器 G 选取随机噪声 z 作为输入,通过生成器的不断拟合,最终输出一个和真实样本尺寸相同,分布相似的伪造样本G(z) 。生成器的本质是一个使用生成式方法的模型,它对数据的分布假设和分布参数进行学习,然后根据学习到的模型重新采样出新的样本。
        从数学上来说,生成式方法对于给定的真实数据,首先需要对数据的显式变量或隐含变量做分布假设;然后再将真实数据输入到模型中对变量、参数进行训练;最后得到一个学习后的近似分布,这个分布可以用来生成新的数据。从机器学习的角度来说,模型不会去做分布假设,而是通过不断地学习真实数据,对模型进行修正,最后也可以得到一个学习后的模型来做样本生成任务。这种方法不同于数学方法,学习的过程对人类理解较不直观。

        生成器借助现有的数据来生成新数据,比如使用从随机产生的一组数字向量(称为潜在空间 latent space)中生成数据(图像、音频等)。所以在构建的时候你首先要明确生成目标,然后将生成结果交给判别网络做下一步的处理。

1.2. 判别器

        GANs中,判别器 D 对于输入的样本 x,输出一个[0,1] 之间的概率数值 D(x)。x 可能是来自于原始数据集中的真实样本 x,也可能是来自于生成器 G 的人工样本G(z)。通常约定,概率值D(x) 越接近于1就代表此样本为真实样本的可能性更大;反之概率值越小则此样本为伪造样本的可能性越大。也就是说,这里的判别器是一个二分类的神经网络分类器,目的不是判定输入数据的原始类别,而是区分输入样本的真伪。可以注意到,不管在生成器还是判别器中,样本的类别信息都没有用到,也表明 GAN 是一个无监督的学习过程。

        判别器试图区分接收的数据属于真实数据还是由生成网络生成的数据,它需要基于实现定于的类别对其进行分类。通常来说,GAN使用在二分类问题上。判别结果为0~1之间的数字,用来表示本次输入被认为是真实数据的可能性。当判定结果为1时,则认为它来自真实数据,反之则属于生成数据。

1.3. 基本原理

        GAN是博弈论和机器学习相结合的产物,于2014年Ian Goodfellow的论文中问世,一经问世即火爆足以看出人们对于这种算法的认可和狂热的研究热忱。想要更详细的了解GAN,就要知道它是怎么来的,以及这种算法出现的意义是什么。研究者最初想要通过计算机完成自动生成数据的功能,例如通过训练某种算法模型,让某模型学习过一些苹果的图片后能自动生成苹果的图片,具备些功能的算法即认为具有生成功能。但是GAN不是第一个生成算法,而是以往的生成算法在衡量生成图片和真实图片的差距时采用均方误差作为损失函数,但是研究者发现有时均方误差一样的两张生成图片效果却截然不同,鉴于此不足Ian Goodfellow提出了GAN。

         那么GAN是如何完成生成图片这项功能的呢,如上图所示,GAN是由两个模型组成的:生成模型G和判别模型D。首先第一代生成模型1G的输入是随机噪声z,然后生成模型会生成一张初级照片,训练一代判别模型1D另其进行二分类操作,将生成的图片判别为0,而真实图片判别为1;为了欺瞒一代鉴别器,于是一代生成模型开始优化,然后它进阶成了二代,当它生成的数据成功欺瞒1D时,鉴别模型也会优化更新,进而升级为2D,按照同样的过程也会不断更新出N代的G和D。

2.代码实现

2.1 定义超参数

import os
import argparse
import numpy as np
import torch
import torch.nn as nn
from torch.autograd import Variable
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.utils import save_image
import torchvision.transforms as transforms

# 创建文件夹
os.makedirs('./output/images/', exist_ok=True)
os.makedirs('./output/', exist_ok=True)
os.makedirs('./data/MNIST/', exist_ok=True)

# 超参数配置
n_epochs = 50
batch_size = 64
lr = 0.0002
b1 = 0.5
b2 = 0.999
n_cpu = 2
latent_dim = 100
img_size = 28
channels = 1
sample_interval = 500

# 图像的尺寸:(1, 28, 28),和图像的像素面积:(784)
img_shape = (channels, img_size, img_size)
img_area = np.prod(img_shape)

# 设置cuda:(cuda:0)
cuda = True if torch.cuda.is_available() else False
print(cuda)

2.2 下载数据集

## mnist数据集下载
mnist = datasets.MNIST(root='./data/', 
                       train=True, 
                       download=True,
                       transform=transforms.Compose([transforms.Resize(img_size), transforms.ToTensor(), transforms.Normalize([0.5], [0.5])]))

2.3 配置数据

# 配置数据到加载器
dataloader = DataLoader(mnist, batch_size=batch_size, shuffle=True)

2.4 定义模型

2.4.1 定义鉴别器

'''
定义判别器 Discriminator
将图片28x28展开成784,然后通过多层感知器,
中间经过斜率设置为0.2的LeakyReLU激活函数,
最后接sigmoid激活函数得到一个0到1之间的概率进行二分类
'''
class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
        self.model = nn.Sequential(
                     nn.Linear(img_area, 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)  # 鉴别器输入是一个被view展开的(784)的一维图像:(64, 784)
        validity = self.model(img_flat)       # 通过鉴别器网络
        return validity                       # 鉴别器返回的是一个[0, 1]间的概率

2.4.2 定义生成器


'''
定义生成器 Generator
输入一个100维的0~1之间的高斯分布,
然后通过第一层线性变换将其映射到256维,
然后通过LeakyReLU激活函数,
接着进行一个线性变换,
再经过一个LeakyReLU激活函数,
然后经过陷先变换将其变成784维,
最后经过Tanh激活函数,
是希望生成的假的图片数据分布,能够再-1~1之间。
'''
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
        # prod():返回给定轴上的数组元素的乘积:1*28*28=784
        self.model = nn.Sequential(
                     *block(latent_dim, 128, normalize=False),
                     *block(128, 256),
                     *block(256, 512),
                     *block(512, 1024),
                     nn.Linear(1024, img_area),
                     nn.Tanh())
    
    # view():相当于numpy中的reshape,重新定义矩阵的形状:这里是reshape(64, 1,28, 28)
    def forward(self, z):                          # 输入的是(64, 100)的噪声数据
        imgs = self.model(z)                       # 噪声数据通过生成器模型
        imgs = imgs.view(imgs.size(0), *img_shape)  # reshape成(64, 1,28, 28)
        return imgs                                # 输出为64张大小为(1, 28, 28)的图像

2.5 训练模型

# 创建生成器、判别器对象
generator = Generator()
discriminator = Discriminator()
# 定义loss的度量方式(二分类的交叉熵)
criterion = torch.nn.BCELoss()
# 定义又换函数,学习率为0.0003
# betas: 用于计算梯度以及梯度平方的运行平均值的系数
optimizer_G = torch.optim.Adam(generator.parameters(), lr=lr, betas=(b1, b2))
optimizer_D = torch.optim.Adam(discriminator.parameters(), lr=lr, betas=(b1, b2))
# 若有显卡,在cuda模式中运行
if torch.cuda.is_available():
    generator = generator.cuda()
    discriminator = discriminator.cuda()
    criterion = criterion.cuda()


# 进行多个epoch的训练
for epoch in range(n_epochs):
    for i, (imgs, _) in enumerate(dataloader):
        ''' 训练判别器 Train Discriminator '''
        # 分为两部分:1、真的图像判别为真;2、假的图像判别为假
        imgs = imgs.view(imgs.size(0), -1)
        real_img = Variable(imgs).cuda()
        real_label = Variable(torch.ones(imgs.size(0), 1)).cuda()
        fake_label = Variable(torch.zeros(imgs.size(0), 1)).cuda()
        # 计算真实图片的损失
        real_out = discriminator(real_img)
        loss_real_D = criterion(real_out, real_label)
        real_scores = real_out
        # 计算假的图片的损失
        # detach(): 从当前计算图中分离下来避免梯度传到G,因为G不用更新
        z = Variable(torch.randn(imgs.size(0), latent_dim)).cuda()
        fake_img = generator(z).detach()
        fake_out = discriminator(fake_img)
        loss_fake_D = criterion(fake_out, fake_label)
        fake_scores = fake_out
        # 损失函数和优化
        loss_D = loss_real_D + loss_fake_D
        optimizer_D.zero_grad()
        loss_D.backward()
        optimizer_D.step()
        
        ''' 训练生成器 Train Generator '''
        # 原理:目的是希望生成的假的图片被判别器判断为真的图片,
        # 在此过程中,将判别器固定,将假的图片传入判别器的结果与真实的label对应,
        # 反向传播更新的参数是生成网络里面的参数,
        # 这样可以通过更新生成网络里面的参数,来训练网络,使得生成的图片让判别器以为是真的, 这样就达到了对抗的目的
        z = Variable(torch.randn(imgs.size(0), latent_dim)).cuda()
        fake_img = generator(z)
        output = discriminator(fake_img)
        # 损失函数和优化
        loss_G = criterion(output, real_label)
        optimizer_G.zero_grad()
        loss_G.backward()
        optimizer_G.step()
        
        ''' 打印训练过程中的日志 '''
        # item():取出单元素张量的元素值并返回该值,保持原元素类型不变
        if (i + 1) % 300 == 0:
            print("[Epoch %d/%d] [Batch %d/%d] [D loss: %f] [G loss: %f] [D real: %f] [D fake: %f]"
            % (epoch, n_epochs, i, len(dataloader), loss_D.item(), loss_G.item(), real_scores.data.mean(), fake_scores.data.mean()))
        # 保存训练过程中的图像
        batches_done = epoch * len(dataloader) + i
        if batches_done % sample_interval == 0:
            save_image(fake_img.data[:25], "./output/images/%d.png" % batches_done, nrow=5, normalize=True)

2.6  保存模型

# 保存模型
torch.save(generator.state_dict(), './output/generator.pth')
torch.save(discriminator.state_dict(), './output/discriminator.pth')

3.效果展示

训练500次之后的训练结果训练5000次之后的训练结果
训练30000次之后的训练结果训练50000次之后的训练结果

  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

房FF房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值