变分自编码

一 变分自编码(Variational Auto-Encoder)

变分自编码不再是学习样本的个体,而是学习样本的规律,这样训练出来的自编码不单具有重构样本的功能,还具有仿照样本的功能。

变分自编码,其实就是在编码过程中改变了样本的分布("变分"可以理解为改变分布)。前面所说的"学习样本的规律",具体指的就是样本的分布,假设我们知道样本的分布函数,就可以从这个函数中随便的取一个样本,然后进行网络解码层向前传导,这样就可以生成一个新的样本。

为了得到这个样本的分布函数,模型训练的目的不再是样本本身,而是通过加一个约束项,将网络生成一个服从于高斯分布的数据集,这样按照高斯分布里的均值和方差规则就可以任意取相关的数据,然后通过解码层还原成样本。我们先来看一下VAE的结构框图,后面来介绍一下VAE的原理:

二 分布变换

我们希望构建一个隐层变量Z生成目标数据X的模型,但是实现上有所不同。更准确地讲,它们是假设了Z服从某些常见的分布(比如正态分布或均匀分布),然后希望训练一个模型X=g(Z),这个模型能够将原来的的概率分布映射到训练集的概率分布,也就是说,它们的目的都是进行分布之间的变换。

那现在假设Z服从标准的正态分布,就可以从中采样得到若干个Z1,Z2,…,Zn,然后对它做变换得到X^1=g(Z1),X^2=g(Z2),…,X^n=g(Zn),我们怎么判断这个通过f构造出来的数据集,它的分布跟我们目标的数据集分布是不是一样的呢?有读者说不是有KL散度吗?当然不行,因为KL散度是根据两个概率分布的表达式来算它们的相似度的,然而目前我们并不知道它们的概率分布的表达式,我们只有一批从构造的分布采样而来的数据{X^1,X^2,…,X^n},还有一批从真实的分布采样而来的数据{X1,X2,…,Xn}(也就是我们希望生成的训练集)。我们只有样本本身,没有分布表达式,当然也就没有方法算KL散度。生成模型的难题就是判断生成分布与真实分布的相似度,因为我们只知道两者的采样结果,不知道它们的分布表达式。虽然遇到困难,但还是要想办法解决的。VAE使用了一个精致迂回的技巧。

三 VAE慢谈

这一部分我们先回顾一般教程是怎么介绍VAE的,然后再探究有什么问题,接着就自然地发现了VAE真正的面目。

1.经典回顾

首先我们有一批数据样本{X1,…,Xn},其整体用X来描述,我们本想根据{X1,…,Xn}得到X的分布p(X),如果能得到的话,那我直接根据p(X)来采样,就可以得到所有可能的X了,这是一个终极理想的生成模型了。当然,这个理想很难实现,于是我们将分布改一改:

这里我们就不区分求和还是求积分了,意思对了就行。此时p(X|Z)就描述了一个由Z来生成X的模型,而我们假设Z服从标准正态分布,也就是p(Z)=N(0,I)。如果这个理想能实现,那么我们就可以先从标准正态分布中采样一个Z,然后根据Z来算一个X,也是一个很棒的生成模型。接下来就是结合自编码器来实现重构,保证有效信息没有丢失,再加上一系列的推导,最后把模型实现。框架的示意图如下:

看出了什么问题了吗?如果像这个图的话,我们其实完全不清楚:究竟经过重新采样出来的Zk,是不是还对应着原来的Xk,所以我们如果直接最小化D(X^k,Xk)2这里D代表某种距离函数)是很不科学的,而事实上你看代码也会发现根本不是这样实现的。也就是说,很多教程说了一大通头头是道的话,然后写代码时却不是按照所写的文字来写,可是他们也不觉得这样会有矛盾。

2.VAE初现

其实,在整个VAE模型中,我们并没有去使用p(Z)(先验分布)是正态分布的假设,我们用的是假设p(Z|X)(后验分布)是正态分布!!

具体来说,给定一个真实样本Xk,我们假设存在一个专属于Xk的分布p(Z|Xk)(学名叫后验分布),并进一步假设这个分布是(独立的、多元的)正态分布。为什么要强调“专属”呢?因为我们后面要训练一个生成器X=g(Z),希望能够把从分布p(Z|Xk)采样出来的一个Zk还原为Xk。如果假设p(Z)是正态分布,然后从p(Z)中采样一个Z,那么我们怎么知道这个Z对应于哪个真实的X呢?现在p(Z|Xk)专属于Xk,我们有理由说从这个分布采样出来的Z应该要还原到Xk中去。

事实上,在论文《Auto-Encoding Variational Bayes》的应用部分,也特别强调了这一点:

In this case, we can let the
variational approximate posterior be a multivariate Gaussian with a diagonal covariance structure:

(注:这里是直接摘录原论文,本文所用的符号跟原论文不尽一致,望读者不会混淆。)

论文中的式(9)是实现整个模型的关键,不知道为什么很多教程在介绍VAE时都没有把它凸显出来。尽管论文也提到p(Z)是标准正态分布,然而那其实并不是本质重要的。

回到本文,这时候每一个Xk都配上了一个专属的正态分布,才方便后面的生成器做还原。但这样有多少个X就有多少个正态分布了。我们知道正态分布有两组参数:均值μ和方差σ2(多元的话,它们都是向量),那我怎么找出专属于Xk的正态分布p(Z|Xk)的均值和方差呢?好像并没有什么直接的思路。那好吧,那我就用神经网络来拟合出来吧!

于是我们构建两个神经网络μk=f1(Xk),logσ2=f2(Xk)来算它们了。我们选择拟合logσ2而不是直接拟合σ2,是因为σ2总是非负的,需要加激活函数处理,而拟合logσ2不需要加激活函数,因为它可正可负。到这里,我能知道专属于Xk的均值和方差了,也就知道它的正态分布长什么样了,然后从这个专属分布中采样一个Zk出来,然后经过一个生成器得到X^k=g(Zk),现在我们可以放心地最小化D(X^k,Xk)2,因为Zk是从专属Xk的分布中采样出来的,这个生成器应该要把开始的Xk还原回来。于是可以画出VAE的示意:

事实上,VAE是为每个样本构造专属的正态分布,然后采样来重构。

3.分布标准化

让我们来思考一下,根据上图的训练过程,最终会得到什么结果。

首先,我们希望重构X,也就是最小化D(X^k,Xk)2,但是这个重构过程受到噪声的影响,因为Zk是通过重新采样过的,不是直接由encoder算出来的。显然噪声会增加重构的难度,不过好在这个噪声强度(也就是方差)通过一个神经网络算出来的,所以最终模型为了重构得更好,肯定会想尽办法让方差为0。而方差为0的话,也就没有随机性了,所以不管怎么采样其实都只是得到确定的结果(也就是均值),只拟合一个当然比拟合多个要容易,而均值是通过另外一个神经网络算出来的。

说白了,模型会慢慢退化成普通的AutoEncoder,噪声不再起作用。

这样不就白费力气了吗?说好的生成模型呢?

别急别急,其实VAE还让所有的p(Z|X)都向标准正态分布看齐,这样就防止了噪声为零,同时保证了模型具有生成能力。怎么理解“保证了生成能力”呢?如果所有的p(Z|X)都很接近标准正态分布N(0,I),那么根据定义:

这样我们就能达到我们的先验假设:p(Z)是标准正态分布。然后我们就可以放心地从N(0,I)中采样来生成图像了。

为了使模型具有生成能力,VAE要求每个p(Z|X)都向正态分布看齐。

那怎么让所有的p(Z|X)都向N(0,I)看齐呢?如果没有外部知识的话,其实最直接的方法应该是在重构误差的基础上中加入额外的loss:

因为它们分别代表了均值μk和方差的对数logσ2,达到N(0,I)就是希望二者尽量接近于0了。不过,这又会面临着这两个损失的比例要怎么选取的问题,选取得不好,生成的图像会比较模糊。所以,原论文直接算了一般(各分量独立的)正态分布与标准正态分布的KL散度KL(N(μ,σ2)||N(0,I))作为这个额外的loss,计算结果为:

这里的d是隐变量Z的维度,而μ(i)和σ2分别代表一般正态分布的均值向量和方差向量的第i个分量。直接用这个式子做补充loss,就不用考虑均值损失和方差损失的相对比例问题了。显然,这个loss也可以分两部分理解:

推导

由于我们考虑的是各分量独立的多元正态分布,因此只需要推导一元正态分布的情形即可,根据定义我们可以写出:

整个结果分为三项积分,第一项实际上就是?log?σ2乘以概率密度的积分,所以结果是-log?σ2;第二项实际是正态分布的二阶矩,熟悉正态分布的朋友应该都清楚正态分布的二阶矩为μ22;而根据定义,第三项实际上就是“-方差除以方差=-1”。所以总结果就是:

4重要参数技巧

最后是实现模型的一个技巧,英文名是reparameterization trick,我这里叫它做重参数吧。其实很简单,就是我们要从p(Z|Xk)中采样一个Zk出来,尽管我们知道了p(Z|Xk)是正态分布N(μ,σ2),但我们应该从 N(μ,σ2)采样,但这个采样操作对 μ和 σ2是不可导的,导致常规的通过误差反传的梯度下降法(GD)不能使用。“采样”这个操作是不可导的,但是采样的结果是可导的。我们利用:

这说明(z?μ)/σ=ε是服从均值为0、方差为1的标准正态分布的,要同时把dz考虑进去,是因为乘上dz才算是概率,去掉dz是概率密度而不是概率。这时候我们得到:

从N(μ,σ2)中采样一个Z,相当于从N(0,I)中采样一个ε,然后让Z=μ+ε×σ。

于是,我们将从N(μ,σ2)采样变成了从N(0,I)中采样,然后通过参数变换得到从N(μ,σ2)中采样的结果。这样一来,“采样”这个操作就不用参与梯度下降了,改为采样的结果参与,使得整个模型可训练了。

四 VAE的本质

VAE的本质是什么?VAE虽然也称是AE(AutoEncoder)的一种,但它的做法(或者说它对网络的诠释)是别具一格的。在VAE中,它的Encoder有两个,一个用来计算均值,一个用来计算方差,这已经让人意外了:Encoder不是用来Encode的,是用来算均值和方差的,这真是大新闻了,还有均值和方差不都是统计量吗,怎么是用神经网络来算的?

事实上,我觉得VAE从让普通人望而生畏的变分和贝叶斯理论出发,最后落地到一个具体的模型中,虽然走了比较长的一段路,但最终的模型其实是很接地气的:它本质上就是在我们常规的自编码器的基础上,对encoder的结果(在VAE中对应着计算均值的网络)加上了“高斯噪声”,使得结果decoder能够对噪声有鲁棒性;而那个额外的KL loss(目的是让均值为0,方差为1),事实上就是相当于对encoder的一个正则项,希望encoder出来的东西均有零均值。

那另外一个encoder(对应着计算方差的网络)的作用呢?它是用来动态调节噪声的强度的。直觉上来想,当decoder还没有训练好时(重构误差远大于KL loss),就会适当降低噪声(KL loss增加),使得拟合起来容易一些(重构误差开始下降);反之,如果decoder训练得还不错时(重构误差小于KL loss),这时候噪声就会增加(KL loss减少),使得拟合更加困难了(重构误差又开始增加),这时候decoder就要想办法提高它的生成能力了。

五 使用VAE模拟生成MNIST数据

1.定义占位符

该网络与之前的略有不同,编码器为两个全连接层,第一个全连接层由784个维度的输入变化256个维度的输出,第二个全连接层并列连接了两个输出网络,mean和lg_var(可以看做噪声项,VAE跟普通的自编码器差别不大,无非是多加了该噪声并对该噪声做了约束),每个网络都输出了两个维度的输出。然后两个输出通过一个公式的计算,输入到以一个2节点为开始的解码部分,接着后面为两个全连接的解码层,第一个由两个维度的输入到256个维度的输出,第二个由256个维度的输入到784个维度的输出。

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.examples.tutorials.mnist import input_data
from scipy.stats import norm

mnist = input_data.read_data_sets(‘MNIST-data‘,one_hot=True)

print(type(mnist)) #<class ‘tensorflow.contrib.learn.python.learn.datasets.base.Datasets‘>

print(‘Training data shape:‘,mnist.train.images.shape)           #Training data shape: (55000, 784)
print(‘Test data shape:‘,mnist.test.images.shape)                #Test data shape: (10000, 784)
print(‘Validation data shape:‘,mnist.validation.images.shape)    #Validation data shape: (5000, 784)
print(‘Training label shape:‘,mnist.train.labels.shape)          #Training label shape: (55000, 10)

train_X = mnist.train.images
train_Y = mnist.train.labels
test_X = mnist.test.images
test_Y = mnist.test.labels

‘‘‘
定义网络参数
‘‘‘
n_input = 784
n_hidden_1 = 256
n_hidden_2 = 2
learning_rate = 0.001
training_epochs = 20               #迭代轮数
batch_size = 128                   #小批量数量大小
display_epoch = 3
show_num = 10

x = tf.placeholder(dtype=tf.float32,shape=[None,n_input])
#后面通过它输入分布数据,用来生成模拟样本数据
zinput = tf.placeholder(dtype=tf.float32,shape=[None,n_hidden_2])

2.定义学习参数

mean_w1和mean_b1是成圣mean的权重和偏置,log_sigma_w1和log_sigma_b1是生成log_sigma的权重和偏置。

‘‘‘
定义学习参数
‘‘‘
weights = {
        ‘w1‘:tf.Variable(tf.truncated_normal([n_input,n_hidden_1],stddev = 0.001)),
        ‘mean_w1‘:tf.Variable(tf.truncated_normal([n_hidden_1,n_hidden_2],stddev = 0.001)),
        ‘log_sigma_w1‘:tf.Variable(tf.truncated_normal([n_hidden_1,n_hidden_2],stddev = 0.001)),
        ‘w2‘:tf.Variable(tf.truncated_normal([n_hidden_2,n_hidden_1],stddev = 0.001)),
        ‘w3‘:tf.Variable(tf.truncated_normal([n_hidden_1,n_input],stddev = 0.001))
        }

biases = {
        ‘b1‘:tf.Variable(tf.zeros([n_hidden_1])),
        ‘mean_b1‘:tf.Variable(tf.zeros([n_hidden_2])),
        ‘log_sigma_b1‘:tf.Variable(tf.zeros([n_hidden_2])),
        ‘b2‘:tf.Variable(tf.zeros([n_hidden_1])),
        ‘b3‘:tf.Variable(tf.zeros([n_input]))
        }

注意:这里初始化权重时,使用了很小的值0.001。这里设置的非常小心,由于在算KL离散度时计算的是与标准高斯分布的距离,如果网络初始生成的模型均值和方差都很大,那么与标准高斯分布的差距就会非常大,这样会导致模型训练不出来,生成NAN的情况。

3.定义网络结构

‘‘‘
定义网络结构
‘‘‘
#第一个全连接层是由784个维度的输入样->256个维度的输出
h1 = tf.nn.relu(tf.add(tf.matmul(x,weights[‘w1‘]),biases[‘b1‘]))
#第二个全连接层并列了两个输出网络
z_mean = tf.add(tf.matmul(h1,weights[‘mean_w1‘]),biases[‘mean_b1‘])
z_log_sigma_sq = tf.add(tf.matmul(h1,weights[‘log_sigma_w1‘]),biases[‘log_sigma_b1‘])

#然后将两个输出通过一个公式的计算,输入到以一个2节点为开始的解码部分 高斯分布样本
eps = tf.random_normal(tf.stack([tf.shape(h1)[0],n_hidden_2]),0,1,dtype=tf.float32)
z = tf.add(z_mean,tf.multiply(tf.sqrt(tf.exp(z_log_sigma_sq)),eps))

#解码器 由2个维度的输入->256个维度的输出
h2 = tf.nn.relu(tf.matmul(z,weights[‘w2‘]) + biases[‘b2‘])
#解码器 由256个维度的输入->784个维度的输出  即还原成原始输入数据
reconstruction = tf.matmul(h2,weights[‘w3‘]) + biases[‘b3‘]

#这两个节点不属于训练中的结构,是为了生成指定数据时用的
h2out = tf.nn.relu(tf.matmul(zinput,weights[‘w2‘]) + biases[‘b2‘])
reconstructionout = tf.matmul(h2out,weights[‘w3‘]) + biases[‘b3‘]

4 反向传播

这里定义损失函数加入了KL散度:

‘‘‘
构建模型的反向传播
‘‘‘
#计算重建loss
#计算原始数据和重构数据之间的损失,这里除了使用平方差代价函数,也可以使用交叉熵代价函数
reconstr_loss = 0.5*tf.reduce_sum((reconstruction-x)**2)
print(reconstr_loss.shape)    #(,) 标量
#使用KL离散度的公式
latent_loss = -0.5*tf.reduce_sum(1 + z_log_sigma_sq - tf.square(z_mean) - tf.exp(z_log_sigma_sq),1)
print(latent_loss.shape)      #(128,)
cost = tf.reduce_mean(reconstr_loss+latent_loss)

#定义优化器
optimizer = tf.train.AdamOptimizer(learning_rate).minimize(cost)

num_batch = int(np.ceil(mnist.train.num_examples / batch_size))

5.开始训练,并可视化输出

‘‘‘
开始训练
‘‘‘
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())

    print(‘开始训练‘)
    for epoch in range(training_epochs):
        total_cost = 0.0
        for i in range(num_batch):
            batch_x,batch_y = mnist.train.next_batch(batch_size)
            _,loss = sess.run([optimizer,cost],feed_dict={x:batch_x})
            total_cost += loss

        #打印信息
        if epoch % display_epoch == 0:
            print(‘Epoch {}/{}  average cost {:.9f}‘.format(epoch+1,training_epochs,total_cost/num_batch))

    print(‘训练完成‘)

    #测试
    print(‘Result:‘,cost.eval({x:mnist.test.images}))
    #数据可视化
    reconstruction = sess.run(reconstruction,feed_dict = {x:mnist.test.images[:show_num]})
    plt.figure(figsize=(1.0*show_num,1*2))
    for i in range(show_num):
        #原始图像
        plt.subplot(2,show_num,i+1)
        plt.imshow(np.reshape(mnist.test.images[i],(28,28)),cmap=‘gray‘)
        plt.axis(‘off‘)

        #变分自编码器重构图像
        plt.subplot(2,show_num,i+show_num+1)
        plt.imshow(np.reshape(reconstruction[i],(28,28)),cmap=‘gray‘)
        plt.axis(‘off‘)
    plt.show()

    #绘制均值和方差代表的二维数据
    plt.figure(figsize=(5,4))
    #将onehot转为一维编码
    labels = [np.argmax(y) for y in mnist.test.labels]
    mean,log_sigma = sess.run([z_mean,z_log_sigma_sq],feed_dict={x:mnist.test.images})
    plt.scatter(mean[:,0],mean[:,1],c=labels)
    plt.colorbar()
    plt.show()
    ‘‘‘
    plt.figure(figsize=(5,4))
    plt.scatter(log_sigma[:,0],log_sigma[:,1],c=labels)
    plt.colorbar()
    plt.show()
    ‘‘‘

    ‘‘‘
    高斯分布取样,生成模拟数据
    ‘‘‘
    n = 15   #15 x 15的figure
    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 = sess.run(reconstructionout,feed_dict={zinput: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=‘gray‘)
    plt.show()  

为了进一步验证模型学习到了数据分布的情况,这次在高斯分布抽样中四级去一些点,将其映射到模型中的z,然后通过解码部分还原成真实图片,效果如下:

注意:代码中的norm.ppf()函数的作用是从按照百分比由大到小排列后的标准高斯分布中取值。norm代表标准高斯分布,ppf代表累积分布函数的反函数。例如x=ppf(0.05),就表示在集合中小于x的数所占的概率等于0.05。因此我们可以利用标准高斯分布的分布函数,计算出x的值。

完整代码:

# -*- coding: utf-8 -*-
"""
Created on Thu May 31 15:34:08 2018

@author: zy
"""

‘‘‘
变分自编码
‘‘‘

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.examples.tutorials.mnist import input_data
from scipy.stats import norm

mnist = input_data.read_data_sets(‘MNIST-data‘,one_hot=True)

print(type(mnist)) #<class ‘tensorflow.contrib.learn.python.learn.datasets.base.Datasets‘>

print(‘Training data shape:‘,mnist.train.images.shape)           #Training data shape: (55000, 784)
print(‘Test data shape:‘,mnist.test.images.shape)                #Test data shape: (10000, 784)
print(‘Validation data shape:‘,mnist.validation.images.shape)    #Validation data shape: (5000, 784)
print(‘Training label shape:‘,mnist.train.labels.shape)          #Training label shape: (55000, 10)

train_X = mnist.train.images
train_Y = mnist.train.labels
test_X = mnist.test.images
test_Y = mnist.test.labels

‘‘‘
定义网络参数
‘‘‘
n_input = 784
n_hidden_1 = 256
n_hidden_2 = 2
learning_rate = 0.001
training_epochs = 20               #迭代轮数
batch_size = 128                   #小批量数量大小
display_epoch = 3
show_num = 10

x = tf.placeholder(dtype=tf.float32,shape=[None,n_input])
#后面通过它输入分布数据,用来生成模拟样本数据
zinput = tf.placeholder(dtype=tf.float32,shape=[None,n_hidden_2])

‘‘‘
定义学习参数
‘‘‘
weights = {
        ‘w1‘:tf.Variable(tf.truncated_normal([n_input,n_hidden_1],stddev = 0.001)),
        ‘mean_w1‘:tf.Variable(tf.truncated_normal([n_hidden_1,n_hidden_2],stddev = 0.001)),
        ‘log_sigma_w1‘:tf.Variable(tf.truncated_normal([n_hidden_1,n_hidden_2],stddev = 0.001)),
        ‘w2‘:tf.Variable(tf.truncated_normal([n_hidden_2,n_hidden_1],stddev = 0.001)),
        ‘w3‘:tf.Variable(tf.truncated_normal([n_hidden_1,n_input],stddev = 0.001))
        }

biases = {
        ‘b1‘:tf.Variable(tf.zeros([n_hidden_1])),
        ‘mean_b1‘:tf.Variable(tf.zeros([n_hidden_2])),
        ‘log_sigma_b1‘:tf.Variable(tf.zeros([n_hidden_2])),
        ‘b2‘:tf.Variable(tf.zeros([n_hidden_1])),
        ‘b3‘:tf.Variable(tf.zeros([n_input]))
        }

‘‘‘
定义网络结构
‘‘‘
#第一个全连接层是由784个维度的输入样->256个维度的输出
h1 = tf.nn.relu(tf.add(tf.matmul(x,weights[‘w1‘]),biases[‘b1‘]))
#第二个全连接层并列了两个输出网络
z_mean = tf.add(tf.matmul(h1,weights[‘mean_w1‘]),biases[‘mean_b1‘])
z_log_sigma_sq = tf.add(tf.matmul(h1,weights[‘log_sigma_w1‘]),biases[‘log_sigma_b1‘])

#然后将两个输出通过一个公式的计算,输入到以一个2节点为开始的解码部分 高斯分布样本
eps = tf.random_normal(tf.stack([tf.shape(h1)[0],n_hidden_2]),0,1,dtype=tf.float32)
z = tf.add(z_mean,tf.multiply(tf.sqrt(tf.exp(z_log_sigma_sq)),eps))

#解码器 由2个维度的输入->256个维度的输出
h2 = tf.nn.relu(tf.matmul(z,weights[‘w2‘]) + biases[‘b2‘])
#解码器 由256个维度的输入->784个维度的输出  即还原成原始输入数据
reconstruction = tf.matmul(h2,weights[‘w3‘]) + biases[‘b3‘]

#这两个节点不属于训练中的结构,是为了生成指定数据时用的
h2out = tf.nn.relu(tf.matmul(zinput,weights[‘w2‘]) + biases[‘b2‘])
reconstructionout = tf.matmul(h2out,weights[‘w3‘]) + biases[‘b3‘]

‘‘‘
构建模型的反向传播
‘‘‘
#计算重建loss
#计算原始数据和重构数据之间的损失,这里除了使用平方差代价函数,也可以使用交叉熵代价函数
reconstr_loss = 0.5*tf.reduce_sum((reconstruction-x)**2)
print(reconstr_loss.shape)    #(,) 标量
#使用KL离散度的公式
latent_loss = -0.5*tf.reduce_sum(1 + z_log_sigma_sq - tf.square(z_mean) - tf.exp(z_log_sigma_sq),1)
print(latent_loss.shape)      #(128,)
cost = tf.reduce_mean(reconstr_loss+latent_loss)

#定义优化器
optimizer = tf.train.AdamOptimizer(learning_rate).minimize(cost)

num_batch = int(np.ceil(mnist.train.num_examples / batch_size))

‘‘‘
开始训练
‘‘‘
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())

    print(‘开始训练‘)
    for epoch in range(training_epochs):
        total_cost = 0.0
        for i in range(num_batch):
            batch_x,batch_y = mnist.train.next_batch(batch_size)
            _,loss = sess.run([optimizer,cost],feed_dict={x:batch_x})
            total_cost += loss

        #打印信息
        if epoch % display_epoch == 0:
            print(‘Epoch {}/{}  average cost {:.9f}‘.format(epoch+1,training_epochs,total_cost/num_batch))

    print(‘训练完成‘)

    #测试
    print(‘Result:‘,cost.eval({x:mnist.test.images}))
    #数据可视化
    reconstruction = sess.run(reconstruction,feed_dict = {x:mnist.test.images[:show_num]})
    plt.figure(figsize=(1.0*show_num,1*2))
    for i in range(show_num):
        #原始图像
        plt.subplot(2,show_num,i+1)
        plt.imshow(np.reshape(mnist.test.images[i],(28,28)),cmap=‘gray‘)
        plt.axis(‘off‘)

        #变分自编码器重构图像
        plt.subplot(2,show_num,i+show_num+1)
        plt.imshow(np.reshape(reconstruction[i],(28,28)),cmap=‘gray‘)
        plt.axis(‘off‘)
    plt.show()

    #绘制均值和方差代表的二维数据
    plt.figure(figsize=(5,4))
    #将onehot转为一维编码
    labels = [np.argmax(y) for y in mnist.test.labels]
    mean,log_sigma = sess.run([z_mean,z_log_sigma_sq],feed_dict={x:mnist.test.images})
    plt.scatter(mean[:,0],mean[:,1],c=labels)
    plt.colorbar()
    plt.show()
    ‘‘‘
    plt.figure(figsize=(5,4))
    plt.scatter(log_sigma[:,0],log_sigma[:,1],c=labels)
    plt.colorbar()
    plt.show()
    ‘‘‘

    ‘‘‘
    高斯分布取样,生成模拟数据
    ‘‘‘
    n = 15   #15 x 15的figure
    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 = sess.run(reconstructionout,feed_dict={zinput: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=‘gray‘)
    plt.show()

参考文章:变分自编码器(一):原来是这么一回事(推荐)

变分自编码器(二):从贝叶斯观点出发

变分自编码器(三):这样做为什么能成?

基于CNN和VAE的作诗机器人:随机成诗

【Learning Notes】变分自编码器(Variational Auto-Encoder,VAE)

Auto-Encoding Variational Bayes

原文地址:https://www.cnblogs.com/zyly/p/9121029.html

时间: 2024-10-10 01:30:22

变分自编码的相关文章

【Learning Notes】变分自编码(Variational Auto-Encoder,VAE)

近年,随着有监督学习的低枝果实被采摘的所剩无几,无监督学习成为了研究热点.VAE(Variational Auto-Encoder,变分自编码器)[1,2] 和 GAN(Generative Adversarial Networks) 等模型,受到越来越多的关注. 笔者最近也在学习 VAE 的知识(从深度学习角度).首先,作为工程师,我想要正确的实现 VAE 算法,以及了解 VAE 能够帮助我们解决什么实际问题:作为人工智能从业者,我同时希望在一定程度上了解背后的原理. 作为学习笔记,本文按照由

MCMC算法解析

MCMC算法的核心思想是我们已知一个概率密度函数,需要从这个概率分布中采样,来分析这个分布的一些统计特性,然而这个这个函数非常之复杂,怎么去采样?这时,就可以借助MCMC的思想. 它与变分自编码不同在于:VAE是已知一些样本点,这些样本肯定是来自于同一分布,但是我们不知道这个分布函数的具体表达式,然而我们需要从这个分布中去采取新的样本,怎么采样,这时,就需要借助VAE的思想. 个人的一点总结,不知道是否正确,如果有不同的理解,希望指正批评! MCMC原理讲解 以下内容博客转自:https://w

变分自动编码器

变分自动编码器 自编码器 中间层的编码维度要远远小于输出数据,整个模型训练目标为最小化重建输入数据误差 标准自编码器面临的问题在于 自编玛器将输入数据转为隐空间中的表达式不是连续的,使得解码器对于存在于类别之间的区域无法进行解码,因此提出了变分自编码器 变分自编码器 变分自编码器的隐空间设计为连续的分布以便进行随机采样和插值,编码器输出两个n维向量,分别为均值向量u以及标准差向量sigma:随后通过对u和sigam作为均值和方差采样得到随机变量x,n次采样后形成n维采样后结果作为编码输出,送入后

变分自编码器解析

概述 译自https://jaan.io/what-is-variational-autoencoder-vae-tutorial/ 在讨论变分自动编码器时,为什么深度学习研究人员和概率机器学习人员会感到困惑? 什么是变体自动编码器? 为什么这个词会引起混乱? 这是因为神经网络和概率模型在基本概念和描述语言上存在差异.此教程的目标是弥合这一思想鸿沟,允许在这些领域之间进行更多的协作和讨论,并提供一致的实现方法. 变分自编码器用起来很酷,让我们能够设计复杂的数据生成模型,并将其应用于大型数据集.它

Python中编码的详细讲解

看这篇文章前,你应该已经知道了为什么有编码,以及编码的种类情况 ASCII 占1个字节,只支持英文 GB2312 占2个字节,支持6700+汉字 GBK GB2312的升级版,支持21000+汉字 Shift-JIS 日本字符 ks_c_5601-1987 韩国编码 TIS-620 泰国编码 由于每个国家都有自己的字符,所以其对应关系也涵盖了自己国家的字符,但是以上编码都存在局限性,即:仅涵盖本国字符,无其他国家字符的对应关系.应运而生出现了万国码,他涵盖了全球所有的文字和二进制的对应关系, U

java编码规范

右括号") "与其后面的关键字之间,关键字与其后面的左括号"("或"{"之间,以及"}"与"{"之间,要以一个空格隔开:除". "外,所有二元操作符的前.后要加空格:在逗号后边加一个空格. 说明: 一个紧跟着括号的关键词应该被空格分开: 空白应该位于参数列表中逗号的后面: 所有的二元运算符,除了".",应该使用空格将之与操作数分开.一元操作符和操作数之间不应该加空格,

微信实现定位城市并获取城市编码

最近在做一个项目是将用户的当前所在市县定位出来并展示在手机端页面,同时还要获取到该市县的城市编码从而进行数据过滤,这里重点讲定位城市及获取城市编码 前端页面代码: 首先引用腾讯地图的一个js <script type="text/javascript" src="https://3gimg.qq.com/lightmap/components/geolocation/geolocation.min.js" ></script> 同时在页面加载

python字符编码

1. 字符编码简介 阶段一:现代计算机起源于美国,最早诞生也是基于英文考虑的ASCII ASCII:一个Bytes代表一个字符(英文字符/键盘上的所有其他字符),1Bytes=8bit,8bit可以表示0-2**8-1种变化,即可以表示256个字符 ASCII最初只用了后七位,127个数字,已经完全能够代表键盘上所有的字符了(英文字符/键盘的所有其他字符) 后来为了将拉丁文也编码进了ASCII表,将最高位也占用了 阶段二:为了满足中文,中国人定制了GBK GBK:2Bytes代表一个字符 为了满

刨根究底字符编码之十二——UTF-8究竟是怎么编码的

UTF-8究竟是怎么编码的 1. UTF-8编码是Unicode字符集的一种编码方式(CEF),其特点是使用变长字节数(即变长码元序列.变宽码元序列)来编码.一般是1到4个字节,当然,也可以更长. 为什么要变长呢?这可以理解为按需分配,比如一个字节足以容纳所有的ASCII码字符,那何必补一堆0用更多的字节来存储呢? 实际上变长编码有其优势也有其劣势,优势是节省空间.自动纠错性能好.利于传输.扩展性强,劣势是不利于程序内部处理,比如正则表达式检索:而UTF-32这样等长码元序列(即等宽码元序列)的