# -*- coding: utf-8 -*-
"""GAN for MNIST Tutorial의 사본
Automatically generated by Colaboratory.
Original file is located at
https://colab.research.google.com/drive/13chH2RWgHPgTJW5H12Oj756Eq_Js49yl
#### <b>GAN 실습</b>
* 논문 제목: Generative Adversarial Networks <b>(NIPS 2014)</b>
* 가장 기본적인 GAN 모델을 학습해보는 실습을 진행합니다.
* 학습 데이터셋: <b>MNIST</b> (1 X 28 X 28)
#### <b>필요한 라이브러리 불러오기</b>
* 실습을 위한 PyTorch 라이브러리를 불러옵니다.
"""
import torch # 머신러닝을 위한 torch 라이브러리
import torch.nn as nn # 생성자와 판별자의 아키텍쳐를 구별하기 위해
from torchvision import datasets # mnist를 불러오기 위해
import torchvision.transforms as transforms # 불러온 데이터 셋을 의도한 방향으로 변형하여 전처리하기 위해
from torchvision.utils import save_image # 학습 과정에서 반복적으로 생성된 이미지를 출력하기 위해
"""#### <b>생성자(Generator) 및 판별자(Discriminator) 모델 정의</b>"""
latent_dim = 100 # latent 벡터를 뽑기 위한 노이즈 분포의 크기
# 정규분포 이용
# 생성자(Generator) 클래스 정의
class Generator(nn.Module):
def __init__(self):
super(Generator, self).__init__() # 자식 클래스에서 부모 클래스의 내용을 사용하고 싶을 때 suoper 이용(객체지향)
# 하나의 블록(block) 정의 # 블록을 원하는 만큼 사용 가능
def block(input_dim, output_dim, normalize=True):
layers = [nn.Linear(input_dim, output_dim)] # 하나의 선형함수 거친후에
if normalize:
# 배치 정규화(batch normalization) 수행(차원 동일)
layers.append(nn.BatchNorm1d(output_dim, 0.8))
layers.append(nn.LeakyReLU(0.2, inplace=True)) # ReLU : 0 이하의 값은 다음 레이어에 전달하지 않습니다. 0이상의 값은 그대로 출력합니다. (한번 0을 전달하면 이후의 뉴런들의 출력값이 모두 0이 된다.)
return layers # -> LeakyReLU : ReLU와 거의 비슷한 형태를 갖습니다. 입력 값이 음수일 때 완만한 선형 함수를 그려줍니다. 일반적으로 알파를 0.01로 설정합니다.(ax (x <= 0))
# 생성자 모델은 연속적인 여러 개의 블록을 가짐
self.model = nn.Sequential(
*block(latent_dim, 128, normalize=False),
*block(128, 256),
*block(256, 512),
*block(512, 1024),
nn.Linear(1024, 1 * 28 * 28), # 결과적으로 1 * 1 * 28의 하나의 mnist 데이터 생성
nn.Tanh() # 탄젠트로 -1 ~ 1 사이의 값을 가지게 한다
)
def forward(self, z): # 노이즈 벡터 z가 들어왔을 때 이미지 형태로 변환 후 리턴
img = self.model(z)
img = img.view(img.size(0), 1, 28, 28)
return img
# 판별자(Discriminator) 클래스 정의
class Discriminator(nn.Module):
def __init__(self):
super(Discriminator, self).__init__()
self.model = nn.Sequential( # 판별자는 이미지를 받아와서 선형함수와 Activate Function 인 LeakyReLU 를 여러번 수행한다
nn.Linear(1 * 28 * 28, 512),
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(512, 256),
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(256, 1),
nn.Sigmoid(), # 결과적으로 Sigmoid 함수로 확률 값을 내보낼 수 있도록 한다 -> Sigmoid : 0~1로 비선형적으로 서서히 변화하는 함수(신경망 활성화 함수)
)
# 이미지에 대한 판별 결과를 반환
def forward(self, img):
flattened = img.view(img.size(0), -1) # 이미지가 들어왔을때 벡터 형태로 나열 후
output = self.model(flattened) # 모델에 넣어 결과 리턴
return output
"""#### <b>학습 데이터셋 불러오기</b>
* 학습을 위해 MNIST 데이터셋을 불러옵니다.
"""
transforms_train = transforms.Compose([
transforms.Resize(28),
transforms.ToTensor(),
transforms.Normalize([0.5], [0.5])
]) # 텐서 형태로 크기에 맞춰 만든다
# 학습 데이터를 불러와 위의 형태로 전처리
train_dataset = datasets.MNIST(root="./dataset", train=True, download=True, transform=transforms_train)
# 하나의 batch에 이미지 128개
dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=128, shuffle=True, num_workers=4)
"""#### <b>모델 학습 및 샘플링</b>
* 학습을 위해 생성자와 판별자 모델을 초기화합니다.
* 적절한 하이퍼 파라미터를 설정합니다.
"""
# 생성자(generator)와 판별자(discriminator) 초기화
generator = Generator()
discriminator = Discriminator()
# .cuda() -> GPU 이용
generator.cuda()
discriminator.cuda()
# 손실 함수(loss function)
# 손실함수 BCELoss : 마지막 레이어의 값이 0~1(시그모이드 함수 사용 필수)
# <-> CrossEntropy : 마지막 레이어의 노드 수가 2개 이상
adversarial_loss = nn.BCELoss()
adversarial_loss.cuda()
# 학습률(learning rate) 설정
lr = 0.0002
# 생성자와 판별자를 위한 최적화 함수
# 옵티마이저는 학습 데이터(Train data)셋을 이용하여 모델을 학습 할 때
# 데이터의 실제 결과와 모델이 예측한 결과를 기반으로 잘 줄일 수 있게 만들어주는 역할을 한다.
# (Optimize : 최대한 좋게 만들다)
optimizer_G = torch.optim.Adam(generator.parameters(), lr=lr, betas=(0.5, 0.999))
optimizer_D = torch.optim.Adam(discriminator.parameters(), lr=lr, betas=(0.5, 0.999))
# betas 파라미터는 가장 많이 사용되는 하이퍼 파라미터
"""* 모델을 학습하면서 주기적으로 샘플링하여 결과를 확인할 수 있습니다."""
import time
# 200번 반복
n_epochs = 200 # 학습의 횟수(epoch) 설정
sample_interval = 2000 # 몇 번의 배치(batch)마다 결과를 출력할 것인지 설정
start_time = time.time()
for epoch in range(n_epochs):
# enumerate : 몇 번째 반복인지 튜플로 저장하여 알려줌 (print(i))
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)).cuda()
# 이미지 생성
generated_imgs = generator(z)
# 생성자(generator)의 손실(loss) 값 계산
# real 이미지로 만들 수 있도록 학습
g_loss = adversarial_loss(discriminator(generated_imgs), real)
# 생성자(generator) 업데이트
g_loss.backward() # backward : 기울기 계산
optimizer_G.step() # step : 반복적으로 작업을 실행
""" 판별자(discriminator)를 학습합니다. """
optimizer_D.zero_grad()
# 판별자(discriminator)의 손실(loss) 값 계산
# 실제 이미지는 실제로 분류
real_loss = adversarial_loss(discriminator(real_imgs), real)
# 생성자의 이미지는 가짜로 분류
fake_loss = adversarial_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)
# 하나의 epoch이 끝날 때마다 로그(log) 출력
print(f"[Epoch {epoch}/{n_epochs}] [D loss: {d_loss.item():.6f}]
[G loss: {g_loss.item():.6f}] [Elapsed time: {time.time() - start_time:.2f}s]")
"""* 생성된 이미지 예시를 출력합니다."""
from IPython.display import Image
Image('92000.png')