AE 그리고 VAE

Lia·2022년 9월 5일
0

AI

목록 보기
11/11

AE (Auto Encoder)

오토 인코더는 아래와 같은 구조를 가지고 있습니다.

출처 > A Better Autoencoder for Image: Convolutional Autoencoder


인코더는 고차원 입력 데이터를 저차원 표현 벡터로 압축하고
디코더는 표현 벡터를 다시 원본 차원으로 압축 해제합니다.

표현 벡터는 원본 이미지를 저차원 잠재 공간으로 압축한 것입니다.
이때 디코더는 저차원 잠재 공간에 있는 한 지점을 재구성 이미지로 변환 시키는 것을
학습하기 때문에 잠재 공간에 있는 어떠한 지점을 선택하더라도
새 이미지를 생성할 수 있습니다.

인코더는 입력 이미지를 잠재 공간의 한 포인트로 매핑하는걸 목표로 하기 때문에
합성곱(Convolution)층으로 구성됩니다.

전치 합성곱 (Convolutional Transpose)

디코더가 잠재 공간의 한 포인트를 재구성 이미지로 변환 하기 위해서는
전치 합성곱층을 사용합니다.

일반적인 표준 합성곱 층과 달리 전치 합성곱층의 strides
이미지 픽셀 사이에 추가되는 제로 패딩을 결정합니다.

또한 전치 합성곱은 항상 커널을 1픽셀 씩 이동하게 됩니다.


VAE (Variational Auto Encoder)

VAE(이하 변이형 오토인코더)와 AE(이하 오토 인코더)의 차이점은
인코더와 손실함수 뿐입니다.

변이형 오토인코더에서 인코더는 입력 이미지를 잠재 공간에 있는
포인트 주변의 다변수 정규 분포에 매핑합니다.

다음 공식을 사용하여 평균이 μ\mu이고 편차가 σ\sigma인 정규 분포에서
포인트 zz를 샘플링할 수 있습니다.

z=μ+σϵz = \mu + \sigma\epsilon

이때 ϵ\epsilon은 표준 정규 분포에서 샘플링됩니다.

변이형 오토인코더는 입력 이미지를 받아 잠재 공간의
다변수 정규 분포를 정의하는 mulog_var 2개의 벡터로 인코딩 합니다.

σ=exp(logvar)\sigma = \exp(\mathsf{logvar})

쿨백 - 라이블러 발산

오토인코더는 원본 이미지와 재구성 이미지 사이의 RMSE 손실로만 구성되었지만
변이형 오토인코더는 추가적으로 쿨백 - 라이블러 발산 (Kullback - Leibler divergence)
을 사용합니다.

수식은 아래와 같습니다.

DKL[N(μ,σ)N(0,1)]=12(1 + log(σ2)  μ2  σ2)D_{KL}[N(\mu,\sigma)||N(0,1)] = -\frac{1}{2}\sum(1\ +\ log(\sigma^2)\ -\ \mu^2\ -\ \sigma^2)

이 식의 덧셈은 잠재 공간의 모든 차원에 대해서 수행됩니다.
kl_loss는 모든 차원의 대해 mu = 0 이고 log_var = 0 일때 최소입니다.

VAE Demonstration

VAE는 다음과 같이 정의할 수 있습니다.

class VAE():
    def __init__(self,
                 input_dim,
                 encoder_conv_filters,
                 encoder_conv_kernel_size,
                 encoder_conv_strides,
                 decoder_conv_t_filters,
                 decoder_conv_t_kernel_size,
                 decoder_conv_t_strides,
                 z_dim,
                 use_batch_norm = False,
                 use_dropout = False):

        self.name = 'variational_autoencoder'

        self.input_dim = input_dim
        self.encoder_conv_filters = encoder_conv_filters
        self.encoder_conv_kernel_size = encoder_conv_kernel_size
        self.encoder_conv_strides = encoder_conv_strides
        self.decoder_conv_t_filters = decoder_conv_t_filters
        self.decoder_conv_t_kernel_size = decoder_conv_t_kernel_size
        self.decoder_conv_t_strides = decoder_conv_t_strides
        self.z_dim = z_dim

        self.use_batch_norm = use_batch_norm
        self.use_dropout = use_dropout

        self.n_layers_encoder = len(encoder_conv_filters)
        self.n_layers_decoder = len(decoder_conv_t_filters)

        self._build()

    def _build(self):

        encoder_input = Input(shape=self.input_dim, name='encoder_input')

        x = encoder_input

        for i in range(self.n_layers_encoder):
            conv_layer = Conv2D(
                filters = self.encoder_conv_filters[i],
                kernel_size = self.encoder_conv_kernel_size[i],
                strides = self.encoder_conv_strides[i],
                padding = 'same',
                name = 'encoder_conv_' + str(i)
            )

            x = conv_layer(x)

            if self.use_batch_norm:
                x = BatchNormalization()(x)

            x = LeakyReLU()(x)
            if self.use_dropout:
                x = Dropout(rate = 0.2)(x)

        shape_before_flattening = K.int_shape(x)[1:]
        x = Flatten()(x)

        self.mu = Dense(self.z_dim, name='mu')(x)
        self.log_var = Dense(self.z_dim, name='log_var')(x)

        encoder_mu_log_var = Model(encoder_input, (self.mu, self.log_var))

        def sampling(args):
            mu, log_var = args
            epsilon = K.random_normal(shape=K.shape(mu), mean=0., stddev=1.)
            return mu + K.exp(log_var / 2) * epsilon

        encoder_output = Lambda(sampling, name='encoder_output')([self.mu, self.log_var])
        self.encoder = Model(encoder_input, encoder_output)

        decoder_input = Input(shape=(self.z_dim,), name='decoder_input')

        x = Dense(np.prod(shape_before_flattening))(decoder_input)
        x = Reshape(shape_before_flattening)(x)

        for i in range(self.n_layers_decoder):
            conv_t_layer = Conv2DTranspose(
                filters = self.decoder_conv_t_filters[i],
                kernel_size = self.decoder_conv_t_kernel_size[i],
                strides = self.decoder_conv_t_strides[i],
                padding = 'same',
                name = 'decoder_conv_t_' + str(i)
            )

            x = conv_t_layer(x)

            if i < self.n_layers_decoder - 1:
                if self.use_batch_norm:
                    x = BatchNormalization()(x)
                x = LeakyReLU()(x)
                if self.use_dropout:
                    x = Dropout(rate = 0.2)(x)
            else:
                x = Activation('sigmoid')(x)

        decoder_output = x
        self.decoder = Model(decoder_input, decoder_output)

        model_input = encoder_input
        model_output = self.decoder(encoder_output)

        self.model = Model(model_input, model_output)

    def compile(self, learning_rate, r_loss_factor):

        optimizer = tf.keras.optimizers.Adam(lr=learning_rate)

        def vae_r_loss(y_true, y_pred):
            r_loss = K.mean(K.square(y_true - y_pred), axis = [1,2,3])
            return r_loss_factor * r_loss

        def vae_kl_loss(y_true, y_pred):
            kl_loss = -0.5 * K.sum(1 + self.log_var - K.square(self.mu) - K.exp(self.log_var), axis = 1)
            return kl_loss

        def vae_loss(y_true, y_pred):
            r_loss = vae_r_loss(y_true, y_pred)
            kl_loss = vae_kl_loss(y_true, y_pred)
            return r_loss + kl_loss

        self.model.compile(optimizer=optimizer, loss=vae_loss, metrics=[vae_r_loss, vae_kl_loss])

    def train(self, data_flow, epochs):
        self.model.fit_generator(data_flow, epochs=epochs, shuffle=True)

    def save_weights(self, path):
        self.model.save_weights(path)

    def load_weights(self, path):
        self.model.load_weights(path)

제가 사용한 데이터셋은 Towards the Automatic Anime Characters Creation with Generative Adversarial Networks 논문에 소개된 데이터셋을 사용하였습니다.

캐글 데이터로도 다운받을 수 있습니다.

모델 예측

다음 그림은 훈련 epochs=200을 수행한 VAE 모델의 예측 결과입니다.
(랜덤한 잠재공간 샘플을 디코더가 예측한 16개 결과입니다)

profile
하고싶은게 많아요

0개의 댓글