딥러닝 파이토치 강좌, 변형 오토인코더

딥러닝은 기계 학습의 한 분야로, 신경망을 활용하여 데이터로부터 패턴을 학습하는 기법입니다. 오늘 이 글에서는 변형 오토인코더(Variational Autoencoder, VAE)에 대해 깊이 있게 다루어 보겠습니다.

1. 오토인코더란?

오토인코더는 비지도 학습 방법으로 일반적으로 입력 데이터를 압축한 후 복원하는 과정을 학습합니다. 오토인코더는 인코더와 디코더 두 부분으로 구성됩니다.

  • 인코더(Encoder): 입력 데이터를 잠재 공간(latent space)으로 맵핑합니다.
  • 디코더(Decoder): 잠재 공간의 데이터를 원래 입력 데이터로 복원합니다.

1.1 오토인코더의 과정

오토인코더의 훈련 과정은 입력 데이터와 출력 데이터의 차이를 줄이는 방향으로 진행됩니다. 이를 위해 손실 함수(loss function)를 사용하여 실제 출력과 예측 출력 간의 차이를 측정합니다. 일반적으로 Mean Squared Error (MSE) 손실 함수가 많이 사용됩니다.

2. 변형 오토인코더(Variational Autoencoder)

변형 오토인코더는 기존의 오토인코더를 확장한 모델로, 입력 데이터의 확률 분포를 추정합니다. VAE는 생성 모델로서, 새로운 데이터를 생성할 수 있는 능력을 가지고 있습니다.

2.1 VAE의 구성

VAE는 다음과 같은 두 가지 주요 요소로 구성됩니다:

  • 잠재 변수: 입력 데이터를 인코딩할 때, 인코더는 평균(μ)과 표준편차(σ)를 출력하여 잠재 변수의 분포를 추정합니다.
  • 재구성 손실(Reconstruction Loss): 디코더가 생성한 출력과 원래 입력 간의 차이를 측정합니다.

2.2 손실 함수

VAE의 손실 함수는 두 부분으로 나눌 수 있습니다:

  • 재구성 손실: 실제 입력과 재구성된 입력 간의 손실을 측정하는 부분입니다.
  • Kullback-Leibler Divergence: 잠재 분포와 정규 분포 간의 차이를 측정합니다.

VAE 손실 함수 정의:

L = E[log p(x|z)] - D_{KL}(q(z|x) || p(z))
    

여기서:

  • E[log p(x|z)]: 입력 x가 주어졌을 때 z에 대한 로그 우도(log likelihood)입니다.
  • D_{KL}: Kullback-Leibler Divergence로 두 분포 간의 차이를 측정합니다.

3. 파이토치로 VAE 구현하기

이제 변형 오토인코더의 기본 구성 요소와 손실 함수를 이해했으므로, 파이토치로 VAE를 구현해 보겠습니다.

3.1 라이브러리 설치

pip install torch torchvision matplotlib
    

3.2 데이터셋 준비

MNIST 데이터셋을 사용하여 손글씨 숫자를 인식하는 VAE를 구현해 보겠습니다. MNIST는 28×28 픽셀의 흑백 이미지로 구성된 데이터셋입니다.

import torch
from torchvision import datasets, transforms

transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Lambda(lambda x: x.view(-1))
])

mnist_train = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(mnist_train, batch_size=128, shuffle=True)
    

3.3 모델 정의

변형 오토인코더 모델을 구성하기 위해, 인코더와 디코더 클래스 정의합니다.

import torch.nn as nn

class Encoder(nn.Module):
    def __init__(self, input_dim, latent_dim):
        super(Encoder, self).__init__()
        self.fc1 = nn.Linear(input_dim, 400)
        self.fc21 = nn.Linear(400, latent_dim)  # 평균
        self.fc22 = nn.Linear(400, latent_dim)  # 로그 분산
        
    def forward(self, x):
        h1 = torch.relu(self.fc1(x))
        mu = self.fc21(h1)
        logvar = self.fc22(h1)
        return mu, logvar


class Decoder(nn.Module):
    def __init__(self, latent_dim, output_dim):
        super(Decoder, self).__init__()
        self.fc1 = nn.Linear(latent_dim, 400)
        self.fc2 = nn.Linear(400, output_dim)
        
    def forward(self, z):
        h2 = torch.relu(self.fc1(z))
        return torch.sigmoid(self.fc2(h2))
    
class VAE(nn.Module):
    def __init__(self, input_dim, latent_dim):
        super(VAE, self).__init__()
        self.encoder = Encoder(input_dim, latent_dim)
        self.decoder = Decoder(latent_dim, input_dim)
        
    def reparameterize(self, mu, logvar):
        std = torch.exp(0.5 * logvar)
        eps = torch.randn_like(std)
        return mu + eps * std
    
    def forward(self, x):
        mu, logvar = self.encoder(x)
        z = self.reparameterize(mu, logvar)
        return self.decoder(z), mu, logvar
    

3.4 손실 함수 정의

VAE에서 손실 함수를 정의합니다. 여기서는 pytorch의 기능을 이용해 구현합니다.

def vae_loss(recon_x, x, mu, logvar):
    BCE = nn.functional.binary_cross_entropy(recon_x, x, reduction='sum')
    KLD = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
    return BCE + KLD
    

3.5 모델 훈련

훈련 루프를 사용해 모델을 훈련합니다. 손실 함수를 다시 계산하고, 역전파를 수행하여 가중치를 업데이트합니다.

import torch.optim as optim

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = VAE(784, 20).to(device)
optimizer = optim.Adam(model.parameters(), lr=1e-3)

model.train()
for epoch in range(10):
    train_loss = 0
    for batch_idx, (data, _) in enumerate(train_loader):
        data = data.to(device)
        optimizer.zero_grad()
        recon_batch, mu, logvar = model(data)
        loss = vae_loss(recon_batch, data, mu, logvar)
        loss.backward()
        train_loss += loss.item()
        optimizer.step()
    
    print(f'Epoch {epoch+1}, Loss: {train_loss / len(train_loader.dataset)}')
    

3.6 결과 확인

훈련이 완료되면, 모델을 사용하여 새로운 데이터를 생성하고, 훈련 데이터와 얼마나 유사한지 확인할 수 있습니다.

import matplotlib.pyplot as plt

def visualize_results(model, num_images=10):
    with torch.no_grad():
        z = torch.randn(num_images, 20).to(device)
        sample = model.decoder(z).cpu()
        sample = sample.view(num_images, 1, 28, 28)
        
    plt.figure(figsize=(10, 1))
    for i in range(num_images):
        plt.subplot(1, num_images, i + 1)
        plt.imshow(sample[i].squeeze(), cmap='gray')
        plt.axis('off')
    plt.show()

visualize_results(model)
    

4. 결론

이번 강좌에서는 변형 오토인코더의 개념과 파이토치로 구현하는 방법에 대해 살펴보았습니다. VAE는 데이터의 잠재적인 분포를 학습하고 새로운 샘플을 생성할 수 있는 기능이 있어 다양한 생성 모델링에 활용될 수 있습니다. 이 기술을 활용하여 이미지, 텍스트 및 오디오 데이터 생성과 같은 여러 가지 흥미로운 작업을 수행할 수 있습니다.

더 나아가, VAE는 GAN과 같은 다른 생성 모델과 결합하여 더욱 강력하고 다양한 생성 모델을 구현하는 데 기여할 수 있습니다. 특히, VAE는 고차원 데이터의 잠재 공간을 탐색하고 샘플링할 수 있도록 도와줍니다.

참고 문헌