소스코드 원본 링크 : https://pytorch.org/tutorials/beginner/dcgan_faces_tutorial.html
코드 구현 전 꼭 읽어봐야 할 링크 : https://dreamgonfly.github.io/2018/03/17/gan-explained.html
Introduction
이번 튜토리얼은 한글로 번역된 버전이 아직 없지만 굉장히 hot한 주제라서 Pytorch tutorial 글을 직접 번역 하기로 했다.
DCGAN에 대해 소개하도록 하겠다. 많은 유명 인사들의 사진을 보여주고 새로운 유명 인사를 만들기 위해 GAN(Generative Adversarial Network) 을 사용할 것이다. 이번 글은 GAN에 대한 사전 지식은 필요하지 않지만 어떻게 동작하는지 추측하는 데 많은 시간을 필요로 한다. 이 튜토리얼은 GPU 1개 또는 2개를 사용할 것을 권장한다.
Generative Adversarial Networks
What is a GAN?
GAN은 학습 데이터의 분포를 capture 하여 동일한 분포에서 새로운 데이터를 생성할 수 있도록 DL 모델을 학습시키는 프레임 워크이다. GAN은 Generator와 Discriminator 라는 두 개의 별개의 모델로 구성된다. Generator의 역할은 학습 이미지처럼 보이는 ‘가짜’ 이미지를 생성하는 것이다. Discriminator의 역할은 이미지를 보고 그것이 실제 이미지인지 Generator로 부터 온 fake 이미지 인지 결정하는 것이다. 논문을 보면 Generator를 위조 지폐기 Discriminator를 형사라고 재밌게 비유 하였다. 학습을 하는 동안 Generator는 진짜와 구별이 힘들게 만들 더 나은 가짜 이미지를 생성하여 Discriminator를 속이려고 한다. 그러는 동안 Discriminator는 더 나은 형사가 되려고 노력하면서 진짜와 가짜 이미지를 올바르게 구분하려고 노력한다. 이것이 Generator를 위조 지폐기, Discriminator를 형사라고 비유한 이유이다.
이 과정에서 평형(equilibrium)은 Generator가 학습 데이터에서 직접 온 것처럼 보이는 완벽한 가짜를 생성하는 경우이며, 이 경우 Discriminator는 Generator의 이미지가 실제 인지 가짜인지를 50%로 신뢰한다고 추측한다.
이제 Discriminator로 시작하여 튜토리얼 전체에 걸쳐 사용되는 표기법에 대해 정의하자. x를 이미지 데이터라고 하자. D(x)는 x가 Generator가 아닌 학습 데이터에서 나온 스칼라 확률을 출력하는 Discriminator 네트워크이다. 여기서는 이미지를 다루기 때문에 D(x)에 대한 입력은 HWC 크기 3x64x64의 이미지 이다. 직관적으로 D(x)는 x가 학습 데이터에서 오면 High가 되어야 하고, x가 생성기에서 오면 Low가 되어야 한다. D(x)는 또한 전통적인 binary classifier 라고 생각할 수 있다.
Generator의 표기법을 위해 z를 표준 정규 분포로부터 샘플링 된 잠재적 공간 벡터라고 하자. G(z)는 잠재적 공간 벡터 z를 데이터 공간에 매핑하는 Generator 함수이다. G의 목표는 학습 데이터가 나오는 분포 pdata를 추정하여 추정된 분포 pg로 부터 가짜 샘플을 생성할 수 있도록 하는 것이다.
D(G(z))는 Generator G의 출력이 실제 이미지일 스칼라 확률 이다. Goodfellow 의 논문에서 설명된 바와 같이 D와 G는 minimax 게임을 한다. 이 minimax 게임에서 D는 진짜와 가짜를 정확하게 분류 할 확률 log(D(x)) 를 최대화하려고 G는 D가 가짜임을 출력할 확률 log(1-D(G(x))를 최소화하려고 한다. 논문에 의하면, GAN의 손실 함수는 다음과 같다.
이론상으로는 minimax 게임에 대한 솔루션은 pg = pdata이고, 입력이 진짜이거나 가짜인 경우 Discriminator는 무작위로 추측한다.
What is a DCGAN ?
DCGAN은 Discriminator와 Generator 에서 각각 convolutional 과 convolutional-transpose layers를 명시적으로 사용한다는 것을 제외하고는 위에서 설명한 GAN의 직접적인 확장이다.
Discriminator는 strided convolution layers, batch norm layers 및 LeakyReLu activation 으로 구성된다. 입력은 3x64x64 사이즈의 이미지이고 출력은 입력이 실제 데이터의 분포에서 왔을 스칼라 확률이다.
Generator는 convolutional-transpose layers, batch norm layers 및 ReLu activation으로 구성된다. 입력은 표준 정규 분포에서 그려지는 잠재적 벡터 z이며 출력은 3x64x64 RGB 이미지다. strided conv-transpose layers는 잠재적인 벡터가 이미지와 동일한 모양(same shape)을 가진 볼륨이 되도록 변형시켜준다. 논문에서 저자는 optimizer를 설정하는 방법, 손실 함수를 계산하는 방법 및 모델 가중치를 초기화하는 방법에 대한 몇 가지 tip을 제공한다. 이 모든 내용은 다음 섹션에서 설명하겠다.
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
# Set random seem for reproducibility
manualSeed = 999
#manualSeed = random.randint(1, 10000) # use if you want new results
print("Random Seed: ", manualSeed)
random.seed(manualSeed)
torch.manual_seed(manualSeed)
Inputs
실행을 위한 몇가지 입력 변수를 정의하자.
- dataroot - the path to the root of the dataset folder. We will talk more about the dataset in the next section
- workers - the number of worker threads for loading the data with the DataLoader
- batch_size - the batch size used in training. The DCGAN paper uses a batch size of 128
- image_size - the spatial size of the images used for training. This implementation defaults to 64x64. If another size is desired, the structures of D and G must be changed. See here for more details
- nc - number of color channels in the input images. For color images this is 3
- nz - length of latent vector
- ngf - relates to the depth of feature maps carried through the generator
- ndf - sets the depth of feature maps propagated through the discriminator
- num_epochs - number of training epochs to run. Training for longer will probably lead to better results but will also take much longer
- lr - learning rate for training. As described in the DCGAN paper, this number should be 0.0002
- beta1 - beta1 hyperparameter for Adam optimizers. As described in paper, this number should be 0.5
- ngpu - number of GPUs available. If this is 0, code will run in CPU mode. If this number is greater than 0 it will run on that number of GPUs
# 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
Data
결과 디렉토리 구조는 다음과 같다.
/path/to/celeba
-> img_align_celeba
-> 188242.jpg
-> 173822.jpg
-> 284702.jpg
-> 537394.jpg
...
ImageFolder 데이터셋 클래스 를 사용하기 때문에 데이터셋 집합의 루트 폴더에 하위 디렉토리가 있어야 한다. 이제 데이터셋을 만들고 데이터 로더를 만들고 실행할 장치를 설정하고 학습 데이터의 일부를 시각화 할 수 있다.
# 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)))