http://www.datakit.cn/blog/2017/02/05/t_sne_full.htmlhtml
t-SNE(t-distributed stochastic neighbor embedding)是用於降維的一種機器學習算法,是由 Laurens van der Maaten 和 Geoffrey Hinton在08年提出來。此外,t-SNE 是一種非線性降維算法,很是適用於高維數據降維到2維或者3維,進行可視化。python
t-SNE是由SNE(Stochastic Neighbor Embedding, SNE; Hinton and Roweis, 2002)發展而來。咱們先介紹SNE的基本原理,以後再擴展到t-SNE。最後再看一下t-SNE的實現以及一些優化。web
SNE是經過仿射(affinitie)變換將數據點映射到機率分佈上,主要包括兩個步驟:算法
咱們看到t-SNE模型是非監督的降維,他跟kmeans等不一樣,他不能經過訓練獲得一些東西以後再用於其它數據(好比kmeans能夠經過訓練獲得k個點,再用於其它數據集,而t-SNE只能單獨的對數據作操做,也就是說他只有fit_transform,而沒有fit操做)數據庫
SNE是先將歐幾里得距離轉換爲條件機率來表達點與點之間的類似度。具體來講,給定一個N個高維的數據 x1,...,xNx1,...,xN(注意N不是維度), t-SNE首先是計算機率pijpij,正比於xixi和xjxj之間的類似度(這種機率是咱們自主構建的),即:express
這裏的有一個參數是σiσi,對於不一樣的點xixi取值不同,後續會討論如何設置。此外設置px∣x=0px∣x=0,由於咱們關注的是兩兩之間的類似度。數組
那對於低維度下的yiyi,咱們能夠指定高斯分佈爲方差爲12√12,所以它們之間的類似度以下:markdown
一樣,設定qi∣i=0qi∣i=0.網絡
若是降維的效果比較好,局部特徵保留完整,那麼 pi∣j=qi∣jpi∣j=qi∣j, 所以咱們優化兩個分佈之間的距離-KL散度(Kullback-Leibler divergences),那麼目標函數(cost function)以下:app
這裏的PiPi表示了給定點xixi下,其餘全部數據點的條件機率分佈。須要注意的是KL散度具備不對稱性,在低維映射中不一樣的距離對應的懲罰權重是不一樣的,具體來講:距離較遠的兩個點來表達距離較近的兩個點會產生更大的cost,相反,用較近的兩個點來表達較遠的兩個點產生的cost相對較小(注意:相似於迴歸容易受異常值影響,但效果相反)。即用較小的 qj∣i=0.2qj∣i=0.2 來建模較大的 pj∣i=0.8pj∣i=0.8, cost=plog(pq)plog(pq)=1.11,一樣用較大的qj∣i=0.8qj∣i=0.8來建模較大的pj∣i=0.2pj∣i=0.2, cost=-0.277, 所以,SNE會傾向於保留數據中的局部特徵。
思考:瞭解了基本思路以後,你會怎麼選擇σσ,固定初始化?
下面咱們開始正式的推導SNE。首先不一樣的點具備不一樣的σiσi,PiPi的熵(entropy)會隨着σiσi的增長而增長。SNE使用困惑度(perplexity)的概念,用二分搜索的方式來尋找一個最佳的σσ。其中困惑度指:
這裏的H(Pi)H(Pi)是PiPi的熵,即:
困惑度能夠解釋爲一個點附近的有效近鄰點個數。SNE對困惑度的調整比較有魯棒性,一般選擇5-50之間,給定以後,使用二分搜索的方式尋找合適的σσ
那麼核心問題是如何求解梯度了,目標函數等價於∑∑−plog(q)∑∑−plog(q)這個式子與softmax很是的相似,咱們知道softmax的目標函數是∑−ylogp∑−ylogp,對應的梯度是y−py−p(注:這裏的softmax中y表示label,p表示預估值)。 一樣咱們能夠推導SNE的目標函數中的i在j下的條件機率狀況的梯度是2(pi∣j−qi∣j)(yi−yj)2(pi∣j−qi∣j)(yi−yj), 一樣j在i下的條件機率的梯度是2(pj∣i−qj∣i)(yi−yj)2(pj∣i−qj∣i)(yi−yj), 最後獲得完整的梯度公式以下:
在初始化中,能夠用較小的σσ下的高斯分佈來進行初始化。爲了加速優化過程和避免陷入局部最優解,梯度中須要使用一個相對較大的動量(momentum)。即參數更新中除了當前的梯度,還要引入以前的梯度累加的指數衰減項,以下:
這裏的Y(t)Y(t)表示迭代t次的解,ηη表示學習速率,α(t)α(t)表示迭代t次的動量。
此外,在初始優化的階段,每次迭代中能夠引入一些高斯噪聲,以後像模擬退火同樣逐漸減少該噪聲,能夠用來避免陷入局部最優解。所以,SNE在選擇高斯噪聲,以及學習速率,何時開始衰減,動量選擇等等超參數上,須要跑屢次優化才能夠。
思考:SNE有哪些不足? 面對SNE的不足,你會作什麼改進?
儘管SNE提供了很好的可視化方法,可是他很難優化,並且存在」crowding problem」(擁擠問題)。後續中,Hinton等人又提出了t-SNE的方法。與SNE不一樣,主要以下:
t-SNE在低維空間下使用更重長尾分佈的t分佈來避免crowding問題和優化問題。在這裏,首先介紹一下對稱版的SNE,以後介紹crowding問題,以後再介紹t-SNE。
優化pi∣jpi∣j和qi∣jqi∣j的KL散度的一種替換思路是,使用聯合機率分佈來替換條件機率分佈,即P是高維空間裏各個點的聯合機率分佈,Q是低維空間下的,目標函數爲:
這裏的piipii,qiiqii爲0,咱們將這種SNE稱之爲symmetric SNE(對稱SNE),由於他假設了對於任意i,pij=pji,qij=qjipij=pji,qij=qji,所以機率分佈能夠改寫爲:
這種表達方式,使得總體簡潔了不少。可是會引入異常值的問題。好比xixi是異常值,那麼∣∣xi−xj∣∣2∣∣xi−xj∣∣2會很大,對應的全部的j, pijpij都會很小(以前是僅在xixi下很小),致使低維映射下的yiyi對cost影響很小。
思考: 對於異常值,你會作什麼改進?pipi表示什麼?
爲了解決這個問題,咱們將聯合機率分佈定義修正爲: pij=pi∣j+pj∣i2pij=pi∣j+pj∣i2, 這保證了∑jpij>12n∑jpij>12n, 使得每一個點對於cost都會有必定的貢獻。對稱SNE的最大優勢是梯度計算變得簡單了,以下:
實驗中,發現對稱SNE可以產生和SNE同樣好的結果,有時甚至略好一點。
擁擠問題就是說各個簇彙集在一塊兒,沒法區分。好比有一種狀況,高維度數據在降維到10維下,能夠有很好的表達,可是降維到兩維後沒法獲得可信映射,好比降維如10維中有11個點之間兩兩等距離的,在二維下就沒法獲得可信的映射結果(最多3個點)。 進一步的說明,假設一個以數據點xixi爲中心,半徑爲r的m維球(三維空間就是球),其體積是按rmrm增加的,假設數據點是在m維球中均勻分佈的,咱們來看看其餘數據點與xixi的距離隨維度增大而產生的變化。
從上圖能夠看到,隨着維度的增大,大部分數據點都彙集在m維球的表面附近,與點xixi的距離分佈極不均衡。若是直接將這種距離關係保留到低維,就會出現擁擠問題。
怎麼解決crowding問題呢?
Cook et al.(2007) 提出一種slight repulsion的方式,在基線機率分佈(uniform background)中引入一個較小的混合因子ρρ,這樣qijqij就永遠不會小於2ρn(n−1)2ρn(n−1) (由於一共了n(n-1)個pairs),這樣在高維空間中比較遠的兩個點之間的qijqij老是會比pijpij大一點。這種稱之爲UNI-SNE,效果一般比標準的SNE要好。優化UNI-SNE的方法是先讓ρρ爲0,使用標準的SNE優化,以後用模擬退火的方法的時候,再慢慢增長ρρ. 直接優化UNI-SNE是不行的(即一開始ρρ不爲0),由於距離較遠的兩個點基本是同樣的qijqij(等於基線分佈), 即便pijpij很大,一些距離變化很難在qijqij中產生做用。也就是說優化中剛開始距離較遠的兩個聚類點,後續就沒法再把他們拉近了。
對稱SNE實際上在高維度下 另一種減輕」擁擠問題」的方法:在高維空間下,在高維空間下咱們使用高斯分佈將距離轉換爲機率分佈,在低維空間下,咱們使用更加偏重長尾分佈的方式來將距離轉換爲機率分佈,使得高維度下中低等的距離在映射後可以有一個較大的距離。
咱們對比一下高斯分佈和t分佈(如上圖,code見probability/distribution.md), t分佈受異常值影響更小,擬合結果更爲合理,較好的捕獲了數據的總體特徵。
使用了t分佈以後的q變化,以下:
此外,t分佈是無限多個高斯分佈的疊加,計算上不是指數的,會方便不少。優化的梯度以下:
t-sne的有效性,也能夠從上圖中看到:橫軸表示距離,縱軸表示類似度, 能夠看到,對於較大類似度的點,t分佈在低維空間中的距離須要稍小一點;而對於低類似度的點,t分佈在低維空間中的距離須要更遠。這剛好知足了咱們的需求,即同一簇內的點(距離較近)聚合的更緊密,不一樣簇之間的點(距離較遠)更加疏遠。
總結一下,t-SNE的梯度更新有兩大優點:
算法詳細過程以下:
優化過程當中能夠嘗試的兩個trick:
優化的過程動態圖以下:
主要不足有四個:
後續有機會補充。
文中的插圖繪製:
# coding:utf-8 import numpy as np from numpy.linalg import norm from matplotlib import pyplot as plt plt.style.use('ggplot') def sne_crowding(): npoints = 1000 # 抽取1000個m維球內均勻分佈的點 plt.figure(figsize=(20, 5)) for i, m in enumerate((2, 3, 5, 8)): # 這裏模擬m維球中的均勻分佈用到了拒絕採樣, # 即先生成m維立方中的均勻分佈,再剔除m維球外部的點 accepts = [] while len(accepts) < 1000: points = np.random.rand(500, m) accepts.extend([d for d in norm(points, axis=1) if d <= 1.0]) # 拒絕採樣 accepts = accepts[:npoints] ax = plt.subplot(1, 4, i+1) if i == 0: ax.set_ylabel('count') if i == 2: ax.set_xlabel('distance') ax.hist(accepts, bins=np.linspace(0., 1., 50)) ax.set_title('m=%s' %m) plt.savefig("./images/sne_crowding.png") x = np.linspace(0, 4, 100) ta = 1 / (1 + np.square(x)) tb = np.sum(ta) - 1 qa = np.exp(-np.square(x)) qb = np.sum(qa) - 1 def sne_norm_t_dist_cost(): plt.figure(figsize=(