第十一课.DCGAN与CycleGAN

DCGAN

DCGAN简介

DCGAN是基于DeConvlution的GAN,DeConvlution即反卷积,反卷积 DeConv 又叫 Fractional Strided Conv ,或者转置卷积 TransposeConv(更规范的表述),计算操作回顾第九课反卷积和反池化部分;
DCGAN的训练过程和GAN是一样的,只是数据从mnist变得更复杂,所以基于卷积网络进行特征提取作为判别器,基于反卷积作为生成器输出伪造图像;
论文中,生成器从100维向量作为隐式空间,生成 ( 3 , 64 , 64 ) (3,64,64) (3,64,64)的图像,生成器网络如下:
fig1
下面将会使用DCGAN生成celebA,注意,实验中的生成器与上图的结构不一样

DCGAN生成celebA

celebA介绍

CelebA是CelebFaces Attribute的缩写,意即名人人脸属性数据集,其包含10,177个名人身份的202,599张人脸图片,每张图片都做好了特征标记,CelebA由香港中文大学开放提供,广泛用于人脸相关的计算机视觉训练任务,官方网址:Large-scale CelebFaces Attributes (CelebA) Dataset
fig2下载后有如下目录:
fig3
Anno存放标注信息文件,Eval是training、validation及testing数据集的划分注释,Img则是存放相应的人脸图像,README.txt是CelebA介绍文件;
在Img下有以下目录:
fig4
本次实验的DCGAN生成celebA,只使用img_align_celeba.zip

准备工作

首先导入必要的包和模块:

import torchvision.utils as vutils
import torchvision
from torchvision import transforms

import torch
import torch.nn as nn

import numpy as np
import matplotlib.pyplot as plt

device=torch.device("cuda" if torch.cuda.is_available() else "cpu")

基于ImageFolder加载数据集:

image_size=64
batch_size=128
dataroot="./data"
"""
目录结构:
data
	img_align_celeba
		图片1
		图片2
		...
DCGAN脚本
"""
num_workers = 0

dataset = torchvision.datasets.ImageFolder(root=dataroot, transform=transforms.Compose([
    transforms.Resize(image_size),
    transforms.CenterCrop(image_size),
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
]))

ImageFolder
一般用法为:

dataset = torchvision.datasets.ImageFolder(root,transform=None)

torchvision.datasets.ImageFolder 要求数据集按照如下方式组织:

# 第一类
root/dog/xxx.png
root/dog/xxy.png
root/dog/xxz.png
# 第二类
root/cat/123.png
root/cat/nsdf3.png
root/cat/asd932_.png
...

根目录 root 下存储的是类别文件夹(如cat,dog),每个类别文件夹下存储对应类别的图像;
ImageFolder的参数有:

  • root:图片存储的根目录,即各类别文件夹所在目录的上一级目录,在上面的例子中是'./root'
  • transform:对图片进行预处理的操作

读取目录成功后,各个类别的标签依次对应为 0,1, 2,…
ImageFolder有以下实例变量:

  • self.classes:用一个 list 保存类别名称,["dog","cat"]
  • self.class_to_idx:类别的标签,字典保存,{"dog":0,"cat":1}
  • self.imgs:一个列表,每个元素为元组(图片路径,标签),{img path,0}

使用dataloader:

dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True, num_workers=num_workers)

基于dataloader获取一个batch数据:

real_batch=next(iter(dataloader))

# real_batch[0]保存图像张量,real_batch[1]保存每个图像的类别

# real_batch[0] (batchsize,channel,height,width)
# real_batch[1] (batchsize,)

# 取出一个图像张量
print(real_batch[0][0])

使用torchvision.utils.make_grid将多幅图像可视化到grid中,从batch中获取了64张图像,可视化到 8 × 8 8 \times 8 8×8的网格里,注意plt.inshow(),接收数组或PIL图像:

plt.figure(figsize=(8,8))
plt.title("Training Images")

plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:64], padding=2, normalize=True).cpu().numpy(), (1,2,0)))
plt.show()

fig5


torchvision.utils.make_grid
用于生成一个图像组成的网格,常用参数有:

torchvision.utils.make_grid(tensor, padding=2, normalize=False, range=None)
  • tensor:一个4维的张量,比如形状为 ( B , C , H , W ) (B,C,H,W) (B,C,H,W)
  • padding:每个子图周围的零填充(黑色)
  • normalize:如果为True,将网格中所有图像的值缩放到参数range之间,如果为False,则不缩放
  • range: 元组(min, max),默认为该网格对应张量的像素最小值与像素最大值

模型定义

在论文中,提到了一个初始化技巧,将有助于训练:

  • 把模型的所有卷积网络参数都初始化为mean=0, std=0.02
  • BatchNorm的可学习参数初始化为mean=1.0, std=0.02

定义初始化参数的函数:

def weights_init(m):
	# 获取对象m所属类的类名
    classname = m.__class__.__name__
    # 如果Conv在classname中出现了,进行以下初始化
    if classname.find('Conv') != -1:
        nn.init.normal_(m.weight.data, 0.0, 0.02)

    # 如果BatchNorm在classname中出现了,进行以下初始化
    elif classname.find('BatchNorm') != -1:
        nn.init.normal_(m.weight.data, 1.0, 0.02)

        # torch.nn.init.constant_(tensor, val) 向tensor填充值val
        nn.init.constant_(m.bias.data, 0)

定义生成网络,注意一个细节,由于之后输入判别器的真实图像(即真实图像预处理后的张量)值在-1到1之间,所以用tanh作为激活函数,nn.ConvTranspose2d的使用方法回顾第九课nn.BatchNorm2d的使用方法回顾第七课

nz = 100 # latent vector的大小
ngf = 64 # 生成器各层滤波器基数
ndf = 64 # 判别器各层滤波器基数
nc = 3 # 生成器输出张量的通道数

class Generator(nn.Module):
    def __init__(self):
        super().__init__()
        self.main=nn.Sequential(
            # torch.nn.ConvTranspose2d(in_channels, out_channels,kernel_size, stride=1, padding=0, bias=True, dilation=1)

            # input (nz) x 1 x 1

            nn.ConvTranspose2d(nz, ngf * 8, 4, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(ngf * 8),
            nn.ReLU(),
            # state size. (ngf*8) x 4 x 4
            nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(ngf * 4),
            nn.ReLU(),
            # state size. (ngf*4) x 8 x 8
            nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(ngf * 2),
            nn.ReLU(),
            # state size. (ngf*2) x 16 x 16
            nn.ConvTranspose2d(ngf * 2, ngf, 4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(ngf),
            nn.ReLU(),
            # state size. (ngf) x 32 x 32

            nn.ConvTranspose2d(ngf, nc, 4, stride=2, padding=1, bias=False),
            # 由于输入判别器的真实图像(真实图像预处理后的张量)值在-1到1之间,所以用tanh作激活函数
            nn.Tanh()
            # state size. (nc) x 64 x 64
        )

    def forward(self,x):
        return self.main(x)

实例化生成器,并初始化其参数:

netg=Generator().to(device)
print(netg)

# nn.Module.apply将weights_init函数递归地应用到netg的每一个子模型
netg.apply(weights_init)

nn.Module.apply

apply(fn)

参数:

  • fn (m)

model.apply函数可以不断遍历model的各个模块m,并在模块m上应用函数fn


定义判别网络,并实例化网络,初始化参数:

class Discriminator(nn.Module):
    def __init__(self):
        super().__init__()
        self.main=nn.Sequential(
            # input is (nc) x 64 x 64
            nn.Conv2d(nc, ndf, 4, stride=2, padding=1, bias=False),
            nn.LeakyReLU(0.2),
            # state size. (ndf) x 32 x 32
            nn.Conv2d(ndf, ndf * 2, 4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(ndf * 2),
            nn.LeakyReLU(0.2),
            # state size. (ndf*2) x 16 x 16
            nn.Conv2d(ndf * 2, ndf * 4, 4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(ndf * 4),
            nn.LeakyReLU(0.2),
            # state size. (ndf*4) x 8 x 8
            nn.Conv2d(ndf * 4, ndf * 8, 4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(ndf * 8),
            nn.LeakyReLU(0.2),
            # state size. (ndf*8) x 4 x 4
            nn.Conv2d(ndf * 8, 1, 4, stride=1, padding=0, bias=False),
            nn.Sigmoid()
            # state size. (1) x 1 x 1
        )

    def forward(self,x):
        return self.main(x)

netd=Discriminator().to(device)
netd.apply(weights_init)

训练生成网络与判别网络

训练过程与第十课的GAN训练过程一样,先设置训练参数,依然采用二元交叉熵计算损失,并选择Adam作为优化方法:

# 训练设置
lr = 0.0002
beta1 = 0.5

loss_fn = nn.BCELoss()

d_optimizer = torch.optim.Adam(netd.parameters(), lr=lr, betas=(beta1, 0.999))
g_optimizer = torch.optim.Adam(netg.parameters(), lr=lr, betas=(beta1, 0.999))

训练网络:

# 训练
total_steps = len(dataloader)
num_epochs = 5

for epoch in range(num_epochs):
    for i, (images, _) in enumerate(dataloader):
        # iamges (batch_size,c,h,w)
        batch_size=images.shape[0]
        images = images.to(device)

        real_labels = torch.ones(batch_size).to(device) # (batch_size,)
        fake_labels = torch.zeros(batch_size).to(device) # (batch_size,)

        real_outputs = netd(images).view(-1) # (batch_size,)

        # 真实图片的损失
        d_loss_real = loss_fn(real_outputs, real_labels)

        # 生成 fake images
        # latent space
        z = torch.randn(batch_size, nz, 1, 1).to(device) # (batch_size,100,1,1)
        fake_images = netg(z) # (batch_size,3,64,64)

        # 将G的输出从计算图中剥离,避免在训练D时涉及到G的梯度
        fake_outputs = netd(fake_images.detach()).view(-1) # (batch_size,)

        d_loss_fake = loss_fn(fake_outputs, fake_labels)

        d_loss = d_loss_real + d_loss_fake

        # 更新D
        d_optimizer.zero_grad()
        d_loss.backward()
        d_optimizer.step()

        # 更新G
        # 不能detach,因为要追踪G的梯度
        g_outputs = netd(fake_images).view(-1) # (batch_size,)
        g_loss = loss_fn(g_outputs, real_labels)

        # 由于D还在计算图中,所以需要将D的梯度置0
        d_optimizer.zero_grad()
        g_optimizer.zero_grad()
        g_loss.backward()
        g_optimizer.step()

        # 一般来说 G loss 越小越好
        if i % 10 == 0:
            print("Epoch[{}/{}],Step[{}/{}],D_loss:{},G_loss:{},D(x):{},D(G(z)):{}".format(
                epoch, num_epochs, i, total_steps,
                d_loss.item(),
                g_loss.item(),
                real_outputs.mean().item(),
                fake_outputs.mean().item()
            ))

保存两个网络的参数:

torch.save(netd.state_dict(), "dcgan_d.th")
torch.save(netg.state_dict(), "dcgan_g.th")

使用生成器生成伪造图像:

# 生成器生成图片
# (64,100,1,1) nz:latent space
fixed_noise = torch.randn(64, nz, 1, 1, device=device)

# 不进行梯度跟踪,节省显存
with torch.no_grad():
    fake = netg(fixed_noise).clone().cpu() # (batch_size,3,64,64)

real_batch = next(iter(dataloader))

# Plot the real images
plt.figure(figsize=(30,30))
plt.subplot(1,2,1)
plt.title("Real Images")
plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:64], padding=5, normalize=True).cpu(),(1,2,0)))

# Plot the fake images
plt.subplot(1,2,2)
plt.title("Fake Images")
plt.imshow(np.transpose(vutils.make_grid(fake, padding=2, normalize=True), (1,2,0)))
plt.show()

fig6

CycleGAN

CycleGAN即Cycle-Consistent Adversarial Networks,也用于生成图像,但其生成的图像内容可以由人来主观进行选择,可以用于风格迁移,去除马赛克,更换人脸等其他用途;CycleGAN与常规配对训练数据的不同之处在于,CycleGAN可以实现无配对数据的训练;
fig7CycleGAN的创新点在于能够在源域和目标域之间,无须建立训练数据间一对一的映射,就可实现源到目标域的迁移;
网络的架构如下:
fig8CycleGAN网络即图 ( a ) (a) (a)所示,可以看出其实包含了两个GAN,cycle-consistency loss确保了两个分布之间的转换;
( a ) (a) (a)中,存在两个分布 X X X Y Y Y,而生成器G,F分别是 X X X Y Y Y Y Y Y X X X的映射,两个判别器Dx,Dy可以对转换后的图片进行判别;
( b ) (b) (b)中,生成器G将 X X X分布转换到 Y Y Y分布,记为 Y ^ \widehat{Y} Y ,将该分布与真实的 Y Y Y传递给判别器Dy进行训练,提高Dy对于 Y Y Y分布数据的分辨能力,单向从 X X X Y Y Y可以训练G与Dy,目标函数为:
L G A N ( G , D Y , X , Y ) = E y ∼ p d a t a ( y ) [ l o g D Y ( y ) ] + E x ∼ p d a t a ( x ) [ l o g ( 1 − D Y ( G ( x ) ) ] L_{GAN}(G,D_{Y},X,Y)=E_{y\sim p_{data}(y)}[logD_{Y}(y)]+E_{x\sim p_{data}(x)}[log(1-D_{Y}(G(x))] LGAN(G,DY,X,Y)=Eypdata(y)[logDY(y)]+Expdata(x)[log(1DY(G(x))]
其中, E x ∼ p d a t a ( x ) E_{x\sim p_{data}(x)} Expdata(x)代表从 X X X分布中采样得到的分布期望;
此时再将 Y ^ \widehat{Y} Y 用生成器F转换到 X X X分布,记为 X ^ \widehat{X} X ,现在加入cycle-consistency函数,确保还原后的 X ^ \widehat{X} X 与原始 X X X接近,使用L1损失衡量(两项之差的绝对值):
L c y c , X = E x ∼ p d a t a ( x ) [ ∣ ∣ F ( G ( x ) ) − x ∣ ∣ 1 ] L_{cyc,X}=E_{x\sim p_{data}(x)}[||F(G(x))-x||_{1}] Lcyc,X=Expdata(x)[F(G(x))x1]
同样的进行分布 Y Y Y X X X转换:
L G A N ( F , D X , X , Y ) = E x ∼ p d a t a ( x ) [ l o g D X ( x ) ] + E y ∼ p d a t a ( y ) [ l o g ( 1 − D X ( F ( y ) ) ] L_{GAN}(F,D_{X},X,Y)=E_{x\sim p_{data}(x)}[logD_{X}(x)]+E_{y\sim p_{data}(y)}[log(1-D_{X}(F(y))] LGAN(F,DX,X,Y)=Expdata(x)[logDX(x)]+Eypdata(y)[log(1DX(F(y))]
L c y c , Y = E y ∼ p d a t a ( y ) [ ∣ ∣ G ( F ( y ) ) − y ∣ ∣ 1 ] L_{cyc,Y}=E_{y\sim p_{data}(y)}[||G(F(y))-y||_{1}] Lcyc,Y=Eypdata(y)[G(F(y))y1]
总的目标函数为:
L G , F , D X , D Y = L G A N ( G , D Y , X , Y ) + L G A N ( F , D X , X , Y ) + L c y c , X + L c y c , Y L_{G,F,D_{X},D_{Y}}=L_{GAN}(G,D_{Y},X,Y)+L_{GAN}(F,D_{X},X,Y)+L_{cyc,X}+L_{cyc,Y} LG,F,DX,DY=LGAN(G,DY,X,Y)+LGAN(F,DX,X,Y)+Lcyc,X+Lcyc,Y
根据该目标优化参数,从而使CycleGAN在无配对数据集上进行学习:
m i n G , F m a x D x , D Y L G , F , D X , D Y min_{G,F}max_{D_{x},D_{Y}}L_{G,F,D_{X},D_{Y}} minG,FmaxDx,DYLG,F,DX,DY
从优化的目标公式中看出,应先训练判别器,再训练生成器;训练判别器时,应最大化目标函数,训练生成器时,应最小化目标函数;
下面是CycleGAN实现的图像迁移:
fig66fig99

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值