에듀테크 프로젝트 대시보드
생성적 적대 신경망 (GAN)
역전파로 학습하는 신경망 하나만 있으면 "정답이 있는" 분류·회귀 문제를 풀 수 있습니다. 그런데 "진짜 같은 이미지를 새로 만들어라"처럼 정답 라벨이 없는 문제는 어떻게 학습시킬까요? 2014년 이안 굿펠로우가 제안한 GAN은 신경망 두 개를 서로 경쟁시켜 이 문제를 풉니다. 생성자(Generator) G는 위조지폐범처럼 무작위 노이즈로 가짜를 찍어내고, 판별자(Discriminator) D는 감별사처럼 진짜와 가짜를 구분하려 합니다. 둘 다 점점 실력이 늘면서, G는 결국 D조차 속을 만큼 진짜 같은 샘플을 만들게 됩니다.
D는 x_real에는 1(진짜), x_fake에는 0(가짜)이라고 답하도록 학습됩니다.
G는 정답 라벨이 없는 대신, D의 판단을 손실로 사용합니다 —
"내가 만든 게 D를 얼마나 잘 속였는가"가 곧 G의 성적표입니다.
# 원논문의 적대적 게임 (Goodfellow et al., 2014) min_G max_D V(D,G) = E[log D(x_real)] + E[log(1 − D(G(z)))] # D는 이 값을 최대화(진짜=1, 가짜=0 잘 맞히기) # G는 이 값을 최소화(D가 가짜를 진짜로 착각하게 만들기)
log(1−D(G(z)))의 기울기가 거의 0이 되어 G가 학습되지 않습니다(기울기 소실).
그래서 실전에서는 G의 손실을 −log D(G(z))로 바꿔 씁니다 —
"D를 속이는 데 실패했다"를 최소화하는 대신 "D가 진짜라고 믿게 만들기"를 최대화하는 것으로 부호만
바꾼 것인데, 초반 기울기가 훨씬 커서 학습이 안정적입니다. 아래 데모도 이 트릭을 사용합니다.
파란 점은 실제 데이터(원 위에 고르게 놓인 8개 가우시안 봉우리), 분홍 점은 지금 G가 만들어낸 가짜 샘플입니다. 배경의 밝기는 판별자 D가 그 위치를 얼마나 "진짜"라고 믿는지를 나타내는 확률 히트맵입니다. 학습 시작을 누르고 분홍 점들이 8개 봉우리를 골고루 덮는지, 아니면 한두 곳에만 몰리는지(모드 붕괴) 관찰해 보세요.
균형점(내쉬 균형)에서는 D가 진짜·가짜를 구분 못 해 확신도가 50% 근처로 수렴합니다. 학습률이 너무 크면 G와 D가 서로 튕겨내며 진동(불안정)하고, 점들이 한 봉우리에만 뭉치면 모드 붕괴입니다 — 그때는 새로 뽑기를 눌러보세요.
| 문제 | 증상 | 완화책 |
|---|---|---|
| 기울기 소실 | G의 손실이 초반에 거의 안 움직임 | 비-포화 손실(위 2번) |
| 모드 붕괴 | 다양한 실제 데이터 중 일부만 흉내냄 | 미니배치 판별, unrolled GAN |
| 훈련 불안정 | D·G 손실이 수렴 안 하고 진동 | 학습률 축소, WGAN(거리 기반 손실) |
| 균형 판단 어려움 | "충분히 학습됐다"를 손실만으로 알기 어려움 | 샘플 육안 확인, FID 점수 |
| GAN | VAE | 확산 모델(Diffusion) | |
|---|---|---|---|
| 학습 방식 | 생성자·판별자 적대적 경쟁 | 인코더·디코더 재구성 + 잠재분포 정규화 | 노이즈를 점진적으로 제거하도록 학습 |
| 생성 속도 | 빠름 (forward 1회) | 빠름 (forward 1회) | 느림 (수십~수천 스텝 반복) |
| 대표 이슈 | 모드 붕괴·불안정 | 흐릿한 결과물 | 느린 샘플링 속도 |
| 대표 사례 | StyleGAN, 딥페이크 | 이미지 압축·표현 학습 | Stable Diffusion, DALL·E |
for step in range(num_steps): # ── ① 판별자 D 학습: 진짜=1, 가짜=0 을 구분하도록 ── x_real = sample_real_batch() z = sample_noise_batch() x_fake = G(z).detach() # G는 이 단계에서 갱신 안 함 loss_D = bce(D(x_real), 1) + bce(D(x_fake), 0) update(D, grad(loss_D, D.params)) # ── ② 생성자 G 학습: D가 가짜를 진짜라고 믿도록 ── z = sample_noise_batch() x_fake = G(z) loss_G = bce(D(x_fake), 1) # 비-포화 트릭: 목표 라벨을 1로 update(G, grad(loss_G, G.params)) # D는 이 단계에서 고정, 기울기만 통과