[譯] 降維技術中經常使用的幾種降維方法

在本文中,我將盡最大的努力來闡明降維技術中經常使用的三種降維方法:即 PCA、t-SNE 和自動編碼器。本文的動機是因爲這些方法多被看成黑盒使用,而有時會被誤用。理解它們將能夠幫助你們決定什麼時候以及如何使用它們。前端

我將使用 TensorFlow 從頭開始介紹每一個方法的內部結構和對應代碼(其中不包括 t-SNE)。爲何使用 TensorFlow?由於它主要用於深度學習,讓咱們使用它作一些其它的挑戰性任務 :) 本文的代碼能夠在這個筆記中找到。android


動機

在處理實際問題和實際數據時,咱們一般面臨的是高達百萬維度的數據。ios

雖然數據在其原始高維結構中更可以表達本身的特性,但有時候咱們可能須要下降它的維度。 通常須要減小維度的一般與可視化相關(減小到 2 到 3 個維度上,這樣咱們就能夠繪製它),但狀況並不是老是如此。git

有時,在實際應用中咱們可能認爲性能比精度更重要,這樣咱們就能夠把 1000 維的數據下降到 10 維,這樣咱們就能更快地處理它(好比在距離計算中)。github

有時緯度下降的需求是真實存在的,而且有不少的應用。算法

在開始以前,若是你必須爲下列狀況選擇一種降維技術,那麼你會選擇哪種呢?數據庫

  1. 你的系統使用餘弦類似度來度量距離,可是你須要把它進行可視化,展現給一些沒有技術背景的董事會成員,他們可能根本不熟悉餘弦類似度的概念 —— 你會怎麼作呢?後端

  2. 你須要將數據壓縮到儘量小的維度,而且你獲得的約束條件是保持大約 80% 的數據,你又會怎麼作呢?bash

  3. 你有一個數據庫,其中包含通過長時間收集的某類數據,而且數據(相似的類型)不斷地添加進來。網絡

你須要下降數據的維度,不管是目前已有的數據仍是源源不斷的新數據,你會選擇哪一種方法呢?

我但願本文能幫助你更好地理解降維,這樣你就能處理好相似的問題。

讓咱們從 PCA 方法開始。


PCA 方法

PCA(Principal Component Analysis)方法大概是書本中最古老的降維技術了。

所以 PCA 已經獲得了很好的研究,而且不少的方法也能夠獲得相同的解,咱們將在這裏討論其中的兩種方法,即特徵分解和奇異值分解(SVD),而後咱們將在 TensorFlow 中實現 SVD 方法。

從如今開始,X 爲咱們要處理的數據矩陣,形狀爲 (n, p),其中 n 表示樣本數量,p 表示維度。

給定 X,以上這兩種方法都試圖以它們本身的方式,來處理和分解 X,以後咱們能夠將分解後的結果相乘,以在更小的維數中表示最多的信息。我知道這聽起來很可怕,因此這裏我將省去大部分的數學知識,只保留有助於理解這些方法優缺點的部分。

所以特徵分解和奇異之分解是兩種不一樣的矩陣分解方法,讓咱們看看它們在主成分分析中有什麼用,以及它們之間有什麼聯繫。

先看一眼下面的流程圖,我立刻就會解釋其中的內容。

圖 1 PCA 工做流圖

爲何要關心這個呢?由於這兩個過程當中有一些很是基本的東西,它們能夠告訴咱們不少關於主成分分析(PCA)的知識。

正如你所看到的這樣,這兩種方法都是純粹的線性代數方法,這基本上告訴咱們,使用 PCA 方法就是從不一樣的角度觀察真實數據 —— 這是 PCA 方法獨有的,而其它的方法則是將數據從低維進行隨機表示,而且試圖讓它表現得像高維度數據同樣。

另外值得注意的是,全部的操做都是線性的,因此使用 SVD 的方法速度很是快。

一樣給定相同的數據,PCA 方法老是能給出相同的答案(另外兩種方法則不是這樣)。

注意到在 SVD 方法中咱們如何選擇參數 r(r 是咱們想要降到的維度)對於更低的維度來講可以保留更大的 Σ 值? 其中 Σ 有一些特別之處。

Σ 是一個對角矩陣,其中有 p(p 爲維度大小)個對角值(通常又稱爲奇異值),而且它們的大小表示了它們對於信息保存的重要性。

所以咱們能夠選擇將維度減小,且減小到一個要大約保留的維度數量。給定一個維度數量的百分比,而後我將在代碼中演示它(好比在丟失最多 15% 的數據約束下,咱們給出的減小維度的能力)。

正如你看到的同樣,使用 TensorFlow 實現這個功能是至關簡單的 —— 咱們只須要編寫一個類,該類中包含有一個 fit 方法和 reduce 方法便可,咱們須要提供的參數是須要降維到的維度。

代碼(PCA)

讓咱們來看一下 fit 方法是什麼樣的,這裏的 self.X 包含參與計算的數據,而且其類型爲 self.dtype=tf.float32

def fit(self):
    self.graph = tf.Graph()
    with self.graph.as_default():
        self.X = tf.placeholder(self.dtype, shape=self.data.shape)

        # 執行 SVD 方法
        singular_values, u, _ = tf.svd(self.X)

        # 建立 sigma 矩陣
        sigma = tf.diag(singular_values)

    with tf.Session(graph=self.graph) as session:
        self.u, self.singular_values, self.sigma = session.run([u, singular_values, sigma],
                                                               feed_dict={self.X: self.data})
複製代碼

所以方法 fit 的目的是建立咱們後面會使用到的 Σ 和 U。 咱們從 tf.svd 這一行開始,它給咱們計算出了對應的奇異值,也就是圖 1 中表示爲 Σ 的對角線的值,同時它也計算出了 U 和 V 矩陣。

而後 tf.diag 是 TensorFlow 中將一維向量轉換成對角線矩陣的方法,這裏將奇異值轉換成對角線矩陣 Σ。

fit 方法的最後咱們就能夠計算出奇異值、矩陣 Σ 和 U。

如今讓咱們來實現 reduce 方法。

def reduce(self, n_dimensions=None, keep_info=None):
    if keep_info:
        # 奇異值規範化
        normalized_singular_values = self.singular_values / sum(self.singular_values)

        # 爲每一個維度建立保存信息的梯形累計和
        ladder = np.cumsum(normalized_singular_values)

        # 獲取超過給定信息閾值的第一個索引
        index = next(idx for idx, value in enumerate(ladder) if value >= keep_info) + 1
        n_dimensions = index

    with self.graph.as_default():
        # 從 sigma 中刪去相關部分
        sigma = tf.slice(self.sigma, [0, 0], [self.data.shape[1], n_dimensions])

        # PCA 方法
        pca = tf.matmul(self.u, sigma)

    with tf.Session(graph=self.graph) as session:
        return session.run(pca, feed_dict={self.X: self.data})
複製代碼

如上所示,reduce 方法接受參數 keep_info 或者 n_dimensions(這裏我沒有寫輸入檢查,輸入檢查是必需要有的呀)。
若是使用時提供參數 n_dimensions,該方法會簡單將數據維度下降到該值,可是若是咱們使用時提供參數 keep_info,該參數是一個 0 到 1 的浮點數,那麼該方法就須要保留原始數據的該數據對應百分比(好比 0.9 —— 表示保留原始數據的 90%)。 在第一個 「if」 判斷語句中,咱們將數據進行了歸一化,而且檢查了須要使用多少個奇異值,接下來基本上是從 keep_info 中求出 n_dimensions 的值。

在圖中,咱們只是把 Σ(sigma) 矩陣進行切片以對應儘量多的需求數據,而後咱們執行矩陣乘法。

讓咱們在鳶尾花(iris)數據集上試一下,這是包含有 3 種形狀爲(150,4)的鳶尾花數據集。

from sklearn import datasets
import matplotlib.pyplot as plt
import seaborn as sns

tf_pca = TF_PCA(iris_dataset.data, iris_dataset.target)
tf_pca.fit()
pca = tf_pca.reduce(keep_info=0.9)  # Results in 2 dimensions

color_mapping = {0: sns.xkcd_rgb['bright purple'], 1: sns.xkcd_rgb['lime'], 2: sns.xkcd_rgb['ochre']}
colors = list(map(lambda x: color_mapping[x], tf_pca.target))

plt.scatter(pca[:, 0], pca[:, 1], c=colors)
複製代碼

圖 2 在 Iris 數據集上經過 PCA 方法進行二維展現

還不錯,對吧?


t-SNE 方法

t-SNE 相對於 PCA 來講是一個相對較新的方法,源自於 2008 年的一篇論文(原始論文連接)。

它比 PCA 方法理解起來要複雜,因此請耐心聽我說。
咱們對 t-SNE 的表示法是這樣的,X 表示原始數據,P 是一個矩陣,它保存着高維(原始)空間中 X 的點之間的親密程度(即距離),Q 也是一個矩陣,它保存着低維空間中數據點之間的親密程度。若是咱們有 n 個數據樣本,Q 和 P 都是 n×n 的矩陣(即從任意點到包括它自身在內的任意點之間的距離)。

如今 t-SNE 方法有它本身「獨特的方式」(咱們將很快介紹)來衡量物體之間的距離,一種測量高維空間中數據點之間的距離的方法,而另一種方法是在低維度空間中測量數據點之間的距離,還有第三種方法是度量 P 和 Q 之間的距離的方法。 從原始文獻可知,一個點 x_j 與另外一個點 x_i 的類似度由 _p_j|i 給出,其中,若是在以 x_i 爲中心的高斯分佈下,按其機率密度的比例選取鄰點 x_j

「什麼?」別擔憂,就像我說的那樣,t-SNE 有它本身的距離測量方法,因此咱們來看一下距離(親密程度)測量公式,從中找出一些解釋來理解 t-SNE 的行爲。

從高級層面來講,這就是該算法的工做原理(注意,它與 PCA 不一樣,它是一種迭代算法)。

圖 3 t-SNE 工做流

讓咱們一步一步來。

該算法接受兩個輸入,一個是數據自己,另外一個是複雜度(Perp)。

複雜度簡單來說就是你想要如何平衡在優化過程當中的數據局部(關閉點)結構和全局結構之間的焦點 —— 本文建議將其保持在 5 到 50 之間。

較高的複雜度意味着數據點會考慮更多的點做爲其近鄰點,較低的值則意味着考慮較少的點。

複雜度真的會影響到可視化效果,而且必定要當心,由於它會在低維數據的可視化中產生一些誤導現象 —— 所以我強烈建議閱讀這篇很是好的文章如何正確使用 t-SNE,這篇文章涉及到了複雜度取值不一樣的影響。

這種複雜度從何而來?它是用來計算出方程 (1)中的 σ_i,由於它們有一個由二叉搜索樹構成的單調鏈接。

所以 σ_i 基本上是使用咱們爲算法提供的複雜度數據,以不一樣的方式計算出來的。

讓咱們來看一下 t-SNE 中的公式可以告訴咱們什麼。

在咱們研究方程(1)和方程(2)以前須要知道的是,p_ii 被設置爲 0,q_ii 也被設置爲 0(這只是一個給定的值,咱們將它應用於兩個類似的點,方程不會輸出 0)。

因此咱們來看方程(1)和方程(2),請注意,若是兩個點是接近的(在高緯度結構表示下),那麼分子將產生一個約爲 1 的值,而若是它們相距很遠,那麼咱們會獲得一個無限小的值 —— 這在後面將有助於咱們理解成本函數。

如今咱們已經看到了 t-SNE 的一些性質。

一個是因爲關聯關係方程的創建方式的緣由,在 t-SNE 圖中解釋距離是有問題的。

這意味着集羣之間的距離和集羣大小可能會產生誤導,而且也會受到所選擇的複雜度大小的影響(再次提醒,我將參考上面文中提到的文章中的方法,以查看這些現象的可視化數據)。

另一件須要注意的事情是方程(1)中咱們如何計算點與點之間的歐氏距離的?這裏很是強大的地方是,咱們能夠將距離度量切換成其它任何咱們想要使用的距離度量方法,好比餘弦距離、曼哈頓距離等等(只要保持空間度量便可),並保持低維結構下的親和力是相同的 —— 這將以歐幾里德距離方式下,致使繪製比較複雜的距離。

舉個例子,若是你是 CTO,而且有一些數據,你想要使用餘弦類似性來做爲其距離度量,而你的 CEO 但願你如今來作一些展現來表現這些數據的特性,我不肯定你有時間來向董事會解釋餘弦類似性以及如何操做集羣數據,你只須要簡單地繪製出餘弦類似集羣便可,這正如在 t-SNE 算法中使用的歐幾里德距離集羣同樣 —— 這就是我想說的,這已經很好了。

在代碼中,你能夠在 scikit-learn 中經過提供一個距離矩陣來使用 TSNE 方法來實現這些。

好了,如今咱們知道了,若是 x_ix_j 相距很近時,p_ij/q_ij 的值就會很大,相反地,相距很遠時其值就會很小。

讓咱們經過繪製出其圖像來看一下咱們的損失函數(咱們稱之爲 Kullback–Leibler 散度)的影響,另外來檢查一下沒有求和部分時的公式(3)。

圖 4 t-SNE 的不包括求和部分的損失函數

這很難理解,可是我確實把座標軸的名字寫在這裏了。 能夠看到,損失函數是非對稱的。

當點在高維空間(p 軸)附近時,它會產生一個很大的代價值,可是卻由低維度空間下的相距較遠的點來表示,一樣地,高維度空間下較遠的點會產生較小的代價值,卻由低維度空間下相距較近的點來表示。

這進一步說明了 t-SNE 繪製圖的距離解釋能力問題。

在鳶尾花數據集上使用 t-SNE 算法,看看在不一樣的複雜度下會發生什麼

model = TSNE(learning_rate=100, n_components=2, random_state=0, perplexity=5)
tsne5 = model.fit_transform(iris_dataset.data)

model = TSNE(learning_rate=100, n_components=2, random_state=0, perplexity=30)
tsne30 = model.fit_transform(iris_dataset.data)

model = TSNE(learning_rate=100, n_components=2, random_state=0, perplexity=50)
tsne50 = model.fit_transform(iris_dataset.data)

plt.figure(1)
plt.subplot(311)
plt.scatter(tsne5[:, 0], tsne5[:, 1], c=colors)

plt.subplot(312)
plt.scatter(tsne30[:, 0], tsne30[:, 1], c=colors)

plt.subplot(313)
plt.scatter(tsne50[:, 0], tsne50[:, 1], c=colors)

plt.show()
複製代碼

圖 5 在鳶尾花數據集上使用 t-SNE 算法,不一樣的複雜度

正如咱們從數學的角度理解的那樣,能夠看到,若是給定一個很好的複雜度的值,數據確實可以很好地聚類,同時請注意超參數的敏感性(若是不提供梯度降低的學習率,咱們就沒法找到該聚類)。

在咱們繼續以前,我想說,若是你可以正確地應用,那麼 t-SNE 就是一個很是強大的方法,不要把你所學到的知識用到消極的一面上去,只要知道如何使用它便可。

接下來是自動編碼器的內容。


自動編碼器

PCA 和 t-SNE 是一種方法,而自動編碼器則是一類方法。
自動編碼器是一種神經網絡,該網絡的目標是經過使用更少的隱藏節點(編碼器輸出節點)來預測輸入(訓練該網絡以輸出與輸入儘量類似的結果),經過使用更少的隱藏節點(編碼器輸出節點)來編碼與輸入節點儘量多的信息。

咱們的 4 維鳶尾花數據集的自動編碼器基本實現如圖 6 所示,其中鏈接輸入層和隱藏層的連線被稱爲「編碼器」,隱藏層和輸出層之間的連線被稱爲「解碼器」。

圖 6 鳶尾花數據集上基礎自動編碼器結構

那麼爲何自動編碼器是是一個方法簇呢?由於咱們惟一的限制是輸入和輸出層的相同維度,而在內部咱們卻能夠建立任何咱們想要使用的並可以最好地編碼高維數據便可。

自動編碼器從一些隨機的低維表示法(z)開始,經過改變鏈接輸入層和隱藏層,以及鏈接隱藏層和輸出層的權重,而且使用梯度降低方法計算出最優的解決方案。

到目前爲止,咱們已經能夠學習一些關於自動編碼器的重要知識,由於咱們控制着網絡內部結構,因此咱們能夠設計編碼器,使其可以選擇特徵之間很是複雜的關係。

自動編碼器的另外一個優勢,是在訓練結束時咱們就獲得了指向隱藏層的權重,咱們能夠對特定的輸入進行訓練,若是稍後咱們遇到另一個數據,咱們可使用這個權重來下降它的維數,而不須要從新訓練 —— 可是須要當心,這樣的方案只有在數據點與訓練數據類似時纔會有效。

在這種狀況下,自動編碼器的數學原理可能很簡單,研究這個並非頗有用,由於咱們選擇的每一個內部架構和成本函數的數學原理都是不一樣的。

可是若是咱們花點時間想一想如何優化自動編碼器的權重,這樣咱們就會明白成本函數的定義有着很是重要的做用。

由於自動編碼器會使用成本函數來決定它的預測的效果,因此咱們可使用這個能力來強調咱們想要的。
不管咱們想要歐幾里德距離仍是其它距離度量值,咱們均可以經過成本函數將它反映到編碼的數據上,使用不一樣的距離方法,使用對稱和非對稱函數等等。

更有說服力的是,因爲自動編碼器本質上是一個神經網絡,咱們甚至能夠在訓練時對類和樣本進行加權,從而賦予數據中的某些現象更多的內涵。

這爲咱們壓縮數據提供了很大的靈活性。

自動編碼器很是強大,而且在某些狀況下與其它方法(好比谷歌的「PCA 對自動編碼器方法」)相比顯示了一些很是好的結果,所以它們確定是一種有效的方法。

讓咱們使用 TensorFlow 來實現一個基本的自動編碼器,使用鳶尾花數據集來測試使用並繪製其圖像

代碼(自動編碼器)

又一次,咱們須要實現 fitreduce 方法

def fit(self, n_dimensions):
    graph = tf.Graph()
    with graph.as_default():

        # 輸入變量
        X = tf.placeholder(self.dtype, shape=(None, self.features.shape[1]))

        # 網絡參數
        encoder_weights = tf.Variable(tf.random_normal(shape=(self.features.shape[1], n_dimensions)))
        encoder_bias = tf.Variable(tf.zeros(shape=[n_dimensions]))

        decoder_weights = tf.Variable(tf.random_normal(shape=(n_dimensions, self.features.shape[1])))
        decoder_bias = tf.Variable(tf.zeros(shape=[self.features.shape[1]]))

        # 編碼部分
        encoding = tf.nn.sigmoid(tf.add(tf.matmul(X, encoder_weights), encoder_bias))

        # 解碼部分
        predicted_x = tf.nn.sigmoid(tf.add(tf.matmul(encoding, decoder_weights), decoder_bias))

        # 以最小平方偏差定義成本函數和優化器
        cost = tf.reduce_mean(tf.pow(tf.subtract(predicted_x, X), 2))
        optimizer = tf.train.AdamOptimizer().minimize(cost)

    with tf.Session(graph=graph) as session:
        # 初始化全局的變量參數
        session.run(tf.global_variables_initializer())

        for batch_x in batch_generator(self.features):
            self.encoder['weights'], self.encoder['bias'], _ = session.run([encoder_weights, encoder_bias, optimizer],
                                                                        feed_dict={X: batch_x})
複製代碼

這裏沒有什麼特別之處,代碼基本能夠自解釋,咱們將編碼器的權重保存在誤差中,這樣咱們就能夠在接下來的 reduce 方法中減小數據。

def reduce(self):
    return np.add(np.matmul(self.features, self.encoder['weights']), self.encoder['bias'])
複製代碼

呵,就是這麼簡單 :)

讓咱們看看效果如何(批量大小爲 50,1000 個輪次迭代)

圖 7 簡單的自動編碼器在鳶尾花數據集上的輸出

即便不改變內部結構,咱們使用不一樣的批量大小、輪次數量和不一樣的優化器等參數,也可能會獲得不一樣的結果 —— 這只是剛剛開始的。

注意,我只是隨機選擇了一些超參數的值,在真實的場景中,咱們將經過交叉驗證或測試數據來衡量咱們這麼作的效果,並找到最佳的設置。

結語

像這樣的帖文一般會以一些比較圖表、優缺點等來結尾。 但這與我想要達到的目標偏偏相反。

個人目標是揭示這些方法的內部實現,這樣讀者就能理解每一個方法的優缺點了。
我但願你能享受本次閱讀,而且學到一些新的東西。

從文章的開頭,到上面這三個問題,你如今感受舒服多了吧?

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索