학습 목표
GAN의 생성자-판별자 적대적 학습 구조를 이해한다
Min-Max 목적 함수의 수학적 의미를 설명할 수 있다
모드 붕괴(Mode Collapse)와 학습 불안정 문제를 안다
Wasserstein GAN의 개선 아이디어를 이해한다
왜 중요한가
생성적 적대 신경망(GAN, Generative Adversarial Network)은 2014년 Goodfellow et al.이 제안한 생성 모델입니다. 두 신경망이 적대적으로 경쟁 하며 학습하여, VAE보다 선명하고 사실적인 데이터를 생성합니다. 이미지 생성, 스타일 변환, 데이터 증강 등에 혁신적인 성과를 이뤘습니다.
구성 요소 역할 목표 생성자(Generator) G G G 노이즈 z z z 에서 가짜 데이터 생성 판별자를 속이는 것 판별자(Discriminator) D D D 진짜/가짜 데이터 구분 진짜와 가짜를 정확히 분류
목적 함수
Min-Max 게임
min G max D V ( D , G ) = E x ∼ p data [ log D ( x ) ] + E z ∼ p z [ log ( 1 − D ( G ( z ) ) ) ] \min_G \max_D \; V(D, G) = \mathbb{E}_{x \sim p_{\text{data}}}[\log D(x)] + \mathbb{E}_{z \sim p_z}[\log(1 - D(G(z)))] G min D max V ( D , G ) = E x ∼ p data [ log D ( x )] + E z ∼ p z [ log ( 1 − D ( G ( z )))]
판별자 D D D : V V V 를 최대화 — 진짜는 1, 가짜는 0으로 분류
생성자 G G G : V V V 를 최소화 — 판별자가 가짜를 진짜로 착각하도록
비포화 생성자 손실 (Non-Saturating Loss)
실전에서는 생성자의 기울기 소실을 방지하기 위해 변형된 손실을 사용합니다.
L G = − E z ∼ p z [ log D ( G ( z ) ) ] \mathcal{L}_G = -\mathbb{E}_{z \sim p_z}[\log D(G(z))] L G = − E z ∼ p z [ log D ( G ( z ))]
import torch
import torch.nn as nn
class Generator ( nn . Module ):
"""생성자: 노이즈 → 이미지"""
def __init__ ( self , latent_dim = 100 , img_dim = 784 ):
super (). __init__ ()
self .net = nn.Sequential(
nn.Linear(latent_dim, 256 ),
nn.LeakyReLU( 0.2 ),
nn.BatchNorm1d( 256 ),
nn.Linear( 256 , 512 ),
nn.LeakyReLU( 0.2 ),
nn.BatchNorm1d( 512 ),
nn.Linear( 512 , img_dim),
nn.Tanh(), # 출력 [-1, 1]
)
def forward ( self , z ):
return self .net(z)
class Discriminator ( nn . Module ):
"""판별자: 이미지 → 진짜/가짜 확률"""
def __init__ ( self , img_dim = 784 ):
super (). __init__ ()
self .net = nn.Sequential(
nn.Linear(img_dim, 512 ),
nn.LeakyReLU( 0.2 ),
nn.Dropout( 0.3 ),
nn.Linear( 512 , 256 ),
nn.LeakyReLU( 0.2 ),
nn.Dropout( 0.3 ),
nn.Linear( 256 , 1 ),
nn.Sigmoid(),
)
def forward ( self , x ):
return self .net(x)
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
# 하이퍼파라미터
latent_dim = 100
lr = 2e-4
epochs = 100
# 데이터 ([-1, 1] 정규화)
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize([ 0.5 ], [ 0.5 ]),
])
train_dataset = datasets.MNIST( root = './data' , train = True , transform = transform, download = True )
train_loader = DataLoader(train_dataset, batch_size = 128 , shuffle = True )
# 모델
G = Generator( latent_dim = latent_dim).to( 'cuda' )
D = Discriminator().to( 'cuda' )
# 옵티마이저 (Adam, β₁=0.5 권장)
opt_G = optim.Adam(G.parameters(), lr = lr, betas = ( 0.5 , 0.999 ))
opt_D = optim.Adam(D.parameters(), lr = lr, betas = ( 0.5 , 0.999 ))
criterion = nn.BCELoss()
for epoch in range (epochs):
for real_images, _ in train_loader:
batch_size = real_images.size( 0 )
real_images = real_images.view(batch_size, - 1 ).to( 'cuda' )
# 레이블
real_labels = torch.ones(batch_size, 1 ).to( 'cuda' )
fake_labels = torch.zeros(batch_size, 1 ).to( 'cuda' )
# ── 판별자 학습 ──
z = torch.randn(batch_size, latent_dim).to( 'cuda' )
fake_images = G(z).detach() # 생성자 기울기 차단
d_real = D(real_images)
d_fake = D(fake_images)
d_loss = criterion(d_real, real_labels) + criterion(d_fake, fake_labels)
opt_D.zero_grad()
d_loss.backward()
opt_D.step()
# ── 생성자 학습 ──
z = torch.randn(batch_size, latent_dim).to( 'cuda' )
fake_images = G(z)
d_fake = D(fake_images)
g_loss = criterion(d_fake, real_labels) # 비포화 손실
opt_G.zero_grad()
g_loss.backward()
opt_G.step()
print ( f "Epoch { epoch + 1 } : D 손실= { d_loss.item() :.4f} , G 손실= { g_loss.item() :.4f} " )
학습 문제와 해결
모드 붕괴 (Mode Collapse)
생성자가 다양성 없이 소수의 출력만 반복 생성하는 현상입니다.
학습 안정화 기법
기법 설명 효과 레이블 스무딩 진짜 레이블을 0.9로, 가짜를 0.1로 판별자 과신 방지 스펙트럴 노말라이제이션 판별자 가중치의 스펙트럴 노름 제한 리프시츠 연속성 보장 학습률 균형 판별자와 생성자의 학습률 조절 일방적 지배 방지 그래디언트 페널티 기울기 크기에 패널티 WGAN-GP
Wasserstein GAN (WGAN)
JS Divergence 대신 **Wasserstein 거리(Earth Mover’s Distance)**를 사용하여 학습을 안정화합니다.
L WGAN = E x ∼ p data [ D ( x ) ] − E z ∼ p z [ D ( G ( z ) ) ] \mathcal{L}_{\text{WGAN}} = \mathbb{E}_{x \sim p_{\text{data}}}[D(x)] - \mathbb{E}_{z \sim p_z}[D(G(z))] L WGAN = E x ∼ p data [ D ( x )] − E z ∼ p z [ D ( G ( z ))]
# WGAN-GP 판별자 손실 (개념)
def wgan_gp_d_loss ( D , real , fake , lambda_gp = 10 ):
"""WGAN-GP 판별자 손실"""
d_real = D(real).mean()
d_fake = D(fake.detach()).mean()
# Gradient Penalty
alpha = torch.rand(real.size( 0 ), 1 ).to(real.device)
interpolated = (alpha * real + ( 1 - alpha) * fake.detach()).requires_grad_( True )
d_interp = D(interpolated)
gradients = torch.autograd.grad(
outputs = d_interp, inputs = interpolated,
grad_outputs = torch.ones_like(d_interp),
create_graph = True ,
)[ 0 ]
gp = ((gradients.norm( 2 , dim = 1 ) - 1 ) ** 2 ).mean()
return d_fake - d_real + lambda_gp * gp
GAN 변형 비교
모델 연도 손실 함수 핵심 개선 GAN (원본) 2014 BCE 적대적 학습 제안 WGAN 2017 Wasserstein 학습 안정성 WGAN-GP 2017 Wasserstein + GP 그래디언트 페널티 SNGAN 2018 BCE + SN 스펙트럴 노말라이제이션
GAN 학습은 매우 불안정 합니다. 판별자가 너무 강하면 생성자의 기울기가 소실되고, 생성자가 너무 강하면 모드 붕괴가 발생합니다. 학습 곡선을 주의 깊게 모니터링하고, 생성 결과를 주기적으로 시각화해야 합니다.
참고 논문
논문 학회/연도 핵심 기여 Generative Adversarial Nets (Goodfellow et al.) NeurIPS 2014 GAN 제안 Wasserstein GAN (Arjovsky et al.) ICML 2017 Wasserstein 거리 기반 학습 Improved Training of Wasserstein GANs (Gulrajani et al.) NeurIPS 2017 Gradient Penalty
체크리스트
다음 문서
GAN 아키텍처 DCGAN, StyleGAN, CycleGAN — 실전 GAN 모델