无监督学习——流形学习(t-SNE)

之前我们已经说过PCA通常是用于数据变换的首选方法,使人能够用散点图将其可视化,但这一方法的性质(先旋转然后减少方向)限制了其有效性。而有一类可用于可视化的算法叫做流形学习算法,它允许进行更复杂的映射,通常也可以给出更好的可视化。其中一个特别有用的算法就是t-SNE算法

PCA原理传送门:无监督学习与主成分分析(PCA)

算法原理

流形学习算法主要用于可视化,因此很少用来生成两个以上的新特征。其中一些算法(包括t-SNE)计算训练数据的一种新表示,但不允许变换新数据。这意味着这些算法不能用于测试集:准确地说,它们只能用于训练数据。流形学习对探索性数据分析是很有用的,但如果最终目的是监督学习的话,则很少使用。

t-SNE背后的思想是找到数据的一个二维表示,尽可能地保持数据点之间的距离。t-SNE首先给出每个数据点的随机二维表示,然后尝试让原始特征空间中距离较近的点更加靠近,原始特征空间中相距较远的点更加远离。t-SNE重点关注距离较近的点,而不是保持距离较远的点之间的距离。换句话说,它试图保存那些保存表示哪些点比较靠近的信息。

数据来源

数据是来自于scikit-learn包含的一个手写数字数据集,在这个数据集中,每个数据点都是0到9之间手写数字的一张8x8的灰度图像,图像如下:
PS:最近博主很忙,实在是没有时间找新数据来做了。。。

1
2
3
4
5
6
7
8
from sklearn.datasets import load_digits
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']###防止中文显示不出来
plt.rcParams['axes.unicode_minus'] = False###防止坐标轴符号显示不出来
digits=load_digits()
fig,axes=plt.subplots(2,5,figsize=(10,5),subplot_kw={"xticks":(),"yticks":()})
for ax,img in zip(axes.ravel(),digits.images):
ax.imshow(img)

在这里插入图片描述

数据操作

为了与PCA进行比较,我们先用PCA将降到二维的数据可视化。对前两个主成分作图,并按照类别对数据点着色:

1
2
3
4
5
6
7
8
9
10
11
12
from sklearn.decomposition import PCA
pca=PCA(n_components=2)###构建一个PCA模型
pca.fit(digits.data)###将digits数据变换到前两个主成分上
digits_pca=pca.transform(digits.data)
colors=["#476A2A","#7851B8","#BD3430","#4A2D4E","#875525","#A83683","#4E656E","#853541","#3A3120","#535D8E"]
plt.figure(figsize=(10,10))
plt.xlim(digits_pca[:,0].min(),digits_pca[:,0].max())
plt.ylim(digits_pca[:,1].min(),digits_pca[:,1].max())
for i in range(len(digits.data)):###将数据绘制成文本散点
plt.text(digits_pca[i,0],digits_pca[i,1],str(digits.target[i]),color=colors[digits.target[i]],fontdict={"weight":"bold","size":9})
plt.xlabel("第一主成分")
plt.ylabel("第二主成分")

结果如下:

在这里插入图片描述

这里我们将每个类别对应的数字作为符号来显示每个类别的位置。从上图可以看出,除了0,4,6以外,大部分数字都是重叠在一起的。

接下来我们将t-SNE应用于同一数据集,并对结果进行比较。由于t-SNE不支持变换新数据,所以TSNE类没有transfrom方法。我们可以调用fit_transform方法来代替,它会构建模型并立刻返回变换后的数据,代码如下所示:

1
2
3
from sklearn.manifold import TSNE
tsne=TSNE(random_state=42)###使用fit_transform而不是fit,因为TSNE没有transform方法
digits_tsne=tsne.fit_transform(digits.data)###运行时间较久

接下来我们也将处理过的数据可视化:

1
2
3
4
5
6
7
plt.figure(figsize=(10,10))
plt.xlim(digits_tsne[:,0].min(),digits_tsne[:,0].max()+1)
plt.ylim(digits_tsne[:,1].min(),digits_tsne[:,1].max()+1)
for i in range(len(digits.data)):###将数据绘制成文本散点
plt.text(digits_tsne[i,0],digits_tsne[i,1],str(digits.target[i]),color=colors[digits.target[i]],fontdict={"weight":"bold","size":9})
plt.xlabel("第一分量")
plt.ylabel("第二分量")

在这里插入图片描述

结果自不必多说,与PCA相比,t-SNE的结果非常棒。所有类型都被明确分开。数字1到9被分成几块,但大多数类别都形成一个密集的组。要记住,这种方法并不知道类别标签:它完全是无监督的。但它能够找到数据的一种二维表示,仅根据原始空间中数据点之间的靠近程度就能够将各个类别明确分开

t-SNE算法有一些调节参数,不过默认参数的效果通常就很好。感兴趣的朋友可以尝试修改perplexity和early_exaggeration,但作用一般很小。

总结

其实博主在了解这个算法的时候就在思考,这个算法有什么作用。毕竟说的再好听,也要在现实中有用才行。

实际上,t-SNE直接用于降维,并后接分类器比较少见。

当我们意识到需要降维时,一般是发现了特征间的高度线性相关,而t-SNE主打的是非线性降维。如果我们发现了线性相关,可能用PCA处理就可以了。即使发现了“非线性相关性”,我们也不会尝试用t-SNE降维再搭配一个线性分类模型,而会直接选择非线性的分类模型去处理。复杂的非线性关系不适合强行降维再做分类,而应该用非线性模型直接处理。如果是高度稀疏的矩阵,也有适合的分类器直接用,也没必要降维。

所以想了想,觉得t-SNE应该比较适合可视化,就像上面的图像一样,了解和验证数据或者模型。至于降维的话,还有很多局限性有待解决。

有很多地方做的不是很好,欢迎网友来提出建议,也希望可以遇到些朋友来一起交流讨论。