☰
sklearn流形学习实战:从PCA到t-SNE的非线性降维全解析
2026/10/13 8:29:36 网站建设 项目流程

流形学习这个标题里的“流型”,其实就是机器学习里常说的“流形”(Manifold),“流型”属于笔误。做数据分析、特征工程或者模型可视化时,我经常遇到一个场景:手头有几百个维度的高维特征,想看数据之间的真实结构,但直接画出来没法看,PCA 压到二维又是一团浆糊。后来在 scikit-learn 里把流形学习那一组方法挨个试了一遍,才发现很多问题被低估了。这里把我在实际项目里使用 sklearn 流形学习算法的完整经验写下来,从设计思路、算法选型、参数调整,到现场排查问题的步骤,一次讲清楚。

1. 流形学习到底在解决什么问题

1.1 从一张卷起来的纸说起

先用手边最好懂的例子解释“流形”。拿一张 A4 纸,它的本质是二维平面。把纸卷成圆筒或者拧成螺旋,再放进三维空间里观察,每个点都有 x、y、z 三个坐标,看起来像三维数据。但纸本身的自由度并没有增加,它还是那张二维的纸,只是被非线性地映射到了三维空间。

高维数据的真实分布经常就是这种情况。比如说一批商品评论经文本向量化之后,维度可能到几万,但真正决定评论内容的可能只有“情感倾向”“话题主题”“表达风格”这么几个隐变量。数据点并不是铺满整个几万维空间,而是集中在一个低维的、通常还带弯曲结构的曲面上。所谓流形学习,就是把这个低维曲面重新找出来,把数据“展开”回一个能看、能用的低维表示。

1.2 流形学习和普通降维的关键差异

传统降维,尤其像 PCA,做的事情是寻找一组线性方向,把数据投影到方差最大的方向上。线性方法的局限在于,它假设高维空间里的弯曲结构可以被一个线性平面近似。数据如果是在一张卷起来的纸上,PCA 强行投影的结果就是把本来互相邻近但分别位于纸两端的点拉到一起,破坏掉原有的局部结构。

流形学习不一样,它本质上是在建一个局部关系的图。每一步只相信“距离足够近”的点之间的几何关系,然后通过图结构把整个数据集在整个空间里摊开。所以它对非线性结构的还原能力是 PCA 这类线性方法给不了的。

这也解释了为什么流形学习在实际项目中的作用集中在三个方向:

一是可视化。把高维特征压到二维或三维,观察样本的聚集、分离和异常点,这是用最多的场景。

二是特征提取。低维嵌入结果可以当作新特征输入下游模型,很多场景里比原始高维特征效果更稳,维度也更低。

三是数据探索。在做聚类之前先用流形嵌入看看数据的大致分布形态,能帮助判断用多少聚类中心更合理。

2. sklearn 里的流形学习算法应该怎么选

2.1 先搞清楚几个算法的血缘关系

scikit-learn 的sklearn.manifold模块里不是一堆散装算法,它们内部有一条清晰的发展主线。

最底层的是经典多维缩放,MDS。它直接使用样本间的距离矩阵,目标是让低维空间里点与点的距离尽量还原高维空间里的距离。MDS 对线性结构效果好,但对弯曲流形效果一般,因为它拿的是全局欧氏距离。

紧接着 Isomap 在 MDS 的基础上做了一个关键改动:它不再用欧氏距离,而是用沿流形表面的测地线距离。具体做法是先用 K 近邻建立邻接图,然后在图上计算最短路径,把最短路径距离当作新的“距离矩阵”,再送入类似 MDS 的框架。这个改动解决了全局距离在弯曲数据上失效的问题。

另一条分支是局部线性嵌入 LLE。LLE 不太关注全局距离,它假设每个点和它的 K 个近邻位于流形上的一个局部线性区域,于是先用近邻点线性重构每个点,求出重构权重,再把这些权重保存下来,在低维空间里让重构误差最小化。

再往后就是谱嵌入(Spectral Embedding)和 t-SNE。谱嵌入其实是把邻接图转化成拉普拉斯矩阵,然后对拉普拉斯矩阵做特征分解,取最小的几个非零特征值对应的特征向量作为嵌入。t-SNE 则完全不走距离矩阵或特征分解的路线,它把高维空间的相似度变成概率分布,再让低维空间的概率分布去逼近它,用 KL 散度作为损失。

2.2 每种算法的使用边界

实际项目里我用这些算法的经验是:它们没有绝对好坏,只有适不适合当前的数据规模和任务目标。

Isomap 对数据局部噪声非常敏感。因为测地线距离依赖 K 近邻图,一旦近邻图出现一条跨越空洞的“短路”边,两点之间的最短路径会被严重低估,最后嵌入结果会出现肉眼可见的变形。它适合结构清晰、采样较密、噪声较少的几何类数据,比如由物体位姿变化生成的图像特征。

LLE 系列更适合数据分布在多个局部线性块上的情况。它有几个变体,包括标准 LLE、改进的 Modified LLE、Hessian LLE、LTSA 局部切空间对齐,在 sklearn 里通过method参数切换。Modified LLE 和 Hessian LLE 对邻居数的敏感程度低于标准 LLE,但计算量更大。

MDS 适合需要严格保留样本间距离结构的场景。比如用户相似度矩阵已经有业务定义,直接用 MDS 把相似度变成二维坐标来做分布分析,这个场景我经常用到。

t-SNE 是可视化任务里的默认首选,它擅长把高维聚类结构拉到二维之后依然分得开。但它的低维坐标之间的距离没有太多实际意义,簇之间的距离不能解读为真实相似度。

谱嵌入适合数据量大、先建图再降维的场景,而且它对图的构建方式比较灵活。

2.3 选型速查表

算法核心思想关键参数最适合的场景主要注意点
MDS全局距离还原dissimilarity距离矩阵已知、保留全局距离无法处理强弯曲流形
Isomap测地线距离 + MDSn_neighbors几何结构清晰、低噪声数据对噪声和 K 值敏感
LLE局部线性重构n_neighbors,method局部线性结构数据邻域大小影响明显
Spectral Embedding拉普拉斯特征分解affinity,gamma图结构数据、大规模点云结果与图构造方式强相关
t-SNE概率分布匹配perplexity,learning_rate高维聚类可视化坐标不可解释、计算量大

3. 上手实操:从数据准备到完整实现

3.1 数据准备环节最容易忽略的事

流形学习算法本质上都在计算样本间的距离或邻接关系,所以数据尺度问题比线性模型更致命。我曾经在处理业务特征时直接喂了一组包含年龄、收入、点击次数、消费金额的数据,收入范围是几千到几万,点击次数是几次到几百次,结果距离矩阵几乎完全被收入这一列主导,其他特征等于没参与。

对多数流形学习算法,尤其是基于欧氏距离计算近邻的 Isomap、LLE、谱嵌入,第一步应该做标准化。

from sklearn.preprocessing import StandardScaler X_scaled = StandardScaler().fit_transform(X_raw)

但需要说明一个特例:如果数据本身已经是同一量纲的像素值、光谱值或基因表达量,强行标准化反而会抹平强度信息。比如手写数字图片数据,灰度值范围在 0 到 255 之间,特征本身同源,直接使用原始值往往效果不差,关键是保持特征间可比性。是否标准化,核心判断依据是“特征之间是否量纲一致、取值范围是否存在人为偏差”。

3.2 最小实现:手写数字数据集上的完整流程

用 sklearn 自带的手写数字数据集做演示最直观。这个数据集每张图是 8×8 的灰度图,展开成 64 个像素特征,有 10 个类别。数据维度不高但带有像素间的强相关性,适合观察流形学习的效果。

先加载数据,然后分别用 Isomap、LLE 和 t-SNE 降到二维,对比可视化结果。

import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import load_digits from sklearn.manifold import Isomap, LocallyLinearEmbedding, TSNE digits = load_digits() X = digits.data # (1797, 64) y = digits.target

Isomap 的调用非常简单,核心参数是两个:邻居数n_neighbors和目标维度n_components。这里降到二维做可视化,邻居数先取一个常见值 8。

iso_model = Isomap(n_neighbors=8, n_components=2) X_iso = iso_model.fit_transform(X) plt.figure(figsize=(6, 5)) plt.scatter(X_iso[:, 0], X_iso[:, 1], c=y, cmap='tab10', s=10) plt.title('Isomap embedding') plt.show()

LLE 的接口基本一样,注意它多了一个method参数。标准 LLE 用method='standard',也可以换成'modified'、'hessian'或'ltsa'。

lle_model = LocallyLinearEmbedding( n_neighbors=10, n_components=2, method='modified', eigen_solver='auto' ) X_lle = lle_model.fit_transform(X)

t-SNE 的接口稍有不同,它没有transform方法,不能像 Isomap 那样先 fit 再用 transform 处理新样本。所以直接调用fit_transform是唯一方式。

tsne_model = TSNE( n_components=2, perplexity=30, learning_rate='auto', init='pca', random_state=42 ) X_tsne = tsne_model.fit_transform(X)

跑完这三个模型,能明显看到 t-SNE 对类别的分离程度最好,不同数字基本形成独立的小簇。Isomap 的布局更像一个连续的结构,部分类别在过渡区域交叠。LLE 的效果介于两者之间,它对局部近邻关系非常敏感,邻居数稍微一变,整体布局就变样。

3.3 关键参数到底在控制什么

n_neighbors是 Isomap、LLE、谱嵌入这类基于邻域方法的最核心参数。它直接决定“局部”有多大。取值太小,邻接图可能分裂成多个不连通的分量,无法算出完整的测地线路径。取值太大,局部信息被全局信息淹没,方法会逐渐退化得像 PCA。

我在项目里一般先尝试 5、8、12、20 这几个值,然后看两点:一是邻接图是否连通,二是低维嵌入的可视化是否稳定。更严谨的方式是提取模型的distortion_或reconstruction_error_,在不同 K 值下对比误差曲线,选择拐点处的 K。

n_components不一定只做可视化时取 2。做特征提取时,我会把目标维度设置在 8 到 30 之间,因为低维嵌入不是给人看的,它只需要保留足够的鉴别信息。这里有一个值得提醒的点:n_components增加的收益往往不是线性的,比如 Isomap 在二维和三维之间的变化很大,但在二十维和三十维之间的差距很小。

eigen_solver控制特征分解的实现路径。'auto'让 sklearn 自己选,'arpack'用迭代法求少量特征向量,速度快,适合大规模稀疏场景。'dense'直接稠密分解,适合小数据但精度更高。数据量过万时,我基本不主动选'dense',内存会非常紧张。

3.4 t-SNE 的难点不在接口,在参数感受

t-SNE 的两个参数perplexity和learning_rate对结果的影响远超一般人的预期。

perplexity可以理解成每个点视作近邻的“有效邻居数”。取值过小,比如小于样本量的一定比例,嵌入结果容易出现碎散的小团;取值过大,所有点都趋向于被拉成均匀分布。经验上我习惯在数据集规模较小时取 5 到 30,在几万样本时取 30 到 50 尝试。注意这个值不是越大越好,关键是多次尝试后看簇的结构是否有可解释性。

learning_rate控制梯度下降步长。sklearn 从某个版本开始支持learning_rate='auto',它会根据样本量自动设置一个推荐值,对新手是友好的。如果手动设置,太小的学习率会让损失函数卡在一个次优状态,点分布像被冻住一样;太大会让低维坐标剧烈震荡。我遇到嵌入结果出现“大团中包小团”的奇怪纹理时,第一反应不是换算法,而是调一下learning_rate和perplexity。

init='pca'是我的默认选择。随机初始化时,每次运行结果都不同,而且不同初始化之间的布局差异可能非常大。如果只做探索性可视化,这会造成重复运行的结论不一致。用 PCA 初始化可以给一个相对稳定起点,配合random_state固定随机种子,结果就能复现。

4. 高频踩坑:邻居数、噪声和随机性问题

4.1 邻接图断裂怎么判断和处理

基于 K 近邻的算法在最坏情况下会遇到“图不连通”的问题。比如一个离群点离主体数据很远,它在 K 值范围内找不到足够近的邻居,或者某一片密度极低的区域把图分成了两半。此时 Isomap 会报错,或者默默产生一个毫无结构的嵌入。

判断图是否连通,可以直接看模型算出的测地线距离矩阵中是否存在无穷大值。更简单的办法是逐步增大n_neighbors,看嵌入结果是否在某一个 K 值附近发生突变。如果在某个 K 之后结构突然变得合理,说明之前图不连通的问题被解决了。

处理这类问题的另一个思路是先用离群点检测把孤立样本剔除,再跑流形学习。不要在小样本上强行增大 K 值,K 接近样本量会让整个方法退化。

4.2 局部噪声引起的“短路”

Isomap 这类方法最大的坑之一是噪声导致的“短路”。假设数据分布在一条环形流形上,正常的近邻关系沿圆环走。如果环形中间存在少量噪声点,它们在欧氏距离上离圆环两端都很近,就可能被选为近邻,测地线距离就会直接穿过圆环内部,等于抄了一条近路。结果嵌入图里环状结构会被拉成奇怪形状,甚至出现伪聚类。

我对这个问题的处理方式有三个:第一,尽量先做降噪预处理,例如 PCA 先压到保留 95% 方差的主成分空间,再跑流形学习;第二,调小n_neighbors来降低穿捷径进图的概率;第三,检查近邻图中是否存在“跨区域”的边,通过可视化邻接关系来判断。

4.3 t-SNE 和 LLE 的随机性问题

LLE 本身是确定性算法,但它对邻居数选择非常敏感,而且不同实现方式之间的差异可能让嵌入结果看起来像两组完全不同的数据。Modified LLE 和 Hessian LLE 的数学过程更复杂,对同样的 K 值,它们会给出和标准 LLE 完全不同的布局。

t-SNE 的随机性更直接。同一个数据集、同样的参数,不同随机种子会得到位置浓淡差异很大的图。这不是 bug,而是优化问题局部最优和梯度噪声的体现。但用户视角里,不可复现会造成信任危机。所以我的习惯是:所有涉及随机性的实验统一设置random_state,并把这个值写进项目文档,不然过两周连你自己都说不清图是怎么来的。

4.4 样本量和内存的权衡

流形学习算法大多要计算样本间的距离矩阵或近邻图。MDS 对全距离矩阵做分解,数据量到两三万的时候内存消耗已经到了不太舒服的程度;Isomap 还要再多一步完整的最短路径矩阵,内存压力更大。t-SNE 使用 Barnes-Hut 近似之后能支撑较大样本量,但它的计算时间依然不低。

当样本量很大时,我倾向先抽样或先聚类,而不是硬跑全量数据。如果目标是看数据整体结构,从每个簇里均匀抽一部分,效果几乎不会差。

4.5 结果质量的判断方式

流形学习的结果不像分类模型有准确率可用,判断质量更多靠“结构合理性”和“下游任务收益”。

一个实用的办法是把嵌入结果当作特征,喂给一个简单分类器或聚类算法,比较它在流形特征和原始特征上的表现。另一个办法是检查近邻保持率:在高维空间里互为近邻的样本,在低维空间里是否依然靠得很近。这个指标虽然不是 sklearn 直接给出,但可以用 KNN 在高维和低维分别计算重合度来近似度量。

我建议不要只盯着可视化的“美观程度”来判断算法好坏。有些嵌入展开得好看,但低维坐标已经完全丢失真实距离信息;有些嵌入看起来很拥挤,反而在下游任务里表现出色。

5. 实现层面的一点心得和扩展方向

5.1 统一接口让模型替换成本很低

sklearn 的流形学习模块设计得很统一,几乎所有算法都有fit、fit_transform,属性里有embedding_等。这种统一接口带来的好处是:你在代码里换算法,往往只需要换一行类的名字和几个参数。

这给实际工作带来的最大价值是可以快速对比。我会写一个简单的循环,把 MDS、Isomap、LLE、谱嵌入、t-SNE 依次跑一遍,统一画在同一坐标刻度下,再对每个结果做一轮近邻保持率评估。哪个结果在下游任务上更合理,就选择哪个作为最终嵌入方案。

5.2 组合使用比单模型更稳

我自己用得比较多的一种方案是“预降维 + 流形嵌入”。直接在高维稀疏特征上跑 t-SNE,距离会被噪声主导;先经过 PCA 把维度降到几十维,再去算近邻关系,嵌入质量通常明显提升。sklearn 里可以用 Pipeline 把这两步串起来,方便做参数搜索。

另一个常用组合是“流形嵌入 + 聚类”。先用 t-SNE 或谱嵌入降到低维,再对嵌入坐标跑聚类算法。这种方法比直接在高维空间聚类更容易得到结构清晰的簇,尤其当原始特征存在非线性冗余时。

5.3 如果想进一步深入可以从哪几个方向走

流形学习并不止步于 sklearn 提供的这些经典方法。实际项目里还可以尝试 UMAP,它在大型数据的可视化上更快,理论上对局部结构和全局结构的平衡也有自己的设计逻辑。另外自动编码器本质上也在做一个非线性降维,只要约束潜变量维度低于输入维度,就可以和流形学习相互对照。

我之前遇到一个项目,数据是设备传感器的时间序列片段,直接分类效果很差。后来先用流形学习把片段嵌入到低维空间,再对嵌入坐标做时序聚类,才把设备的几种运行模式分开。这个案例给我的体会是:流形学习不要当作一个独立的可视化工具来用,把它嵌进特征工程链路里,它的价值会大得多。

从实操角度说,我的经验就是不要一上来追求复杂参数组合,先把数据集、目标、样本量确认清楚,然后固定一个随机种子,逐个算法跑出来对比。流形学习的上手门槛其实很低,难的是理解每个参数背后控制了什么几何假设。搞清楚这些之后,sklearn 这套实现会让你省掉大量造轮子的时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询