美文网首页
变分自编码器的理解

变分自编码器的理解

作者: 神经网络爱好者 | 来源:发表于2019-11-08 15:06 被阅读0次

    1、变分自编码器(VAE)的定义

      VAE,希望构建一个由隐变量z生成目标数据X的模型,它们假设z服从某种常见分布(正态分布或伯努利分布)。换成数学语言就是,首先我们有一批样本{X_1,X_2....,X_n},整体用X描述,我们希望得到新的X的分布p(\widetilde{X})。但是这个分布很难求出,于是我们将分布改写:
    p(\widetilde{X})=\sum_{z}\ p(\widetilde{X}|z)p(z)其中p(z)假设服从正态分布,根据贝叶斯定理可得:
    p(\widetilde{X}|z)=\frac{p(z|X)p(X)}{p(z)}
    由上述公式可得,p(z|X)就是encoder模型,p(\widetilde{X}|z)就是生成模型,对应图中decoder部分。因此我们假定某个分布q(z|X)p(z|X)近似,希望两者尽可能近。因此我们的优化目标就是:
    min(KL(q(z|X)||p(z|X)))

    2、Loss function--KL散度的推导与简化

    KL(q(z|X)||p(z|X))=E_q(z|X)[log(p(X|z)]-KL(q(z|X)||p(z))
    第一项实际上是一个重建error loss,使用交叉熵损失和均方误差都可以;
    第二项,我们说是为了让我们假设的后验证分布q(z|X)和先验分布p(z), 尽量接近,论文中假设p(z)是一个标准高斯分布,KL loss。
    KL(q(z|X)||p(z))=\frac{1}{2}\sum_{k=1}^{n}\ \left(\mu_{(k)}^2(X) + \sigma_{(k)}^2(X)-ln\sigma_{(k)}^2(X) - 1 \right)

    3、工程技巧的实现

    (1)encoder拟合的Variance并是不\sigma^2而是log\sigma^2,是因为\sigma^2总是非负的,需要加激活函数处理;而log\sigma^2可正可负,不需要加激活函数。
    (2)重参数技巧

    代码解读:

    #! -*- coding: utf-8 -*-
    
    '''用Keras实现的VAE,CNN版本
       目前只保证支持Tensorflow后端
       改写自
       https://github.com/keras-team/keras/blob/master/examples/variational_autoencoder_deconv.py
    '''
    
    from __future__ import print_function
    
    import numpy as np
    import matplotlib.pyplot as plt
    from scipy.stats import norm
    
    from keras.layers import Dense, Input
    from keras.layers import Conv2D, Flatten, Lambda
    from keras.layers import Reshape, Conv2DTranspose
    from keras.models import Model
    from keras import backend as K
    from keras.datasets import mnist
    
    
    # 加载MNIST数据集
    (x_train, y_train_), (x_test, y_test_) = mnist.load_data()
    
    image_size = x_train.shape[1]
    x_train = np.reshape(x_train, [-1, image_size, image_size, 1])
    x_test = np.reshape(x_test, [-1, image_size, image_size, 1])
    x_train = x_train.astype('float32') / 255
    x_test = x_test.astype('float32') / 255
    
    
    # 网络参数
    input_shape = (image_size, image_size, 1)
    batch_size = 100
    kernel_size = 3
    filters = 16
    latent_dim = 2 # 隐变量取2维只是为了方便后面画图
    epochs = 30
    
    
    x_in = Input(shape=input_shape)
    x = x_in
    
    for i in range(2):
        filters *= 2
        x = Conv2D(filters=filters,
                   kernel_size=kernel_size,
                   activation='relu',
                   strides=2,
                   padding='same')(x)
    
    # 备份当前shape,等下构建decoder的时候要用
    shape = K.int_shape(x)
    
    x = Flatten()(x)
    x = Dense(16, activation='relu')(x)
    # 算p(Z|X)的均值和方差
    z_mean = Dense(latent_dim)(x)
    z_log_var = Dense(latent_dim)(x)
    
    # 重参数技巧
    def sampling(args):
        z_mean, z_log_var = args
        epsilon = K.random_normal(shape=K.shape(z_mean))
        return z_mean + K.exp(z_log_var / 2) * epsilon
    
    # 重参数层,相当于给输入加入噪声
    z = Lambda(sampling, output_shape=(latent_dim,))([z_mean, z_log_var])
    
    # 解码层,也就是生成器部分
    # 先搭建为一个独立的模型,然后再调用模型
    latent_inputs = Input(shape=(latent_dim,))
    x = Dense(shape[1] * shape[2] * shape[3], activation='relu')(latent_inputs)
    x = Reshape((shape[1], shape[2], shape[3]))(x)
    
    for i in range(2):
        x = Conv2DTranspose(filters=filters,
                            kernel_size=kernel_size,
                            activation='relu',
                            strides=2,
                            padding='same')(x)
        filters //= 2
    
    outputs = Conv2DTranspose(filters=1,
                              kernel_size=kernel_size,
                              activation='sigmoid',
                              padding='same')(x)
    
    # 搭建为一个独立的模型
    decoder = Model(latent_inputs, outputs)
    
    x_out = decoder(z)
    
    # 建立模型
    vae = Model(x_in, x_out)
    
    # xent_loss是重构loss,kl_loss是KL loss
    xent_loss = K.sum(K.binary_crossentropy(x_in, x_out), axis=[1, 2, 3])
    kl_loss = - 0.5 * K.sum(1 + z_log_var - K.square(z_mean) - K.exp(z_log_var), axis=-1)
    vae_loss = K.mean(xent_loss + kl_loss)
    
    # add_loss是新增的方法,用于更灵活地添加各种loss
    vae.add_loss(vae_loss)
    vae.compile(optimizer='rmsprop')
    vae.summary()
    
    vae.fit(x_train,
            shuffle=True,
            epochs=epochs,
            batch_size=batch_size,
            validation_data=(x_test, None))
    
    
    # 构建encoder,然后观察各个数字在隐空间的分布
    encoder = Model(x_in, z_mean)
    
    x_test_encoded = encoder.predict(x_test, batch_size=batch_size)
    plt.figure(figsize=(6, 6))
    plt.scatter(x_test_encoded[:, 0], x_test_encoded[:, 1], c=y_test_)
    plt.colorbar()
    plt.show()
    
    
    # 观察隐变量的两个维度变化是如何影响输出结果的
    n = 15  # figure with 15x15 digits
    digit_size = 28
    figure = np.zeros((digit_size * n, digit_size * n))
    
    #用正态分布的分位数来构建隐变量对
    grid_x = norm.ppf(np.linspace(0.05, 0.95, n))
    grid_y = norm.ppf(np.linspace(0.05, 0.95, n))
    
    for i, yi in enumerate(grid_x):
        for j, xi in enumerate(grid_y):
            z_sample = np.array([[xi, yi]])
            x_decoded = decoder.predict(z_sample)
            digit = x_decoded[0].reshape(digit_size, digit_size)
            figure[i * digit_size: (i + 1) * digit_size,
                   j * digit_size: (j + 1) * digit_size] = digit
    
    plt.figure(figsize=(10, 10))
    plt.imshow(figure, cmap='Greys_r')
    plt.show()
    

    参考:

    1、KL散度(Kullback-Leibler Divergence)介绍及详细公式推导
    2、科学空间:变分自编码器
    3、变分自编码器介绍、推导及实现
    4、vae_tutorial
    5、变分自编码器VAE

    相关文章

      网友评论

          本文标题:变分自编码器的理解

          本文链接:https://www.haomeiwen.com/subject/ultuvctx.html