(https://arxiv.org/pdf/1312.6114v10.pdf)
在最大化含有隐藏变量的后验概率问题时,一般我们可以用EM算法,采样算法以及变分贝叶斯等方法。其中变分贝叶斯是用一个较为简单的Q函数去逼近后验概率密度函数,其中的主要问题是如何求解Q函数。传统的变分贝叶斯,其Q函数的求解可以用平均场理论。在变分自编码中,作者将要求解的Q函数假设为高斯分布,这样一来直接将问题转化成求解Q函数的均值和方差。
为了更好的理解变分自编码,我们首先应该知道一些变分贝叶斯的理论基础。因此,我们会先简单介绍一下变分法和变分贝叶斯。
首先我们看一个经典数学问题,最速线问题。该问题的描述是:在重力作用且忽略摩擦力的情况下,一个质点在一点A以速率为零开始,沿某条曲线,去到一点不高于A的B,怎样的曲线能令所需的时间最短呢?
将该问题看作一个”函数“,那么该“函数”的特点是:其定义域为函数(曲线),而值域为实数(最短距离)。我们称类似这样的”函数”为泛函。而求解泛函极值的方法称为变分法。
我们都知道贝叶斯的目的是求得后验概率,朴素贝叶斯是这样的,假设已知P(A),P(A|B),P(B),我们可以根据贝叶斯公式求得后验概率P(B|A):
由于朴素贝叶斯公式中的先验概率分布以及各种边沿概率分布都比较好求,因此可以直接利用贝叶斯公式。然而在有些情况下,我们不可能获取所有事件的数据,如事件B,这就是含有隐藏变量的情况。或者后验概率及其复杂,很难求解。在这些情况下,我们就无法根据贝叶斯公式直接求的后验概率。这样的情况下,特别是后验概率分布未知且难以求解的情况下,我们可以利用变分贝叶斯求解。
后验概率函数太复杂,那么我们是否可以用一个简单一点的函数来逼近?答案是可以的,我们称这个函数为Q。因此,我们现在的问题是,如何从众多的相对简单的函数中找到一个合适的函数来逼近后验概率函数。这其实就是变分问题,在引入Q函数后的贝叶斯公式就是一个泛函。尽管这是个变分问题,但是我们没有必要对变分法非常熟悉。我们上面说了那么多就是为了说明什么是变分贝叶斯。
那么我们用数学语言来描述变分贝叶斯。假设有已知数据X,求隐藏变量Z的后验概率P(Z|X)。我们之前说过用Q(Z)来逼近P(Z|X)。那么我们可以用KL散度来度量Q(Z)和P(Z|X)的相似度,我们的目标也就是最小化KL散度,即:
或者
其中L(Q)称为变分自由能。由公式3我们可以看出logP(X)是由KL散度和变分自由能构成的,因此为了最小化KL散度,我们只需最大化变分自由能L(Q)。
在传统的变分贝叶斯中,可以利用平均场理论,通过优化L(Q(Z))来求解Q(Z)。平均场理论和变分自编码是求解Q(Z)的不同方法,因此我们可以先不关注平均场理论。
在变分自编码中,作者假设Q(Z)为高斯分布,P(Z)为标准正太分布。这样一来我们所要做的事情就是求解出高斯分布的均值和方差。论文中直接将高斯分布的均值和方差作为网络的一部分输出。由于整个变分贝叶斯网络的结构是由其损失函数决定的,因此接下来我们主要由公式入手,推导出变分自编码的损失函数。
由式(3)我们可以得出:
其中J为隐藏变量Z的维数。而
到此我们已经推导出了变分自编码的损失函数,接下来我们分析一下这个损失函数在网络中是怎么表现出来的。
论文中直接将自编码机编码层的最后一层作为Q(Z)的均值和方差,这边就可以计算式(4)等号右边第一项的损失函数。在得到均值和方差后,利用采样算法采样出Z,然后Z作为解码网络的输入,解码网络输出X,然后计算X的重构误差最大化
我门上面有提到用采样算法采样出Z,在边我们简单说明一下。论文中为了使整个网络可导,使用了可导的蒙特卡罗采样算法,公式如下:
其中随机数e满足[0,1]的均匀分布。
从第3节的介绍,我们知道了损失函数来自网络的两个输出,分别是:均值方差向量和重构出来的X。在论文中,作者用了三个网络模块构成了变分自编码机。具体的:
首先构建两个网络模块,这两个网络模块有共同的输入,输出分别是均值和方差,计算KL散度。
然后,利用求得的均值方差采样出Z,接着构建一个解码网络。其输入是Z,输出是X,计算重构误差。
# -*- coding: utf-8 -*-
"""
Varational Auto Encoder Example.
变分自动编码器
"""
from __future__ import division, print_function, absolute_import
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
#Import MNIST data
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)
# 基础模块
class Layer:
def __init__(self, input, n_output):
self.input = input
W = tf.Variable(tf.truncated_normal([ int(self.input.get_shape()[1]), n_output ], stddev = 0.001))#tf.shape(input)[0]
b = tf.Variable(tf.constant(0., shape = [ n_output ]))
self.raw_output = tf.matmul(input, W) + b
self.output = tf.nn.relu(self.raw_output)
# 样本集X
n_X = 784 # 28 * 28
n_z = 20 # latent variable count
X = tf.placeholder(tf.float32, shape = [ None, n_X ])
# Encoder
## \mu(X) 采用二层网络
ENCODER_HIDDEN_COUNT = 400
mu = Layer(Layer(X, ENCODER_HIDDEN_COUNT).output, n_z).raw_output
## \Sigma(X) 采用二层网络
log_sigma = Layer(Layer(X, ENCODER_HIDDEN_COUNT).output, n_z).raw_output # 为了训练不出nan? 至少实验的时候,直接让这个网络代表sigma是算不出来的,请高人指点!!!
sigma = tf.exp(log_sigma)
## KLD = D[N(mu(X), sigma(X))||N(0, I)] = 1/2 * sum(sigma_i + mu_i^2 - log(sigma_i) - 1)
KLD = 0.5 * tf.reduce_sum(sigma + tf.pow(mu, 2) - log_sigma - 1, reduction_indices = 1) # reduction_indices = 1代表按照每个样本计算一条KLD
# epsilon = N(0, I) 采样模块
epsilon = tf.random_normal(tf.shape(sigma), name = 'epsilon')
# z = mu + sigma^ 0.5 * epsilon
z = mu + tf.exp(0.5 * log_sigma) * epsilon
# Decoder ||f(z) - X|| ^ 2 重建的X与X的欧式距离,更加成熟的做法是使用crossentropy
def buildDecoderNetwork(z):
DECODER_HIDDEN_COUNT = 400
layer1 = Layer(z, DECODER_HIDDEN_COUNT)
layer2 = Layer(layer1.output, n_X)
return layer2.raw_output
reconstructed_X = buildDecoderNetwork(z)
reconstruction_loss = tf.reduce_sum(tf.nn.sigmoid_cross_entropy_with_logits(reconstructed_X, X), reduction_indices = 1)
loss = tf.reduce_mean(reconstruction_loss + KLD)
# minimize loss
n_steps = 100000
learning_rate = 0.01
batch_size = 100
optimizer = tf.train.AdamOptimizer(learning_rate).minimize(loss)
with tf.Session() as sess:
sess.run(tf.initialize_all_variables())
for step in xrange(1, n_steps):
batch_x, batch_y = mnist.train.next_batch(batch_size)
_, l = sess.run([ optimizer, loss ], feed_dict = { X: batch_x })
if step % 100 == 0:
print('Step', step, ', Loss:', l)