Processing math: 100%
본문 바로가기

비지도학습/GAN

[1주차] AE/VAE/GAN(수정중)

1. 오토 인코더(Auto Encoder)

"Neural network that is trained to attempt to copy its input to its output"

  • Auto Encoder = Encoder+ Decoder

  • 오토인코더의 목적은 어떤 데이터를 잘 압축하는 것, 어떤 데이터의 특징을 잘 뽑는 것, 어떤 데이터의 차원을 잘 줄이는 것이다.

  • 입출력이 동일한 네트워크 L(x,y)

  • L2Lossfunction=||xy||2

    L=Σni=1(yif(xi))2

  • z : latent variable, space(잠재 변수, 공간), feature(특징), hidden representation으로 불림

  • VAE나 GAN과 실제로는 응용 목적이 다름(차원 축소, 정보 검색, 이상 검출, 영상 처리, 기계 번역 등에 활용)

  • 최적화 공식

    argminA,BE[(x,BA(x))]
  • A : 인코더, B : 디코더

  • E : x의 분산에 대한 기대값

  • △ : 디코더의 결과와 인코더의 입력값 사이의 거리를 측정하는 재생성 손실함수(Restruction Loss function)

  • Convolutional auto encoder, conditional auto encoder,stacked auto encoder...
  • 코드

 

import os

import torch
import torchvision
from torch import nn
from torch.autograd import Variable
from torch.utils.data import DataLoader
from torchvision import transforms
from torchvision.datasets import MNIST
from torchvision.utils import save_image


def to_img(x):
    x = 0.5 * (x + 1)
    x = x.clamp(0, 1) #값을 0~1사이로 묶는다(relu와 같음)
    x = x.view(x.size(0), 1, 28, 28) #크기 변경: tensor의 크기(size)나 모양(shape)을 변경
    return x

class autoencoder(nn.Module):
    def __init__(self):
        super(autoencoder, self).__init__()
        self.encoder = nn.Sequential(
            nn.Linear(28 * 28, 128),
            nn.ReLU(True),
            nn.Linear(128, 64),
            nn.ReLU(True), nn.Linear(64, 12), nn.ReLU(True), nn.Linear(12, 3))
        self.decoder = nn.Sequential(
            nn.Linear(3, 12),
            nn.ReLU(True),
            nn.Linear(12, 64),
            nn.ReLU(True),
            nn.Linear(64, 128),
            nn.ReLU(True), nn.Linear(128, 28 * 28), nn.Tanh())

    def forward(self, x):
        x = self.encoder(x)
        x = self.decoder(x)
        return x


if not os.path.exists('./mlp_img'):
    os.mkdir('./mlp_img')
    print("Directory created!")

num_epochs = 100
batch_size = 128
learning_rate = 1e-3

img_transform = transforms.Compose([    # 샘플에 transform 적용
    transforms.ToTensor(),        # numpy array 이미지에서 tensor(torch) 이미지로 변경
                                  # 이미지의 경우 픽셀 값 하나는 0 ~ 255 값을 갖는다. 
																	#하지만 ToTensor()로 타입 변경시 0 ~ 1 사이의 값으로 바뀜.
    transforms.Normalize([0.5], [0.5])  # -1 ~ 1사이의 값으로 normalized 시킴
])

dataset = MNIST('./data', transform=img_transform)#,download=True)
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)


model = autoencoder().cuda()
criterion = nn.MSELoss() #출력과 대상간의 평균제곱오차(mean-squared error)를 계산
optimizer = torch.optim.Adam(
    model.parameters(), lr=learning_rate, weight_decay=1e-5)

for epoch in range(num_epochs):
    for data in dataloader:
        img, _ = data
        img = img.view(img.size(0), -1)
        img = Variable(img).cuda()
        # ===================forward=====================
        output = model(img)
        loss = criterion(output, img)
        # ===================backward====================
        # 역전파 단계 전에, Optimizer 객체를 사용하여 (모델의 학습 가능한 가중치인)
        # 갱신할 변수들에 대한 모든 변화도를 0으로 만듭니다. 이렇게 하는 이유는
        # 기본적으로 .backward()를 호출할 때마다 변화도가 버퍼(buffer)에 (덮어쓰지 않고)
        # 누적되기 때문입니다. 더 자세한 내용은 torch.autograd.backward에 대한 문서를
        # 참조하세요.
        optimizer.zero_grad()

        # 역전파 단계: 모델의 매개변수에 대한 손실의 변화도를 계산합니다.
        loss.backward()

        # Optimizer의 step 함수를 호출하면 매개변수가 갱신됩니다.
        optimizer.step()
    # ===================log========================
    print('epoch [{}/{}], loss:{:.4f}'
          .format(epoch + 1, num_epochs, loss.data))
    if epoch % 10 == 0:
        pic = to_img(output.cpu().data)
        save_image(pic, './mlp_img/image_{}.png'.format(epoch))

torch.save(model.state_dict(), './sim_autoencoder.pth') #모델저장
  • 결과

2. 변형 오토인코더, VAE(Variational Auto Encoder)

  • VAE의 목표

    1. Data와 같은 분포를 가지는 sample 분포에서 sample을 뽑은 후
    2. 새로운 것을 생성
  • 생김새는 Autoencoder와 비슷하지만 VAE는 목적이 반대임

    • 오토인코더는 Encoder를 위해 개발됨(차원 축소가 주요 목적)
    • VAE는 Decoder를 위해 개발됨 → Generative model으로 어떤 새로운 x를 만들어내는 것이 목적
  • VAE의 특징

    • 복잡한 데이터 생성 모델을 설계하고 대규모 set에 적응할 수 있도록 해줌

    • AutoEncoder의 확률 모델적 변형으로 모델로부터 input data와 유사한 새로운 데이터를 샘플링 생성 모델(Generative Model)

    • Decoder가 최소한 학습 데이터는 생성해낼 수 있게 됨 → 생성된 데이터가 학습 데이터와 닮음

    • input data를 잠재변수 로 encoding 한 후, 스스로 input을 복원해내는 방법

    • VAE의 loss함수는 input x와 복원된 x간의 loss로 정의

    • VAE의 AutoEncoder가 input을 따라 그리는 것에만 맞게 학습시킴

    • VAE는 data 분포가 잘 학습되기만 하면 sampling (=data generation)이 저절로 따라옴

    • 위의 그림과 같이 data의 분포를 학습하고 싶은데, 이 data가 다루기 힘들기 때문에 variational inference(변화 추론)하는 방법

    • VAE는 training data x(i), i=1~N, 들이 어떤 latent 표현 z로 부터 생성된다는 것이다. PCA(주성분분석)과 비슷하게 어떤 도메인 데이터 X가 좀 더 단순한 변수 Z로 표현이 가능하다는 것과 같은 개념이다.

    • 예를 들어 x가 얼굴 이미지면, z는 얼굴의 크기, 모양, 방향 등의 특징들을 나타내는 vector일 것이다. VAE는 이런 latent vector Z의 분포를 알아내어 샘플링 한 후 true conditional P(x|z(i))을 통해 그 Z에 대응되는 데이터 X를 샘플링 하는 것이 목적.

    • VAE의 구조도

       

       

       

      **1) *Input :* xiqΦ(x)μi,σi** **2) μi,σi,εizi(sampling=Reparameterization trick)** **3) zigθ(zi)pi *: output(generation)***

    1) Input : xiqΦ(x)μi,σi

     

     

    2) μi,σi,εizi(sampling=Reparameterization trick)

    • sampling : 어떤 data의 true 분포가 있으면 그 분포에서 하나를 뽑아 기존 DB에 있지 않은 새로운 data를 생성하기 위해

       

      ▪ 그냥 샘플링하면 Non-differentiable! 미분이 안되는 문제 발생

      "This means we cannot propagate the gradients from the reconstruction error to the encoder."

      • Reparameterization(sampling) : Backpropagation을 하기위함

       

      • 샘플링 시 z1과 z2는 같은 분포 :

        • "정규분포에서 z1를 샘플링하는 것"= "입실론을 정규분포(자세히는 N(0,1))에서 샘플링하고 그 값을 분산과 곱하고 평균을 더해 z2를 샘플링 → 코드에서 epsilon을 먼저 정규분포에서 random하게 뽑고, 그 epsilon을 exp(z_log_var)과 곱하고 z_mean을 더한다. 그렇게 형성된 값이 z가 된다.
        • 표준정규분포 : N(0,I) → 평균이 0이고 공분산이 I, 표준편차가 1인
      • Loss Function

        • 학습 데이터의 likelihood(가능도)를 최대화(maximize)하는 파라미터를 학습

           

          pθ(x)=pθ(z)pθ(x|z)dz

           

           

           

          이때, DKL(qϕ(z|x(i))||pθ(z|xi))는 intractable하기 때문에 계산❌❌

          → KL divergence ≥ 0

          • 기대값(Expected value) : 확률론에서 확률 변수의 기대값은 각 사건이 벌어졌을 때의 이득과 그 사건이 벌어질 확률을 곱한 것을 전체 사건에 대해 합한 값

          • Bayes' Rule

            p(z|x)=p(x|z)p(z)p(x)

          • KL Divergence

          • Variational Lower bound(ELBO)

         

        Reconstruction Error와 Regularization
        • Reconstruction Error : 원데이터에 대한 likelihood

          • input과 output의 차이점을 최소화(cross entropy)

            Eqϕ(z|xi)[log(p(xi|gθ(z)))]=log(p(xi|gθ(z)))1LΣzi,llog(pθ(xi|zi,l))log(pθ(xi|zi,l))=ΣDj=1xi,jlogpi,j+(1xi,j)log(1pi,j)

          • 현재 샘플링용 함수에 대한 negative log likelihood

          • xi에 대한 복원 오차(AutoEncoder 관점)

        • Regularization : KLD(Kulback-leibler divergence)

          • ***KL Divergence =***KL(qθ(z|xi)||p(z))

            12Jj=1(μ2i,j+σ2i,jln(σ2i,j)1)

            DKL(N0||N1)=12(tr(Σ11Σ0)+μ1μ0)TΣ11(μ1μ0)k+ln(detΣ1detΣ0)

            • x가 주어졌을 때 인코더를 통과해서 나오는 확률 z의 확률 분포가 정규분포 p(z)와의 거리가 최소
          • 현재 샘플링용 함수에 대한 추가 조건

          • 샘플링의 용의성/생성 데이터에 대한 통제성을 위한 조건을 prior에 부여하고 이와 유사해야 한다는 조건을 부여

    •  

    3) zigθ(zi)pi : output(generation)

  • 최적화

     

    가정 1 : [Encoder : appoximation class]

    -multivariate gaussian distribution with a diagonal covariance

    qϕ(z|xi) N(μi,σ2iI)

    가정 2 : [prior]

    -multivariate normal distribution (표준정규분포)

    -모든 표준편차가 1인 단위행렬로 단순화

    p(z) N(0,I)

    가정 3 : [Decoder,likelihood]

    -multivariate Bernoulli or Gaussian distribution

 

  • 코드
import torch
import torchvision
from torch import nn
from torch import optim
import torch.nn.functional as F
from torch.autograd import Variable
from torch.utils.data import DataLoader
from torchvision import transforms
from torchvision.utils import save_image
from torchvision.datasets import MNIST
import os

def to_img(x):
    x = x.clamp(0, 1)
    x = x.view(x.size(0), 1, 28, 28)
    return x

class VAE(nn.Module):
    def __init__(self):
        super(VAE, self).__init__()

				#--------------인코더---------------
        self.fc1 = nn.Linear(784, 400)
        self.fc21 = nn.Linear(400, 20)
        self.fc22 = nn.Linear(400, 20)
				#--------------디코더---------------
        self.fc3 = nn.Linear(20, 400)
        self.fc4 = nn.Linear(400, 784)

    def encode(self, x):
        h1 = F.relu(self.fc1(x))
        return self.fc21(h1), self.fc22(h1)

    def reparametrize(self, mu, logvar):
        std = logvar.mul(0.5).exp_() #표준편차
				
				#각각의 정규분포로부터의 랜덤한 숫자의 텐서
        if torch.cuda.is_available():
            eps = torch.cuda.FloatTensor(std.size()).normal_()
        else:
            eps = torch.FloatTensor(std.size()).normal_()
        eps = Variable(eps) #연산을 사용하여 순전파를 계산
        return eps.mul(std).add_(mu)#엡실론*표준편차+평균

    def decode(self, z):
        h3 = F.relu(self.fc3(z))
        return F.sigmoid(self.fc4(h3))

    def forward(self, x):
        mu, logvar = self.encode(x)
        z = self.reparametrize(mu, logvar)
        return self.decode(z), mu, logvar

def loss_function(recon_x, x, mu, logvar):
    """
    recon_x: generating images
    x: origin images
    mu: latent mean
    logvar: latent log variance
    """
		#2진분류계산
    BCE = reconstruction_function(recon_x, x)  # mse loss
    # loss = 0.5 * sum(1 + log(sigma^2) - mu^2 - sigma^2)
    KLD_element = mu.pow(2).add_(logvar.exp()).mul_(-1).add_(1).add_(logvar)
    KLD = torch.sum(KLD_element).mul_(-0.5)
    # KL divergence
    return BCE + KLD

if not os.path.exists('./vae_img'):
    os.mkdir('./vae_img')

#변수 선언
num_epochs = 100
batch_size = 128
learning_rate = 1e-3

#이미지를 텐서의 형태로 만들어줌
img_transform = transforms.Compose([
    transforms.ToTensor()
    # transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

#MNIST 데이터셋
dataset = MNIST('./data', transform=img_transform)#, download=True)
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)

#모델 & gpu 설정
model = VAE()
if torch.cuda.is_available():
    model.cuda()
#L2norm 계산
reconstruction_function = nn.MSELoss(size_average=False)
#변수 최적화
optimizer = optim.Adam(model.parameters(), lr=1e-3)

for epoch in range(num_epochs):
    model.train()
    train_loss = 0
    for batch_idx, data in enumerate(dataloader):
        img, _ = data
        img = img.view(img.size(0), -1)
        img = Variable(img) #Variable 연산을 사용하여 손실을 계산하고 출력합니다.
        if torch.cuda.is_available():
            img = img.cuda()
        optimizer.zero_grad()# 가중치 갱신 후에는 수동으로 변화도를 0으로 만듭니다.
        recon_batch, mu, logvar = model(img)
        loss = loss_function(recon_batch, img, mu, logvar)#생성데이터와 원본데이터 비교
        loss.backward()#모델의 매개변수에 대한 손실의 변화도를 계산합니다.
        train_loss += loss.data #손실값 업데이트
        optimizer.step() #함수를 호출하면 매개변수가 갱신됩니다.
        if batch_idx % 100 == 0:
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                epoch,
                batch_idx * len(img),
                len(dataloader.dataset), 100. * batch_idx / len(dataloader),
                loss.data / len(img)))

    print('====> Epoch: {} Average loss: {:.4f}'.format(
        epoch, train_loss / len(dataloader.dataset)))
    if epoch % 10 == 0:
        save = to_img(recon_batch.cpu().data)
        save_image(save, './vae_img/image_{}.png'.format(epoch))

torch.save(model.state_dict(), './vae.pth')
  • 결과

     


3. GAN(Generative Adversarial Network)

 


◾ 정리

 

  • Auto encoder : 이미지를 입력으로 받아 출력으로 반환하도록 신경망 학습(데이터의 차원을 줄여서 데이터 압축, 특징 추출이 주요 목적)
  • VAE : 이미지를 생성하는 것에 주력
  • GAN : 적대적 이미지 생성 네트워크

◾ Reference