2019/3/2

Autoencoders (自編碼器)

30 Autoencoders (自編碼器) 

► 什麼是Autoencoders?
Autoencoders 是一種常和非監督式學習、降低維度、和資料壓縮相關聯的神經網路架構,學習以隱藏層中較少的神經元產生和輸入層相同的輸出,使隱藏層可以較少的參數學習輸入的特徵,以較少的神經元表示輸入的特徵可降低輸入資料集的維度。自編碼器一般會有兩級,一級是編碼一級是解碼,在編碼級系統模型學習是以較少維度的壓向量來代表輸入,在解碼級以此壓向量來表示輸出,一般損失(Loss)是以輸入和輸出的熵數距離(entropy distance)來表示,藉由降低損失我們可以訓練參數來壓縮資料並使之能夠用另一組參數來重建資料。一般化的自編碼器可用下圖表示
自編碼器
圖中左端是原始的樣本 (Original),經編碼器(Encoder) 得一壓縮過(Compressed)的樣本 ,若再經解碼器(Decoder) 可得重建 (Reconstruction)後的樣本。
► Autoencoders 的種類
  • Simple autoencoder: 簡易自編碼器,此種自編碼器的隱藏層裡的神經元數目比輸入特徵數少,目的是要用較少的維度去代表較多的微度,有點像是主成份分析(PCA),這個種類有時稱undercomplete (欠完整) autoencoders。它可是是只含一個隱藏層或是多個隱藏層。當有多個時,此種編碼器可分成編碼器和解碼器兩部份,編碼器部份負責編碼,解碼器部份負責解碼,因有很多隱藏層疊積而成,又稱Stacked (疊積) autoencoders。如果我們把編碼器的功能寫成$f(\cdot)$且把解碼器功能寫成$g(\cdot)$,而損失函數是$L(\cdot)$,則系統模型就是要最小化$L(x,g(f(x))$,$x$是輸入的特徵。
  • Sparse autoencoder: 稀疏自編碼器,此種自編碼器的損失函數中加入一個正則化項,如$L(x,g(f(x))+\Omega(h)$,其中$h$可看成系統參數的數目或複雜度,此種sparsity regularization 的方式是用於降低系統模型的複雜度。
  • Convolutional autoencoder (CAE): 摺積自編碼器,此種編碼器隱藏層使用摺積操作處理輸入的特徵,亦即是一種CNN 方式的自編碼器,典型如輸入層$\rightarrow $ 摺積層$\rightarrow $ 池化層$\rightarrow $ 摺積層$\rightarrow $ 池化層$\rightarrow $ 輸出層。第一組摺積和池化層用以降低特徵的維度,第二組用以還原特徵。
  • Variational autoencoder (VAE): 變分自編碼器,是一種最新發展的自編碼器,屬於生成式模型 (generative model),它藉由產生機率模型來製造和原來樣本相同或相似的輸出。在VAE 中編碼器將輸入樣本轉成於潛藏空間(latent space) 的參數 ,並用以取樣潛藏點。解碼器用此潛藏點來重製樣本,因此於VAE 中,學習聚焦於如何讓由輸入重製輸出的機率最大化。我們會在稍後的單元討論此種自編碼器。
► Stacked autoencoders 例子
以下我們由參考文獻裡的一個例子來了解如何用TensorFlow 實作 Stacked autoencoders 。
import tensorflow as tf
%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import os
import sys

def plot_image(image, shape=[28, 28]):
    plt.imshow(image.reshape(shape), cmap="Greys", interpolation="nearest")
    plt.axis("off")
plot_image 定義一個畫圖的函式,最後會用到。
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("/tmp/data/")
上兩行會下載壓縮的資料集到工作磁碟機上的/tmp/data/目錄底下,並抽出資料集。
tf.reset_default_graph()
from functools import partial
n_inputs = 28 * 28
n_hidden1 = 300
n_hidden2 = 150  # codings
n_hidden3 = n_hidden1
n_outputs = n_inputs
learning_rate = 0.01
l2_reg = 0.0001
X = tf.placeholder(tf.float32, shape=[None, n_inputs])
he_init = tf.contrib.layers.variance_scaling_initializer() 
l2_regularizer = tf.contrib.layers.l2_regularizer(l2_reg)
my_dense_layer = partial(tf.layers.dense,
                         activation=tf.nn.elu,
                         kernel_initializer=he_init,
                         kernel_regularizer=l2_regularizer)

hidden1 = my_dense_layer(X, n_hidden1)
hidden2 = my_dense_layer(hidden1, n_hidden2)
hidden3 = my_dense_layer(hidden2, n_hidden3)
outputs = my_dense_layer(hidden3, n_outputs, activation=None)
reconstruction_loss = tf.reduce_mean(tf.square(outputs - X))
reg_losses = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)
loss = tf.add_n([reconstruction_loss] + reg_losses)
optimizer = tf.train.AdamOptimizer(learning_rate)
training_op = optimizer.minimize(loss)
init = tf.global_variables_initializer()
saver = tf.train.Saver() 
第26行使用partial 定義叫用 tf.layers.dense 時要使用的參數,定義之後要叫用時就比較方便,不必一一輸入那些參數。第41行定義Saver 用於儲存系統模型,儲存後可叫出使用。
n_epochs = 5
batch_size = 150

with tf.Session() as sess:
    init.run()
    for epoch in range(n_epochs):
        n_batches = mnist.train.num_examples // batch_size
        for iteration in range(n_batches):
            print("\r{}%".format(100 * iteration // n_batches), end="")  
            sys.stdout.flush()                                          
            X_batch, y_batch = mnist.train.next_batch(batch_size)
            sess.run(training_op, feed_dict={X: X_batch})
        loss_train = reconstruction_loss.eval(feed_dict={X: X_batch})    
        print("\r{}".format(epoch), "Train MSE:", loss_train)           
        saver.save(sess, "./my_model_all_layers.ckpt")   
啟動sess 最後將模型存於工作目錄。
def show_reconstructed_digits(X, outputs, model_path = None, n_test_digits = 2):
    with tf.Session() as sess:
        if model_path:
            saver.restore(sess, model_path)
        X_test = mnist.test.images[:n_test_digits]
        outputs_val = outputs.eval(feed_dict={X: X_test})

    fig = plt.figure(figsize=(8, 3 * n_test_digits))
    for digit_index in range(n_test_digits):
        plt.subplot(n_test_digits, 2, digit_index * 2 + 1)
        plot_image(X_test[digit_index])
        plt.subplot(n_test_digits, 2, digit_index * 2 + 2)
        plot_image(outputs_val[digit_index])
        
show_reconstructed_digits(X, outputs, "./my_model_all_layers.ckpt")
先建立一個函式用於重建圖片數字,再呼叫使用。最後所得的圖片如下
左邊原圖,右邊是經過自編碼器的輸出
此例中先用所有資料集進行訓練,再用測試集裡的兩個圖片輸入系統並求輸出,事實上這樣並不能真正見到自編碼器把維度變小。如果我們系統拆成兩個部份,輸入到hidden2視成編碼器,hidden2到輸出視成解碼器,並將所有資料集及測試集的資料用編碼器進行編碼,則編出來的資料每張圖片就只有150 pixels ,再用這些編過碼的資料用解碼器解碼就能得到768 pixels 的圖片,雖然會有些失真,但如上圖所示可以顯示出原來的數字,這樣就能看出自編碼器把維度降低的效果了。

參考文獻
Aurélien Géron, Hands-On Machine Learning with Scikit-Learn and TensorFlow, O'Reilly, 2017

沒有留言:

張貼留言