ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

PCA与MDS降维方法详解:线性投影与距离保持的两条路径

PCA与MDS降维方法详解:线性投影与距离保持的两条路径 做用户画像聚类的时候我拿到过一份上千维的特征表不降维直接跑聚类结果全是一团浆糊后来做了降维再聚类几个类别之间的边界立马就清楚了。从那以后我就养成了习惯任何高维数据到手先花十分钟做一轮降维摸底再决定下一步怎么走。这篇文章就把降维里两条经典路线——MDS降维方法和线性降维方法——放在一起讲透。MDS这个名词在面试题里出现的频率不低但很多人被它唬住以为是什么高深的流形学习。实际上MDS和PCA的关系比想象中近得多在欧氏距离下经典MDS求出来的坐标和PCA投影基本是同一套东西。把这两块打通你对降维的几何直觉会上一个台阶。这篇文章适合刚接触降维、想弄懂原理也想要能直接跑的代码的朋友有经验的读者可以重点看第5节里面那些坑是我实际踩过的。1. 降维到底在解决什么问题两条路线背后的几何直觉1.1 为什么需要降维不是“压缩”而是“看清结构”高维数据的麻烦远不只是“展示不出来”这么简单。想象你拿一把尺子去量30维空间里的点两点之间的欧氏距离会变得非常接近这就是所谓的“维度灾难”。我见过很多人抱怨明明特征做了标准化K近邻的效果仍然很飘其实不是算法的问题是高维空间本身就极度稀疏距离的区分度被稀释了。类似的现象还会导致聚类不稳定、回归过拟合、可视化一片糊。降维要做的事不是简单丢掉几个无关紧要的特征而是把数据从高维空间映射到一个更低维的空间尽量保留原始数据的“结构”——这个结构可能是方差大的方向可能是样本之间的相对远近也可能是类别之间的可分性。你可以把它类比成整理照片几千张高清原图直接看会淹没在细节里但你把它们按“相似程度”摆到一张大白纸上关系和分组一眼就能看出来。降维就是那张大白纸。1.2 两条路线的分岔线性映射与距离保持围绕“保留结构”这个目标降维方法分成了两个互相对应的流派。线性降维的思路是找一组线性投影方向把高维数据直接投影到低维子空间里典型代表就是PCA和LDA。它的输入是一张样本特征矩阵输出是一个投影矩阵和一份低维坐标。线性意味着结果有很强的可解释性——你能够看到每个原始特征在投影方向上的权重新样本过来也能直接套用同一个投影矩阵映射过去这在真实业务落地里非常方便。MDS的出发点完全不同。它不关心特征矩阵只关心样本两两之间的“距离”或者“不相似度”输入是一个 N×N 的距离矩阵输出是一组低维坐标让这组坐标在低维空间里重新算出来的距离尽量去逼近输入的距离矩阵。它甚至可以接受欧氏距离以外的任意度量比如文本里的余弦距离、用户行为里的Jaccard距离这正是它灵活的地方。这两条路线看起来八竿子打不着但几何上有一个漂亮的交叉点当距离矩阵用的是欧氏距离时经典MDS得到的坐标和PCA投影出来的是同一份结果只差一个旋转或镜像。理解了这句话你就把线性降维和MDS之间的墙拆掉了。2. 线性降维方法核心拆解PCA、LDA与SVD的关系2.1 PCA最大方差与最小重构误差PCA大概是所有降维方法里最常用、也最值得先吃透的一个。它的目标一句话概括寻找一组正交方向让数据投影到这些方向上的方差尽可能大。为什么要最大化方差因为方差大的方向承载了样本之间最多的差异信息反过来说数据在某个方向上几乎不变那这个方向上的信息就可以丢掉。这个逻辑跟“最小化重构误差”是等价的——最大化投影方差等价于让数据在低维空间里重构回原空间时误差最小。计算步骤非常清晰对数据矩阵做中心化让每个特征的均值为0。计算协方差矩阵。对协方差矩阵做特征值分解。按特征值大小排序取前 k 个特征值对应的特征向量组成投影矩阵。用中心化后的数据乘上投影矩阵得到低维坐标。用 NumPy 手写一个 PCA 大概这样import numpy as np def pca_fit_transform(X, n_components2): # X: (n_samples, n_features)已经中心化的数据会更好 X_centered X - X.mean(axis0) # 协方差矩阵 cov np.cov(X_centered, rowvarFalse) # 对称矩阵用 eigh 数值更稳定 eig_vals, eig_vecs np.linalg.eigh(cov) # 特征值从大到小排列取前 n_components 个 idx np.argsort(eig_vals)[::-1][:n_components] W eig_vecs[:, idx] # 投影后的低维坐标 Z X_centered W return Z, eig_vals[idx]这段代码里我特意用了np.linalg.eigh而不是np.linalg.eig因为协方差矩阵是对称矩阵eigh专门针对对称矩阵做了优化数值上更稳。实际工程中更推荐对中心化后的数据矩阵直接做 SVD而不是先算协方差矩阵因为协方差矩阵会放大数值误差SVD 一步到位还能很方便地处理大规模数据U, S, Vt np.linalg.svd(X_centered, full_matricesFalse) # Vt 的行就是主方向 W Vt[:n_components].T Z X_centered WSVD 和特征分解在这个场景下解的是同一个问题X^T X 的特征向量就是 V^T 的行主成分得分就是 X V。区别只是数值路径不同。PCA 里还有一个容易被忽视的点解释方差比例。每个主成分的特征值除以所有特征值之和就是这个主成分“解释了”多少信息。我在实际项目里通常会先看一眼前两个主成分累计解释了多少方差如果连 50% 都不到说明数据噪声很大或者降维本身解决不了当前问题得换思路。2.2 LDA给线性降维加上“标签定向”PCA 是无监督的它不管样本属于哪一类只找方差最大的方向。这在很多场景下够用但如果你手里有标签而且降维的最终目的是分类PCA 不一定能帮你找到最有利于判别的方向。这时候该上 LDA。LDA 的目标是找一个投影方向让类间离散度尽可能大、类内离散度尽可能小。翻译成人话就是不同类别的点投影之后尽量分开同一个类别的点投影之后尽量聚拢。数学上定义两个散度矩阵类内散度矩阵 S_w衡量每个类内部样本离自己类中心的远近。类间散度矩阵 S_b衡量各类中心离全局中心的远近加上类别样本数的加权。LDA 要求解的是广义特征分解问题S_b w λ S_w w取前 k 个最大广义特征值对应的方向作为投影矩阵。这里有个硬性约束很多人容易踩坑LDA 最多能降到的维度不超过“类别数减一”。二分类最多降到 1 维三分类最多降到 2 维。如果你想保留更多维度LDA 帮不了你得回到 PCA。举一个直观的例子一批样本是“正常”和“异常”两类原始特征有50维。直接跑PCA前两维可能主要反映的是数据整体的分布形状正常和异常还是重叠在一起但用LDA投影到1维重点变成了“怎么把两类分得最开”分类器在这个1维特征上的表现通常会明显好于直接用前两维PCA特征。这也是LDA在特征工程里经常作为分类前一步的原因。2.3 线性族的选型PCA、LDA、SVD与因子分析很多新手搞不清楚 PCA、SVD、因子分析这几个东西有什么区别其实它们的关系是“兄弟工具”的关系。SVD 本身不是一种降维方法它是矩阵分解的数值工具但恰好是 PCA 最稳定的实现方式。你可以把 PCA 理解成目标SVD 理解成达成目标的引擎。除了 PCASVD 在推荐系统、文本主题分析里也被广泛使用只是目标函数完全不同。因子分析则是另一个思路它假设观测变量是由少数几个不可观测的“潜在因子”驱动的比如六门考试成绩背后可能有“逻辑能力”和“记忆力”两个潜在因子。因子分析和 PCA 看起来很像但 PCA 找的是“方差最大的方向”因子分析找的是“能解释变量之间相关结构的潜在因子”。如果你的目的是做可解释性、找出隐藏结构因子分析更合适如果只是压缩维度PCA 更快更直接。方法是否使用标签输出内容核心目标典型场景PCA否投影方向 低维坐标最大化方差无标签数据压缩、可视化去相关LDA是判别方向 低维坐标最大化类间/类内距离比分类前的特征提取经典MDS否低维坐标保持样本间距离距离矩阵可视化、相似度分析非度量MDS否低维坐标保持样本间距离的排序用户调研、偏好感知图选型的时候我一般先问自己三个问题有没有标签要不要对新样本做映射距离的定义是什么。有标签就优先试 LDA没标签、只想快速看结构就用 PCA如果业务方给的就是相似度矩阵或者你要用余弦距离这类非欧度量PCA 就不适用了这时候 MDS 才是正解。3. MDS降维方法核心拆解从距离矩阵到低维坐标3.1 经典MDS的数学骨架二次距离、双中心化与特征分解MDS 的输入是一张距离矩阵 DD_ij 表示样本 i 和样本 j 之间的距离。它不关心样本在原始空间长什么样只根据距离推算出“如果把这些距离保持住那么每个样本在低维空间里应该放在哪个位置”。经典 MDS 的数学推导有几个关键步骤理解了这几个步骤你就能把 MDS 看穿。假设低维坐标是 YN×k我们希望低维空间中样本间的欧氏距离平方尽量等于 D 的平方。也就是说||y_i - y_j||² ≈ D_ij²展开左边||y_i||² ||y_j||² - 2 y_i^T y_j。如果直接解这个方程比较麻烦因为二次项和交叉项混在一起。聪明的地方在于构造一个“双中心化”操作把它线性化。定义中心化矩阵 J I - (1/N) 11^T它的作用是把矩阵的每一列都减去列均值。然后构造B -1/2 J D² J其中 D² 是逐元素平方的距离矩阵。可以证明如果距离矩阵 D 来自中心化后的欧氏空间那么 B 正好等于低维坐标的 Gram 矩阵 Y Y^T。换句话说B 和 Y Y^T 在数学上是同一个东西。于是求 Y 的问题就变成了对 B 做特征分解取前 k 个最大的特征值和对应的特征向量然后开方调整尺度。代码实现很直接import numpy as np def classic_mds(D, n_components2): n D.shape[0] # 距离平方 D2 D ** 2 # 双中心化 J np.eye(n) - np.ones((n, n)) / n B -0.5 * J D2 J # 对称矩阵特征分解 eig_vals, eig_vecs np.linalg.eigh(B) # 从大到小排序取前 n_components 个 idx np.argsort(eig_vals)[::-1][:n_components] eig_vals eig_vals[idx] eig_vecs eig_vecs[:, idx] # 特征向量是单位正交的需要乘上特征值的平方根来“伸展”坐标 Y eig_vecs * np.sqrt(eig_vals) return Y, eig_vals注意最后一步特征值分解得到的特征向量是单位向量它的长度是1但 Gram 矩阵的对角线包含了坐标的平方范数所以必须让每个特征向量乘以对应特征值的平方根坐标尺度才是对的。这一步漏了你得到的图会被压扁或者拉伸变形。为什么这个方法和 PCA 等价一句话解释如果原始数据 X 已经中心化那么样本间的欧氏距离平方 D² 可以写成 D² diag(G)1^T 1diag(G)^T - 2G其中 G X X^T 是 Gram 矩阵。对它做双中心化之后B 恰好等于 X X^T。而 PCA 是在对 X^T X 做特征分解。一个是对样本间的 Gram 矩阵分解一个是对特征间的协方差矩阵分解它们共享同一个奇异值结构。所以最后得到的坐标本质上是同一个低维嵌入只是可能有左右方向的翻转。3.2 度量MDS与非度量MDS要“数值”还是要“次序”经典 MDS 要求距离矩阵满足欧氏空间的度量约束而且它试图让低维空间的距离在数值上逼近原始距离这叫“度量 MDS”。但真实业务里很多“相似度”数据根本不满足欧氏度量比如用户对品牌偏好的打分差异、消费者对不同产品的感知相似度。这时候强行用数值逼近不一定有意义排序关系反而更重要。非度量 MDS 的思路就变成了只要低维空间里样本的相对远近顺序和原始距离矩阵的排序一致就行。比如原始距离 A-B 是 1、B-C 是 2、A-C 是 100非度量 MDS 不要求低维空间里 1 和 100 的绝对比例被还原只要求 A-C 最远、A-B 最近。这种放松让它在处理感觉打分、排序偏好这类数据时特别稳定。数学上它通过单调回归来拟合“距离的排序”迭代优化一个叫“应力”stress的目标函数。实际使用中如果验证集上的经典 MDS 出现大量负特征值或者得到的图交叉得很奇怪我就会切到非度量 MDS 试试往往能得到更符合直觉的相对位置关系。3.3 MDS的实操要点距离度量、样本外映射与大矩阵优化MDS 最大的卖点是距离度量可换但这也是最大的坑。同样一份数据你用欧氏距离、余弦距离、曼哈顿距离分别跑 MDS出来的图完全不一样。这不是代码问题而是你在提取数据的不同侧面。我在处理文本数据时喜欢用余弦距离处理数值型用户画像时用欧氏距离处理共现关系时用 Jaccard 距离。动手之前先问清楚你要刻画的“相似”到底是什么。第二个坑是样本外映射。PCA 训练完留下一个投影矩阵新样本直接乘上去就行。但经典 MDS 做的是特征分解没有一个显式的 f(x) 可以套用。新样本来了你得重新对整个距离矩阵做一次 MDS这在生产环境里几乎不可接受。常见的解决办法是 Landmark MDS先从全部样本里挑一小撮“地标点”对地标点做经典 MDS之后的地标坐标固定住任何一个新样本只要算出它到地标点的距离就能通过最小二乘反推出它的低维坐标。这个思路在精度损失不大的情况下把“部署”问题解决了。第三个坑是计算复杂度。距离矩阵是 N×N 的N 到一万矩阵就是 800MB特征分解的时间也很可观N 到十万基本就别想直接 MDS 了。这时候要么抽样要么上 Landmark 方案要么干脆退回到 PCA。我一直觉得降维方法之间不是互相取代的关系而是根据数据规模和业务约束选最合适的那一个。4. 实操过程手写MDS与PCA并对比效果4.1 模拟数据与手写PCA为了把原理和代码对应起来我构造一个模拟数据集三类样本原始特征 20 维其中前两维决定了类别的分离结构后 18 维全是噪声。用这样的数据能清楚看到降维到底保留了哪些信息。import numpy as np from sklearn.datasets import make_blobs X, y make_blobs(n_samples300, centers3, n_features20, random_state42) # 把后18维替换成纯噪声模拟“有效维度少、噪声维度多”的真实场景 rng np.random.default_rng(42) X[:, 2:] rng.normal(sizeX[:, 2:].shape) Z_pca, evals pca_fit_transform(X, n_components2)跑完之后看解释方差前两维的方差占比通常会很低因为 18 维噪声把总方差摊薄了。但这个低比值不代表降维失败反而说明数据里大部分维度是噪声真正决定类别的结构被保留在前两维里。可视化一下三类样本在二维平面里分得清清楚楚这就达到了“看清结构”的目的。4.2 手写经典MDS与PCA碰一碰接下来用我上面写的classic_mds处理同样的数据输入直接用欧氏距离矩阵from scipy.spatial.distance import pdist, squareform D squareform(pdist(X, metriceuclidean)) Z_mds, mds_evals classic_mds(D, n_components2)把Z_pca和Z_mds放到同一个坐标系里对比你会发现两幅散点图长得几乎一样——类别结构、相对密集区域、离群点全都对得上。唯一可能不同的是某个轴的整体方向反了比如 PCA 的第一个轴指向右上MDS 的第一个轴指向左下。这非常正常因为特征分解出来的特征向量方向本身就带任意性符号不改变样本之间的相对距离和聚类结构。这就是我开头说的那件事在欧氏距离下经典 MDS 和 PCA 是同一个解的两种表达。很多人在面试里被问到两者的关系时背一句“它们等价”就完事其实如果当场能指出“经典MDS是对双中心化距离矩阵做特征分解PCA是对协方差矩阵做特征分解两者共享同样的奇异向量结构”面试官立刻就会知道你是真动手调过代码的。4.3 非欧距离下的MDS换一种度量立刻不同同样的数据集我把距离度量换成余弦距离结果就不一样了。from sklearn.metrics.pairwise import cosine_similarity # 余弦距离 1 - 余弦相似度需要保证矩阵对角线为0 sim cosine_similarity(X) D_cos 1 - sim np.fill_diagonal(D_cos, 0) Z_mds_cos, _ classic_mds(D_cos, n_components2)这一步有一个很容易踩的细节直接用1 - cosine_similarity得到的结果对角线是 1 而不是 0必须先手动把对角线置零。否则距离矩阵对角线上多了 1双中心化之后会把原来干净的 Gram 矩阵结构污染掉得到的低维坐标会整体偏斜。跑出来的坐标和 PCA 的投影有明显差异因为余弦距离只看方向不看大小它对样本向量的“长度”免疫。这在处理文本 TF-IDF 特征时特别有用——文档长度差异会导致欧氏距离被长文档主导但余弦距离只看内容方向的相似度。4.4 sklearn里怎么用MDS参数与陷阱如果不想手写直接用sklearn.manifold.MDS也可以但它的实现和我手写的经典 MDS 有一点重要区别sklearn 用的是 SMACOF 迭代优化不是直接做特征分解。默认情况下它会随机初始化多次挑应力最小的解。from sklearn.manifold import MDS mds MDS(n_components2, dissimilarityprecomputed, metricTrue, n_init4, max_iter300, random_state42) Z_sk mds.fit_transform(D)几个参数的实际含义dissimilarityprecomputed表示我传入的是距离矩阵如果设为euclidean它会内部先算欧氏距离。metricTrue是度量 MDSmetricFalse是非度量 MDS。n_init4是随机初始化次数越大越容易跳出局部最优但时间成倍增加。max_iter300是最大迭代轮数数据量大时可以适当降低但别低于50。手写特征分解得到的坐标是解析解一次算出sklearn 得到的坐标是数值优化解多次迭代逼近。两者最终结果基本一致但如果你要追求稳定复现、或者要做大规模数据的精确计算手写特征分解其实更直接。我个人的习惯是探索阶段用 sklearn 快速出图正式做分析时自己写因为自己写的东西每一行都知道在干什么。5. 常见问题与排查技巧实录5.1 负特征值扎堆距离矩阵可能是“假欧氏”运行经典 MDS 时B矩阵的特征值里出现负数是很常见的。少量负特征值多数来自数据噪声问题不大但如果负特征值数量很多、绝对值还特别大基本上可以断定你输入距离矩阵并不满足欧氏度量所谓“距离”可能只是视觉相似度之类的主观打分。遇到这种情况我的排查顺序是先按特征值从大到小排列看有几个正特征值明显比其余的大。如果正特征值本身就很少而且前 k 个累计占比很低那就不要死磕经典 MDS 了切到非度量 MDS 试试。另一个老办法是给距离加一个常数把它“撑”成欧氏距离但这个方法现在用得少了因为它会改变样本间的相对比例。5.2 坐标镜像翻转特征向量方向的符号问题我第一次拿 MDS 结果和别人对图的时候差点以为是代码写错了——明明用同一个数据集两边的散点图形状一模一样但 x 轴完全反了。后来我才反应过来特征分解给出的特征向量方向本来就是任意的v 和 -v 都是同一个特征向量。这不只是一个 学术上的小细节在实际协作里很麻烦。如果你们团队里有人用 sklearn有人用 R 的 cmdscale两边生成的图很可能左右翻转或上下翻转。解决办法是统一固定random_state同时在展示前做一个 Procrustes 对齐——把两组坐标通过旋转、缩放、平移对齐到同一个参考坐标系里。这不是算法的错不要在这种问题上浪费太多时间。5.3 大样本算不动内存与计算的取舍距离矩阵的规模是 O(N²)一万样本就是一亿个浮点数约 800MB十万样本直接 80GB内存爆掉是分分钟的事。我在实际工作中超过两万样本就不太敢直接怼经典 MDS 了。几个可行的替代方案随机抽样做一次 MDS看整体形状然后再用其他方法验证细节。用 Landmark MDS选几百个地标点先嵌入再映射其余样本。如果只是可视化用 t-SNE 或 UMAP 这类更现代的流形方法它们对大数据量更友好。如果业务要求可解释、可部署直接用 PCA别折腾 MDS。5.4 坐标轴没有业务含义MDS落地解释的四种做法MDS 的坐标轴不像 PCA 那样能直接说成“维度1代表某个特征方向”它只是一个相对位置这让它在跨部门沟通时特别吃亏。我在给业务方解释 MDS 结果时一般会做四件事给每个样本按业务标签着色让大家先看出“哪些样本聚在一起”。在关键的点附近标注原始特征的典型值反推坐标位置背后的含义。不用坐标轴的意义去解释只讲距离的含义“这两个点离得近代表它们的行为更像”。如果非得给坐标轴一个解释就用特征相关性去拟合看哪个原始特征与这个轴的相关性最高。现象可能原因处理方式负特征值占比高距离矩阵不满足欧氏度量改用非度量MDS或调整距离定义两次运行结果轴方向相反特征向量方向符号不确定固定随机种子或做Procrustes对齐样本一多内存就不够距离矩阵O(N²)随机抽样、Landmark MDS、换PCA低维图上所有点挤成一团原始数据本身区分度过低检查特征质量考虑换距离度量新样本无法映射到旧坐标MDS没有显式投影函数预埋地标点用最小二乘反推坐标实际做数据分析这一年多我最大的体会是降维方法不要分开学要把它们当成一整个工具箱。PCA 能快速摸清全局方差结构LDA 能在有标签时帮你找到判别方向MDS 能处理各种奇怪的相似度矩阵。先跑一遍 PCA 看解释方差再根据业务场景决定要不要换 MDS 或 LDA成本很低收益却很直观。最后再分享一个实用小技巧无论你最后选了哪种降维方法输出坐标之后都记得把坐标均值归零、方差归一。这样 Exec 汇报时不同方案的结果可以直接叠图对比不会因为尺度不一致而误判聚类效果。这一点看着不起眼但能让你的图比大多数人做得专业一截。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表