1. 서론
최근 몇 년 간 인공지능 분야에서 생성적 적대 신경망(Generative Adversarial Networks, GAN)과 변분 오토인코더(Variational Autoencoder, VAE)는 데이터 생성 및 변형에서 혁신적인 기술로 자리 잡았습니다. 이들 모델은 서로 다른 방식으로 데이터를 생성하는데, GAN은 두 개의 신경망이 경쟁하는 구조로 이루어져 있고, VAE는 확률적 모델로 데이터를 압축하고 생성하는 방식으로 작동합니다.
2. GAN의 개념과 구조
GAN은 Ian Goodfellow가 2014년에 제안한 모델로, 생성기(Generator)와 판별기(Discriminator)로 구성됩니다. 생성기는 무작위 노이즈를 입력받아 데이터를 생성하고, 판별기는 입력받은 데이터가 진짜인지 가짜인지 판단합니다. 이 두 네트워크는 서로 경쟁하며 학습하게 되며, 이 과정에서 생성기는 점점 더 사실적인 데이터를 생성하게 됩니다.
2.1 GAN 작동 원리
GAN의 훈련 과정은 다음과 같습니다:
- 생성기 훈련: 생성기는 랜덤 노이즈 벡터를 입력받아 가짜 이미지를 생성합니다. 생성된 이미지는 판별기의 입력으로 전달됩니다.
- 판별기 훈련: 판별기는 진짜 이미지와 가짜 이미지를 받아 각각의 확률을 출력합니다. 판별기의 목표는 가짜 이미지를 올바르게 식별하는 것입니다.
- 손실 함수 계산: 생성기와 판별기의 손실 함수가 계산됩니다. 생성기의 목표는 판별기를 속이는 것이고, 판별기는 가짜 이미지를 올바르게 식별하는 것입니다.
- 네트워크 업데이트: 손실을 기반으로 네트워크의 가중치가 업데이트됩니다.
- 반복: 위의 과정을 반복하며 각 네트워크의 성능이 향상됩니다.
3. VAE의 개념과 구조
변분 오토인코더(VAE)는 오토인코더의 변형으로, 데이터의 분포를 모델링하여 새로운 데이터를 생성하는 능력을 제공합니다. VAE는 인코더(Encoder)와 디코더(Decoder)로 구성되어 있으며, 데이터의 잠재 공간(latent space)을 학습합니다.
3.1 VAE 작동 원리
VAE의 훈련 과정은 다음과 같습니다:
- 입력 데이터 인코딩: 인코더는 입력 데이터를 잠재 공간으로 매핑하여 평균과 분산을 생성합니다.
- 샘플링: 평균과 분산을 사용하여 잠재 공간에서 샘플링합니다.
- 디코딩: 샘플링한 잠재 벡터를 디코더에 입력하여 원래 데이터와 유사한 데이터를 생성합니다.
- 손실 함수 계산: VAE는 재구성 손실과 Kullback-Leibler (KL) 발산을 포함하는 손실 함수를 최소화합니다.
- 네트워크 업데이트: 손실을 기반으로 가중치가 업데이트됩니다.
- 반복: 위의 과정을 반복하며 모델의 품질이 향상됩니다.
4. GAN과 VAE의 차이점
GAN과 VAE는 모두 데이터를 생성하는 모델이지만, 그 접근 방식에 있어 몇 가지 중요한 차이점이 있습니다:
- 모델 구조: GAN은 생성기와 판별기로 구성되어 경쟁하는 구조를 가지며, VAE는 인코더-디코더 구조로 구성되어 있습니다.
- 손실 함수: GAN은 두 네트워크의 대립 관계를 통해 학습하며, VAE는 재구성 및 KL 발산을 통해 학습합니다.
- 데이터 생성 방식: GAN은 현실적인 이미지를 생성하는 데 뛰어난 반면, VAE는 다양성과 연속성이 강조됩니다.
5. 파이토치를 활용한 GAN 구현
이제 파이토치를 활용하여 GAN을 구현해 보겠습니다. MNIST 데이터셋을 통해 손글씨 숫자 이미지를 생성하는 예제를 살펴봅니다.
5.1 라이브러리 설치
pip install torch torchvision matplotlib
5.2 데이터셋 불러오기
import torch
from torchvision import datasets, transforms
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))])
train_data = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(train_data, batch_size=64, shuffle=True)
5.3 GAN 모델 정의
class Generator(torch.nn.Module):
def __init__(self):
super(Generator, self).__init__()
self.model = torch.nn.Sequential(
torch.nn.Linear(100, 256),
torch.nn.ReLU(),
torch.nn.Linear(256, 512),
torch.nn.ReLU(),
torch.nn.Linear(512, 1024),
torch.nn.ReLU(),
torch.nn.Linear(1024, 784),
torch.nn.Tanh()
)
def forward(self, x):
return self.model(x).view(-1, 1, 28, 28)
class Discriminator(torch.nn.Module):
def __init__(self):
super(Discriminator, self).__init__()
self.model = torch.nn.Sequential(
torch.nn.Flatten(),
torch.nn.Linear(784, 512),
torch.nn.LeakyReLU(0.2),
torch.nn.Linear(512, 256),
torch.nn.LeakyReLU(0.2),
torch.nn.Linear(256, 1),
torch.nn.Sigmoid()
)
def forward(self, x):
return self.model(x)
5.4 모델 훈련
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
generator = Generator().to(device)
discriminator = Discriminator().to(device)
criterion = torch.nn.BCELoss()
optimizer_G = torch.optim.Adam(generator.parameters(), lr=0.0002, betas=(0.5, 0.999))
optimizer_D = torch.optim.Adam(discriminator.parameters(), lr=0.0002, betas=(0.5, 0.999))
num_epochs = 200
for epoch in range(num_epochs):
for i, (images, _) in enumerate(train_loader):
images = images.to(device)
batch_size = images.size(0)
# 진짜 및 가짜 레이블 생성
real_labels = torch.ones(batch_size, 1).to(device)
fake_labels = torch.zeros(batch_size, 1).to(device)
# 판별기 훈련
optimizer_D.zero_grad()
outputs = discriminator(images)
d_loss_real = criterion(outputs, real_labels)
d_loss_real.backward()
noise = torch.randn(batch_size, 100).to(device)
fake_images = generator(noise)
outputs = discriminator(fake_images.detach())
d_loss_fake = criterion(outputs, fake_labels)
d_loss_fake.backward()
optimizer_D.step()
# 생성기 훈련
optimizer_G.zero_grad()
outputs = discriminator(fake_images)
g_loss = criterion(outputs, real_labels)
g_loss.backward()
optimizer_G.step()
print(f'Epoch [{epoch}/{num_epochs}], d_loss: {d_loss_real.item() + d_loss_fake.item():.4f}, g_loss: {g_loss.item():.4f}')
6. 파이토치를 활용한 VAE 구현
이제 VAE를 구현해 보겠습니다. 이번에도 MNIST 데이터셋을 사용하여 손글씨 숫자 이미지를 생성하는 예제를 살펴보겠습니다.
6.1 VAE 모델 정의
class VAE(torch.nn.Module):
def __init__(self):
super(VAE, self).__init__()
self.encoder = torch.nn.Sequential(
torch.nn.Flatten(),
torch.nn.Linear(784, 400),
torch.nn.ReLU()
)
self.fc_mu = torch.nn.Linear(400, 20)
self.fc_var = torch.nn.Linear(400, 20)
self.decoder = torch.nn.Sequential(
torch.nn.Linear(20, 400),
torch.nn.ReLU(),
torch.nn.Linear(400, 784),
torch.nn.Sigmoid()
)
def encode(self, x):
h = self.encoder(x)
return self.fc_mu(h), self.fc_var(h)
def reparameterize(self, mu, logvar):
std = torch.exp(0.5 * logvar)
eps = torch.randn_like(std)
return mu + eps * std
def decode(self, z):
return self.decoder(z)
def forward(self, x):
mu, logvar = self.encode(x)
z = self.reparameterize(mu, logvar)
recon_x = self.decode(z)
return recon_x, mu, logvar
6.2 VAE 손실 함수
def vae_loss(recon_x, x, mu, logvar):
BCE = torch.nn.functional.binary_cross_entropy(recon_x, x, reduction='sum')
return BCE + 0.5 * torch.sum(torch.exp(logvar) + mu.pow(2) - 1 - logvar)
6.3 VAE 모델 훈련
vae = VAE().to(device)
optimizer_VAE = torch.optim.Adam(vae.parameters(), lr=1e-3)
num_epochs = 100
for epoch in range(num_epochs):
for i, (images, _) in enumerate(train_loader):
images = images.to(device)
optimizer_VAE.zero_grad()
recon_images, mu, logvar = vae(images)
loss = vae_loss(recon_images, images, mu, logvar)
loss.backward()
optimizer_VAE.step()
print(f'Epoch [{epoch}/{num_epochs}], Loss: {loss.item():.4f}')
7. 결론
본 글에서는 GAN과 VAE의 개념과 구조에 대해 살펴보고, 파이토치를 활용하여 이들 모델을 구현해 보았습니다. GAN은 생성기와 판별기가 경쟁하는 구조를 통해 사실적인 이미지를 생성하는 데 강력한 반면, VAE는 잠재 공간을 통해 데이터를 모델링하고 생성하는 데 우수한 성능을 보입니다. 이 두 모델의 특징을 이해하고 활용하면 다양한 데이터 생성 문제를 해결할 수 있습니다.