TensorFlow上實現AutoEncoder自編碼器

marsjhao發表於2020-04-06

一、概述

AutoEncoder大致是一個將資料的高維特徵進行壓縮降維編碼,再經過相反的解碼過程的一種學習方法。學習過程中通過解碼得到的最終結果與原資料進行比較,通過修正權重偏置引數降低損失函式,不斷提高對原資料的復原能力。學習完成後,前半段的編碼過程得到結果即可代表原資料的低維“特徵值”。通過學習得到的自編碼器模型可以實現將高維資料壓縮至所期望的維度,原理與PCA相似。


二、模型實現

1. AutoEncoder

首先在MNIST資料集上,實現特徵壓縮和特徵解壓並視覺化比較解壓後的資料與原資料的對照。

先看程式碼:

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt

# 匯入MNIST資料
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=False)

learning_rate = 0.01
training_epochs = 10
batch_size = 256
display_step = 1
examples_to_show = 10
n_input = 784

# tf Graph input (only pictures)
X = tf.placeholder("float", [None, n_input])

# 用字典的方式儲存各隱藏層的引數
n_hidden_1 = 256 # 第一編碼層神經元個數
n_hidden_2 = 128 # 第二編碼層神經元個數
# 權重和偏置的變化在編碼層和解碼層順序是相逆的
# 權重引數矩陣維度是每層的 輸入*輸出,偏置引數維度取決於輸出層的單元數
weights = {
    'encoder_h1': tf.Variable(tf.random_normal([n_input, n_hidden_1])),
    'encoder_h2': tf.Variable(tf.random_normal([n_hidden_1, n_hidden_2])),
    'decoder_h1': tf.Variable(tf.random_normal([n_hidden_2, n_hidden_1])),
    'decoder_h2': tf.Variable(tf.random_normal([n_hidden_1, n_input])),
}
biases = {
    'encoder_b1': tf.Variable(tf.random_normal([n_hidden_1])),
    'encoder_b2': tf.Variable(tf.random_normal([n_hidden_2])),
    'decoder_b1': tf.Variable(tf.random_normal([n_hidden_1])),
    'decoder_b2': tf.Variable(tf.random_normal([n_input])),
}

# 每一層結構都是 xW + b
# 構建編碼器
def encoder(x):
    layer_1 = tf.nn.sigmoid(tf.add(tf.matmul(x, weights['encoder_h1']),
                                   biases['encoder_b1']))
    layer_2 = tf.nn.sigmoid(tf.add(tf.matmul(layer_1, weights['encoder_h2']),
                                   biases['encoder_b2']))
    return layer_2


# 構建解碼器
def decoder(x):
    layer_1 = tf.nn.sigmoid(tf.add(tf.matmul(x, weights['decoder_h1']),
                                   biases['decoder_b1']))
    layer_2 = tf.nn.sigmoid(tf.add(tf.matmul(layer_1, weights['decoder_h2']),
                                   biases['decoder_b2']))
    return layer_2

# 構建模型
encoder_op = encoder(X)
decoder_op = decoder(encoder_op)

# 預測
y_pred = decoder_op
y_true = X

# 定義代價函式和優化器
cost = tf.reduce_mean(tf.pow(y_true - y_pred, 2)) #最小二乘法
optimizer = tf.train.AdamOptimizer(learning_rate).minimize(cost)

with tf.Session() as sess:
    # tf.initialize_all_variables() no long valid from
    # 2017-03-02 if using tensorflow >= 0.12
    if int((tf.__version__).split('.')[1]) < 12 and int((tf.__version__).split('.')[0]) < 1:
        init = tf.initialize_all_variables()
    else:
        init = tf.global_variables_initializer()
    sess.run(init)
    # 首先計算總批數,保證每次迴圈訓練集中的每個樣本都參與訓練,不同於批量訓練
    total_batch = int(mnist.train.num_examples/batch_size) #總批數
    for epoch in range(training_epochs):
        for i in range(total_batch):
            batch_xs, batch_ys = mnist.train.next_batch(batch_size)  # max(x) = 1, min(x) = 0
            # Run optimization op (backprop) and cost op (to get loss value)
            _, c = sess.run([optimizer, cost], feed_dict={X: batch_xs})
        if epoch % display_step == 0:
            print("Epoch:", '%04d' % (epoch+1), "cost=", "{:.9f}".format(c))
    print("Optimization Finished!")

    encode_decode = sess.run(
        y_pred, feed_dict={X: mnist.test.images[:examples_to_show]})
    f, a = plt.subplots(2, 10, figsize=(10, 2))
    for i in range(examples_to_show):
        a[0][i].imshow(np.reshape(mnist.test.images[i], (28, 28)))
        a[1][i].imshow(np.reshape(encode_decode[i], (28, 28)))
    plt.show()

程式碼解讀:

首先,匯入將要使用到的各種庫和資料集,定義各個引數如學習率、訓練迭代次數等,清晰明瞭便於後期修改。由於自編碼器的神經網路結構非常有規律性,都是xW + b的結構,故將每一層的權重W和偏置b的變數tf.Variable統一置於一個字典中,通過字典的key值更加清晰明瞭的描述。模型構建思路上,將編碼器部分和解碼器部分分開構建,每一層的啟用函式使用Sigmoid函式,編碼器通常與編碼器使用同樣的啟用函式。通常編碼器部分和解碼器部分是一個互逆的過程,例如我們設計將784維降至256維再降至128維的編碼器,解碼器對應的就是從128維解碼至256維再解碼至784維。定義代價函式,代價函式表示為解碼器的輸出與原始輸入的最小二乘法表達,優化器採用AdamOptimizer訓練階段每次迴圈將所有的訓練資料都參與訓練。經過訓練,最終將訓練結果與原資料視覺化進行對照,如下圖,還原度較高。如果增大訓練迴圈次數或者增加自編碼器的層數,可以得到更好的還原效果。

執行結果:


2. Encoder

Encoder編碼器工作原理與AutoEncoder相同,我們將編碼得到的低維“特徵值”在低維空間中視覺化出來,直觀顯示資料的聚類效果。具體地說,將784維的MNIST資料一步步的從784到128到64到10最後降至2維,在2維座標系中展示遇上一個例子不同的是,在編碼器的最後一層中我們不採用Sigmoid啟用函式,而是將採用預設的線性啟用函式,使輸出為(-∞,+∞)。

完整程式碼:

import tensorflow as tf
import matplotlib.pyplot as plt

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=False)

learning_rate = 0.01
training_epochs = 10
batch_size = 256
display_step = 1
n_input = 784
X = tf.placeholder("float", [None, n_input])

n_hidden_1 = 128
n_hidden_2 = 64
n_hidden_3 = 10
n_hidden_4 = 2
weights = {
    'encoder_h1': tf.Variable(tf.truncated_normal([n_input, n_hidden_1],)),
    'encoder_h2': tf.Variable(tf.truncated_normal([n_hidden_1, n_hidden_2],)),
    'encoder_h3': tf.Variable(tf.truncated_normal([n_hidden_2, n_hidden_3],)),
    'encoder_h4': tf.Variable(tf.truncated_normal([n_hidden_3, n_hidden_4],)),
    'decoder_h1': tf.Variable(tf.truncated_normal([n_hidden_4, n_hidden_3],)),
    'decoder_h2': tf.Variable(tf.truncated_normal([n_hidden_3, n_hidden_2],)),
    'decoder_h3': tf.Variable(tf.truncated_normal([n_hidden_2, n_hidden_1],)),
    'decoder_h4': tf.Variable(tf.truncated_normal([n_hidden_1, n_input],)),
}
biases = {
    'encoder_b1': tf.Variable(tf.random_normal([n_hidden_1])),
    'encoder_b2': tf.Variable(tf.random_normal([n_hidden_2])),
    'encoder_b3': tf.Variable(tf.random_normal([n_hidden_3])),
    'encoder_b4': tf.Variable(tf.random_normal([n_hidden_4])),
    'decoder_b1': tf.Variable(tf.random_normal([n_hidden_3])),
    'decoder_b2': tf.Variable(tf.random_normal([n_hidden_2])),
    'decoder_b3': tf.Variable(tf.random_normal([n_hidden_1])),
    'decoder_b4': tf.Variable(tf.random_normal([n_input])),
}
def encoder(x):
    layer_1 = tf.nn.sigmoid(tf.add(tf.matmul(x, weights['encoder_h1']),
                                   biases['encoder_b1']))
    layer_2 = tf.nn.sigmoid(tf.add(tf.matmul(layer_1, weights['encoder_h2']),
                                   biases['encoder_b2']))
    layer_3 = tf.nn.sigmoid(tf.add(tf.matmul(layer_2, weights['encoder_h3']),
                                   biases['encoder_b3']))
    # 為了便於編碼層的輸出,編碼層隨後一層不使用啟用函式
    layer_4 = tf.add(tf.matmul(layer_3, weights['encoder_h4']),
                                    biases['encoder_b4'])
    return layer_4

def decoder(x):
    layer_1 = tf.nn.sigmoid(tf.add(tf.matmul(x, weights['decoder_h1']),
                                   biases['decoder_b1']))
    layer_2 = tf.nn.sigmoid(tf.add(tf.matmul(layer_1, weights['decoder_h2']),
                                   biases['decoder_b2']))
    layer_3 = tf.nn.sigmoid(tf.add(tf.matmul(layer_2, weights['decoder_h3']),
                                biases['decoder_b3']))
    layer_4 = tf.nn.sigmoid(tf.add(tf.matmul(layer_3, weights['decoder_h4']),
                                biases['decoder_b4']))
    return layer_4

encoder_op = encoder(X)
decoder_op = decoder(encoder_op)

y_pred = decoder_op
y_true = X

cost = tf.reduce_mean(tf.pow(y_true - y_pred, 2))
optimizer = tf.train.AdamOptimizer(learning_rate).minimize(cost)

with tf.Session() as sess:
    # tf.initialize_all_variables() no long valid from
    # 2017-03-02 if using tensorflow >= 0.12
    if int((tf.__version__).split('.')[1]) < 12 and int((tf.__version__).split('.')[0]) < 1:
        init = tf.initialize_all_variables()
    else:
        init = tf.global_variables_initializer()
    sess.run(init)
    total_batch = int(mnist.train.num_examples/batch_size)
    for epoch in range(training_epochs):
        for i in range(total_batch):
            batch_xs, batch_ys = mnist.train.next_batch(batch_size)  # max(x) = 1, min(x) = 0
            _, c = sess.run([optimizer, cost], feed_dict={X: batch_xs})
        if epoch % display_step == 0:
            print("Epoch:", '%04d' % (epoch+1), "cost=", "{:.9f}".format(c))
    print("Optimization Finished!")

    encoder_result = sess.run(encoder_op, feed_dict={X: mnist.test.images})
    plt.scatter(encoder_result[:, 0], encoder_result[:, 1], c=mnist.test.labels)
    plt.colorbar()
    plt.show()

實驗結果:


由結果可知,2維編碼特徵有較好的聚類效果,圖中每個顏色代表了一個數字,聚集性很好。

當然,本次實驗所得到的結果只是對AutoEncoder做一個簡單的介紹,要想得到期望的效果,還應該設計更加複雜的自編碼器結構,得到區分性更好的特徵。

相關文章