오토인코더의 잠재 공간엔 데이터가 없는 '구멍'이 있어요 — 노이즈 훈련(β)으로 골짜기를 메워서, 정규분포에서 뽑기만 하면 새 데이터가 나오는 생성 모델로 바꿔 봅시다.
● 청록 점 = 원본 데이터 (축을 따라 두 덩어리로 뭉쳐 있어요) · — 파란 직선 = 이미 학습이 끝난 잠재 축 · ● 파란 작은 점 = 투영(복원)된 점 · ✚ = 데이터 중심 · ⭐ = z로 디코딩해 만든 점 (보라 링 = z 슬라이더). 두 덩어리 사이의 빈 구간이 이번 페이지의 주인공이에요.
가로축 = 잠재값 z, 세로축 = 밀도. 파란 막대 = 데이터의 z에 훈련 노이즈(σ = β×0.6)를 섞은 분포 · 회색 곡선 = 표준정규 N(0,1) · ┊ 보라 점선 = 지금 z 슬라이더 위치. β = 0이면 두 봉우리 + 가운데 빈 골짜기(AE), β를 올리면 봉우리가 뭉개지며 회색 곡선 같은 종 모양에 가까워지는지 보세요.
정직한 단순화: 실제 VAE는 인코더가 평균·분산을 내고 재매개변수화(reparameterization)로 학습해요. 여기선 잠재값 z에 노이즈를 주입하는 것으로 그 효과만 체험합니다.
디코딩 결과: x̂ = (6.64, 6.15)
z ~ N(0, 1)에서 10개를 뽑아 디코딩한 ⭐들 — 정규분포에서 뽑기만 하면 새 데이터가 나온다는 게 VAE의 생성 방식이에요.
복원 오차 = 축과 수직 방향의 기본 잔차 + 노이즈로 생기는 오차(σ² 환산). 골짜기 채움 비율 = 가운데(z≈0) 밀도 ÷ 봉우리 밀도 — 0%면 깊은 골짜기, 100%에 가까우면 매끈한 분포예요. β를 올리면 골짜기는 메워지지만 오차가 커지는 트레이드오프를 확인하세요.
import torch
import torch.nn as nn
class VAE(nn.Module):
def __init__(self):
super().__init__()
self.enc = nn.Sequential(nn.Linear(784, 128), nn.ReLU())
self.mu = nn.Linear(128, 32) # 잠재분포의 평균 μ
self.logvar = nn.Linear(128, 32) # 잠재분포의 로그분산
self.dec = nn.Sequential(nn.Linear(32, 128), nn.ReLU(),
nn.Linear(128, 784), nn.Sigmoid())
def reparameterize(self, mu, logvar):
eps = torch.randn_like(mu) # 노이즈를 섞어 z를 뽑기
return mu + eps * torch.exp(0.5 * logvar)
def forward(self, x):
h = self.enc(x)
mu, logvar = self.mu(h), self.logvar(h)
z = self.reparameterize(mu, logvar)
return self.dec(z), mu, logvar
# 손실 = 복원 오차 + β × KL(잠재분포 ‖ N(0,1))
recon = ((x_hat - x) ** 2).sum()
kl = -0.5 * torch.sum(1 + logvar - mu ** 2 - logvar.exp())
loss = recon + beta * kl