[译]浅析t-SNE 原理及其应⽤
声明: 本⽂转译⾃Data Camp上Manish Pathak的⽂章《Introduction to t-SNE》
译者注: 本⽂⾔简意赅的阐述了数据降维( Dimensionality Reduction technique)技术中PCA 以及t-Distributed Stochastic Neighbor
Embedding(t-SNE)算法的相关实现原理以及利弊,并且使⽤Python 基于Fashion-MNIST 数据集描述了对PCA 以及t-SNE 算法的基本应⽤。本
⼈觉得相关概念阐述的⽐较清晰因此特别转译在此博客,但如果读完本⽂后想深究t-SNE 背后的数学原理还是推荐看原论⽂,论⽂地址会在附录给出。本⼈会在原⽂基础上添加⼀些相关注释(本⼈学习和相关⼯作中的⼀些理解)以[注]为标记。原⽂在实现的过程中使⽤的是Python2,为了贴合当下Python ⽣态体系,因此本⼈使⽤Python3重新复现。由于本⼈英⽂⽔平以及技术⽔平有限,我会尽最⼤的能⼒确保翻译得当,倘若⽂中出现翻译不恰当的地⽅还请诸位海涵且帮助我纠正错误,可通过⽂章底部的邮箱地址联系我。感谢!以下为译⽂内容 。
通过本教程,你将会对当下较为流⾏的t-SNE 降维技术有⼀定的认知并能掌握其基础应⽤。本⽂涉及内容如下:
了解数据降维以及常⽤⼿段类型。
了解主成分分析(Principal Component Analysis (PCA))技术原理以及如何在Python 上应⽤。
了解t分布随机邻近嵌⼊(t-Distributed Stochastic Neighbor Embedding (t-SNE))原理以及如何在Python 上应⽤。可视化这两种算法的降维结果。⽐较这两种算法之间的优缺点
数据降维
如果你已经有过在⼤型数据集(包含⾮常多的特征)上进⾏相关⼯作的经历,你应该可以深刻(fathom)的体会到想要理解或者挖掘特征间关系是⾮常困难的。这⼀问题不仅体现在数据探索性分析(EDA)上,⽽且还会影响机器学习模型的性能,因此你可能会因此让你的模型过拟合或者破坏了你对模型的某些假设,例如独⽴特征对线性回归模型的影响。这时数据降维就显得格外重要。在机器学习中,降维技术是减少随机变量个数,得到⼀组“不相关”主变量的过程。通过减少特征空间的维数,你仅需要考虑⼀⼩部分特征之间的关系,⽽且你可以很轻易地在这部分特征上做探索或者可视化,同时也可以减少你的模型出现过拟合的可能。[注1] 考虑线性相关的两个变量以及线性⽆关的两个变量对整个模型的影响。降维通常可以通过以下⽅式实现:
特征限制:通过限制特征来缩减特征空间。虽然可以达到降维⽬的,但其缺点就是你会丢失⼀部分被
你删掉的特征所蕴含的信息。特征选择:应⽤⼀些统计检验的⽅法去根据它们的重要程度进⾏排序,然后选择其中最主要的⼏个特征⼦集。这类⽅法依然会⾯临信息丢失的风险,⽽且还会因为不同的检验⽅法会导致出现不同的特征排序⽅式,因此是不稳定的。
特征提取:通过创建⼀些融合了若⼲旧特征的独⽴特征。这类技术可以很好的应⽤在线性或⾮线性的降维技术中。
主成分分析(PCA )
主成分分析是⼀种线性的特征提取技术,它将数据通过线性映射的⽅式投影到低维空间中,通过这样的⽅式能够确保原数据在低维空间中⽅差最⼤。它通过计算其特征的协⽅差矩阵中的特征向量来实现这⼀⽬的。与最⼤的特征值(主成分)⼀⼀对应的特征向量会被⽤于重建成新的数据,并且保证这些数据在该特征向量⽅向上的⽅差最⼤。
穿山甲的药用价值简单来说,PCA以特定的⽅式融合了你所有的输⼊属性(特征)值,这样你就可以在删除不重要特征的同时不必担⼼丢失最有价值的部分。还有⼀个更为显著的好处,经过PCA处理之后的每⼀个新特征都是独⽴于其他特征的。[注2] 矩阵分解中的所有特征向量都是线性⽆关的。
t 分布随机邻近插⼊(t-SNE )
t-SNE 是⼀种⾮线性的降维技术,⾮常适合⽤于⾼维数据的可视化。⼴泛应⽤于图像处理、⾃然语⾔处理,基因数据以及语⾳处理。为了保
证⾜够浅显易懂,这⾥仅对t-SNE 的⼯作原理做简要介绍:
[1][2]
该算法⼀开始通过计算在⾼维空间中的数据点的相似度概率和与其对应的低维空间中的点的相似度的概率。点的相似度计算⽅法是:以A为中⼼的⾼斯分布中,如果按概率密度的⽐例选取相邻点,则点A将选择点B作为其相邻点的条件概率,以此计算点A的相似性。为了更好将数据投影⾄低维空间中,算法尝试去最⼩化⾼维数据空间和低维数据空间之间的条件概率(相似度)之差。
为了评估t-SNE 条件概率差和的最⼩化,使⽤梯度下降的⽅法最⼩化原分布中数据与映射分布中的对应数据的KL散度(Kullback-Leibler divergence)的总和。
[注3] 更容易理解的⽅式是通过欧式距离算出的AB两点的距离转换为条件概率以此来表达点与点之间的相似度,此概率与⼤,AB两点的相似度就越⾼。
吕布
[注4] 即相对熵或称信息增益。让其值变⼩也就是为了让相似度更⾼的数据点聚集在⼀起。信息增益⼩则说明区分该实例的难度⼤,换个⾓度来说就是这两个实例⾮常相似。关于信息增益相关概念可以浏
览⼀下我的另⼀篇如果有兴趣更进⼀步研究t-SNE 的同学可以查看附录中的论⽂1。
简单来说,t-SNE 主要⽬的在最⼩化两种分布的差异性:第⼀个是度量输⼊实例成对相似性,另⼀种是嵌⼊在相应低维空间中的成对点的相似性。
通过上述⽅式,t-SNE 映射多维数据到低维空间中并且试图找到基于多个特征的数据点的相似性来区分观测数据群,从⽽发现数据中的模式。然⽽,经过这⼀过程后,输⼊特征开始变得模糊,并且你⽆法仅基于t-SNE 的结果进⾏任何推断。因此这也是为什么t-SNE 主要还是⽤来做EDA和可视化的原因。
[注5] 与PCA ⽐较就可以很显然的看出,经过PCA 处理过后的结果能够得知每⼀个成分的⽅差贡献度(解释⽅差),然后t-SNE 仅仅是基于相似度进⾏判定,没办法从其结果推断类似的信息。
t-SNE 应⽤ Python 实现
现在你可以在Python中基于开源数据集应⽤t-SNE 算法并且将其降维结果可视化。与此同时,你也要在相同数据集应⽤PCA 算法并可视化结果,然后与t-SNE 进⾏⽐较。
本次的数据集使⽤Fashion-MNIST 并且您可以进⾏下载。
八宝菜的做法
Fashion-MNIST 是类似MNIST ⼿写图像数据集的公共数据集,由70,000条已标注为10种类别的时尚服装数据,每⼀个实例都是由28x28
的灰度图像组成,其中训练数据集含有60,000条,测试数据集有10,000条,⽤此数据集⽐⽤原MNIST 数据集能更好的对⽐结
果。Fashion-MNIST 的标签与MNIST ⼀样是0-9,但是不同的是这是个数字代表的是对应的时尚服装产品,下⾯对每⼀个数字对应的含义进⾏解释说明:
标注编号
描述
0T-shirt/top(T恤)1Trour(裤⼦)2Pullover(套衫)3Dress(裙⼦)4Coat(外套)5Sandal(凉鞋)6Shirt(汗衫)7Sneaker(运动鞋)
8Bag(包)9
Ankle boot(踝靴)
虎字开头的成语
之后你可以在Fashion-MNIST 官⽅仓库的utils ⽂件下的mnist_reader.py 找到读取该数据集的特定⽅法,即load_mnist(),如下:
[3][4][5]
def load_mnist(path, kind='train'):
import os
import gzip
import numpy as np
"""Load MNIST data from `path`"""
labels_path = os.path.join(path,
'%'
% kind)
images_path = os.path.join(path,
'%'
% kind)
with gzip.open(labels_path,'rb')as lbpath:
labels = np.ad(), dtype=np.uint8,
offt=8)
with gzip.open(images_path,'rb')as imgpath:
images = np.ad(), dtype=np.uint8,
offt=16).reshape(len(labels),784)
return images, labels
之后你可以通过load_mnist()⽅法读取训练和测试数据集并将其应⽤在你的算法上即可,只要将数据集的.gz⽂件路径作为第⼀个参数以及数据类型kind作为第⼆个参数传⼊该函数即可,如
X_train, y_train = load_mnist('.', kind='train')# 我的.gz⽂件就放在当前路径下
读取数据之后,你可以检查⼀下训练数据的基本属性,如shape,你会看到训练数据由60,000个实例以及784个特征组成
X_train.shape大闸蟹要蒸多久
(60000,784)
同样的标注数据也可以看到是由0-910个标签组成
y_train
array([9,0,0,...,3,0,5], dtype=uint8)
[6]
接下来,为了保证能够代码的完整性⽐如引⼊所需要的第三⽅库,同时为了确保可复现,还需要设置Random State参数为123。代码如下:
[注6] 我们知道计算机的随机都是伪随机,因此为了确保代码结果是可复现的都会设置⼀个随机因⼦,⾄于这个值是多少并没有规定,例如我本⼈就喜欢设置成42,原因是在《银河系漫游指南》中42是超级计算机得出的⽣命终极答案。
import time
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patheffects as PathEffects
%matplotlib inline
import aborn as sns
sns.t_style('darkgrid')
sns.t_palette('muted')
sns.t_context("notebook", font_scale=1.5,
rc={"lines.linewidth":2.5})
RS =123
为了能够兼容两个算法结果的可视化展⽰,需要创建⼀个fashion_scatter()函数。该函数接受两个参数:参数1 x 接受⼀个算法结果的2维矩阵输⼊;参数2 colors接受⼀个1维的标签数组。该函数会根据colors中的标签对x散点数据继续染⾊。
fashion_scatter()定义如下:
# Utility function to visualize the outputs of PCA and t-SNE
def fashion_scatter(x, colors):
# choo a color palette with aborn.
num_class =len(np.unique(colors))
palette = np.lor_palette("hls", num_class))
# create a scatter plot.
f = plt.figure(figsize=(8,8))
ax = plt.subplot(aspect='equal')
sc = ax.scatter(x[:,0], x[:,1], lw=0, s=40, c=palette[colors.astype(np.int)])
plt.xlim(-25,25)
plt.ylim(-25,25)
ax.axis('off')
ax.axis('tight')
# add the labels for each digit corresponding to the label
txts =[]
胎心多少是男孩还是女孩for i in range(num_class):
# Position of each label at median of data points.
xtext, ytext = np.median(x[colors == i,:], axis=0)
txt = ax.text(xtext, ytext,str(i), fontsize=24)
txt.t_path_effects([
PathEffects.Stroke(linewidth=5, foreground="w"),
PathEffects.Normal()])
txts.append(txt)
return f, ax, sc, txts
为了不让你的机器承担过重的内存与运⾏时间压⼒,在本次实践中我们只取训练集中前20,000条作为训练数据,同时你要确保这20,000条数据⼀定要涵盖10个标签。
x_subt = X_train[0:20000]
y_subt = y_train[0:20000]
np.unique(y_subt)
array([0,1,2,3,4,5,6,7,8,9], dtype=uint8)
现在可以在训练⼦集上使⽤PCA算法并可视化其结果了,为了⽅便我们直接使⽤scikit-learn提供的PCA算法,将n_components设置为4,这个参数决定了最后输出的数据维度。关于PCA的更多⽤法可以⾃⾏前往scikit-learn的官⽹查看
from sklearn.decomposition import PCA
time_start = time.time()
pca = PCA(n_components=4)
pca_result = pca.fit_transform(x_subt)
print(f'PCA done! Time elapd: {time.time()-time_start} conds')
PCA done! Time elapd: 0.6091551780700684 conds
现在可以将pca_reuslt存⼊DataFrame中,之后我们可以检查这四个主成分的各⾃的数据⽅差。
安徽农业大学是几本pca_df = pd .DataFrame (columns = ['pca1','pca2','pca3','pca4'])pca_df ['pca1'] = pca_result [:,0]pca_df ['pca2'] = pca_result [:,1]pca_df ['pca3'] = pca_result [:,2]pca_df ['pca4'] = pca_result [:,3]
print (f 'Variance explained per principal component: {plained_variance_ratio_}')
Variance explained per principal component: [0.29021329 0.1778743 0.06015076 0.04975864]
注意:训练⼦集中的第⼀和第⼆主成分的解释⽅差之和⼏乎达到了48%。因此我们仅对这两个主成分的数据进⾏可视化即可。
top_two_comp = pca_df [['pca1','pca2']] # taking first and cond principal component f , ax , sc , txts = fashion_scatter (top_two_comp .values ,y_subt ) # Visualizing the PCA output f .show
()
如图中所⽰,PCA 算法试图将不同点区分且将相同的点聚集起来。因此,这个图也可以⽤来做数据探索性分析。当然主成分1与主成分2还可以在分类和聚类算法上直接充当特征。
现在我们使⽤t-SNE 来做相同的事情,当然t-SNE 也在被scikit-learn 实现了,我们直接使⽤即可,在此我们提供⼀些t-SNE 常⽤的参数说明,更多关于t-SNE 的说明可以参考scikit-learn 的官⽅⽂档
n_components (默认为2): 嵌⼊空间的维数。
perplexity (默认为30): 复杂度的含义与其他流⾏学习⽅法中的最邻近个数的含义相似,通常在5-50之间考虑。early_exaggeration (默认为12.0): 控制原始空间在嵌⼊空间中的密度集⼤⼩。learning_rate (默认为200.0): 学习率的常见范围在10.0~1000.0之间。n_iter (默认为1000): 算法优化的的最⼤迭代次数,⾄少应该设置为250次。
method (默认为 ‘barnes_hut’): 学习⽅法,Barnes-Hut⽅法运⾏时间为O(NlogN)。method='exact’会⽐较慢,算法执⾏时间为O(N )。
[注7] 流⾏学习⽅法是指从⾼维采样数据中恢复低维流形结构,即找到⾼维空间中的低维流形。引⾃简书《》。在此我们先以默认参数运⾏t-SNE 算法:开业海报
[7]2