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=||x−y||2
L=Σni=1(yi−f(xi))2

-
z : latent variable, space(잠재 변수, 공간), feature(특징), hidden representation으로 불림
-
VAE나 GAN과 실제로는 응용 목적이 다름(차원 축소, 정보 검색, 이상 검출, 영상 처리, 기계 번역 등에 활용)
-
최적화 공식
∗∗argminA,BE[△(x,B◦A(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의 목표
- Data와 같은 분포를 가지는 sample 분포에서 sample을 뽑은 후
- 새로운 것을 생성
-
생김새는 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 : xi→qΦ(x)→μi,σi
2) μi,σi,εi→zi(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 : 원데이터에 대한 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+(1−xi,j)log(1−pi,j)
-
현재 샘플링용 함수에 대한 negative log likelihood
-
xi에 대한 복원 오차(AutoEncoder 관점)
-
-
Regularization : KLD(Kulback-leibler divergence)
-
***KL Divergence =***KL(qθ(z|xi)||p(z))
12J∑j=1(μ2i,j+σ2i,j−ln(σ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) zi→gθ(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
- Auto-Encoding Variational Bayes,2013
- Autoencoders, Dor Bank, Noam Koenigstein, Raja Giryes,2003
- Generative Adversarial Network,https://www.youtube.com/watch?v=AVvlDmhHgC4
- 네이버 이활석님의 슬라이드노트
- 네이버 이활석님의 오토인코더의 모든 것 강의 in Youtube
- 이웅원님의 VAE 관련 tutorial 글
- https://mlexplained.com/2017/12/28/an-intuitive-explanation-of-variational-autoencoders-vaes-part-1/