數據降維與可視化——t-SNE
t-SNE是目前來說效果最好的數據降維與可視化方法,但是它的缺點也很明顯,比如:占內存大,運行時間長。但是,當我們想要對高維數據進行分類,又不清楚這個數據集有沒有很好的可分性(即同類之間間隔小,異類之間間隔大),可以通過t-SNE投影到2維或者3維的空間中觀察一下。如果在低維空間中具有可分性,則數據是可分的;如果在高維空間中不具有可分性,可能是數據不可分,也可能僅僅是因為不能投影到低維空間。
下面會簡單介紹t-SNE的原理,參數和實例。
t-distributed Stochastic Neighbor Embedding(t-SNE)
t-SNE(TSNE)將數據點之間的相似度轉換為概率。原始空間中的相似度由高斯聯合概率表示,嵌入空間的相似度由“學生t分布”表示。
雖然Isomap,LLE和variants等數據降維和可視化方法,更適合展開單個連續的低維的manifold。但如果要准確的可視化樣本間的相似度關系,如對於下圖所示的S曲線(不同顏色的圖像表示不同類別的數據),t-SNE表現更好。因為t-SNE主要是關注數據的局部結構。
通過原始空間和嵌入空間的聯合概率的Kullback-Leibler(KL)散度來評估可視化效果的好壞,也就是說用有關KL散度的函數作為loss函數,然后通過梯度下降最小化loss函數,最終獲得收斂結果。注意,該loss不是凸函數,即具有不同初始值的多次運行將收斂於KL散度函數的局部最小值中,以致獲得不同的結果。因此,嘗試不同的隨機數種子(Python中可以通過設置seed來獲得不同的隨機分布)有時候是有用的,並選擇具有最低KL散度值的結果。
使用t-SNE的缺點大概是:
- t-SNE的計算復雜度很高,在數百萬個樣本數據集中可能需要幾個小時,而PCA可以在幾秒鍾或幾分鍾內完成
- Barnes-Hut t-SNE方法(下面講)限於二維或三維嵌入。
- 算法是隨機的,具有不同種子的多次實驗可以產生不同的結果。雖然選擇loss最小的結果就行,但可能需要多次實驗以選擇超參數。
- 全局結構未明確保留。這個問題可以通過PCA初始化點(使用
init ='pca'
)來緩解。
優化 t-SNE
t-SNE的主要目的是高維數據的可視化。因此,當數據嵌入二維或三維時,效果最好。有時候優化KL散度可能有點棘手。有五個參數可以控制t-SNE的優化,即會影響最后的可視化質量:
- perplexity困惑度
- early exaggeration factor前期放大系數
- learning rate學習率
- maximum number of iterations最大迭代次數
- angle角度
Barnes-Hut t-SNE
Barnes-Hut t-SNE主要是對傳統t-SNE在速度上做了優化,是現在最流行的t-SNE方法,同時它與傳統t-SNE還有一些不同:
- Barnes-Hut僅在目標維度為3或更小時才起作用。以2D可視化為主。
- Barnes-Hut僅適用於密集的輸入數據。稀疏數據矩陣只能用特定的方法嵌入,或者可以通過投影近似,例如使用sklearn.decomposition.TruncatedSVD
- Barnes-Hut是一個近似值。使用angle參數對近似進行控制,因此當參數
method="exact"
時,TSNE()
使用傳統方法,此時angle參數不能使用。 - Barnes-Hut可以處理更多的數據。 Barnes-Hut可用於嵌入數十萬個數據點。
為了可視化的目的(這是t-SNE的主要用處),強烈建議使用Barnes-Hut方法。method="exact"
時,傳統的t-SNE方法盡管可以達到該算法的理論極限,效果更好,但受制於計算約束,只能對小數據集的可視化。
對於MNIST來說,t-SNE可視化后可以自然的將字符按標簽分開,見本文最后的例程;而PCA降維可視化后的手寫字符,不同類別之間會重疊在一起,這也證明了t-SNE的非線性特性的強大之處。值得注意的是:未能在2D中用t-SNE顯現良好分離的均勻標記的組不一定意味着數據不能被監督模型正確分類,還可能是因為2維不足以准確地表示數據的內部結構。
注意事項
- 數據集在所有特征維度上的尺度應該相同
參數說明
parameters | description |
---|---|
n_components | int, 默認為2,嵌入空間的維度(嵌入空間的意思就是結果空間) |
perplexity | float, 默認為30,數據集越大,需要參數值越大,建議值位5-50 |
early_exaggeration | float, 默認為12.0,控制原始空間中的自然集群在嵌入式空間中的緊密程度以及它們之間的空間。 對於較大的值,嵌入式空間中自然群集之間的空間將更大。 再次,這個參數的選擇不是很關鍵。 如果在初始優化期間成本函數增加,則可能是該參數值過高。 |
learning_rate | float, default:200.0, 學習率,建議取值為10.0-1000.0 |
n_iter | int, default:1000, 最大迭代次數 |
n_iter_without_progress | int, default:300, 另一種形式的最大迭代次數,必須是50的倍數 |
min_grad_norm | float, default:1e-7, 如果梯度低於該值,則停止算法 |
metric | string or callable, 精確度的計量方式 |
init | string or numpy array, default:”random”, 可以是’random’, ‘pca’或者一個numpy數組(shape=(n_samples, n_components)。 |
verbose | int, default:0, 訓練過程是否可視 |
random_state | int, RandomState instance or None, default:None,控制隨機數的生成 |
method | string, default:’barnes_hut’, 對於大數據集用默認值,對於小數據集用’exact’ |
angle | float, default:0.5, 只有method='barnes_hut' 時可用 |
attributes | description |
---|---|
embedding_ | 嵌入向量 |
kl_divergence | 最后的KL散度 |
n_iter_ | 迭代的次數 |
Methods | description |
---|---|
fit | 將X投影到一個嵌入空間 |
fit_transform | 將X投影到一個嵌入空間並返回轉換結果 |
get_params | 獲取t-SNE的參數 |
set_params | 設置t-SNE的參數 |
實例
Hello World
一個簡單的例子,輸入4個3維的數據,然后通過t-SNE降維稱2維的數據。
import numpy as np from sklearn.manifold import TSNE X = np.array([[0, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 1]]) tsne = TSNE(n_components=2) tsne.fit_transform(X) print(tsne.embedding_) '''輸出 [[ 3.17274952 -186.43092346] [ 43.70787048 -283.6920166 ] [ 100.43157196 -145.89025879] [ 140.96669006 -243.15138245]]'''
S曲線的降維與可視化
S曲線上的數據是高維的數據,其中不同顏色表示數據的不同類別。當我們通過t-SNE嵌入到二維空間中后,可以看到數據點之間的類別信息完美的保留了下來
# coding='utf-8' """# 一個對S曲線數據集上進行各種降維的說明。""" from time import time import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from matplotlib.ticker import NullFormatter from sklearn import manifold, datasets # # Next line to silence pyflakes. This import is needed. # Axes3D n_points = 1000 # X是一個(1000, 3)的2維數據,color是一個(1000,)的1維數據 X, color = datasets.samples_generator.make_s_curve(n_points, random_state=0) n_neighbors = 10 n_components = 2 fig = plt.figure(figsize=(8, 8)) # 創建了一個figure,標題為"Manifold Learning with 1000 points, 10 neighbors" plt.suptitle("Manifold Learning with %i points, %i neighbors" % (1000, n_neighbors), fontsize=14) '''繪制S曲線的3D圖像''' ax = fig.add_subplot(211, projection='3d') ax.scatter(X[:, 0], X[:, 1], X[:, 2], c=color, cmap=plt.cm.Spectral) ax.view_init(4, -72) # 初始化視角 '''t-SNE''' t0 = time() tsne = manifold.TSNE(n_components=n_components, init='pca', random_state=0) Y = tsne.fit_transform(X) # 轉換后的輸出 t1 = time() print("t-SNE: %.2g sec" % (t1 - t0)) # 算法用時 ax = fig.add_subplot(2, 1, 2) plt.scatter(Y[:, 0], Y[:, 1], c=color, cmap=plt.cm.Spectral) plt.title("t-SNE (%.2g sec)" % (t1 - t0)) ax.xaxis.set_major_formatter(NullFormatter()) # 設置標簽顯示格式為空 ax.yaxis.set_major_formatter(NullFormatter()) # plt.axis('tight') plt.show()
手寫數字的降維可視化
這里的手寫數字數據集是一堆8*8的數組,每一個數組都代表着一個手寫數字。如下圖所示。
t-SNE將8*8即64維的數據降維成2維,並在平面圖中顯示,這里只選取了0-5,6個手寫數字。
# coding='utf-8' """t-SNE對手寫數字進行可視化""" from time import time import numpy as np import matplotlib.pyplot as plt from sklearn import datasets from sklearn.manifold import TSNE def get_data(): digits = datasets.load_digits(n_class=6) data = digits.data label = digits.target n_samples, n_features = data.shape return data, label, n_samples, n_features def plot_embedding(data, label, title): x_min, x_max = np.min(data, 0), np.max(data, 0) data = (data - x_min) / (x_max - x_min) fig = plt.figure() ax = plt.subplot(111) for i in range(data.shape[0]): plt.text(data[i, 0], data[i, 1], str(label[i]), color=plt.cm.Set1(label[i] / 10.), fontdict={'weight': 'bold', 'size': 9}) plt.xticks([]) plt.yticks([]) plt.title(title) return fig def main(): data, label, n_samples, n_features = get_data() print('Computing t-SNE embedding') tsne = TSNE(n_components=2, init='pca', random_state=0) t0 = time() result = tsne.fit_transform(data) fig = plot_embedding(result, label, 't-SNE embedding of the digits (time %.2fs)' % (time() - t0)) plt.show(fig) if __name__ == '__main__': main()