DCGAN
by
본 논문은 여기 에서 확인하실 수 있습니다.
여기를 참고했습니다.
about DCGAN
DCGAN은, 기존 GAN의 단점을 보완하여 더욱 성능이 높아진 네트워크입니다.
다음 네 가지 항목들을 통해, 성능을 높였습니다.
- 
    GAN의 구조에 대한 제약 조건들을 제안하여, 안정적으로 훈련이 진행되도록 한다. 
- 
    Trained된 discriminator를 사용했다. 
- 
    Intermediate 부분의 filter들을 visualize함을 통해, 특정 필터가 특정 object를 어떻게 생기게 했나를 보여주었다. 
- 
    G 에 vector arithmetic 특성을 주어, 생성된 샘플의 sementic representation을 조작할 수 있음을 보여준다. 
DCGAN의 구조
- 
    maxpooling 대신 strided convoution을 사용, FCN 대신 global average pooling 사용 
- 
    Batch norm 사용(결과가 통일되는 model collapse 방지, initialization, gradient flow가 더 잘되도록 함) 
DCGAN의 훈련 detail
Global average pooling 설명: https://gaussian37.github.io/dl-concept-global_average_pooling/
Transconvolution2D
참고한 곳 :https://wh00300.tistory.com/150
https://hyeongminlee.github.io/post/gan003_dcgan/
https://simonjisu.github.io/python/2019/10/27/convtranspose2d.html
Transconvolution2D은 conv 연산의 반대 개념이다.
자세한 개념은 위 블로그들에 기술되어 있고, 사이즈만 위주로 확인해 보겠다.
(channel , width, height)기준으로,
Conv 연산을 할 때 input이 (1024, 2, 2), padding = 2, stride = 1, kernel size = 3이면,
output은, (1024, 4, 4)이다.
이 때 위 식에 의해 p’ = (k - p - 1) = 0 이다.
s’를 1 이 아닌 2라고 했을 때, transposed Conv의 output size는 다음과 같다.
Output = (Input -1) * stride - (2 * padding) + filter + outer padding
(이 때 padding에는 p’가 들어가는게 핵심이다.)
따라서 적용해보면 5 = (2 - 1) * 2 - (2 * 0) + 3 이다.
output size는 고로 (1024, 5, 5) 이다.
참고 : ConvTranspose2d(a, b, c, d, e)라고 했을 때 각각의 인자를 설명해드리자면, a는 입력으로 들어오는 채널의 수, b는 만들어지는 결과값의 채널 수, c는 커널의 크기, 즉 Convolution 연산을 수행하는 필터의 크기, d는 stride, e는 padding
이 e(padding)은 p'로 이미 전처리 된 값이다.
CODE
import torch
import torch.nn as nn  #G, D의 architecture를 정의하기 위해서 불러옴
from torchvision.datasets import MNIST
#from torchvision import datasets  #Mnist dataset을 불러오기 위해
import torchvision.transforms as transforms  #불러온 dataset을 전처리하기 위해
from torchvision.utils import save_image  #학습이 진행되는 과정에서 반복적으로 생성된 이미지 출력 위해
latent_dim = 100
class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()  #nn.module의 __init__ 사용하기 위해
        self.model = nn.Sequential(
        nn.ConvTranspose2d(latent_dim, 512, 4, 1, 0, bias = False),
        #(in channel, out, kernel, stride, padding)
        nn.BatchNorm2d(512),
        nn.ReLU(),
        #(512, 4, 4)
            
        nn.ConvTranspose2d(512, 256, 4, 2, 1, bias = False),
        nn.BatchNorm2d(256),    
        nn.ReLU(),
        #(256, 8, 8)
            
        nn.ConvTranspose2d(256, 128, 4, 2, 1, bias = False),
        nn.BatchNorm2d(128),
        nn.ReLU(),
        #(128, 16, 16)
        
        nn.ConvTranspose2d(128, 64, 4, 2, 1, bias = False),
        nn.BatchNorm2d(64),
        nn.ReLU(),
        #(64, 32, 32)
            
        nn.ConvTranspose2d(64, 3, 4, 2, 1, bias = False),
        nn.Tanh(),
        #(3, 64, 64)
        )
        #output size = (3)
    def forward(self, z):
        img = self.model(z)  #noise z를 모델에 넣음
        #img = img.view(img.size(0), 1, 28, 28)  #(batch size, 채널, 높이, 너비)를 주어 이미지 형태로 만듦
        return img
class Discriminator(nn.Module):  #이미지를 받아 확률값으로
    def __init__(self):
        super(Discriminator, self).__init__()
        
        #input channel, output chanel만 들어감. 실제 input 형태 : (3 * 64 * 64)
        self.model = nn.Sequential(
        nn.Conv2d(3, 64, 4, 2, 1, bias = False), # size = (3 * 64 * 64) -> (64, 32, 32)    
        #인자 : (in channel, out, kernel, stride, [adding, blas)
        #inplace를 쓴다 -> input data 자체를 수정 -> 메모리 효율 증가 하지만 input 정보 손실
        nn.LeakyReLU(0.2, inplace = True),
               
        nn.Conv2d(64, 128, 4, 2, 1, bias = False),
        nn.BatchNorm2d(128),
        nn.LeakyReLU(0.2, inplace = True),
        #(128, 16, 16)   
        
        nn.Conv2d(128, 256, 4, 2, 1, bias = False),
        nn.BatchNorm2d(256),
        nn.LeakyReLU(0.2, inplace = True),
        #(128, 32, 32)
            
        nn.Conv2d(256, 512, 4, 2, 1, bias = False),
        nn.BatchNorm2d(512),
        nn.LeakyReLU(0.2, inplace = True),
            
        nn.Conv2d(512, 1, 4, 1, 0, bias = False),
        nn.Sigmoid(),
        #(512, 1, 1)
        )
    def forward(self, img):  #한 장의 이미지가 들어왔을 떄 flatten 하여 쭉 나열하고, 모델에 넣음
#         flattened = img.view(img.size(0), -1)
#         output = self.model(flattened)
        output= self.model(img)
        result = output.view(-1, 1).squeeze(1)
        
        return result
def weights_init(m):
    classname = m.__class__.__name__ 
    if classname.find('Conv') != -1:         # Conv weight init
        m.weight.data.normal_(0.0, 0.02) #(평균 0.0 표준편차 0.02에서 data 뽑음)
    elif classname.find('BatchNorm') != -1:  # BatchNorm weight init
        m.weight.data.normal_(1.0, 0.02)
        m.bias.data.fill_(0)
import torchvision.datasets as dsets
transform = transforms.Compose([
        transforms.Scale(64),
        transforms.ToTensor(),                     
        transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))
])
train_dataset = dsets.CIFAR10(root='./data/', train=True, download=True, transform=transform)
dataloader = torch.utils.data.DataLoader(train_dataset, batch_size= 128, shuffle=True)
from torch.autograd import Variable
generator = Generator()
generator.cuda()
discriminator = Discriminator()
discriminator.cuda()
loss = nn.BCELoss()
loss.cuda()
lr = 0.0002
optimizer_D =torch.optim.Adam(discriminator.parameters(), lr=lr, betas=(0.5, 0.999))
optimizer_G = torch.optim.Adam(generator.parameters(), lr=lr, betas=(0.5, 0.999))
import time
EPOCHS = 200
sample_interval = 2000
start_time = time.time()
for epoch in range(EPOCHS):
    for i, (imgs, _) in enumerate(dataloader):
        # 진짜(real) 이미지와 가짜(fake) 이미지에 대한 정답 레이블 생성
        real = torch.cuda.FloatTensor(imgs.size(0), 1).fill_(1.0) # 진짜(real): 1
        fake = torch.cuda.FloatTensor(imgs.size(0), 1).fill_(0.0) # 가짜(fake): 0
        real_imgs = imgs.cuda()
        """ 생성자(generator)를 학습합니다. """
        optimizer_G.zero_grad()
        # 랜덤 노이즈(noise) 샘플링
        z = torch.normal(mean=0, std=1, size=(imgs.shape[0], latent_dim, 1, 1)).cuda()
        
        # 이미지 생성
        generated_imgs = generator(z)
        # 생성자(generator)의 손실(loss) 값 계산
        g_loss = loss(discriminator(generated_imgs), real)
        # 생성자(generator) 업데이트
        g_loss.backward()
        optimizer_G.step()
        """ 판별자(discriminator)를 학습합니다. """
        optimizer_D.zero_grad()
        # 판별자(discriminator)의 손실(loss) 값 계산
        real_loss = loss(discriminator(real_imgs), real)
        fake_loss = loss(discriminator(generated_imgs.detach()), fake)
        d_loss = (real_loss + fake_loss) / 2
        # 판별자(discriminator) 업데이트
        d_loss.backward()
        optimizer_D.step()
        done = epoch * len(dataloader) + i
        if done % sample_interval == 0:
            # 생성된 이미지 중에서 25개만 선택하여 5 X 5 격자 이미지에 출력
            save_image(generated_imgs.data[:25], f"{done}.png", nrow=5, normalize=True)
        
    print(f"[Epoch {epoch}/{EPOCHS}] [D loss : {d_loss.item(): .6f}] [G loss : {g_loss.item():.6f}] [Elapsed time: {time.time() - start_time:.2f}s]")
결과
50/200 에포크 (아직 학습 중)
Subscribe via RSS
