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=(