年初的一篇文章

1. Generative Adversarial Networks(GAN)

由故事可知,GAN需要两个网络——G(Generator)&D(Discriminator),也就是一个绘画者和一个纠错者。

  • G是一个生成图片的网络(就因为这是一个网络,所以需要输入值),而这个输入值就是一个随机的噪声z,通过这个噪声生成图片,记做G(z)。
  • D是一个判别网络,判别一张图片是不是真实的。它的输入参数是一张图片x,输出D(x)代表x为真实图片的概率。如果概率为1,就代表100%是真实的图片;如果概率为0,就代表不可能是真实的图片。

生成网络G的目标就是尽量生成真实的图片去欺骗判别网络D。而D的目标就是尽量把G生成的图片和真实的图片分别开来

最后博弈的结果是什么?在最理想的状态下,G可以生成足以“以假乱真”的图片G(z)。对于D来说,它难以判定G生成的图片究竟是不是真实的,因此D(G(z)) = 0.5。我们把这种状态成为纳什均衡

使用公式表示就是:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0K40Jkak-1639442662167)(https://wang-1304725667.cos.ap-chengdu.myqcloud.com/mardown/20210131175543.png)]

  • 整个式子由两项构成。x表示真实图片,z表示输入G网络的噪声,而G(z)表示G网络生成的图片。
  • D(x)表示D网络判断真实图片是否真实的概率(因为x就是真实的,所以对于D来说,这个值越接近1越好)。而D(G(z))是D网络判断G生成的图片的是否真实的概率。
  • G的目的:上面提到过,D(G(z))是D网络判断G生成的图片是否真实的概率,G应该希望自己生成的图片“越接近真实越好”。也就是说,G希望D(G(z))尽可能得大,这时V(D, G)会变小。因此我们看到式子的最前面的记号是min_G。
  • D的目的:D的能力越强,D(x)应该越大,D(G(x))应该越小。这时V(D,G)会变大。因此式子对于D来说是求最大(max_D)

既然此公式可以作为判别的标准,那么我们就把该公式记为损失函数

在pytorch中,可以直接调用nn.BCELoss()进行表示!

那么,如何构建优化函数呢?

2. DCGAN

我们知道深度学习中对图像处理应用最好的模型是CNN,那么如何把CNN与GAN结合?DCGAN是这方面最好的尝试之一(论文地址:[1511.06434] Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks

DCGAN的原理和GAN是一样的,这里就不在赘述。它只是把上述的G和D换成了两个卷积神经网络(CNN)。但不是直接换就可以了,DCGAN对卷积神经网络的结构做了一些改变,以提高样本的质量和收敛的速度,这些改变有:

  • 取消所有pooling层。G网络中使用转置卷积(transposed convolutional layer)进行上采样,D网络中用加入stride的卷积代替pooling。
  • 在D和G中均使用batch normalization
  • 去掉FC层,使网络变为全卷积网络
  • G网络中使用ReLU作为激活函数,最后一层使用tanh
  • D网络中使用LeakyReLU作为激活函数

3. 代码

结构:

代码:

main.py

import argparse
import torch
import torchvision
import torchvision.utils as vutils
import torch.nn as nn
from random import randint
from model import NetD, NetG

parser = argparse.ArgumentParser()
parser.add_argument('--batchSize', type=int, default=64)
parser.add_argument('--imageSize', type=int, default=96)
parser.add_argument('--nz', type=int, default=100, help='size of the latent z vector')
parser.add_argument('--ngf', type=int, default=64)
parser.add_argument('--ndf', type=int, default=64)
parser.add_argument('--epoch', type=int, default=25, help='number of epochs to train for')
parser.add_argument('--lr', type=float, default=0.0002, help='learning rate, default=0.0002')
parser.add_argument('--beta1', type=float, default=0.5, help='beta1 for adam. default=0.5')
parser.add_argument('--data_path', default='data/', help='folder to train data')
parser.add_argument('--outf', default='imgs/', help='folder to output images and model checkpoints')
opt = parser.parse_args()

# 定义是否使用GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 图像读入与预处理
transforms = torchvision.transforms.Compose([
    torchvision.transforms.Scale(opt.imageSize),
    torchvision.transforms.ToTensor(),
    torchvision.transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)), ])

dataset = torchvision.datasets.ImageFolder(opt.data_path, transform=transforms)

dataloader = torch.utils.data.DataLoader(
    dataset=dataset,
    batch_size=opt.batchSize,
    shuffle=True,
    drop_last=True,
)

# 转到GPU上
netG = NetG(opt.ngf, opt.nz).to(device)
netD = NetD(opt.ndf).to(device)

criterion = nn.BCELoss()
optimizerG = torch.optim.Adam(netG.parameters(), lr=opt.lr, betas=(opt.beta1, 0.999))
optimizerD = torch.optim.Adam(netD.parameters(), lr=opt.lr, betas=(opt.beta1, 0.999))

label = torch.FloatTensor(opt.batchSize)
real_label = 1
fake_label = 0
for epoch in range(100):
    for i, (imgs, _) in enumerate(dataloader):
        # 固定生成器G,训练鉴别器D
        optimizerD.zero_grad()  # 训练D
        # 让D尽可能的把真图片判别为1
        imgs = imgs.to(device)  # 转到GPU上
        output = netD(imgs)
        label.data.fill_(real_label)  # 把评价标准全部列为1(真)
        label = label.to(device)
        errD_real = criterion(output, label)  # 使用D判别时,全部列为真
        errD_real.backward()
        # 让D尽可能把假图片判别为0
        label.data.fill_(fake_label)  # 把评价标准全部列为0(假)
        noise = torch.randn(opt.batchSize, opt.nz, 1, 1)
        noise = noise.to(device)
        fake = netG(noise)  # 生成假图
        output = netD(fake.detach())  # 避免梯度传到G,因为G不用更新。其中,detach就是截断反向传播的梯度流。
        errD_fake = criterion(output, label)  # 使用D判别时,全部列为假
        errD_fake.backward()
        errD = errD_fake + errD_real  # 总的损失参数,仅展示时使用,我们一般分开进行backward,不会使用到这个
        optimizerD.step()

        # 固定鉴别器D,训练生成器G
        optimizerG.zero_grad()
        # 让D尽可能把G生成的假图判别为1
        label.data.fill_(real_label)
        label = label.to(device)
        output = netD(fake)
        errG = criterion(output, label)
        errG.backward()
        optimizerG.step()

        print('[%d/%d][%d/%d] Loss_D: %.3f Loss_G %.3f'
              % (epoch, opt.epoch, i, len(dataloader), errD.item(), errG.item()))

    vutils.save_image(fake.data,
                      '%s/fake_samples_epoch_%03d.png' % (opt.outf, epoch),
                      normalize=True)
    torch.save(netG.state_dict(), '%s/netG_%03d.pth' % (opt.outf, epoch))
    torch.save(netD.state_dict(), '%s/netD_%03d.pth' % (opt.outf, epoch))

model.py

# 我们使用的是 DCGAN, 所以在model是CNN
import torch.nn as nn
# 定义生成器网络G
class NetG(nn.Module):
    def __init__(self, ngf, nz):
        super(NetG, self).__init__()
        # layer1输入的是一个100x1x1的随机噪声, 输出尺寸(ngf*8)x4x4
        self.layer1 = nn.Sequential(
            nn.ConvTranspose2d(nz, ngf * 8, kernel_size=4, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(ngf * 8),
            nn.ReLU(inplace=True)
        )
        # layer2输出尺寸(ngf*4)x8x8
        self.layer2 = nn.Sequential(
            nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf * 4),
            nn.ReLU(inplace=True)
        )
        # layer3输出尺寸(ngf*2)x16x16
        self.layer3 = nn.Sequential(
            nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf * 2),
            nn.ReLU(inplace=True)
        )
        # layer4输出尺寸(ngf)x32x32
        self.layer4 = nn.Sequential(
            nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf),
            nn.ReLU(inplace=True)
        )
        # layer5输出尺寸 3x96x96
        self.layer5 = nn.Sequential(
            nn.ConvTranspose2d(ngf, 3, 5, 3, 1, bias=False),
            nn.Tanh()
        )

    # 定义NetG的前向传播
    def forward(self, x):
        out = self.layer1(x)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        out = self.layer5(out)
        return out


# 定义鉴别器网络D
class NetD(nn.Module):
    def __init__(self, ndf):
        super(NetD, self).__init__()
        # layer1 输入 3 x 96 x 96, 输出 (ndf) x 32 x 32
        self.layer1 = nn.Sequential(
            nn.Conv2d(3, ndf, kernel_size=5, stride=3, padding=1, bias=False),
            nn.BatchNorm2d(ndf),
            nn.LeakyReLU(0.2, inplace=True)
        )
        # layer2 输出 (ndf*2) x 16 x 16
        self.layer2 = nn.Sequential(
            nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 2),
            nn.LeakyReLU(0.2, inplace=True)
        )
        # layer3 输出 (ndf*4) x 8 x 8
        self.layer3 = nn.Sequential(
            nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 4),
            nn.LeakyReLU(0.2, inplace=True)
        )
        # layer4 输出 (ndf*8) x 4 x 4
        self.layer4 = nn.Sequential(
            nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 8),
            nn.LeakyReLU(0.2, inplace=True)
        )
        # layer5 输出一个数(概率)
        self.layer5 = nn.Sequential(
            nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False),
            nn.Sigmoid()
        )

    # 定义NetD的前向传播
    def forward(self ,x):
        out = self.layer1(x)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        out = self.layer5(out)
        return out

4. 结果:

训练1次:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oald9OA2-1639442662174)(https://wang-1304725667.cos.ap-chengdu.myqcloud.com/mardown/20210131191944.png)]

训练50次:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CvxNhYMg-1639442662176)(https://wang-1304725667.cos.ap-chengdu.myqcloud.com/mardown/fake_samples_epoch_049.png)]

训练100次:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YydFTND5-1639442662179)(https://wang-1304725667.cos.ap-chengdu.myqcloud.com/mardown/fake_samples_epoch_099.png)]

5. 附录:

5.1 超参数书写标准——Argparse

我们在写参数的时候,经常是这种情况:

lr = 0.01
batch_size = 64

但是,这样写就出现种情况——不美观、不规范!

为了让代码更加美观,我们使用更加规整的class

class opt(object):
    lr = 0.01
	batch_size = 64

这样的话,已经做到的初步的美观。但是不符合class的思想!

因此,我们使用Argparse进行表示:

书 写 格 式 : − − 参 数 名 称 , t y p e = 数 据 类 型 , d e f a u l t = 默 认 值 \color{red} {书写格式:--参数名称, type=数据类型, default=默认值} :,type=,default=

parser = argparse.ArgumentParser()
parser.add_argument('--batchSize', type=int, default=64)
parser.add_argument('--imageSize', type=int, default=96)
parser.add_argument('--nz', type=int, default=100, help='size of the latent z vector')
parser.add_argument('--ngf', type=int, default=64)
parser.add_argument('--ndf', type=int, default=64)
parser.add_argument('--epoch', type=int, default=25, help='number of epochs to train for')
parser.add_argument('--lr', type=float, default=0.0002, help='learning rate, default=0.0002')
parser.add_argument('--beta1', type=float, default=0.5, help='beta1 for adam. default=0.5')
parser.add_argument('--data_path', default='data/', help='folder to train data')
parser.add_argument('--outf', default='imgs/', help='folder to output images and model checkpoints')
opt = parser.parse_args()

当然,这个还是有更多好处的。比如随时可以调参:

  1. 打开【Edit Run Configuration】

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jIv22zl7-1639442662183)(https://wang-1304725667.cos.ap-chengdu.myqcloud.com/mardown/20210131173007.png)]

  1. 【Edit Run Configuration】展示,并且把加入的参数名称和参数放到这个里面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SqDACltQ-1639442662185)(https://wang-1304725667.cos.ap-chengdu.myqcloud.com/mardown/20210131173224.png)]

  1. 修改参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b0kpiZCm-1639442662189)(https://wang-1304725667.cos.ap-chengdu.myqcloud.com/mardown/20210131172319.png)]

5.2 BCELoss

这个是专门给GANloss函数

5.3 参考文献

https://www.pianshen.com/article/1532910235/

https://zhuanlan.zhihu.com/p/24767059

https://blog.csdn.net/qq_22210253/article/details/85222093

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值