我刚入行那会儿接了一个用户画像项目,特征工程做完,表里躺着一千多列。模型倒是能跑,但特征之间互相纠缠,业务方追问"这个指标为什么重要"的时候,我完全答不上来。后来才想明白,我当时缺的不是更多特征,而是一套系统的数据降维思路。
数据降维,简单说就是在尽量保留原始信息的前提下,把高维数据压缩到低维空间。它在实际项目里几乎无处不在:做可视化要看二维/三维投影,训练模型要缓解维度灾难,业务分析要剔除冗余特征。这篇文章不打算堆公式,而是把数据降维方法按类别从头到尾梳理一遍——不同方法解决什么问题、底层逻辑是什么、什么场景下选哪种,以及我实际项目中踩过的坑。无论你是刚接触特征工程的初学者,还是已经在调参路上摸爬滚打了一阵的工程师,这套分类框架都能帮你少走弯路。
1. 先回答一个前置问题:你到底为什么要降维
很多人一上来就打开 sklearn 调 PCA,但降维之前最该问的一句话是:我到底为了解决什么问题?目的不同,选的方法可以完全不一样。
1.1 维度灾难:特征一多,数据反而"变稀"
特征数量上去之后,数据在高维空间里会变得异常稀疏。举个例子,100 个样本在一维数轴上能把区间铺得很满,但到了 10 维空间里,每个样本周围几乎是空的,样本之间的距离全都变得差不多。这就直接导致基于距离的算法(KNN、K-Means、基于核的模型)失效,因为"最近邻"和"最远邻"之间的距离差异小到可以忽略。
模型的复杂度也会随维度爆炸式增长。想要在一个高维空间里把函数拟合好,需要的样本量是指数级上升的。特征从 10 个加到 100 个,样本量还是原来的几千条,模型很快就过拟合到噪声上去了。
1.2 共线性、噪声与计算压力
第二个常见场景是特征之间高度相关。你做特征工程时,新构造的特征往往和原始特征高度冗余,比如"消费金额"和"消费次数×平均客单价"。这些共线性特征会让线性模型的系数估计变得极不稳定,今天参数是这个值,明天换一批数据就变另一个值,解释起来非常尴尬。
还有很现实的计算压力。一位热编码、文本 TF-IDF、用户行为交叉特征,动不动就是几万维。很多算法的时间复杂度是随特征数平方甚至立方增长的,不降维,训练一轮就要等到天荒地老。光谱上,高维数据里还混着大量无关噪声,降维本身也是一种去噪手段。
1.3 不同目标指向不同方法
我习惯把降维的目的归纳成四类:
- 可视化:把数据压到 2D 或 3D,用眼睛看聚类结构。这类需求首选 t-SNE、UMAP,PCA 也能当快速预览。
- 建模预处理:缓解维度灾难、消除共线性、压缩特征维度。常用 PCA、LDA、SVD,大样本复杂结构上会用自编码器。
- 压缩存储:用低维表示代替原始高维向量。自编码器、PCA 的低秩近似都行。
- 业务解释:要跟业务方讲清楚"为什么是这些特征"。这时候老老实实做特征选择,别用什么主成分。
把这四个目的记在脑子里,再看下面所有方法,就不会觉得是一团乱麻了。
2. 降维的第一道分水岭:特征选择与特征抽取
数据降维方法如果要画一张总分类图,最顶层的分叉一定是:特征选择和特征抽取。这两派的思路本质上是相反的。
2.1 特征选择:从原始特征里"挑"
特征选择做的是一件很朴素的事:原始特征一个都不改,只是把里面没用的、冗余的删掉,留下一个有代表性的子集。它的核心是定义"什么算有用"。
按评价方式可以分成三类:
- 过滤式(Filter):不依赖任何模型,直接算统计量。比如方差过滤(方差太小的特征基本是常数)、相关系数过滤(去掉跟目标弱相关或彼此高度相关的特征)、卡方检验、互信息。优点是快,缺点是完全不顾特征组合起来的效果。
- 包裹式(Wrapper):以模型效果为评价标准,反复尝试不同的特征子集。经典的有递归特征消除(RFE)、前向选择、后向剔除。效果通常更好,但计算开销很大,特征上千时基本跑不动。
- 嵌入式(Embedded):把特征选择融进模型训练过程。最有代表性的就是 Lasso 的 L1 正则,它会把一堆特征的系数压成 0;树模型的特征重要性也可以归到这一类。速度和效果比较均衡,是我在实际项目里用得最多的一类。
特征选择最大的优点是可解释性极强——留下的还是原来的字段,"客户年龄"就是"客户年龄"。缺点是它只能在已有特征里挑,无法组合出新的、信息密度更高的表示。
2.2 特征抽取:从原始特征里"造"
特征抽取相反,它不保留原始特征,而是把原始特征通过某种变换组合成一组全新的、维度更低的变量。PCA 的主成分、LDA 的判别方向、自编码器的瓶颈向量,都是"造"出来的新特征。
这类方法的优势是压缩能力强,能最大限度去除特征间的冗余结构,把信息集中到少数几个新维度里。代价是新的变量几乎没有直观含义——主成分是几十个原始特征的线性组合,你很难跟业务方解释"第三主成分"到底代表什么。
2.3 两者的边界由任务说了算
很多初学者纠结"到底该用特征选择还是特征抽取",我的答案很简单:看你要不要跟人解释。
在金融风控、医疗诊断这类强监管、强解释需求的场景,我几乎只用特征选择,因为模型上线后要回答监管的质疑,主成分说不清楚。但在图像、文本、推荐系统这类"只看最终效果"的场景,特征抽取是绝对主流,没人关心第 17 个主成分是什么。
| 对比维度 | 特征选择 | 特征抽取 |
|---|---|---|
| 降维后特征 | 原始特征子集 | 新构造的组合特征 |
| 可解释性 | 高,保留业务语义 | 低,难解释 |
| 信息利用 | 只能丢弃,不能组合 | 可以组合出高密度信息 |
| 典型方法 | Filter / Wrapper / Embedded | PCA、LDA、t-SNE、UMAP、自编码器 |
| 典型场景 | 风控、医疗、业务解释 | 视觉、文本、推荐、建模压缩 |
实践中也可以两者叠加:先用嵌入式特征选择砍掉明显没用的特征,再用 PCA 压到几十维喂给模型。我自己经常这么干,效果比单用一类好不少。
3. 线性降维三件套:PCA、SVD、LDA 的原理与边界
特征抽取这棵树下,按"是否假设线性关系"又分成线性派和非线性派。线性派是入门门槛最低、应用最广的一类,核心就三个名字:PCA、SVD、LDA。
3.1 PCA:在方差最大的方向上投影
主成分分析(Principal Component Analysis)的思想非常朴素:找一组正交方向,把数据投影上去,让投影后的方差尽可能大。方差大意味着信息保留得多,丢掉的方向方差小,丢掉也不心疼。
具体做法是:先把数据中心化(每个特征减均值),然后计算协方差矩阵,对这个矩阵做特征值分解。特征值大的方向就是主成分方向,特征值本身代表该方向上的方差。投影后第 k 个主成分解释的方差比例就是:
第 k 个特征值 / 所有特征值之和把特征值从大到小排列,前 k 个累计解释比例达到 80% 或 90% 时,你就可以取 k 个主成分。画一个"特征值-序号"的肘部图,看拐点在哪里,是最常用的选 k 方式。
这里有个前提必须强调:特征必须做标准化。否则量纲大的特征方差天然就大,PCA 会优先保它,结果全被"消费金额(万元)"这种大数特征带跑了,年龄、频次这类小数特征直接被忽略。
3.2 SVD:PCA 背后的计算引擎
奇异值分解(Singular Value Decomposition)很多人把它当成和 PCA 并列的另一种方法,这是个常见的误解。更准确的说法是:SVD 是 PCA 最稳定的计算方法。
对中心化后的数据矩阵 X 做 SVD,得到 X = UΣVᵀ,其中 V 的列向量恰好就是 PCA 要求的主成分方向,Σ 里的奇异值与特征值之间只差一个与样本量相关的常数。也就是说,PCA 的本质计算步骤可以直接用 SVD 完成,而且 SVD 不需要先算协方差矩阵,数值稳定性更好,尤其适合"特征数远大于样本数"(p >> n)的情况。
实际工具里更常用的是 TruncatedSVD,也就是截断 SVD——只算前 k 个奇异值和对应的向量,不把完整分解算出来。它最大的好处是能直接处理稀疏矩阵。文本 TF-IDF 出来动辄几万维还是稀疏的,用普通 PCA 得先稠密化,内存直接爆掉,而 TruncatedSVD 可以直接上手,这就是经典的自然语言处理技术 LSA(潜在语义分析)的底层实现。
3.3 LDA:有监督降维的代表
线性判别分析(Linear Discriminant Analysis)和 PCA 最大的不同是:它用到了标签。PCA 只关心方差,LDA 关心的是"投影后类间距离尽量大、类内距离尽量小"。
它的优化目标是一个比值:类间散度除以类内散度。让这个比值最大,找到的方向就能把不同类别尽量分开。LDA 有一个硬性限制:降维后的维度最多不超过类别数减一(C-1)。二分类问题,LDA 最多只能降到 1 维。
很多做分类的同事喜欢先用 LDA 降维再喂给其他分类器,因为它在压缩的同时还考虑了类别信息,通常比无监督的 PCA 更适合分类任务。但它的假设比较强——要求各类别近似服从高斯分布且协方差矩阵相近,数据分布严重偏离时效果会打折。
3.4 三件套怎么选
| 方法 | 是否有监督 | 优化目标 | 降维后维度上限 | 典型场景 |
|---|---|---|---|---|
| PCA | 无 | 投影方差最大 | min(n, p) | 探索分析、去相关、特征压缩 |
| SVD / TruncatedSVD | 无 | 最小化重建误差(低秩近似) | min(n, p) | 文本稀疏矩阵、推荐系统、LSA |
| LDA | 有 | 类间散度 / 类内散度比值最大 | C-1 | 有标签的分类任务前降维 |
我的经验是:拿到新数据,无脑先跑一遍标准化 + PCA 看结构,永远是最快建立直觉的方式。如果标签信息非常重要且数据量不大,再叠加 LDA 对比效果。SVD 则是在数据稀疏、内存受限时的救命稻草。
4. 当线性假设失效:核技巧、流形学习与 t-SNE
线性方法虽然好用,但它的天花板很清楚:数据在原始空间里必须是线性可压缩的。现实中的数据往往没这么乖巧。
4.1 一条卷起来的"瑞士卷"
流形学习的教科书里最爱举的例子是"瑞士卷":三维空间里一张卷起来的薄片,数据其实躺在一个二维流形上,但它在三维空间里是弯弯曲曲的。PCA 在这个数据上表现很差,因为它只会找全局方差最大的直线方向,强行投影会把瑞士卷压成一团乱麻,本来分离的两种点的投影会重叠在一起。
这种现象在真实数据里很常见:手写数字的像素空间、人脸图像的原始像素、传感器波形,本质都嵌在高度弯曲的低维流形上。线性假设一旦不成立,就该请出非线性方法。
4.2 核主成分分析与经典流形学习
非线性降维的第一波尝试是核方法。核主成分分析(KPCA)的思路是:原始空间里数据是弯的,但如果用一个核函数把数据隐式映射到更高维的空间,高维空间里数据反而可能是线性的,再做一次普通 PCA 就行。RBF 核(高斯核)是最常用的选择。KPCA 的问题是新维度非常难解释,而且核矩阵的计算量随样本量平方增长,上万条样本就开始吃力。
紧接着是真正的流形学习家族:
- Isomap:核心是把"欧氏距离"换成"测地距离"。两点之间不是直线距离,而是沿数据流形表面走的最短路径。先构建近邻图,再算图上的最短路径,最后用多维缩放(MDS)在低维空间里还原这些距离。它对邻域参数敏感,噪声大时容易在图上连出"捷径",结果崩掉。
- LLE(局部线性嵌入):假设每个点可以由它的 k 个近邻线性重构,重构权重保留局部几何结构,再在低维空间里让这些权重尽量不变。它比 Isomap 快,但对噪声和 k 的选择同样敏感。
这两兄弟在 scikit-learn 里都还在,但我这几年已经很少在实际项目里首推它们了,因为它们对超参数太敏感,且稳定性和速度都被后来的 UMAP 碾压。
4.3 t-SNE:为可视化而生的非线性降维
t-SNE(t-distributed Stochastic Neighbor Embedding)是可视化界的扛把子。它的思路很有意思:在高维空间里,把点之间的距离转成概率分布——近的点概率高,远的点概率低;然后在低维空间里也构造一个概率分布,让这两个分布尽量一致,用 KL 散度衡量差异,不断迭代优化。
它和 PCA 有个非常大的区别:t-SNE 优化的是"局部结构"——邻居关系保持住了,但全局的簇间距离没有意义。簇之间的远近、大小、密度都不能拿来解读。另一个特点是每次运行结果都可能不同,因为它的优化是带随机性的。
实操中几个参数值得注意:
- perplexity(困惑度):可以理解为"每个点眼里有多少个邻居",一般取 5~50。太小,局部噪声被放大;太大,全局结构虽然显现但计算很慢。我常用 30 作为起点。
- n_iter:迭代次数太少了还没收敛,图是花的,5000 起步比较稳。
- 样本量:几万条以内还好,几十万条就很慢,通常先抽样再做 t-SNE。
还有一条铁律我放在这里,后面还会再讲:t-SNE 的结果只用来"看",不要拿它当特征喂给模型。
5. 深度时代的降维:UMAP 与自编码器
前面讲的方法都有十几年甚至几十年的历史了,但降维这个领域并没有原地踏步。近几年项目里我明显感受到两个新选择在快速普及:UMAP 和自编码器。
5.1 UMAP:又快又能保留结构的后起之秀
UMAP(Uniform Manifold Approximation and Projection)是 2018 年前后火起来的,理念上受流形学习和拓扑数据分析启发。它在高维空间里构造一个带权重的近邻图,用来刻画数据的局部流形结构,然后在低维空间里用类似的办法构造另一个图,让两个图尽量一致。
相比 t-SNE,UMAP 的几个优势非常实际:
- 速度快:百万级样本也能跑,t-SNE 到十万条就让人抓狂。
- 全局结构保留得更好:簇和簇之间的距离有一定含义,不像 t-SNE 那样完全失真。
- 可以对新样本做映射:训练好之后能用 transform 方法把新数据投到同一个低维空间,这在工程上太重要了。经典 t-SNE 没有这个能力,新数据来了只能和旧数据混在一起重新跑。
UMAP 有两个核心超参数:n_neighbors(默认 15,越小越关注局部,越大越倾向全局)和min_dist(默认 0.1,控制低维空间里点能挨多近,值越小簇越紧凑)。我实际用下来的感受是,它作为可视化和探索工具表现优异,即使不用来最终建模,也常能帮你在几分钟内发现数据里的异常簇和离群点。
5.2 自编码器:让网络自己学一套压缩表示
自编码器(Autoencoder)是深度学习在降维领域的代表。结构上就三块:编码器把高维输入压到瓶颈层(这就是降维后的表示),解码器再从瓶颈层把原始输入重建出来。损失函数是重建误差,比如均方误差,训练目标就是让"压扁-还原"这个过程尽量不丢信息。
自编码器最漂亮的地方是,瓶颈层的宽度就是降维后的维度,你想要多少维就设多少个神经元。而且因为中间层是非线性的激活函数,它能捕捉数据里非常复杂的非线性结构,这点 PCA 做不到。
我在项目里用过几种变体:
- 普通自编码器:做特征压缩和预训练,瓶颈向量喂给下游分类器。
- 去噪自编码器(DAE):输入加噪声,目标是还原干净数据,学出来的表示鲁棒性更好。
- 变分自编码器(VAE):瓶颈层学的是分布的参数,降维之外还附带生成能力,图像生成、异常检测里很常见。
自编码器的短板也很明显:训练需要更多数据、调参成本高,结果受随机种子影响,而且它学到的表示完全不可解释。数据量只有几千条时,老实说它多半打不过 PCA + 好特征工程。
5.3 什么时候值得上深度学习方案
我的判断标准大约是这样:
- 样本量低于几千、特征维度几千到几万:经典方法足够,别折腾深度学习。
- 样本量十万级以上、数据是高维复杂结构(图像、文本、行为序列):自编码器或 UMAP 上场,压缩效果和下游任务收益通常远超线性方法。
- 有预训练模型可用的场景:取倒数第二层或倒数第一层的向量作为"降维后特征",往往比从零训自编码器更快更好,本质上这也是一种降维。
6. 拿着数据怎么选:一套可落地的决策流程
讲了这么多方法,落到实际项目里,很多人还是会在屏幕前愣住。我这里整理了一套我自己的决策流程,基本就是连续问五个问题。
6.1 五个问题定位方法
问题一:降维后给谁用?给眼睛看,选 t-SNE 或 UMAP;给模型做特征,选 PCA、LDA、自编码器;给业务解释,选特征选择。这一步直接砍掉一半候选。
问题二:有没有标签?有标签且目标是分类,LDA 和嵌入式特征选择优先考虑;无标签,PCA、SVD、UMAP、自编码器之间选。
问题三:数据是什么形态?稠密数值表,PCA 最顺手;高维稀疏文本或计数矩阵,TruncatedSVD 保命;图像、语音这种原生高维信号,直接考虑卷积自编码器或预训练模型特征。
问题四:数据是线性可分的吗?不确定就先跑 PCA,看前几个主成分的累计解释率。如果前 10 个主成分连 50% 方差都解释不了,说明线性结构的假设很弱,应该上非线性方法。
问题五:老板或业务方要解释吗?要解释,回到特征选择;不要解释,特征抽取随便用。
把这五个问题的答案组合起来,就有了一张很实用的速查表:
| 数据情况 | 推荐起点 | 理由 |
|---|---|---|
| 稠密表格、量纲差异大、无标签 | 标准化 + PCA | 最快建立全局结构认知 |
| 高维稀疏文本 / 行为计数 | TruncatedSVD | 不需要稠密化,内存友好 |
| 有标签、目标分类建模 | LDA 或嵌入式特征选择 | 利用类别信息,兼顾解释性 |
| 数据高度非线性、样本量大 | UMAP / 自编码器 | 处理复杂弯曲流形 |
| 需要给业务方解释"为什么" | Filter + Embedded 特征选择 | 保留原始特征语义 |
| 纯探索性看聚类 | t-SNE / UMAP | 局部结构清晰,聚类直观 |
6.2 四个真实场景的选型示范
场景 A:保险用户画像,2000+ 特征,需要向业务解释。我的做法是先用 LightGBM 的重要性或 Lasso 做嵌入式筛选,砍到 50 个原始特征,再从中做相关性去重,最后留 30 个左右。全程没有用一个主成分,业务方看得懂,模型效果也稳定。
场景 B:文本分类,TF-IDF 出来五万维。直接用 TruncatedSVD 降到 200 维(这就是 LSA),再喂给逻辑回归。五万维稀疏矩阵直接训练逻辑回归也能跑,但降维之后训练速度快一个量级,效果还经常更好,因为去掉了大量稀疏带来的噪声。
场景 C:商品图像检索,用预训练 CNN 的 1024 维特征。检索对延迟敏感,1024 维向量算余弦相似度在大规模库上很贵。我试过直接 PCA 降到 64 维,召回损失很小;也试过 UMAP 降到 64 维,保留的语义结构更好。最后还是选了 PCA——因为上线时 sklearn 的 PCA 是确定性变换,维护简单,UMAP 每次预测都要保证和训练时同一套参数和状态,工程上麻烦不少。
场景 D:单细胞测序或传感器波形探索。这类数据你就是想看看有没有隐含的分群结构,首选 UMAP。先 UMAP 降到 2D 画图,发现明显分群后再回头找标签或聚类,效率极高。
7. 实战踩坑记录:标准化、数据泄漏与结果解释
最后这部分,是我在这几年项目里踩过、也看同事踩过的坑。每一条都对应着真实的调参到半夜的经历。
7.1 不标准化就做 PCA,量纲大的特征会"霸屏"
我第一次独立做 PCA 时就没标准化,结果第一主成分几乎等于"消费金额"一个特征,其他几百个特征加起来只占不到 10% 的贡献。原因是 PCA 找的是方差最大的方向,而"消费金额"的单位是万元,数值天然比"年龄""消费频次"大得多。
所有基于方差和距离的降维方法(PCA、LDA、t-SNE、UMAP、K-Means 前置步骤)都默认特征在同一尺度下才有意义。所以第一步永远是 StandardScaler 或 MinMaxScaler,没有例外。
7.2 把 t-SNE 结果喂给下游模型:典型的误用
t-SNE 只保留局部邻居结构,全局距离是失真的。我曾经见过一个同事把 t-SNE 降到 3 维的结果直接当特征喂给 XGBoost,模型在验证集上看着还行,一换真实环境效果崩了——因为模型学到的是 t-SNE 优化出来的"伪结构",而不是数据的真实分布。
t-SNE 只能用来探索和可视化。如果你确实需要非线性降维后的特征来做建模,用 UMAP 的 transform,或者用自编码器的瓶颈向量,两者都保留了可迁移的结构信息。
7.3 先在全量数据上 fit,再切训练集?数据泄漏
这是所有降维坑里最隐蔽、后果最严重的一个。PCA、特征选择、标准化这些步骤如果先在包含测试集的全量数据上计算,测试集的信息就通过转换参数泄漏进了训练过程,最后验证结果会偏乐观,上线就现原形。
正确的顺序是:先切分训练集测试集,再在训练集上 fit,然后用同一组参数 transform 测试集。代码应该是这样:
from sklearn.decomposition import PCA from sklearn.preprocessing import StandardScaler scaler = StandardScaler() scaler.fit(X_train) X_train_scaled = scaler.transform(X_train) X_test_scaled = scaler.transform(X_test) pca = PCA(n_components=50) pca.fit(X_train_scaled) X_train_pca = pca.transform(X_train_scaled) X_test_pca = pca.transform(X_test_scaled)用 sklearn 的 Pipeline 把标准化和 PCA 包起来,然后对 Pipeline 统一 fit 训练集,是防止手误最可靠的方式。特征选择也一样——同样只准看训练集。
7.4 主成分方向的正负号没有业务含义
PCA 的载荷向量(loading)里某个特征的系数是正还是负,数学上没有确定含义。翻转整个方向,方差解释率一点不变,主成分还是那个主成分。但人看到"主成分 1 中年龄的系数是 -0.8",很容易脑补出"年龄越大,这个主成分越低"的业务解释,这是在过度解读。
主成分只适合用来做投影和压缩,不适合逐项解释系数的业务含义。真要谈"哪个特征重要",用特征选择的排序,或者等模型训完做 permutation importance。
7.5 随机性与超参数:降维结果不可复现怎么办
t-SNE、UMAP、自编码器都有随机初始化,不固定随机种子的话,结果每次跑都不一样,这在团队协作里非常致命。第一次跑出一个聚类图,第二次重跑发现簇的形状变了不少,然后三个人对着三个版本讨论业务结论,纯属浪费时间。
所有这类方法都要设置random_state(UMAP 里是random_state,t-SNE 里也是,自编码器则要固定 PyTorch/TensorFlow 的全局种子)。另外,t-SNE 的困惑度、UMAP 的n_neighbors对图的影响非常大,不要默认参数一路跑到底,至少各试几个值对比一下,选一个符合业务直觉的设置。
我自己现在拿到高维数据,第一步永远是标准化之后跑一个 PCA 先看结构,至于后面上不上非线性方法,完全看 PCA 的解释率结果和项目的时间成本说话。降维这件事,本质上就是拿可解释性换信息密度,想清楚这个交换值不值,比背下所有算法公式重要得多。