降維方法PCA、Isomap、LLE、Autoencoder方法與python實現

所用的數據

選用 MNIST 手寫數字數據集 ( http://yann.lecun.com/exdb/mnist/ )。
該數據集包括 55000 個訓練集樣本和 10000 個測試集樣本。

PCA( 主成分分析)

方法介紹

主成分分析是最常用的一種降維方法。
目標:找到數據本質低維子空間的正交基.( 也就是說使用一個超平面對所有樣本進行恰當的表達)
故而其超平面應具有以下兩個性質:

  1. 最大可分性:樣本在這個超平面上的投影儘可能分開。
  2. 最近重構性:樣本點到這個超平面的距離都足夠近。

首先對於最大可分性,若所有點儘可能分開,也就是說投影后樣本點的方差最大化。

故而 PCA 的數學定義是:一個正交化線性變換,把數據變換到一個新的座標系統中,使得這一數據的任何投影的第一大方差在第一個座標(稱爲第一主成分)上,第二大方差在第二個座標(第二主成分)上,依次類推。
首先假設數據進行了中心化,即 ixi=0\sum_i x_i=0,投影變換後的新座標爲 W={ω1,ω2, ,ωd}W=\{\omega_1,\omega_2,\cdots,\omega_d\},其中 ωi\omega_i 是標準正交基向量(即 ωi2=1\|\omega_i\|_2=1,ωiTωj=0(ij)\omega_i^T\omega_j=0(i\neq j) )。
樣本點 xix_i 在新空間超平面上的投影是 WTxiW^Tx_i,投影后樣本點的方差是 iWTxixiTW\sum_i W^Tx_i x_i^TW,於是優化目標可以寫爲:
maxWtr{WTXXTW}\max \limits_{W}tr\{W^TX X^TW\}
對上式使用拉格朗日乘數法可得:
XXTW=λWXX^TW=\lambda W
所以,這個式子中 λ\lambda 是特徵值, XXTXX^T 是特徵向量。只需要對協方差矩陣 XXTXX^T 進行特徵值分解,將求得的特徵值排序:λ1λ2λd\lambda_1\geq\lambda_2\geq\cdots\geq\lambda_d,再取前 dd' 個特徵值對應的特徵向量構成 W={ω1,ω2, ,ωd}W=\{\omega_1,\omega_2,\cdots,\omega_{d'}\} ,這就是 PCA 的解(實踐中通常通過對 X 進行奇異值分解代替協方差矩陣特徵值分解)。

實驗結果分析

這裏設置降維的維數爲 2 ,速度很快。所得結果爲:

在二維空間中,對於 0 和 2 有分離效果,其他的效果並不十分明顯。

Isomap( 等度量映射)

方法介紹

流形學習是一類借鑑了拓撲流概念的降維方法,
Isomap 的基本出發點是認爲低維流形嵌入到高維空間之後,在高位空間計算直線距離具有誤導性,因爲其在低維流形中是不可達的。
故而利用流形在局部與歐式空間同胚,對每個點基於歐式空間找出近鄰點,建立近鄰連接圖,近鄰點存在連接,非近鄰點不存在連接,計算兩點之間流形圖測地線的問題,就變爲近鄰連接圖最短路徑問題.
Isomap 需要指定近鄰點的個數或者距離閾值,若指定範圍較大,則可能出現流形之間“短路”問題,若範圍過小,則圖中有些區域可能與其他區域不存在連接,出現“斷路”問題。兩者都會給後面的最短路計算造成誤導。

實驗結果分析

這裏設置降維的維數爲 2,設置相鄰點數爲 5 ,使用 MNIST 數據 10000 條。
Isomap 所消耗的時間比較長。
所得結果爲:

在二維空間中有一定的分離效果,但是分離效果並不明顯。

LLE

方法介紹

LLE 的全稱爲局部線型嵌入,其核心爲:流形的局部 ( 一個小的鄰域空間)爲近似線性。故而 LLE 算法認爲每一個數據點都可以由其近鄰點的線性加權組合構造得到。即認爲該數據與其近鄰點的線性加權組合構造得到的數據的誤差最小。加權組合即爲: j=1kwijxj\sum \limits_{j=1}^k w_{ij}x_j,其中 ww 爲加權係數。

定義誤差函數爲;
J(w)=i=1mxijQ(i)wijxj2J(w)=\sum_{i=1}^m \| x_i-\sum \limits_{j\in Q(i)} w_{ij}x_j\|^2
其中, Q(i)Q(i) 表示 ii 的 k 個近鄰樣本集合。我們希望將誤差函數最小化。
對於加權係數,至少有兩個要求:

  1. 其中若 xix_ixjx_j 不爲鄰域,則強制 wij=0w_{ij}=0
  2. 對於加權值要歸一化,即 jWij=1\sum \limits_{j}{\mathbf {W} _{ij}}=1

因爲加權值歸一化,故 jQ(i)wijxi=xi\sum \limits_{j\in Q(i)}w_{ij}x_i=x_i.

如何最小化矩陣。

J(w)=i=1mxijQ(i)wijxj2=i=1mjQ(i)wijxijQ(i)wijxj2=i=1mjQ(i)wij(xixj)2=i=1mWiT(xixj)(xixj)TWiJ(w)=\sum \limits_{i=1}^m \| x_i-\sum \limits_{j\in Q(i)} w_{ij}x_j\|^2 \\ =\sum \limits_{i=1}^m \| \sum \limits_{j\in Q(i)}w_{ij}x_i-\sum \limits_{j\in Q(i)} w_{ij}x_j\|^2 \\ =\sum \limits_{i=1}^m \| \sum \limits_{j\in Q(i)}w_{ij}(x_i-x_j)\|^2 \\ =\sum \limits_{i=1}^m W_i^T (x_i-x_j)(x_i-x_j)^T W_i
其中, Wi=(wi1,wi2, ,wi1)W_i=(w_{i1},w_{i2},\cdots,w_{i1})
若原始數據點在 D 維空間中,算法的目標是將維度減小到 d,使得 dDd\ll D ,低維數據點用 y 表示,低維數據點的最小化誤差函數也應有相同的形式,即爲:
L(w)=i=1myijQ(i)wijyj2L(w)=\sum_{i=1}^m \| y_i-\sum \limits_{j\in Q(i)} w_{ij}y_j\|^2
在高維中高維數據已知,目標是求加權係數矩陣,我們希望這些加權係數對應的線型關係在降維後的低維一樣得到保持。我們在低維是加權係數 W 已知,求對應的低維繫數。

算法的主要步驟分爲:

  1. 尋找每個樣本點的 k 個近鄰點;
  2. 由每個樣本點的近鄰點計算出該樣本點的局部重建權值矩陣;
  3. 由該樣本點的局部重建權值矩陣和其近鄰點計算出該樣本點的輸出值。

實驗結果分析

對比來看,圖中二維的形狀與其他的方法得到的情況不同,對一些數字的分離效果比較好,比如 7 和 0 ,但是對其他數字的效果一般。

Autoencoder

方法介紹

autoencoder 使用人工神經網絡,以無監督學習的方式對高維數據進行編碼成爲低維數據,然後可以將低維數據進行解碼稱爲高維數據的過程。前一個過程稱爲編碼器( encoder ),後一個過程的網絡稱爲解碼器( decoder ),後一個過程也叫做重建的過程。

( 圖片來自 莫煩 python 博客)

如果 decoder 爲 ϕ{\displaystyle \phi },encoder 爲 ψ{\displaystyle \psi},那麼:
ϕ:XF{\displaystyle \phi :{\mathcal {X}}\rightarrow {\mathcal {F}}}
ψ:FX{\displaystyle \psi :{\mathcal {F}}\rightarrow {\mathcal {X}}}
ϕ,ψ=arg minϕ,ψ X(ψϕ)X2{\displaystyle \phi ,\psi ={\underset {\phi ,\psi }{\operatorname {arg\,min} }}\,\|X-(\psi \circ \phi )X\|^{2}}
最簡單的情況下,如果只有一個隱層,自編碼的編碼過程採用輸入 $ {\displaystyle \mathbf {x} \in \mathbb {R} ^{d}={\mathcal {X}}}$ 並將其映射到 zRp=F{\displaystyle \mathbf {z} \in \mathbb {R} ^{p}={\mathcal {F}}}
z=σ(Wx+b){\displaystyle \mathbf {z} =\sigma (\mathbf {Wx} +\mathbf {b} )}
這裏, σ\sigma 是元素激活函數。 W{ \mathbf {W}} 是權重矩陣, b{ \mathbf {b}} 是偏置向量。之後,自動編碼器的解碼器階段將 z{ \mathbf {z}} 映射到與 x{ \mathbf {x}} 形狀相同的重建 x{ \mathbf {x'}}
x=σ(Wz+b){\displaystyle \mathbf {x'} =\sigma '(\mathbf {W'z} +\mathbf {b'} )}
這裏 $ \mathbf {\sigma ‘} ,\mathbf {W’}$ 和 $\mathbf {b’} $ 與 $ \mathbf {\sigma } ,\mathbf {W}$ 和 $\mathbf {b} $ 相對應。編碼器和解碼器都經過訓練,可以最大程度上減小重建錯誤。
L(x,x)=xx2=xσ(W(σ(Wx+b))+b)2{\displaystyle {\mathcal {L}}(\mathbf {x} ,\mathbf {x'} )=\|\mathbf {x} -\mathbf {x'} \|^{2}=\|\mathbf {x} -\sigma '(\mathbf {W'} (\sigma (\mathbf {Wx} +\mathbf {b} ))+\mathbf {b'} )\|^{2}}

實驗結果分析

代碼使用 MNIST 一共 55000 條數據,編碼器和解碼器分別建立了四層神經網絡。神經網絡很依賴於計算能力,下面是一組對比,分別是訓練了一千次與一萬次的結果。
可以看到,在討論的四種方法中, Autoencoder 的分離效果最好。還原也是比較準確的。

一千次訓練之後的結果
一萬次訓練之後的結果
下圖的上面一行是原手寫圖,下面一行是經過 autoencoder 之後的圖片。
一千次訓練之後的結果
一萬次訓練之後的結果
可以看出,把手寫數字壓縮到兩維可能是是比較困難,但是一萬次的訓練看起來還是比一千次的訓練要好一點。

下圖是誤差與訓練次數之間的關係,可以看到隨着訓練次數的增加,誤差減小,但是隨着訓練次數增加,到達一定程度,誤差也不會有明顯下降,可能需要加深神經網絡層數和修改壓縮維度。

源代碼

PCA( 主成分分析)

from __future__ import division, print_function, absolute_import

import pandas as pd
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA

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

print(mnist.train.images.shape, mnist.train.labels.shape)

estimator = PCA(n_components=2)
x_pca = estimator.fit_transform(mnist.train.images)

label=mnist.train.labels

# 顯示圖形,前兩個參數爲降維結果的兩維,第三個是手寫數字的標籤
plt.scatter(x_pca[:, 0], x_pca[:, 1], c=mnist.train.labels)
# 以顏色顯示三維圖像的第三個維度
plt.colorbar()
plt.show()

Isomap( 等度量映射)

from __future__ import division, print_function, absolute_import

import time
import pandas as pd
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA

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

print(mnist.train.images.shape, mnist.train.labels.shape)

from sklearn import manifold
start = time.clock()
# print(mnist.train.images[:10])

trans_data = manifold.Isomap(n_neighbors=5,n_components=2).fit_transform(mnist.train.images[:10000])

elapsed = (time.clock() - start)
print("Time used:",elapsed)
label=mnist.train.labels
# print(label)

# 顯示圖形,前兩個參數爲降維結果的兩維,第三個是手寫數字的標籤
plt.scatter(trans_data[:, 0], trans_data[:, 1], c=mnist.train.labels[:10000])
# 以顏色顯示三維圖像的第三個維度
plt.colorbar()
plt.show()

LLE

from __future__ import division, print_function, absolute_import

import time
import pandas as pd
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA

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

# batch_size = 256
# n_input = 784  # MNIST data input (img shape: 28*28)

# total_batch = int(mnist.train.num_examples/batch_size)

print(mnist.train.images.shape, mnist.train.labels.shape)
# x_digits=minist.train.images
# estimator = PCA(n_components=2)
# x_pca = estimator.fit_transform(mnist.train.images)

from sklearn import manifold
start = time.clock()
# print(mnist.train.images[:10])

trans_data, err = manifold.locally_linear_embedding(X=mnist.train.images[:10000],n_neighbors=5,n_components=2)

elapsed = (time.clock() - start)
print("Time used:",elapsed)
# print(x_pca)
# print(x_pca[:,0])
# print(x_pca[:,1])

label=mnist.train.labels
# print(label)

# 顯示圖形,前兩個參數爲降維結果的兩維,第三個是手寫數字的標籤
plt.scatter(trans_data[:, 0], trans_data[:, 1], c=mnist.train.labels[:10000])
# 以顏色顯示三維圖像的第三個維度
plt.colorbar()
plt.show()

Autoencoder

from __future__ import division, print_function, absolute_import

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("/tmp/data/", one_hot=False)

# Visualize encoder setting
# 參數設置
learning_rate = 0.01    # 0.01 學習率
training_epochs = 1000
batch_size = 256
display_step = 1
# Network Parameters
n_input = 784  # MNIST data input (img shape: 28*28)
# tf Graph input (only pictures)
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)

# Prediction
y_pred = decoder_op
# Targets (Labels) are the input data.
y_true = X

# 定義損失函數,最小化誤差
cost = tf.reduce_mean(tf.pow(y_true - y_pred, 2))
optimizer = tf.train.AdamOptimizer(learning_rate).minimize(cost)


# Launch the graph
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)
    # Training cycle
    for epoch in range(training_epochs):
        # Loop over all batches
        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})
        # Display logs per epoch step
        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[:10]})
    # Compare original images with their reconstructions
    f, a = plt.subplots(2, 10, figsize=(10, 2))
    for i in range(10):
        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()

    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()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章