※ 본 게시물에 사용된 내용의 출처는 대다수 <펭귄브로의 3분 딥러닝-파이토치맛>에서 사용된 자료이며, 개인적인 의견과 해석이 추가된 부분도 존재합니다. 그림의 경우 교재를 따라 그리거나, 제 임의대로 추가 수정한 부분도 존재합니다. 저 역시도 공부하고자 포스팅한 게시물이니, 잘못된 부분은 댓글로 알려주시면 감사하겠습니다.
- GAN의 기초
- GAN으로 새로운 패션 아이템 생성하기
- cGAN으로 생성 제어하기
GAN이란
GAN(Generative Adversarial Network)
- lan Goodfellow(2014)에서 제안한 네트워크 모델
- Unsupervised Learning(비지도학습)의 대표적인 알고리즘
- 서로 대립하는 역할의 두 모델이 경쟁하여 학습하는 방법론
G: Generative
- GAN은 생성모델로 이미지, 음성, sequential data 등 원하는 형태의 데이터를 만드는 모델이다. 이전에 배운 CNN이나 RNN은 데이터를 분석하는 모델이지 생성하는 모델이 아니다.
상단 이미지는 전부 컴퓨터가 만든 얼굴이다. 실제 인물사진이 아닌만큼, 최근 소개되는 GAN모델은 사람도 헷갈릴정도로 정교하게 원하는 데이터를 만들 수 있다.
A: Adversarial
- GAN은 서로 대립관계에 있는 두 개의 모델을 생성해, 적대적으로 경쟁시키면서 발전시키는 것이 핵심
- Generator(생성자) : 가짜 이미지 생성
- Discriminator(판별자) : 이미지의 진위 여부 판별
Generator 모델은 Discriminator모델을 속이기 위해 최대한 진짜 같은 데이터를 만들고, Discriminator는 반대로 위조 데이터와 진짜 데이터를 감별하려고 실력을 키워나간다. 이후 Discriminator의 예측 결과에 따라 각 모델의 Loss가 결정되고, 서로 학습을 반복하게 된다. 이런 경쟁구도 속에서 두 모델의 능력이 상호 발전되어가고, 결론적으로는 Discriminator 모델이 Generator 모델이 만드는 데이터를 구별하기 힘들 정도로 만들어 나가는 것이 목적이다.
N: Network
- GAN은 인공신경망(network) 모델로, generator와 descriminator모두 신경망 기반 모델이다.
GAN 예제
우선 필요한 라이브러리들을 임포트한다.
import os
import torch
import torchvision
import torch.nn as nn
import torch.optim as optim
from torchvision import transforms, datasets
from torchvision.utils import save_image
import matplotlib.pyplot as plt
import numpy as np
Epoch, Batch_size와 같은 학습에 필요한 하이퍼 파라미터를 설정해준다.
# 하이퍼파라미터
EPOCHS = 500
BATCH_SIZE = 100
USE_CUDA = torch.cuda.is_available()
DEVICE = torch.device("cuda" if USE_CUDA else "cpu")
print("Using Device:", DEVICE)
본 예제에서는 FashionMNIST데이터셋을 로딩하여 사용하도록 한다.
train_loader는 앞에서 계속 사용하던 DataLoader이며, 반복문에서 사용하기 위해 학습 이미지와 레이블을 (이미지, 레이블)형태의 튜플로 반환한다.
# Fashion MNIST 데이터셋
trainset = datasets.FashionMNIST(
'./.data',
train=True,
download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
)
train_loader = torch.utils.data.DataLoader(
dataset = trainset,
batch_size = BATCH_SIZE,
shuffle = True
)
Generator, Discriminator 모델을 구현한다. 이전 예제에서 각 신경망 모델은 nn.Module 클래스로 정의했었는데 이번에는 다소 간단하게 하기 위해 Sequential()클래스를 사용해서 각 Layer를 정의해보도록 한다.
+) nn.Sequential()함수는 Pytorch에서의 init함수와 forward함수를 동시에 정의한다 생각하면 간단하다.
# 생성자 (Generator)
G = nn.Sequential(
nn.Linear(64, 256),
nn.ReLU(),
nn.Linear(256, 256),
nn.ReLU(),
nn.Linear(256, 784),
nn.Tanh())
generator 모델은 실제 데이터와 비슷한 가짜 데이터를 만드는 신경망으로, 그림과 같이 정규분포된 64차원의 무작위 tensor를 입력받아, Linear layer(행렬 곱)와 Relu, Tanh 연산(activation function)을 통해 784차원(기존 FashionMNIST 데이터 내 한 이미지 차원과 동일)의 텐서를 내보낸다.
+) 무작위 텐서: generator 모델이 실제 데이터 "분포"를 배우기 때문에, 그나마 실제 데이터 분포와 비슷한 정규분포에서부터 복잡한 분포도를 배울 수 있도록.
이후 discriminator는 이미지 크기인 784 tensor를 받아 동일하게 linear layer와 activation function 과정을 거친 후, 해당 이미지가 가짜인지 진짜인지를 구별한다.
# 판별자 (Discriminator)
D = nn.Sequential(
nn.Linear(784, 256),
nn.LeakyReLU(0.2),
nn.Linear(256, 256),
nn.LeakyReLU(0.2),
nn.Linear(256, 1),
nn.Sigmoid())
+) Leaky ReLU : 양의 기울기만 전달했던 기존의 ReLU와 달리, 약간의 음의 기울기도 다음 layer로 전달한다. 이렇게 할 경우, discriminator에서 계산한 기울기가 0이 아닌 약한 음수로 전환되며 생성자 측에 더 강하게 전달 될 수 있다. GAN의 특성 상 generator가 학습을 하기 위해선 discriminator로부터 기울기를 효과적으로 전달받아야 하기 때문에 중요하다.
이제 generator와 discriminator 학습에 사용될 Loss함수와 Optimizer도 정의해준다.
# 모델의 가중치를 지정한 장치로 보내기
# CUDA(GPU) / CPU
D = D.to(DEVICE)
G = G.to(DEVICE)
# 이진 크로스 엔트로피 (Binary cross entropy) 오차 함수와
# 생성자와 판별자를 최적화할 Adam 모듈
criterion = nn.BCELoss()
d_optimizer = optim.Adam(D.parameters(), lr=0.0002)
g_optimizer = optim.Adam(G.parameters(), lr=0.0002)
이제 본격적으로 GAN학습을 위한 for문을 작성해본다.
total_step = len(train_loader)
for epoch in range(EPOCHS):
for i, (images, _) in enumerate(train_loader):
images = images.reshape(BATCH_SIZE, -1).to(DEVICE)
# '진짜'와 '가짜' 레이블 생성
real_labels = torch.ones(BATCH_SIZE, 1).to(DEVICE)# [1,1,1...]
fake_labels = torch.zeros(BATCH_SIZE, 1).to(DEVICE)# [0.0,0...]
# 판별자가 진짜 이미지를 진짜로 인식하는 오차를 예산
outputs = D(images) # 진짜 이미지를 discriminator의 입력으로 제공
d_loss_real = criterion(outputs, real_labels)
real_score = outputs
# 무작위 텐서로 가짜 이미지 생성
z = torch.randn(BATCH_SIZE, 64).to(DEVICE)
fake_images = G(z) #G의 입력으로 랜덤 텐서 제공, G가 fake image 생성
# 판별자가 가짜 이미지를 가짜로 인식하는 오차를 계산
outputs = D(fake_images)# 가짜 이미지를 discriminator의 입력으로 제공
d_loss_fake = criterion(outputs, fake_labels)
fake_score = outputs
# 진짜와 가짜 이미지를 갖고 낸 오차를 더해서 Discriminator의 오차 계산
d_loss = d_loss_real + d_loss_fake
#------ Discriminator 학습 ------#
# 역전파 알고리즘으로 Discriminator의 학습을 진행
d_optimizer.zero_grad()
g_optimizer.zero_grad()
d_loss.backward()
d_optimizer.step()# Discriminator 학습
# 생성자가 판별자를 속였는지에 대한 오차(Generator의 loss)를 계산
fake_images = G(z)
outputs = D(fake_images) #한번 학습한 D가 fake image를
g_loss = criterion(outputs, real_labels)
#------ Generator 학습 ------#
# 역전파 알고리즘으로 생성자 모델의 학습을 진행
d_optimizer.zero_grad()
g_optimizer.zero_grad()
g_loss.backward()
g_optimizer.step()
# 학습 진행 알아보기
print('Epoch [{}/{}], d_loss: {:.4f}, g_loss: {:.4f}, D(x): {:.2f}, D(G(z)): {:.2f}'
.format(epoch, EPOCHS, d_loss.item(), g_loss.item(),
real_score.mean().item(), fake_score.mean().item()))
실제 돌려보면, 다음과 같이 매 Epoch마다 loss와 스코어를 출력하게 된다. 시간이 다소 걸리는데, generator보다 discriminator의 학습이 상대적으로 더 빠르다.
실제 결과물을 시각화 해보면 다음과 같다.
z = torch.randn(BATCH_SIZE, 64).to(DEVICE)
fake_images = G(z)
for i in range(10):
fake_images_img = np.reshape(fake_images.data.cpu().numpy()[i],(28, 28))
plt.imshow(fake_images_img, cmap = 'gray')
plt.show()
FashionMNIST와 닮은 이미지들을 생성하는 것을 볼 수 있다. 본 예제는 간단한 모델이지만, CNN과 같이 복잡한 모델을 쓸 경우, 훨씬 높은 해상도의 이미지 생성이 가능하다.
cGAN(conditional GAN)
cGAN(conditional GAN, 조건부 GAN)은 이미지 생성 과정에서 생성하고 싶은 레이블 정보를 추가로 넣어, 원하는 이미지가 나올 수 있게끔 하는 과정이다. 기존의 discriminator와 generator 입력에 추가적으로 레이블 정보를 이어 붙여 원하는 라벨에 맞는 데이터를 생성할 수 있게끔 하는 과정이다.
cGAN 예제
- 예제 코드
앞쪽의 하이퍼 파라미터나 필수 라이브러리, 데이터 로딩 과정은 앞서 다룬 예제와 같기 때문에 따로 적지 않는다.
이번 예제에서는 random tensor 크기를 100으로 정하고, 라벨 tensor를 10으로 정해 110 사이즈의 텐서를 모델의 첫 입력으로 제공한다. cGAN의 생성자는 무작위로 생성한 레이블 정보를 받아 해당 레이 블에 대한 이미지를 생성하도록 학습된다.
Generator는 아래 그림과 같이 Label을 추가한 텐서를 입력으로 제공한다.
# 생성자 (Generator)
class Generator(nn.Module):
def __init__(self):
super().__init__()
self.embed = nn.Embedding(10, 10)
# 배치 x 1크기의 레이블 텐서==>배치 x 10의 연속적인 텐서로 전환
# 매번 똑같은 레이블은 항상 똑같은 텐서를 만들기 때문에, 단순히 연속적인 텐서를 레이블 값에 맵핑하기 위함.
# (연속적인 값이 학습에 더 유용)
self.model = nn.Sequential(
nn.Linear(110, 256),
nn.LeakyReLU(0.2, inplace=True),#inplace=True 인자 : 입력을 복사하지 않고, 바로 조작
nn.Linear(256, 512),
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(512, 1024),
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(1024, 784),# 레이블이 추가 되었으니, layer를 한 층 더 늘려 조금 더 복잡하게 구현
nn.Tanh()
)
def forward(self, z, labels):
c = self.embed(labels)
x = torch.cat([z, c], 1)#1차원에 대하여 이어 붙이겠다.
return self.model(x)
Discriminator도 동일하게 label을 추가해준다.
# 판별자 (Discriminator)
class Discriminator(nn.Module):
def __init__(self):
super().__init__()
self.embed = nn.Embedding(10, 10)
# 배치 x 1크기의 레이블 텐서==>배치 x 10의 연속적인 텐서로 전환
# 매번 똑같은 레이블은 항상 똑같은 텐서를 만들기 때문에, 단순히 연속적인 텐서를 레이블 값에 맵핑하기 위함.
# (연속적인 값이 학습에 더 유용)
self.model = nn.Sequential(
nn.Linear(794, 1024),# 레이블 정보를 전달하기 위해 이미지 크기(784)에 10
nn.LeakyReLU(0.2, inplace=True),#판별자에게도 794노드에서 1024노드를 출력하는 계층 하나와
nn.Dropout(0.3), #성능을 늘리기 위해 드롭아웃 계층을 2개 더 추가
nn.Linear(1024, 512),
nn.LeakyReLU(0.2, inplace=True),
nn.Dropout(0.3),
nn.Linear(512, 256),
nn.LeakyReLU(0.2, inplace=True),
nn.Dropout(0.3),
nn.Linear(256, 1),
nn.Sigmoid()#Sigmoid() 함수를 거쳐 각각 가짜와 진짜를 뜻하는 0,1 사이를 반환
)
def forward(self, x, labels):
c = self.embed(labels)
x = torch.cat([x, c], 1)
return self.model(x)
Loss와 optimzer는 이전과 동일하므로 생략한다.
학습 단계도 이전과 같지만 라벨을 위한 텐서가 추가된다는 점 정도만 차이가 있다.
total_step = len(train_loader)
for epoch in range(EPOCHS):
for i, (images, labels) in enumerate(train_loader):
images = images.reshape(BATCH_SIZE, -1).to(DEVICE)
# '진짜'와 '가짜' 레이블 생성
real_labels = torch.ones(BATCH_SIZE, 1).to(DEVICE)
fake_labels = torch.zeros(BATCH_SIZE, 1).to(DEVICE)
# 판별자가 진짜 이미지를 진짜로 인식하는 오차 계산 (데이터셋 레이블 입력)
labels = labels.to(DEVICE)
outputs = D(images, labels)
d_loss_real = criterion(outputs, real_labels)
real_score = outputs
# 무작위 텐서와 무작위 레이블을 생성자에 입력해 가짜 이미지 생성
z = torch.randn(BATCH_SIZE, 100).to(DEVICE)
# g_label= torch.randint로 0과 10 사이의 값을 가진 배치x 1 크기의 텐서
g_label = torch.randint(0, 10, (BATCH_SIZE,)).to(DEVICE)
fake_images = G(z, g_label)# z와 g_label관 관계성을 Generator가 학습하도록 한다.
# 판별자가 가짜 이미지를 가짜로 인식하는 오차 계산
outputs = D(fake_images, g_label)
d_loss_fake = criterion(outputs, fake_labels)
fake_score = outputs
# 진짜와 가짜 이미지를 갖고 낸 오차를 더해서 판별자의 오차 계산
d_loss = d_loss_real + d_loss_fake
# 역전파 알고리즘으로 판별자 모델의 학습을 진행
d_optimizer.zero_grad()
g_optimizer.zero_grad()
d_loss.backward()
d_optimizer.step()
# 생성자가 판별자를 속였는지에 대한 오차 계산(무작위 레이블 입력)
fake_images = G(z, g_label)
outputs = D(fake_images, g_label)
g_loss = criterion(outputs, real_labels)
# 역전파 알고리즘으로 생성자 모델의 학습을 진행
d_optimizer.zero_grad()
g_optimizer.zero_grad()
g_loss.backward()
g_optimizer.step()
print('이폭 [{}/{}] d_loss:{:.4f} g_loss: {:.4f} D(x):{:.2f} D(G(z)):{:.2f}'
.format(epoch,
EPOCHS,
d_loss.item(),
g_loss.item(),
real_score.mean().item(),
fake_score.mean().item()))
이후 실제 training을 진행 후, 시각화 해보면 (아래 클래스 라벨 별 번호)
# 만들고 싶은 아이템 생성하고 시각화하기
item_number = 9 # 아이템 번호
z = torch.randn(1, 100).to(DEVICE) # 배치 크기 1
g_label = torch.full((1,), item_number, dtype=torch.long).to(DEVICE)
sample_images = G(z, g_label)
sample_images_img = np.reshape(sample_images.data.cpu().numpy()
[0],(28, 28))
plt.imshow(sample_images_img, cmap = 'gray')
plt.show()
다음과 같은 생성 이미지를 확인할 수 있다.
'머신러닝 > Pytorch 딥러닝 기초' 카테고리의 다른 글
[python, pytorch] dataloader 시간 지연, time delay (0) | 2021.09.13 |
---|---|
[Pytorch-기초강의] 9. 주어진 환경과 상호작용하며 성장하는 DQN (2) | 2021.06.18 |
[Pytorch-기초강의] 7. 딥러닝을 해킹하는 적대적 공격 (0) | 2021.06.13 |
[Pytorch-기초강의] 6. 순차적인 데이터를 처리하는 RNN(LSTM, GRU) (0) | 2021.05.25 |
[Pytorch-기초강의] 6. 순차적인 데이터를 처리하는 RNN (0) | 2021.05.25 |