本教程将通过一个示例介绍DCGANs。我们将训练一个生成对抗网络(GAN),在训练阶段输入许多真实名人的照片,训练结束的时候该网络能够生成新的名人照片。这里的大部分代码来自github上pytorch/examples中的dcgan实现,本文将对该实现进行全面的解释,并阐明该模型如何工作以及工作原理。但是不要担心,GANs并不需要预先的知识,但是它可能需要新手花一些时间来理解在幕后到底发生了什么。另外,由于训练比较耗时,有一个或两个GPU将有助于节约时间。让我们从头开始吧!
GANs是生成式模型的一种,利用该框架可以使我们的DL模型捕获到训练集的分布,从而可以从相同的分布中生成新的数据。很明显GANs是从无标记的数据中得到数据的分布规律,所以也可以视为无监督学习中的一类。GANs是由Ian Goodfellow在2014年发明的,并在论文Generative Adversarial Nets 中首次描述。它们由两个不同的模型组成,一个是生成器(generator),一个是判别器(discriminator)。生成器的工作是生成看起来像训练图像的假图像。判别器的工作是查看图像并输出它是否是来自训练集的真实图像或有生成器产生的虚假图像。在训练过程中,生成器不断地试图通过生成越来越好的赝品来迷惑判别器,判别器也在努力成为一名更好的侦探,正确地对真伪图像进行分类。这个游戏的平衡是当生成器生成看起来像是直接来自训练数据的完全伪造图片,鉴别器总是以50%的置信度猜测生成器输出是真的还是假的。
现在,让我们从判别器开始定义一些贯穿教程的符号。
: 输入数据, 这里是HWC大小为3x64x64的RGB图像。
: 判别器网络,输出来自训练数据而不是生成器的(标量)概率, 当来自训练数据时,应该是高的,当来自生成器时,应该是低的, 也可以看作是传统的二进制分类器。
: 隐空间上的向量,它是从一个从标准正态分布采样而来。
: 生成器网络,将潜在向量映到数据空间,G的目标是估计训练数据的分布(),这样它就可以从估计的分布()中生成假样本。
: 判定器判定生成器网络G输出的图片是真实图片的(标量)概率。
正如GAN作者Goodfellow在其论文中描述的一样,判别器D和生成器G在玩一个最小-最大(minimax)博弈游戏。判别器D试图最大化正确分类真伪的概率(),生成器G试图最小化判别器D预测其输出图片为假图的概率()。论文中GAN的损失函数(loss function)定义为:
理论上,这个极小极大博弈的解是,判别器随机猜测输入是真还是假。然而,GANs的收敛理论仍在积极研究中,现实中模型并不总是训练到这一点。
DCGAN是上述GAN的直接扩展,只是它分别在判别器和生成器中显式地使用卷积层和转置卷积层。Radford等人首先在论文Unsupervised Representation Learning With Deep Convolutional Generative Adversarial Networks中描述了该方法。判别器由strided convolution 层, batch norm 层, and LeakyReLU 激活函数组成。输入是一个3x64x64的输入图像,输出是一个标量概率,输入来自真实的数据分布。生成器由convolutional-transpose 层、batch norm 层和 ReLU激活函数组成。输入是一个隐向量,它是从一个标准正态分布中采样得到的,输出是一个3x64x64 RGB图像。strided conv-transpose 层允许将潜在矢量转换成与图像形状相同的体积。在本文中,作者还提供了一些关于如何设置优化器,如何计算损失函数,以及如何初始化模型权重的技巧,这些都将在接下来的章节中进行解释。
from __future__ import print_function
#%matplotlib inline
import argparse
import os
import random
import torch
import torch.nn as nn
import torch.nn.parallel
import torch.backends.cudnn as cudnn
import torch.optim as optim
import torch.utils.data
import torchvision.datasets as dset
import torchvision.transforms as transforms
import torchvision.utils as vutils
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from IPython.display import HTML
# 设定随机种子,确保结果可以复现
manualSeed = 999
#manualSeed = random.randint(1, 10000) # 随机生成随机种子,每次运行可产生新的结果
print("Random Seed: ", manualSeed)
random.seed(manualSeed)
torch.manual_seed(manualSeed)
变量名 | 含义 |
---|---|
dataroot | 数据集文件夹根目录的路径,在下一节中,我们将更多地讨论数据集 |
worker | 使用DataLoader加载数据的工作线程数目 |
batch_size | 训练时数据批处理大小,DCGAN论文中使用的batch size大小为128 |
image_size | 训练图像的空间大小,这里默认实现为64x64。如果需要另一种尺寸,则必须更改D和G的结构。 |
nc | 输入图像中颜色通道的数量, RGB的彩色图像值为3 |
nz | 隐向量的长度 |
ngf | 通过生成器后faeature maps的深度 |
ndf | 设置通过判别器传播的feature maps的深度 |
num_epochs | 训练的轮数。训练的轮数越多,更有可能得到更好的结果,但是会花费更多的时间 |
lr | 训练时的学习率。DCGAN论文中使用0.0002 |
beta1 | Adam优化器的beta1超参数,论文中设置为0.5 |
ngpu | 可用GPU的个数。如果值为0则模型运行再CPU上,如果大于0,则会运行在ngpu个GPU上 |
# Root directory for dataset
dataroot = "data/celeba"
# Number of workers for dataloader
workers = 2
# Batch size during training
batch_size = 128
# Spatial size of training images. All images will be resized to this
# size using a transformer.
image_size = 64
# Number of channels in the training images. For color images this is 3
nc = 3
# Size of z latent vector (i.e. size of generator input)
nz = 100
# Size of feature maps in generator
ngf = 64
# Size of feature maps in discriminator
ndf = 64
# Number of training epochs
num_epochs = 5
# Learning rate for optimizers
lr = 0.0002
# Beta1 hyperparam for Adam optimizers
beta1 = 0.5
# Number of GPUs available. Use 0 for CPU mode.
ngpu = 1
在本教程中,我们将使用Celeb-A Faces数据集,该数据集可以从Google Dirve中下载, 当然也可用在网上搜索百度网盘资源下载可能更快。这个数据集将以名为 img_align_celeba.zip的文件被下载。然后设置dataroot 目录为刚才创建的celeba目录。最后的结果目录结构应该为:
/data/celeba/
-> 188242.jpg
-> 173822.jpg
-> 284702.jpg
-> 537394.jpg
...
这是一个重要的步骤,因为我们将使用ImageFolder dataset类,它要求在数据集的根文件夹中有子目录。现在,我们可以创建数据集、创建dataloader、设置模型运行的device,并最终可视化一些训练数据。
# We can use an image folder dataset the way we have it setup.
# Create the dataset
dataset = dset.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)),
]))
# Create the dataloader
dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size,
shuffle=True, num_workers=workers)
# Decide which device we want to run on
device = torch.device("cuda:0" if (torch.cuda.is_available() and ngpu > 0) else "cpu")
# Plot some training images
real_batch = next(iter(dataloader))
plt.figure(figsize=(8,8))
plt.axis("off")
plt.title("Training Images")
plt.imshow(np.transpose(vutils.make_grid(real_batch[0].to(device)[:64], padding=2, normalize=True).cpu(),(1,2,0)))