第二十節,變分自編碼


 一 變分自編碼(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,也是一個很棒的生成模型。接下來就是結合自編碼器來實現重構,保證有效信息沒有丟失,再加上一系列的推導,最后把模型實現。框架的示意圖如下:

vae的傳統理解

 

看出了什么問題了嗎?如果像這個圖的話,我們其實完全不清楚:究竟經過重新采樣出來的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是為每個樣本構造專屬的正態分布,然后采樣來重構

事實上,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()  
    
    
    
    
    
    
View Code

參考文章

[1]變分自編碼器(一):原來是這么一回事(推薦)

[2]變分自編碼器(二):從貝葉斯觀點出發

[3]變分自編碼器(三):這樣做為什么能成?

[4]基於CNN和VAE的作詩機器人:隨機成詩

[5]【Learning Notes】變分自編碼器(Variational Auto-Encoder,VAE)

[6]Auto-Encoding Variational Bayes


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM