파이토치를 활용한 GAN 딥러닝, 뉴럴 스타일 트랜스퍼

1. 서론

최근 몇 년간 인공지능과 딥러닝 분야는 정보 기술의 혁신을 이끌어왔습니다. 그 중에서도 Generative Adversarial Networks(GAN)와
뉴럴 스타일 트랜스퍼는 특히 시각적 콘텐츠 생성 및 변환에 혁신적인 방법론으로 주목받고 있습니다. 본 강좌에서는 GAN의 기본 개념과
뉴럴 스타일 트랜스퍼를 파이토치(PyTorch)를 사용하여 구현하는 방법에 대해 설명하겠습니다.

2. GAN의 기본 개념

GAN은 두 개의 신경망, 즉 생성자(Generator)와 판별자(Discriminator)로 구성됩니다. 생성자는 가짜 데이터를 생성하고,
판별자는 진짜와 가짜 데이터를 구별하는 역할을 합니다. 이 두 네트워크는 서로 경쟁하며 학습합니다. 생성자는 판별자를 속이기 위해 계속해서
데이터의 품질을 높이고, 판별자는 생성자가 만든 데이터를 더 잘 구별하기 위해 학습하게 됩니다.

2.1 GAN의 구조

GAN의 과정은 다음과 같이 요약할 수 있습니다:

  1. 무작위 노이즈를 생성자로 입력하여 가짜 이미지를 생성합니다.
  2. 생성된 이미지와 실제 이미지를 판별자에 입력합니다.
  3. 판별자는 입력된 이미지가 진짜인지 가짜인지에 대한 확률을 출력합니다.
  4. 생성자는 판별자의 피드백을 받아 가짜 이미지를 개선합니다.

3. GAN 구현하기

이제 GAN을 구현해 보겠습니다. 이번 예제에서는 MNIST 데이터셋을 사용하여 숫자 이미지를 생성하는 GAN을 구축하겠습니다.

3.1 필요한 라이브러리 설치


        pip install torch torchvision matplotlib
    

3.2 MNIST 데이터셋 로드


import torch
import torchvision.datasets as dsets
import torchvision.transforms as transforms

# MNIST 데이터셋 다운로드 및 로드
def load_mnist(batch_size):
    transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))])
    train_dataset = dsets.MNIST(root='./data', train=True, transform=transform, download=True)
    train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
    return train_loader

# 배치 사이즈 100으로 설정
batch_size = 100
train_loader = load_mnist(batch_size)
        

3.3 GAN 모델 구축

이제 생성자와 판별자 모델을 정의하겠습니다.


import torch.nn as nn

# 생성자 모델 정의
class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        self.model = nn.Sequential(
            nn.Linear(100, 256),
            nn.ReLU(),
            nn.Linear(256, 512),
            nn.ReLU(),
            nn.Linear(512, 1024),
            nn.ReLU(),
            nn.Linear(1024, 784),
            nn.Tanh()  # MNIST 이미지의 픽셀 값 범위: -1 to 1
        )

    def forward(self, z):
        return self.model(z).reshape(-1, 1, 28, 28)  # MNIST 이미지 형태로 변환

# 판별자 모델 정의
class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
        self.model = nn.Sequential(
            nn.Flatten(),
            nn.Linear(784, 512),
            nn.LeakyReLU(0.2),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2),
            nn.Linear(256, 1),
            nn.Sigmoid()  # 출력값을 0과 1 사이로
        )

    def forward(self, img):
        return self.model(img)
        

3.4 GAN 훈련 과정

이제 GAN을 훈련하는 기능을 구현하겠습니다.


import torchvision.utils as vutils

def train_gan(epochs, train_loader):
    generator = Generator()
    discriminator = Discriminator()

    criterion = nn.BCELoss()
    lr = 0.0002
    beta1 = 0.5
    g_optimizer = torch.optim.Adam(generator.parameters(), lr=lr, betas=(beta1, 0.999))
    d_optimizer = torch.optim.Adam(discriminator.parameters(), lr=lr, betas=(beta1, 0.999))

    for epoch in range(epochs):
        for i, (imgs, _) in enumerate(train_loader):
            # 진짜 레이블과 가짜 레이블 생성
            real_labels = torch.ones(imgs.size(0), 1)
            fake_labels = torch.zeros(imgs.size(0), 1)

            # 판별자 학습
            discriminator.zero_grad()
            outputs = discriminator(imgs)
            d_loss_real = criterion(outputs, real_labels)
            d_loss_real.backward()

            z = torch.randn(imgs.size(0), 100)  # 노이즈 벡터
            fake_imgs = generator(z)
            outputs = discriminator(fake_imgs.detach())
            d_loss_fake = criterion(outputs, fake_labels)
            d_loss_fake.backward()

            d_loss = d_loss_real + d_loss_fake
            d_optimizer.step()

            # 생성자 학습
            generator.zero_grad()
            outputs = discriminator(fake_imgs)
            g_loss = criterion(outputs, real_labels)
            g_loss.backward()
            g_optimizer.step()

            if (i + 1) % 100 == 0:
                print(f'Epoch [{epoch + 1}/{epochs}], Step [{i + 1}/{len(train_loader)}], '
                      f'D Loss: {d_loss.item()}, G Loss: {g_loss.item()}')

        # 생성된 이미지 저장
        if (epoch + 1) % 10 == 0:
            with torch.no_grad():
                fake_imgs = generator(z).detach()
                vutils.save_image(fake_imgs, f'output/fake_images-{epoch + 1}.png', normalize=True)
train_gan(epochs=50, train_loader=train_loader)
        

4. 뉴럴 스타일 트랜스퍼

뉴럴 스타일 트랜스퍼는 이미지의 콘텐츠와 스타일을 분리하여, 콘텐츠 이미지를 스타일 이미지의 특성으로 변환하는 기술입니다.
이 과정은 Convolutional Neural Networks (CNN)을 기반으로 하며, 주로 다음과 같은 단계를 포함합니다:

  1. 콘텐츠 이미지와 스타일 이미지 추출하기.
  2. 두 이미지를 결합하여 최종 이미지를 생성하기.

4.1 필요한 라이브러리 설치


pip install Pillow numpy matplotlib
    

4.2 모델 준비하기


import torch
import torch.nn as nn
from torchvision import models

class StyleTransferModel(nn.Module):
    def __init__(self):
        super(StyleTransferModel, self).__init__()
        self.vgg = models.vgg19(pretrained=True).features.eval()  # VGG19 모델 사용 

    def forward(self, x):
        return self.vgg(x)
        

4.3 스타일과 콘텐츠 손실 정의


class ContentLoss(nn.Module):
    def __init__(self, target):
        super(ContentLoss, self).__init__()
        self.target = target.detach()  # target 그래디언트 계산 방지

    def forward(self, x):
        return nn.functional.mse_loss(x, self.target)

class StyleLoss(nn.Module):
    def __init__(self, target):
        super(StyleLoss, self).__init__()
        self.target = self.gram_matrix(target).detach()  # Gram matrix 계산 

    def gram_matrix(self, x):
        b, c, h, w = x.size()
        features = x.view(b, c, h * w)
        G = torch.bmm(features, features.transpose(1, 2))  # Gram matrix 생성
        return G.div(c * h * w)

    def forward(self, x):
        G = self.gram_matrix(x)
        return nn.functional.mse_loss(G, self.target)
        

4.4 스타일 트랜스퍼 실행

이제 스타일 트랜스퍼를 위한 함수와 손실을 정의한 후, 훈련 루프를 구현합니다. 최종적으로 콘텐츠와 스타일 손실을 결합해 최소화합니다.


def run_style_transfer(content_img, style_img, model, num_steps=500, style_weight=1000000, content_weight=1):
    target = content_img.clone().requires_grad_(True)  # 초기 이미지 생성
    optimizer = torch.optim.LBFGS([target])  # LBFGS 최적화 기법 사용
    
    style_losses = []
    content_losses = []

    for layer in model.children():
        target = layer(target)
        if isinstance(layer, ContentLoss):
            content_losses.append(target)
        if isinstance(layer, StyleLoss):
            style_losses.append(target)

    for step in range(num_steps):
        def closure():
            optimizer.zero_grad()
            target_data = target.data

            style_loss_val = sum([style_loss(target_data).item() for style_loss in style_losses])
            content_loss_val = sum([content_loss(target_data).item() for content_loss in content_losses])
            total_loss = style_weight * style_loss_val + content_weight * content_loss_val

            total_loss.backward()
            return total_loss

        optimizer.step(closure)

    return target.data
        

5. 결론

본 강좌에서는 GAN을 활용하여 이미지 생성 및 뉴럴 스타일 트랜스퍼를 구현하는 방법에 대해 알아보았습니다. GAN은 이미지 생성 기술의 새로운 기준을 제시했으며,
뉴럴 스타일 트랜스퍼는 이미지를 조합하여 독창적인 예술 작품을 만들어내는 방법론입니다. 두 기술 모두 딥러닝의 발전을 이끌고 있으며, 향후 다양한 분야에서
활용될 수 있을 것입니다.

6. 참고 자료

파이토치를 활용한 GAN 딥러닝, 고약한 범법자를 위한 문학 클럽

Generative Adversarial Networks (GANs)는 딥러닝의 가장 혁신적인 발전 중 하나로 간주됩니다. GAN은 두 개의 신경망, 즉 생성자(Generator)와 판별자(Discriminator)로 구성됩니다. 생성자는 데이터를 생성하고, 판별자는 해당 데이터가 진짜인지 가짜인지 판단합니다. 이러한 경쟁 구조는 서로의 성능을 향상시키는 역할을 합니다. 본 강좌에서는 파이토치를 사용하여 GAN을 구축하고, 이를 통해 흥미로운 방식으로 ‘고약한 범법자를 위한 문학 클럽’을 주제로 한 데이터 생성을 수행할 것입니다.

1. GAN의 기본 구조와 원리

GAN의 작동 방식은 다음과 같습니다:

  • 생성자(Generator): 무작위 노이즈(z)를 입력으로 받아 현실적인 데이터를 생성합니다.
  • 판별자(Discriminator): 입력된 데이터가 실제 데이터인지 생성자에 의해 만들어진 데이터인지 판단합니다.
  • 생성자는 판별자를 속이려 하며, 판별자는 생성자를 구별하려 합니다. 이러한 경쟁이 지속되면서 두 네트워크는 점점 더 발전하게 됩니다.

2. 필요한 라이브러리와 데이터셋 준비

파이토치와 기타 필요한 라이브러리를 설치합니다. 그리고 데이터를 준비하는 과정에서 사용할 데이터셋을 선택해야 합니다. 예제에서는 MNIST 데이터셋을 사용하여 숫자 이미지를 생성해보겠습니다. MNIST 데이터셋은 손으로 쓴 숫자 이미지로 구성되어 있습니다.

2.1 환경 설정

pip install torch torchvision

2.2 데이터셋 로드

import torch
from torchvision import datasets, transforms
from torch.utils.data import DataLoader

# MNIST 데이터셋 로드
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])

mnist_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
data_loader = DataLoader(dataset=mnist_dataset, batch_size=64, shuffle=True)

3. GAN 모델 구축

Generative Adversarial Network를 구현하기 위해 생성자와 판별자 모델을 정의합니다.

3.1 생성자 모델

import torch.nn as nn

class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        self.model = nn.Sequential(
            nn.Linear(100, 256),
            nn.ReLU(),
            nn.Linear(256, 512),
            nn.ReLU(),
            nn.Linear(512, 1024),
            nn.ReLU(),
            nn.Linear(1024, 28 * 28),  # MNIST 이미지 크기
            nn.Tanh()  # 생성된 이미지의 픽셀 값 범위를 -1 ~ 1로 조정
        )

    def forward(self, z):
        img = self.model(z)
        img = img.view(img.size(0), 1, 28, 28)  # 이미지 형태로 변환
        return img

3.2 판별자 모델

class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
        self.model = nn.Sequential(
            nn.Linear(28 * 28, 512),
            nn.LeakyReLU(0.2),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2),
            nn.Linear(256, 1),
            nn.Sigmoid()  # 실수가 아닌 0과 1 사이의 값으로 출력
        )

    def forward(self, img):
        img_flat = img.view(img.size(0), -1)  # 이미지를 평탄화
        validity = self.model(img_flat)
        return validity

4. GAN의 학습 과정

GAN의 학습 과정은 다음과 같이 이루어집니다.

  • 진짜 이미지와 생성된 이미지를 판별자에 제공하여 판별자의 손실(loss)을 계산합니다.
  • 생성자를 업데이트하여 생성된 이미지가 실제와 가까워지도록 합니다.
  • 이 과정을 반복하여 각 네트워크가 서로 발전하게 합니다.

4.1 손실 함수 및 최적화기 정의

import torch.optim as optim

# 생성자 및 판별자 인스턴스 생성
generator = Generator()
discriminator = Discriminator()

# 손실 함수 및 최적화기 설정
adversarial_loss = nn.BCELoss()
optimizer_G = optim.Adam(generator.parameters(), lr=0.0002, betas=(0.5, 0.999))
optimizer_D = optim.Adam(discriminator.parameters(), lr=0.0002, betas=(0.5, 0.999))

4.2 학습 루프

num_epochs = 200
for epoch in range(num_epochs):
    for i, (imgs, _) in enumerate(data_loader):
        # 실제 데이터의 레이블: 1
        real_imgs = imgs
        valid = torch.ones(imgs.size(0), 1)  # 진짜 이미지에 대한 정답
        fake = torch.zeros(imgs.size(0), 1)  # 가짜 이미지에 대한 정답

        # 판별자 학습
        optimizer_D.zero_grad()
        z = torch.randn(imgs.size(0), 100)  # 무작위 노이즈 샘플링
        generated_imgs = generator(z)  # 생성된 이미지
        real_loss = adversarial_loss(discriminator(real_imgs), valid)
        fake_loss = adversarial_loss(discriminator(generated_imgs.detach()), fake)
        d_loss = (real_loss + fake_loss) / 2
        d_loss.backward()
        optimizer_D.step()

        # 생성자 학습
        optimizer_G.zero_grad()
        g_loss = adversarial_loss(discriminator(generated_imgs), valid)
        g_loss.backward()
        optimizer_G.step()

    print(f'Epoch {epoch}/{num_epochs} | D Loss: {d_loss.item()} | G Loss: {g_loss.item()}')

5. 결과 시각화

학습이 완료된 후에 생성된 이미지를 시각화하여 결과를 확인합니다.

import matplotlib.pyplot as plt

# 생성된 이미지 시각화
def show_generated_images(generator, num_images=16):
    z = torch.randn(num_images, 100)  # 무작위 노이즈 샘플링
    generated_images = generator(z)
    generated_images = generated_images.detach().numpy()

    fig, axs = plt.subplots(4, 4, figsize=(10, 10))
    for i in range(4):
        for j in range(4):
            axs[i, j].imshow(generated_images[i * 4 + j, 0], cmap='gray')
            axs[i, j].axis('off')
    plt.show()

show_generated_images(generator)

이와 같은 방법으로 GAN을 구축하고 학습시켜 생성된 이미지를 확인할 수 있습니다. GAN의 활용 가능성은 매우 넓으며, 이를 통해 창의적인 작업을 수행할 수 있습니다. 이제 여러분도 GAN의 세계에 한 걸음 더 가까워질 수 있습니다!

6. 결론

Generative Adversarial Networks는 매우 흥미로운 딥러닝 분야로, 많은 연구와 개발에서 활발히 사용되고 있습니다. 이번 강좌에서는 파이토치를 활용하여 GAN의 기본 원리와 구조를 살펴보았고, 실제로 딥러닝 모델을 구축하고 학습하는 과정을 다루었습니다. 여러분이 이 강좌를 통해 GAN에 대한 깊은 이해와 흥미를 느끼길 바라며, 앞으로의 딥러닝 여정에 큰 도움이 되길 바랍니다.


파이토치를 활용한 GAN 딥러닝, WGAN – 와서스테인 GAN

딥러닝의 발전과 함께 이미지 생성, 강화학습, 이미지 변환, 이미지 결합 등 다양한 분야에서 Generative Adversarial Networks(GANs)의 사용이 증가하고 있습니다. GAN은 두 네트워크, 즉 생성자(Generator)와 판별자(Discriminator) 간의 경쟁을 통해 고해상도의 이미지를 생성하는데 사용됩니다. 본 글에서는 GAN의 기본 개념과 함께 WGAN(Wasserstein GAN)의 구조와 작동 방식, 이를 구현하기 위한 파이토치 예제 코드를 다루겠습니다.

1. GAN의 기본 개념

GAN은 Ian Goodfellow가 2014년에 제안한 모델로, 생성자와 판별자라는 두 개의 신경망으로 구성됩니다. 생성자는 난수 벡터를 입력받아 실제와 유사한 데이터를 생성하고, 판별자는 입력 데이터가 실제 데이터인지 생성된 데이터인지를 판단합니다. 이 과정에서 두 신경망은 서로 경쟁하면서 점점 완벽한 데이터를 생성하는 방향으로 학습하게 됩니다.

1.1 GAN의 구조

  • Generator (G): 랜덤 노이즈를 입력으로 받아 데이터를 생성하는 네트워크입니다.
  • Discriminator (D): 실제 데이터와 생성된 데이터 간의 차이를 구별하는 네트워크입니다.

1.2 GAN의 손실 함수

GAN의 손실 함수는 다음과 같습니다.

    L(D) = -E[log(D(x))] - E[log(1 - D(G(z)))],
    L(G) = -E[log(D(G(z)))]
    

여기서 D(x)는 실제 데이터의 진짜로 판단할 확률이며, G(z)는 생성자가 생성한 데이터입니다.

2. WGAN – 와서스테인 GAN

기존의 GAN은 판별자의 손실 함수가 안정적이지 않고 학습이 불안정하다는 문제점이 있었습니다. WGAN은 Wasserstein Distance를 사용하여 이러한 문제점을 해결합니다. Wasserstein 거리(혹은 Earth Mover’s Distance)는 두 확률 분포 간의 최적 운반 비용을 측정하는 방법입니다.

2.1 WGAN의 개선점

  • WGAN은 판별자 대신 비선형 회귀모델인 ‘Critic’을 사용합니다.
  • WGAN의 손실 함수는 다음과 같습니다:
                L(D) = E[D(x)] - E[D(G(z))],
                L(G) = -E[D(G(z))]
                
  • WGAN은 Weight Clipping을 통해 Critic의 Lipschitz 연속성을 보장합니다.
  • Gradient Penalty 기법을 사용하여 Lipschitz 제약 조건을 완화합니다.

2.2 WGAN의 구조

WGAN은 기본 GAN의 구조에서 Critic을 도입해 수정된 형태입니다. 다음은 WGAN의 네트워크 구조입니다:

  • 이전의 판별자는 현재의 Critic으로 대체됩니다.

3. 파이토치를 활용한 WGAN 구현

이제 파이토치로 WGAN을 구현해 보겠습니다. 이번 예제는 MNIST 데이터셋을 사용하여 손글씨 숫자를 생성하는모델을 구축할 것입니다.

3.1 데이터셋 준비

먼저 데이터셋을 불러오고 전처리합니다.


import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader

# 데이터셋을 로드하고 전처리합니다.
transform = transforms.Compose([
    transforms.Resize(28),
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])

train_data = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
train_loader = DataLoader(train_data, batch_size=64, shuffle=True)
    

3.2 WGAN 모델 정의

이제 Generator와 Critic 모델을 정의할 차례입니다.


# 생성자 모델 정의
class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        self.model = nn.Sequential(
            nn.Linear(100, 256),
            nn.ReLU(),
            nn.Linear(256, 512),
            nn.ReLU(),
            nn.Linear(512, 1024),
            nn.ReLU(),
            nn.Linear(1024, 784),
            nn.Tanh()
        )
        
    def forward(self, z):
        return self.model(z).view(-1, 1, 28, 28)  # 28x28 이미지로 변형

# 비평가(critic) 모델 정의
class Critic(nn.Module):
    def __init__(self):
        super(Critic, self).__init__()
        self.model = nn.Sequential(
            nn.Linear(784, 512),
            nn.LeakyReLU(0.2),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2),
            nn.Linear(256, 1)
        )
    
    def forward(self, img):
        return self.model(img.view(-1, 784))  # 784 차원으로 변형
    

3.3 WGAN 학습 과정

이제 WGAN의 학습 과정을 정의합니다.


def train_wgan(num_epochs):
    generator = Generator()
    critic = Critic()
    
    # 옵티마이저 설정
    optimizer_G = optim.RMSprop(generator.parameters(), lr=0.00005)
    optimizer_C = optim.RMSprop(critic.parameters(), lr=0.00005)

    for epoch in range(num_epochs):
        for i, (imgs, _) in enumerate(train_loader):
            imgs = imgs.to(device)

            # Critic 잔여 방정식
            optimizer_C.zero_grad()
            z = torch.randn(imgs.size(0), 100).to(device)
            fake_imgs = generator(z)
            c_real = critic(imgs)
            c_fake = critic(fake_imgs.detach())
            c_loss = c_fake.mean() - c_real.mean()
            c_loss.backward()
            optimizer_C.step()

            # Weight Clipping
            for p in critic.parameters():
                p.data.clamp_(-0.01, 0.01)

            # Generator 업데이트
            if i % 5 == 0:
                optimizer_G.zero_grad()
                g_loss = -critic(fake_imgs).mean()
                g_loss.backward()
                optimizer_G.step()
            
        print(f'Epoch [{epoch}/{num_epochs}], Loss C: {c_loss.item()}, Loss G: {g_loss.item()}')

# GPU 사용 설정
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
train_wgan(num_epochs=50)
    

3.4 결과 시각화

학습이 끝나면 생성된 이미지를 시각화하여 결과를 확인합니다.


import matplotlib.pyplot as plt

def show_generated_images(num_images):
    z = torch.randn(num_images, 100).to(device)
    generated_imgs = generator(z).cpu().detach()
    
    fig, axes = plt.subplots(1, num_images, figsize=(15, 15))
    for i in range(num_images):
        axes[i].imshow(generated_imgs[i][0], cmap='gray')
        axes[i].axis('off')
    plt.show()

# 결과 시각화
show_generated_images(5)
    

4. 결론

WGAN은 기존 GAN의 문제점을 극복하기 위해 Wasserstein Distance를 이용하여 더 안정적인 학습 과정을 제공합니다. 본 글에서는 파이토치를 활용하여 WGAN을 구현하는 방법을 소개하였으며, 이를 통해 생성적 적대 신경망의 이해를 높일 수 있기를 바랍니다. GAN과 그 변형 모델들은 이미지 생성뿐만 아니라 다양한 분야에서 혁신적인 결과를 가져올 수 있는 강력한 도구입니다.

5. 참고문헌

  • Ian J. Goodfellow et al., “Generative Adversarial Nets”, 2014.
  • Martin Arjovsky et al., “Wasserstein Generative Adversarial Networks”, 2017.
  • PyTorch Documentation: https://pytorch.org/docs/stable/index.html

파이토치를 활용한 GAN 딥러닝, 강화학습

1. 소개

Generative Adversarial Networks (GANs)는 2014년 Ian Goodfellow에 의해 제안된 모델로, 두 신경망 간의 경쟁을 통해 데이터를 생성하는 모델입니다. GAN은 특히 이미지 생성, 스타일 변환, 데이터 증강 등에 널리 사용되고 있습니다. 이번 포스팅에서는 GAN의 기본 구조, 파이토치를 이용한 구현 방법, 강화학습의 기본 개념 및 여러 응용 사례를 소개하겠습니다.

2. GAN의 기본 구조

GAN은 두 개의 신경망으로 구성됩니다: 생성자(Generator)와 판별자(Discriminator)입니다. 생성자는 무작위 노이즈를 입력으로 받아 새로운 데이터를 생성하고, 판별자는 입력 데이터가 진짜 데이터인지 생성된 데이터인지를 구별합니다. 이 두 네트워크는 서로 경쟁하면서 학습합니다.

2.1 생성자 (Generator)

생성자는 노이즈 벡터를 받아 진짜처럼 보이는 데이터를 생성합니다. 목표는 판별자를 속이는 것입니다.

2.2 판별자 (Discriminator)

판별자는 입력 데이터의 진위 여부를 판단합니다. 진짜 데이터일 경우 1, 생성된 데이터일 경우 0을 출력합니다.

2.3 GAN의 손실 함수

GAN의 손실 함수는 다음과 같이 설정됩니다:

min_G max_D V(D, G) = E[log(D(x))] + E[log(1 - D(G(z)))]

여기서 E는 기대값을 나타내며, x는 진짜 데이터, G(z)는 생성자가 생성한 데이터입니다. 생성자는 손실을 최소화하려고 하고, 판별자는 손실을 최대화하려 하고 있습니다.

3. 파이토치를 활용한 GAN 구현

이제 GAN을 파이토치로 구현해 보겠습니다. 데이터셋으로는 MNIST 손글씨 숫자 데이터를 사용할 것입니다.

3.1 데이터셋 준비

import torch
import torchvision
from torchvision import datasets, transforms

# 데이터 변환 및 다운로드
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])

# MNIST 데이터셋
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)

3.2 생성자 (Generator) 모델 정의

import torch.nn as nn

class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        self.layer1 = nn.Sequential(
            nn.Linear(100, 256),
            nn.ReLU(True)
        )
        self.layer2 = nn.Sequential(
            nn.Linear(256, 512),
            nn.ReLU(True)
        )
        self.layer3 = nn.Sequential(
            nn.Linear(512, 1024),
            nn.ReLU(True)
        )
        self.layer4 = nn.Sequential(
            nn.Linear(1024, 28*28),
            nn.Tanh()  # 픽셀 값은 -1과 1 사이
        )
    
    def forward(self, z):
        out = self.layer1(z)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        return out.view(-1, 1, 28, 28)  # 이미지 형태로 변환

3.3 판별자 (Discriminator) 모델 정의

class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()
        self.layer1 = nn.Sequential(
            nn.Linear(28*28, 1024),
            nn.LeakyReLU(0.2, inplace=True)
        )
        self.layer2 = nn.Sequential(
            nn.Linear(1024, 512),
            nn.LeakyReLU(0.2, inplace=True)
        )
        self.layer3 = nn.Sequential(
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2, inplace=True)
        )
        self.layer4 = nn.Sequential(
            nn.Linear(256, 1),
            nn.Sigmoid()  # 출력값을 0과 1 사이로
        )
    
    def forward(self, x):
        out = self.layer1(x.view(-1, 28*28))  # 평탄화
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        return out

3.4 모델 훈련

import torch.optim as optim

# 모델 초기화
generator = Generator()
discriminator = Discriminator()

# 손실 함수 및 최적화기 설정
criterion = nn.BCELoss()  # Binary Cross Entropy Loss
optimizer_g = optim.Adam(generator.parameters(), lr=0.0002)
optimizer_d = optim.Adam(discriminator.parameters(), lr=0.0002)

# 훈련
num_epochs = 200
for epoch in range(num_epochs):
    for i, (images, _) in enumerate(train_loader):
        # 진짜 데이터 레이블
        real_labels = torch.ones(images.size(0), 1)
        fake_labels = torch.zeros(images.size(0), 1)

        # 판별자 훈련
        optimizer_d.zero_grad()
        outputs = discriminator(images)
        d_loss_real = criterion(outputs, real_labels)
        d_loss_real.backward()
        
        z = torch.randn(images.size(0), 100)
        fake_images = generator(z)
        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()
    
    if (epoch+1) % 10 == 0:
        print(f'Epoch [{epoch+1}/{num_epochs}], d_loss: {d_loss_real.item() + d_loss_fake.item():.4f}, g_loss: {g_loss.item():.4f}')

3.5 결과 시각화

import matplotlib.pyplot as plt

# 생성된 이미지 시각화 함수
def plot_generated_images(generator, n=10):
    z = torch.randn(n, 100)
    with torch.no_grad():
        generated_images = generator(z).cpu()
    generated_images = generated_images.view(-1, 28, 28)
    
    plt.figure(figsize=(10, 1))
    for i in range(n):
        plt.subplot(1, n, i+1)
        plt.imshow(generated_images[i], cmap='gray')
        plt.axis('off')
    plt.show()

# 이미지 생성
plot_generated_images(generator)

4. 강화학습의 기본 개념

강화학습(Reinforcement Learning, RL)은 에이전트가 환경과 상호작용하며 최적의 행동을 학습하는 기계 학습의 한 분야입니다. 에이전트는 상태를 관찰하고, 행동을 선택하고, 보상을 받으며, 이를 통해 최적의 정책을 학습합니다.

4.1 강화학습의 구성 요소

  • 상태 (State): 에이전트가 현재의 환경을 나타내는 정보입니다.
  • 행동 (Action): 에이전트가 현재 상태에서 수행할 수 있는 작업입니다.
  • 보상 (Reward): 에이전트가 행동을 수행한 후에 환경으로부터 받는 피드백입니다.
  • 정책 (Policy): 에이전트가 각 상태에서 취할 행동의 확률 분포를 나타냅니다.

4.2 강화학습 알고리즘

  • Q-Learning: 가치 기반 방법으로, Q 값을 학습하여 최적의 정책을 유도합니다.
  • 정책 경사 방법 (Policy Gradient): 정책을 직접 학습하는 방법입니다.
  • Actor-Critic: 가치 함수와 정책을 동시에 학습하는 방법입니다.

4.3 파이토치를 활용한 강화학습 구현

간단한 강화학습 구현을 위해 OpenAI의 Gym 라이브러리를 사용할 것입니다. 여기서는 CartPole 환경을 다루겠습니다.

4.3.1 Gym 환경 설정

import gym

# Gym 환경 생성
env = gym.make('CartPole-v1')  # CartPole 환경

4.3.2 DQN 모델 정의

class DQN(nn.Module):
    def __init__(self, input_size, num_actions):
        super(DQN, self).__init__()
        self.fc1 = nn.Linear(input_size, 24)
        self.fc2 = nn.Linear(24, 24)
        self.fc3 = nn.Linear(24, num_actions)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        return self.fc3(x)

4.3.3 모델 훈련

def train_dqn(env, num_episodes):
    model = DQN(input_size=env.observation_space.shape[0], num_actions=env.action_space.n)
    optimizer = optim.Adam(model.parameters())
    criterion = nn.MSELoss()

    for episode in range(num_episodes):
        state = env.reset()
        state = torch.FloatTensor(state)
        done = False
        total_reward = 0

        while not done:
            q_values = model(state)
            action = torch.argmax(q_values).item()  # 또는 epsilon-greedy 정책 사용

            next_state, reward, done, _ = env.step(action)
            next_state = torch.FloatTensor(next_state)

            total_reward += reward

            # DQN 업데이트 로직 추가 필요

            state = next_state

        print(f'Episode {episode+1}, Total Reward: {total_reward}')  

    return model

# DQN 훈련 시작
train_dqn(env, num_episodes=1000)

5. 결론

이번 포스팅에서는 GAN과 강화학습의 기본 개념 및 파이토치를 활용한 구현 방법에 대해 알아보았습니다. GAN은 데이터 생성에 매우 유용한 모델이고, 강화학습은 에이전트가 최적의 정책을 학습하도록 돕는 기법입니다. 이러한 기술들은 다양한 분야에서 응용될 수 있으며, 앞으로의 연구와 발전이 기대됩니다.

6. 참고 자료

파이토치를 활용한 GAN 딥러닝, WGAN-GP

Generative Adversarial Networks(이하 GAN)는 Ian Goodfellow가 2014년에 제안한 강력한 생성 모델입니다. GAN은 두 개의 신경망, 즉 생성자(Generator)와 판별자(Discriminator)로 구성되며, 이 두 네트워크는 서로 경쟁하여 학습합니다. 생성자는 실제 데이터와 유사한 새로운 데이터를 생성하려고 하고, 판별자는 주어진 데이터가 실제 데이터인지 생성된 데이터인지를 구별하려고 합니다. 이들은 지속적으로 개선되면서 최종적으로 매우 평범한 데이터를 신뢰성 있게 생성할 수 있게 됩니다.

본 글에서는 GAN의 변형인 Wasserstein GAN with Gradient Penalty (WGAN-GP)에 대해 설명하고, 파이토치를 사용하여 WGAN-GP를 구현해보겠습니다. WGAN-GP는 Wasserstein 거리(Wasserstein Distance)를 기반으로 하며, 판별자에서의 Gradient Penalty를 추가하여 훈련의 안정성을 높입니다.

1. GAN의 기본 구조

GAN의 기본 구조는 다음과 같습니다.

  • 생성자(Generator): 랜덤 노이즈를 입력받아 가짜 데이터를 생성합니다.
  • 판별자(Discriminator): 실제 데이터와 생성자가 만든 가짜 데이터를 입력받아 이 둘이 얼마나 유사한지를 판단합니다.

GAN의 학습 과정은 다음의 두 단계로 이루어집니다.

이 과정은 반복적으로 수행되어 두 네트워크 모두 개선됩니다. 다만, 기존 GAN은 훈련 불안정성과 모드 붕괴(mode collapse) 문제가 종종 발생하여 보다 안정적인 학습을 위한 여러 접근법이 연구되었습니다.

2. WGAN-GP 소개

WGAN에서는 Wasserstein 거리 개념을 도입하여 GAN의 본질적인 문제를 해결하고자 하였습니다. Wasserstein 거리는 두 분포 간의 차이를 보다 명확히 정의할 수 있어, 네트워크를 훈련하는 데 유리합니다. WGAN의 핵심 아이디어는 판별자가 아닌 “비평가(critic)”라는 개념을 도입하는 것입니다. 비평가는 생성된 데이터와 실제 데이터 간의 거리를 평가하고, 이 평가 결과를 바탕으로 평균 제곱 오차(MSE) 손실이 아닌 Wasserstein 손실을 사용하여 네트워크를 업데이트합니다.

WGAN에서 Gradient Penalty (GP)를 추가함으로써 판별자의 Lipschitz 조건을 준수할 수 있도록 하여 훈련의 안정성을 더욱 높였습니다. Gradient Penalty는 다음과 같이 정의됩니다:

GP = λ * E[(||∇D(x) ||2 - 1)²]

여기서 λ는 하이퍼파라미터이며, D(x)는 판별자의 출력입니다. Gradient Penalty를 통해 판별자의 기울기가 1로 유지되도록 강화할 수 있습니다. 이 방식을 통해 WGAN-GP는 GAN의 불안정을 극복하고 보다 안정적인 훈련이 가능해집니다.

3. WGAN-GP 파이토치 구현

이제 파이토치를 사용하여 WGAN-GP를 구현해보겠습니다. 다음의 단계로 진행됩니다:

  1. 필요한 라이브러리 설치 및 데이터셋 불러오기
  2. 생성자 및 판별자 모델 정의
  3. WGAN-GP 훈련 루프 구현
  4. 결과 시각화

3.1 라이브러리 설치 및 데이터셋 불러오기

우선 필요한 라이브러리를 설치하고, MNIST 데이터셋을 불러옵니다.

!pip install torch torchvision matplotlib
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.datasets as dsets
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np

3.2 생성자 및 판별자 모델 정의

생성자와 판별자 모델을 정의합니다. 생성자는 랜덤한 노이즈 벡터를 입력으로 받아 이미지로 변환하고, 판별자는 입력된 이미지를 바탕으로 진짜인지 가짜인지를 평가합니다.

class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()
        self.model = nn.Sequential(
            nn.Linear(100, 256),
            nn.ReLU(True),
            nn.Linear(256, 512),
            nn.ReLU(True),
            nn.Linear(512, 1024),
            nn.ReLU(True),
            nn.Linear(1024, 28 * 28),
            nn.Tanh()
        )
    
    def forward(self, z):
        return self.model(z).reshape(-1, 1, 28, 28)

class Critic(nn.Module):
    def __init__(self):
        super(Critic, self).__init__()
        self.model = nn.Sequential(
            nn.Linear(28 * 28, 1024),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(1024, 512),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(256, 1)
        )

    def forward(self, x):
        return self.model(x.view(-1, 28 * 28))

3.3 WGAN-GP 훈련 루프 구현

이제 WGAN-GP의 훈련 루프를 구현합니다. 훈련 과정에서는 판별자를 일정 횟수만큼 업데이트한 후 생성자를 업데이트합니다. Gradient Penalty도 손실에 포함됩니다.

def compute_gradient_penalty(critic, real_samples, fake_samples):
    alpha = torch.rand(real_samples.size(0), 1, 1, 1).expand_as(real_samples)
    interpolated_samples = alpha * real_samples + (1 - alpha) * fake_samples
    interpolated_samples.requires_grad_(True)

    d_interpolated = critic(interpolated_samples)

    gradients = torch.autograd.grad(outputs=d_interpolated, inputs=interpolated_samples,
                                    grad_outputs=torch.ones_like(d_interpolated),
                                    create_graph=True, retain_graph=True)[0]

    gradient_penalty = ((gradients.norm(2, dim=1) - 1) ** 2).mean()
    return gradient_penalty
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

generator = Generator().to(device)
critic = Critic().to(device)

learning_rate = 0.00005
num_epochs = 100
critic_iterations = 5
lambda_gp = 10

criterion = nn.MSELoss()
optimizer_generator = optim.Adam(generator.parameters(), lr=learning_rate)
optimizer_critic = optim.Adam(critic.parameters(), lr=learning_rate)

Real_data = dsets.MNIST(root='./data', train=True, transform=transforms.ToTensor(), download=True)

data_loader = torch.utils.data.DataLoader(Real_data, batch_size=64, shuffle=True)

for epoch in range(num_epochs):
    for i, (real_images, _) in enumerate(data_loader):
        real_images = real_images.to(device)

        for _ in range(critic_iterations):
            optimizer_critic.zero_grad()

            # Generate fake images
            z = torch.randn(real_images.size(0), 100).to(device)
            fake_images = generator(z)

            # Get critic scores
            real_validity = critic(real_images)
            fake_validity = critic(fake_images)
            gradient_penalty = compute_gradient_penalty(critic, real_images.data, fake_images.data)

            # Compute loss
            critic_loss = -torch.mean(real_validity) + torch.mean(fake_validity) + lambda_gp * gradient_penalty
            critic_loss.backward()
            optimizer_critic.step()

        # Update generator
        optimizer_generator.zero_grad()
        
        # Get generator score
        fake_images = generator(z)
        validity = critic(fake_images)
        generator_loss = -torch.mean(validity)
        generator_loss.backward()
        optimizer_generator.step()

    if epoch % 10 == 0:
        print(f"Epoch: {epoch}/{num_epochs}, Critic Loss: {critic_loss.item():.4f}, Generator Loss: {generator_loss.item():.4f}")

3.4 결과 시각화

최종적으로 생성된 이미지를 시각화합니다. 이는 학습 과정에서 생성자가 얼마나 잘 학습되었는지를 확인하는 좋은 방법입니다.

def show_generated_images(generator, num_images=25):
    z = torch.randn(num_images, 100).to(device)
    generated_images = generator(z).cpu().detach().numpy()

    plt.figure(figsize=(5, 5))
    for i in range(num_images):
        plt.subplot(5, 5, i + 1)
        plt.imshow(generated_images[i][0], cmap='gray')
        plt.axis('off')
    plt.show()

show_generated_images(generator)

4. 결론

이 글에서는 GAN의 변형 모델인 WGAN-GP에 대해 설명하고, 파이토치를 활용하여 WGAN-GP를 구현해보았습니다. WGAN-GP는 Wasserstein 거리와 Gradient Penalty를 활용하여 보다 안정적인 훈련이 가능하다는 장점을 가지고 있습니다. 이러한 GAN 계열 모델은 이미지 생성, 이미지 변환, 스타일 전이 등 다양한 분야에 활용될 수 있습니다.

딥러닝의 발전과 함께 GAN과 그 변형 모델들은 계속해서 주목받고 있으며, 앞으로의 발전이 기대됩니다. 여러분도 GAN 및 WGAN-GP를 활용하여 다양한 프로젝트에 도전해보시기를 바랍니다!