AI/Hands-on ML

[핸즈온 머신러닝] 17장(3) - 변이형 오토인코더, Variational AutoEncoder

KIM DEON 2021. 5. 19. 02:05

[AI/Hands-on ML] - [핸즈온 머신러닝] 17장(2) - 합성곱 오토인코더, 순환 오토인코더, 잡음 제거 오토인코더, 희소 오토인코더

 

[핸즈온 머신러닝] 17장(2) - 합성곱 오토인코더, 순환 오토인코더, 잡음 제거 오토인코더, 희소 오

[AI/Hands-on ML] - [핸즈온 머신러닝] 17장 - 오토인코더와 GAN을 사용한 표현 학습과 생성적 학습 [핸즈온 머신러닝] 17장 - 오토인코더와 GAN을 사용한 표현 학습과 생성적 학습 17. 오토인코더와 GAN 오

kdeon.tistory.com


17. 8 변이형 오토인코더, Variational AutoEncoder

2014년 소개된 오토인코더로, 매우 인기 있는 오토인코더 중 하나이다. 

 

이전 글에서 봤던 오토인코더들은(Denoising AE, Sparse AE ...), input data가 encoder를 통해 데이터의 특징을 나타내는 하나의 encoding vector(=코딩)으로 변형되었다.

-> 즉, 이 encoder들은 하나의 single value를 출력했었고, decoder는 이 값을 사용해 원본 데이터를 복원하도록 학습이 되었다. 

AE 학습 과정에서, single value로 각 특징을 설명하게 된다. (출처: https://www.jeremyjordan.me/variational-autoencoders/)

반면에 변이형 오토인코더(VAE)는, 잠재 공간(latent space)에서의 관찰을 설명하는 확률적 방식이다. 

-> 다시 말해, VAE의 encoder는 각 특징을 설명하는 single value를 출력하는 것이 아닌, 각 latent attribute에 대한 확률 분포를 출력한다. 

인코더를 통해 확률 분포를 뽑아내고, 확률 분포에서 랜덤 샘플링된 값 z를 통해 이미지를 복원한다. (출처: https://www.jeremyjordan.me/variational-autoencoders/)

변이형 오토인코더의 특징

  • 확률적 오토인코더 - 훈련이 끝난 후에도 출력이 부분적으로 우연에 의해 결정됨 (반면에 잡음 제거 오토인코더는 훈련 시에만 무작위성을 사용함)
  • 생성 오토인코더 - 훈련 세트에서 샘플링된 것 같은 새로운 샘플을 생성

변이 인코더의 작동 방식

- 인코더 뒤에 디코더가 따르는 기본 구조

- 주어진 입력에 대한 코딩을 바로 만드는 대신, 인코더가 평균 코딩 μ 표준편차 σ를 만듦

  • 실제 코딩은 평균이 μ이고, 표준 편차가 σ인 가우시안 분포에서 랜덤하게 샘플링 

- 그 후 디코더가 샘플링된 코딩을 보통처럼 디코딩함

변이형 오토인코더(왼쪽)와 이를 통과하는 샘플(오른쪽)

1. 인코더가 μ, σ를 만들면 코딩이 랜덤하게 선택됨

2. 코딩이 디코드되어 훈련 샘플을 닮은 최종 출력을 만듦

 

- 변이형 오토인코더는 입력이 복잡한 분포를 가지더라도, 간단한 가우시안 분포에서 샘플링된 것처럼 보이는 코딩을 만드는 경향이 있음

- 훈련하는 동안 비용 함수가 코딩을 가우시안 샘플들의 군집처럼 보이도록 코딩 공간(잠재 공간) 안으로 점진적으로 이동시킴

-> 변이형 오토인코더가 훈련이 끝난 뒤 새로운 샘플을 매우 쉽게 생성할 수 있음

=> 가우시안 분포에서 랜덤한 코딩을 샘플링해 디코딩하면 됨

 

비용함수

두 부분으로 구성

- 오토인코더가 입력을 재생산하도록 만드는 일반적인 재구성 손실

- 단순한 가우시안 분포에서 샘플된 것 같은 코딩을 가지도록 오토인코더를 강제하는 잠재 손실

  • 목표 분포(가우시안 분포)와, 실제 코딩 분포 사이의 KL 발산을 사용
  • 코딩 층으로 전달될 수 있는 정보 양을 제한하는(유용한 특성을 학습하도록) 가우시안 잡음 때문에 수식이 조금 복잡하지만, 아래 식으로 잠재 손실을 간단히 계산 가능

변이형 오토인코더의 잠재 손실

- L : 잠재 손실

- n : 코딩의 차원

- μi : i번째 코딩 원소의 평균

- σi : 코딩 원소의 표준편차

- 벡터 μ, σ는 인코더의 출력

 

변이형 오토인코더에서 인코더가 σ 가 아니라 γ = log(σ^2) 을 출력하도록 변경할 수 있음

- 수학적으로 안정적이고, 훈련 속도를 높임

γ  = log(σ^2) 을 사용해 다시 쓴 변이형 오토인코더의 잠재 손실


패션 MNIST 데이터셋으로 변이형 오토인코더 구현

μ와 γ가 주어졌을 때 코딩을 샘플링하는 사용자 정의 층

from tensorflow import keras
K = keras.backend

class Sampling(keras.layers.Layer):
    def call(self, inputs):
        mean, log_var = inputs
		K.random_normal(tf.shape(log_var)) * K.exp(log_var / 2) + mean

- 두 입력 mean (μ), log_var (γ, 즉 log(σ^2)) 을 받음

- random_normal() 함수를 통해 평균이 0이고, 표준편차가 1인 정규분포에서 (γ와 동일한 크기의) 랜덤한 벡터를 샘플링함

- 그 후 σ 를 만들기 위해 exp(γ / 2) 를 곱하고, 마지막으로 μ를 더한 결과를 반환

-> 평균이 μ이고 표준편차가 σ 인 정규분포에서 코딩 벡터를 샘플링한 것과 같은 결과

 

📝 keras.backend.random_normal()

- 생성할 tensor의 shape을 받아, 정규 분포에서 tensor를 반환함.

 

함수형 API를 통해 인코더 생성

codings_size = 10

inputs = keras.layers.Input(shape=[28, 28])
z = keras.layers.Flatten()(inputs)
z = keras.layers.Dense(150, activation="selu")(z)
z = keras.layers.Dense(100, activation="selu")(z)
codings_mean = keras.layers.Dense(codings_size)(z)
codings_log_var = keras.layers.Dense(codings_size)(z)
codings = Sampling()([codings_mean, codings_log_var])
variational_encoder = keras.models.Model(
    inputs=[inputs], outputs=[codings_mean, codings_log_var, codings])

- codings_mean (μ) 와 coding_log_var (γ) 를 출력하는 두 Dense 층이 동일한 입력 (두 번째 Dense 층의 출력)을 사용

- codings_mean (μ) 와 coding_log_var (γ) 를 Sampling 층으로 전달

- 마지막으로 variational_encoder 모델은 출력 세 개를 만듦

- 실제 사용하는 것은 outputs 중 마지막 출력인 codings

 

디코더 생성

decoder_inputs = keras.layers.Input(shape=[codings_size])
x = keras.layers.Dense(100, activation="selu")(decoder_inputs)
x = keras.layers.Dense(150, activation="selu")(x)
x = keras.layers.Dense(28 * 28, activation="sigmoid")(x)
outputs = keras.layers.Reshape([28, 28])(x)
variational_decoder = keras.models.Model(inputs=[decoder_inputs], outputs=[outputs])

-  함수형 API 대신 시퀀셜 API를 사용

- 지금까지 다룬 디코더와 사실상 동일하게 층을 단순히 쌓은 것

 

변이형 오토인코더 모델 생성

_, _, codings = variational_encoder(inputs)
reconstructions = variational_decoder(codings)
variational_ae = keras.models.Model(inputs=[inputs], outputs=[reconstructions])

- 인코더의 처음 두 개 출력을 무시 (= 코딩만 디코더에 주입)

 

잠재 손실과 재구성 손실을 추가

latent_loss = -0.5 * K.sum(
    1 + codings_log_var - K.exp(codings_log_var) - K.square(codings_mean),
    axis=-1)
variational_ae.add_loss(K.mean(latent_loss) / 784.)
variational_ae.compile(loss="binary_crossentropy", optimizer="rmsprop")

- 위에서 나왔던 수식을 통해 배치에 있는 각 샘플의 잠재 손실을 계산

γ  = log(σ^2) 을 사용해 다시 쓴 변이형 오토인코더의 잠재 손실

- 그 다음, 배치에 있는 모든 샘플의 평균 손실을 계산하고 재구성 손실에 비례해 적절한 크기가 되도록 784로 나눔

  • 변이형 오토인코더의 재구성 손실은 픽셀마다 재구성 오차의 합
  • 하지만 케라스가 binary_crossentropy 손실을 계산할 때 합이 아닌 784개 전체 픽셀의 평균을 계산함
  • 따라서 필요한 것보다 재구성 손실이 784배 적음
  • 간단하게 하기위해 잠재 손실을 784로 나눔 => 최종 손실이 784배 작아지므로, 더 큰 학습률을 사용해야 한다는 것을 의미

- 문제 잘 맞는, RMSprop 옵티마이저 사용

 

오토인코더 훈련

history = variational_ae.fit(X_train, X_train, epochs=25, batch_size=128,
                             validation_data=(X_valid, X_valid))

17. 8. 1 패션 MNIST 이미지 생성하기

이미지 생성

codings = tf.random.normal(shape=[12, codings_size])
images = variational_decoder(codings).numpy()

- 가우시안 분포에서 랜덤한 코딩을 샘플링하여 디코딩

변이형 오토인코더로 생성된 패션 MNIST 이미지

- 어느정도 그럴싸한 이미지가 생성됨

- 세부 튜닝 후 오래 훈련하면 더 좋아질 것

 

시맨틱 보간

- 변이형 오토인코더는 시맨틱 보간 수행 가능

- 픽셀 수준이 아닌, 코딩 수준에서의 이미지 보간

시맨틱 보간, 두 이미지의 중간 이미지를 생성한다. (출처: https://www.slashcam.com/news/single/Artificial-intelligence-controlled---GANs-with-sem-16136.html)

1. 두 이미지를 인코더에 통과시켜 얻은 두 코딩을 보간함

2. 그 후 보간된 코딩을 디코딩하여 최종 이미지를 얻음

-> 결과는 원본 이미지 사이에 있는 중간 이미지가 됨

codings_grid = tf.reshape(codings, [1, 3, 4, codings_size])
larger_grid = tf.image.resize(codings_grid, size=[5, 7])
interpolated_codings = tf.reshape(larger_grid, [-1, codings_size])
images = variational_decoder(interpolated_codings).numpy()

- 12개의 코딩을 생성하여, 이를 3x4 격자로 만듦

- tf.image.resize() 함수를 사용해 격자를 5x7 크기로 바꿈

- resize() 함수가 기본적으로 이중 선형 보간을 수행하므로, 늘어난 모든 행과 열이 보간된 코딩을 가짐

- 그 후 디코더로 이미지 생성

 

결과 이미지 (원본 - 테두리, 나머지 - 시맨틱 보간 결과)

시맨틱 보간


지난 몇 년간 변이형 오토인코더가 널리 쓰였으나, GAN이 더 실제같은 이미지를 만들어 인기가 높아짐


참조

https://www.jeremyjordan.me/variational-autoencoders/