출처 : https://dreamgonfly.github.io/blog/gan-explained/

Code

라이브러리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# <코드1> 라이브러리 및 데이터 불러오기

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

#데이터 전처리 방식을 지정한다.
transform = transforms.Compose([
  transforms.ToTensor(), # 데이터를 파이토치의 Tensor 형식으로바꾼다.
  transforms.Normalize(mean=(0.5,), std=(0.5,)) # 픽셀값 0 ~ 1 -> -1 ~ 1
])

#MNIST 데이터셋을 불러온다. 지정한 폴더에 없을 경우 자동으로 다운로드한다.
mnist =datasets.MNIST(root='data', download=True, transform=transform)

#데이터를 한번에 batch_size만큼만 가져오는 dataloader를 만든다.
dataloader =DataLoader(mnist, batch_size=60, shuffle=True)

Generator

random vector z를 받아 가짜 이미지를 생성한다. 이때 random vector z는 Uniform Dist 혹은 normal Dist로 부터 추출한다.

간단한 distribution을 generator를 통해 real data dist로 만들어준다. 만약 생성자 모델에 충분한 수의 매개변수가 있다면 어떤 복잡한 분포도 근사 가능하다.

latent space : z가 존재하는 공간(코드에서는 100차원으로 생성)

4개의 선형 layer(Linear Layer, FC Layer, Linear transform)을 쌓아 생성자를 만들었다.

dimension : 100 -> 256 -> 512 -> 1024 -> 28*28

activation : LeakyReLU(뉴런의 출력 0 이하는 그대로, 0보다 크면 정해진 작은 숫자를 곱한다. 코드에서는 0.2)

생성자의 마지막 layer는 출력을 픽셀값의 범위인 -1 ~ 1로 만들기 위해 tanh를 사용했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# <코드2> GAN의 생성자(Generator)

# 생성자는 랜덤 벡터 z를 입력으로 받아 가짜 이미지를 출력한다.
class Generator(nn.Module):

  # 네트워크 구조
    def __init__(self):
      super(Generator, self).__init__()
      self.main = nn.Sequential(
        nn.Linear(in_features=100, out_features=256),
        nn.LeakyReLU(0.2, inplace=True),
        nn.Linear(in_features=256, out_features=512),
        nn.LeakyReLU(0.2, inplace=True),
        nn.Linear(in_features=512, out_features=1024),
        nn.LeakyReLU(0.2, inplace=True),
        nn.Linear(in_features=1024, out_features=28*28),
        nn.Tanh())
    
  # (batch_size x 100) 크기의 랜덤 벡터를 받아 
  # 이미지를 (batch_size x 1 x 28 x 28) 크기로 출력한다.
    def forward(self, inputs):
      return self.main(inputs).view(-1, 1, 28, 28)

Discriminator

input : 이미지

output : 진짜일 확률(0~1 사이)

Discriminator는 Generator의 구조에서 dropout이 추가된 구조이다.

dropout : 무작위로 p 만큼의 뉴런을 사용하지 않음으로써 overfitting을 막고 구분자가 생성자보다 지나치게 빨리 학습되는것을 방지

출력값을 0~1 사이로 만들기 위해 Sigmoid를 사용했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# <코드3> GAN의 구분자(Discriminator)

# 구분자는 이미지를 입력으로 받아 이미지가 진짜인지 가짜인지 출력한다.
class Discriminator(nn.Module):
    
# 네트워크 구조
  def __init__(self):
    super(Discriminator, self).__init__()
    self.main = nn.Sequential(
      nn.Linear(in_features=28*28, out_features=1024),
      nn.LeakyReLU(0.2, inplace=True),
      nn.Dropout(inplace=True),
      nn.Linear(in_features=1024, out_features=512),
      nn.LeakyReLU(0.2, inplace=True),
      nn.Dropout(inplace=True),
      nn.Linear(in_features=512, out_features=256),
      nn.LeakyReLU(0.2, inplace=True),
      nn.Dropout(inplace=True),
      nn.Linear(in_features=256, out_features=1),
      nn.Sigmoid())
    
  # (batch_size x 1 x 28 x 28) 크기의 이미지를 받아
  # 이미지가 진짜일 확률을 0~1 사이로 출력한다.
   def forward(self, inputs):
    inputs = inputs.view(-1, 28*28)
    return self.main(inputs)

Training

1
2
3
4
# <코드4> 생성자와 구분자 객체 만들기

G = Generator()
D = Discriminator()

loss function : Bunary Cross Entropy

optimizer : Adam

1
2
3
4
5
6
7
8
9
# <코드5> 손실 함수와 최적화 기법 지정하기

# Binary Cross Entropy loss
criterion = nn.BCELoss()

# 생성자의 매개 변수를 최적화하는 Adam optimizer
G_optimizer = Adam(G.parameters(), lr=0.0002, betas=(0.5, 0.999))
# 구분자의 매개 변수를 최적화하는 Adam optimizer
D_optimizer = Adam(D.parameters(), lr=0.0002, betas=(0.5, 0.999))

epoch : 100

Batch_size : 60

1
2
3
4
5
6
7
8
9
10
11
12
# <코드6> 모델 학습을 위한 반복문

# 데이터셋을 100번 돌며 학습한다.
for epoch in range(100):

  # 한번에 batch_size만큼 데이터를 가져온다.
    for real_data, _ in dataloader:
      batch_size = real_data.size(0)
        
  # 데이터를 파이토치의 변수로 변환한다.
      real_data = Variable(real_data)
      # ...(중략)

Training Discriminator

구분자의 손실 함수는 2가지의 합으로 계산된다.

진짜 이미지와 1과의 차이 + 가짜 이미지와 0과의 차이

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# <코드7> 구분자 학습시키기

    # 이미지가 진짜일 때 정답 값은 1이고 가짜일 때는 0이다.
    # 정답지에 해당하는 변수를 만든다.
    target_real = Variable(torch.ones(batch_size, 1))
    target_fake = Variable(torch.zeros(batch_size, 1))

    # 진짜 이미지를 구분자에 넣는다.
    D_result_from_real = D(real_data)

    # 구분자의 출력값이 정답지인 1에서 멀수록 loss가 높아진다.
    D_loss_real = criterion(D_result_from_real, target_real)

    # 생성자에 입력으로 줄 랜덤 벡터 z를 만든다.
    z = Variable(torch.randn((batch_size, 100)))

    # 생성자로 가짜 이미지를 생성한다.
    fake_data = G(z)

    # 생성자가 만든 가짜 이미지를 구분자에 넣는다.
    D_result_from_fake = D(fake_data)

    # 구분자의 출력값이 정답지인 0에서 멀수록 loss가 높아진다.
    D_loss_fake = criterion(D_result_from_fake, target_fake)

    # 구분자의 loss는 두 문제에서 계산된 loss의 합이다.
    D_loss = D_loss_real + D_loss_fake

    # 구분자의 매개 변수의 미분값을 0으로 초기화한다.
    D.zero_grad()

    # 역전파를 통해 매개 변수의 loss에 대한 미분값을 계산한다.
    D_loss.backward()

    # 최적화 기법을 이용해 구분자의 매개 변수를 업데이트한다.
    D_optimizer.step()

Training Generator

loss : 생성된 이미지를 Discriminator에 넣었을 떄 출력과 1과의 차이

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# <코드8> 생성자 학습시키기

    # 생성자에 입력으로 줄 랜덤 벡터 z를 만든다.
    z = Variable(torch.randn((batch_size, 100)))
    z = z.cuda()

    # 생성자로 가짜 이미지를 생성한다.
    fake_data = G(z)

    # 생성자가 만든 가짜 이미지를 구분자에 넣는다.
    D_result_from_fake = D(fake_data)

    # 생성자의 입장에서 구분자의 출력값이 1에서 멀수록 loss가 높아진다.
    G_loss = criterion(D_result_from_fake, target_real)

    # 생성자의 매개 변수의 미분값을 0으로 초기화한다.
    G.zero_grad()

    # 역전파를 통해 매개 변수의 loss에 대한 미분값을 계산한다.
    G_loss.backward()

    # 최적화 기법을 이용해 생성자의 매개 변수를 업데이트한다.
    G_optimizer.step()

DCGAN

Deep Convolutional Network

  • 원래의 GAN은 학습이 어려웠기 때문에 안정적인 학습이 가능한 DCGAN이 많이 쓰이고 있다.
  • Batch Normalizatoin을 사용하여 layer의 입력 데이터 분포가 치우쳐져 있는 경우 평균, 분산을 조정한다.
  • 마지막 layer를 제외하고 생성자의 모든 layer에 ReLU 사용한다.
  • 구분자의 모든 Layer에 LeakyReLU를 사용한다.
  • latent space에 데이터의 특성이 투영되었는지 확인하여 학습이 잘 이뤄졌는지 검증할 수 있다.
  • latent space에 이미지의 특성이 나타나는데, 해당 값을 바꿈으로써 이미지의 특성을 바꿀 수 있다.

Generator

GAN과 마찬가지로 random vector z를 받고 가짜 이미지 생성한다.

구현시 Transposed Convolution, Batch Normalization이 쓰인다는 점이 차이가 있다.

input : B * 100

output : B * 1 * 28 * 28

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# <코드9> DCGAN의 생성자

class Generator(nn.Module):
    
  # 네트워크 구조
  def __init__(self):
    super(Generator, self).__init__()
    self.main = nn.Sequential(
      nn.ConvTranspose2d(in_channels=100, out_channels=28*8, 
        kernel_size=7, stride=1, padding=0, 
        bias=False),
      nn.BatchNorm2d(num_features=28*8),
      nn.ReLU(inplace=True),
      nn.ConvTranspose2d(in_channels=28*8, out_channels=28*4, 
        kernel_size=4, stride=2, padding=1, 
        bias=False),
      nn.BatchNorm2d(num_features=28*4),
      nn.ReLU(True),
      nn.ConvTranspose2d(in_channels=28*4, out_channels=1, 
        kernel_size=4, stride=2, padding=1, 
        bias=False),
      nn.Tanh())
        
  # (batch_size x 100) 크기의 랜덤 벡터를 받아 
  # 이미지를 (batch_size x 1 x 28 x 28) 크기로 출력한다.
  def forward(self, inputs):
    inputs = inputs.view(-1, 100, 1, 1)
    return self.main(inputs)

Discriminator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# <코드10> DCGAN의 구분자

class Discriminator(nn.Module):
    
  # 네트워크 구조
  def __init__(self):
    super(Discriminator, self).__init__()
    self.main = nn.Sequential(
      nn.Conv2d(in_channels=1, out_channels=28*4, 
        kernel_size=4, stride=2, padding=1, 
        bias=False),
      nn.BatchNorm2d(num_features=28*4),
      nn.LeakyReLU(0.2, inplace=True),
      nn.Conv2d(in_channels=28*4, out_channels=28*8, 
        kernel_size=4, stride=2, padding=1, 
        bias=False),
      nn.BatchNorm2d(num_features=28*8),
      nn.LeakyReLU(0.2, inplace=True),
      nn.Conv2d(in_channels=28*8, out_channels=1, 
        kernel_size=7, stride=1, padding=0, 
        bias=False),
      nn.Sigmoid())
        
  # (batch_size x 1 x 28 x 28) 크기의 이미지를 받아
  # 이미지가 진짜일 확률을 0~1 사이로 출력한다.
  def forward(self, inputs):
    o = self.main(inputs)
    return o.view(-1, 1)

태그:

카테고리:

업데이트: