PCA与LDA降维全解析:无监督与有监督的机器学习特征压缩
很多搞机器学习的朋友第一次听到 LDA 和 PCA多半和我一样是从降维这个词开始的。我当时打一场数据竞赛特征工程一路做到三百多列逻辑回归、随机森林、XGBoost 轮着试验证集分数就是卡在一个位置不动。后来一个前辈提醒我你先看看这些特征的共线性用 PCA 把结构拉出来。我照做之后才反应过来问题根本不在模型而是一堆特征在重复表达同一件事噪声比信号还大。从那以后PCA 和 LDA 就成了我处理高维数据时的默认起点也是我给新人讲机器学习时一定会放在一起讲的两个名字。PCA 和 LDA 做的都是把高维数据压缩到低维这件事但借用的信息完全不同。先说结论再讲原因PCA 是无监督算法只关心数据本身的方差结构根本不看你手里有没有类别标签LDA 是有监督算法它要拿标签去计算怎么投影才能让类别分得最开。这一句话的差别会直接影响你的预处理流程、调参方向、甚至最终模型的准确率。我不会跟你堆一堆公式但会把这两个算法的直觉、数学核心、Python 实操、适用边界一次讲清楚。这篇文章适合正在学机器学习的入门者也适合那些已经把 PCA 当必需步骤、但没搞清楚它到底在做什么的工程同学。1. 先别急着算公式两个算法处理的问题完全不同1.1 降维到底在降什么先举一个特别好懂的例子。假设你要评价一栋房子手上有 20 个指标面积、卧室数、卫生间数、装修年份、距地铁站距离……仔细想想面积和卧室数之间是不是高度相关通常面积大的房子卧室也多。这说明信息有冗余20 个指标并不是 20 个独立的信息源可能 5 个左右就能覆盖绝大部分变化。降维做的事情就是把这 20 个原始指标重新组合成少数几个新指标并且让这几个新指标尽量多地保留原始差异。为什么保留差异而不是均值因为均值是每个样本都有的共同点对区分样本没有帮助差异才是我们用来做预测、聚类的依据。统计学里管这种差异叫方差PCA 的核心就是在找投影后方差最大的方向。这里有个经典的几何直觉一堆三维点如果它们扁扁地分布在一个平面上你完全可以把它们压到二维而不损失多少信息。PCA 做的就是找到这个最扁平的平面——或者说找到数据变化最剧烈的那些方向把数据投过去。1.2 PCA 的视角方差最大信息最多PCA 不关心样本属于哪一类。它像一台只看数据不看答案的机器把每个维度上的方差摊开找出一组互相垂直的新坐标轴主成分。第一个主成分捕捉最大的方差第二个主成分在与第一个垂直的约束下捕捉剩余的最大方差依此类推。你可以把 PCA 理解成把一坨数据掰直了看。数据形状像一根黄瓜PCA 找的就是从黄瓜这头到那头的主轴像一片被压扁的面饼PCA 找到的长轴和短轴就是两个主成分。它完全不关心黄瓜上贴的标签是 A 还是 B。正因为这种不看标签的特性PCA 在无监督学习、特征压缩、可视化、去相关这些场景里特别顺手。后面要讲的特征脸就是 PCA 在人脸图像上的典型应用。1.3 LDA 的视角类间远类内近LDA 的全称是 Linear Discriminant Analysis线性判别分析它需要训练集的类别标签。它要解决的原始问题其实不是降维而是分类给定两类样本找一条直线把它们分开。把问题翻译成优化目标LDA 希望投影之后满足两个条件不同类别的样本离得越远越好同一类别的样本聚得越紧越好。前者叫类间距离大后者叫类内方差小。当两者同时满足时类别边界就清晰分类器就简单可靠。举个例子两类人群的身高和体重数据。如果两类体重差不多、身高有区分LDA 找到的投影方向就会偏向身高轴如果两类身高差不多、体重差异明显方向就偏向体重轴。它是在给你的数据选择最带判别性的角度。1.4 为什么说 Fisher 判别和 LDA 是同一个东西你翻老教材还会看到 Fisher 判别分析这个名字。1936 年 Fisher 提出线性判别分析时本意是找一个投影方向让投影后的数据最可分。后来统计学家发现这个方向和 LDA 的最优解是同一个东西。所以 LDA 也叫 Fisher 判别分析只是后来的研究者把判别函数换了个角度看——从找分类面变成了找投影方向。理解这层关系对读论文特别有用。你看到 Fisher Linear DiscriminantFLD时别以为是个新算法它和 LDA 的数学核心完全一致只是强调的角度和推导方式略有不同。工程上我们直接用 scikit-learn 里的 LinearDiscriminantAnalysis 就行。2. 数学核心拆解不背公式但要知道它在算什么2.1 PCA 在解一个特征值问题PCA 的数学过程本质上是对协方差矩阵做特征分解。流程不复杂对每个特征去均值。计算特征的协方差矩阵。对协方差矩阵做特征分解得到特征值和特征向量。把特征值从大到小排序取前 k 个特征向量组成投影矩阵。原始数据乘以投影矩阵得到降维后的数据。特征值的大小对应着这个方向上的方差大小。你如果把所有特征值加起来会发现前 k 个特征值占总和的比例就是这 k 个主成分保留的信息比例——scikit-learn 里可以通过explained_variance_ratio_直接看到这个比例。用更直白的话说PCA 不是挑出几个原始特征而是自己发明了几个新的综合特征。每个主成分是所有原始特征的线性组合所以它没有原始特征的语义。你很难说主成分 1 等于 0.3 倍身高加 0.2 倍体重这是 PCA 可解释性差的原因也是它在很多业务报表里被嫌弃的原因。2.2 LDA 在最大化一个比值LDA 的数学核心可以理解为最大化一个比值类间散度 / 类内散度。分子描述不同类别的中心相距多远分母描述每个类内部的样本有多散。比值越大说明投影后类别越容易分开。LDA 的求解要用到两个矩阵类内散度矩阵 S_w 和类间散度矩阵 S_b。最优投影方向就是 S_w 的逆乘上 S_b 之后的最大特征值对应的特征向量。所以你会发现 LDA 的求解里也有特征分解只不过分解的对象从 PCA 的协方差矩阵变成了一个和类别标签有关的矩阵。这里有个必须注意的点LDA 依赖类内散度矩阵可逆这个条件。如果特征数太多、样本太少S_w 可能是奇异的、不可逆的LDA 直接算就会报错。后面我会专门讲这个坑怎么绕。2.3 为什么 LDA 最多只能得到 C-1 个维度这是 LDA 最容易被忽略的性质。假设你的数据有 C 个类别LDA 能得到的投影维度最多是 C-1不管原始特征有多少个。比如三分类数据集你用 LDA 最多只能降到 2 维。原因在于类间散度矩阵的秩上限是 C-1它撑不起更多的主方向。这个性质在实际使用中很关键。如果只有两类数据LDA 降维后只有 1 个维度你拿去做可视化就只能画一维散点图或者直方图不能像 PCA 那样自由地选 2 维。很多初学者在二分类数据上拿 LDA 当万能降维工具结果发现它只能给出一维然后开始怀疑是自己代码写错了——其实不是是数学上它就只能输出这么多。3. Python 实操把 PCA 和 LDA 跑通并看清差别3.1 第一步标准化不做一定翻车不管 PCA 还是 LDA对数值型特征做的第一步都应该是标准化StandardScaler。原因很简单PCA 找的是方差最大的方向如果身高单位是厘米、体重单位是千克身高的方差天然就比体重大几个数量级PCA 会被量纲牵着走LDA 的类内散度也一样受量纲影响。所以代码开头永远是先 StandardScaler再进 PCA / LDA。这一点看起来像常识但我见过很多人在真实项目里因为偷懒省掉这一步结果降维后的图乱成一团还反过来怀疑算法有问题。3.2 用 scikit-learn 实现 PCA我用经典的鸢尾花数据集演示。它 4 个特征、3 个类别正好可以用 PCA 降到 2 维看散点图再用 LDA 降到 2 维做对比。import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import load_iris from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA data load_iris() X, y data.data, data.target scaler StandardScaler() X_std scaler.fit_transform(X) pca PCA(n_components2) X_pca pca.fit_transform(X_std) print(PCA 解释方差比例:, pca.explained_variance_ratio_) print(累计解释方差:, pca.explained_variance_ratio_.sum())运行后你会看到两个主成分的解释方差比例如 0.72 和 0.23加起来 0.95 左右也就是说 4 维数据降到 2 维保留了大约 95% 的方差信息。这个数字可以帮助你判断降维是否值得。3.3 用 scikit-learn 实现 LDALDA 在拟合时多传一个 y因为它需要类别信息lda LDA(n_components2) X_lda lda.fit_transform(X_std, y)这里必须注意LDA 的 n_components 不能超过类别数减 1。你如果对三分类数据设置 n_components3scikit-learn 会直接抛异常。如果只是想用它做分类而不是可视化可以不指定 n_components它会返回一个分类器。对比两个投影结果一种非常常见的现象是PCA 投影之后的散点图里三朵云之间可能还有交叠LDA 投影之后三朵云往往被推得更开。这不是玄学而是 LDA 的优化目标里就含着类别中心之间距离最大。3.4 可视化对比同一份数据两种投影下面这段代码把两个结果并排画出来fig, axes plt.subplots(1, 2, figsize(12, 5)) for ax, X_proj, title in [ (axes[0], X_pca, PCA 投影), (axes[1], X_lda, LDA 投影), ]: for label in np.unique(y): ax.scatter( X_proj[y label, 0], X_proj[y label, 1], labeldata.target_names[label], alpha0.7, ) ax.set_title(title) ax.legend() plt.tight_layout() plt.show()看这张图的时候重点不要放在哪个更好看上而是放在两个细节上第一PCA 的坐标轴方向和数据整体分布相关和类别无关所以它可能把类别之间的差异部分保留、部分压扁LDA 的方向则会刻意拉开类别中心。第二如果数据线性可分LDA 投影后的类别边界会非常干净这对下一步接一个简单的 KNN 或逻辑回归特别有帮助。4. 决策边界与适用场景什么时候用哪个4.1 一张表说清 PCA 和 LDA 的差异我把两者最关键的差异整理成一张表方便你随时查阅。维度PCALDA监督信息无监督不需要标签有监督必须提供标签优化目标最大化投影后方差最大化类间距离与类内紧凑度的比值得到维度上限不超过 min(样本数, 特征数)不超过 类别数 - 1主要用途降维、可视化、去相关、压缩、特征脸分类、判别分析、有监督降维对标签的依赖不依赖适合探索性分析强依赖标签质量直接影响结果类内结构不关心类别是否可分主动把同类别聚紧、异类别推远受量纲影响明显必须标准化明显必须标准化这张表我建议你存下来。很多面试题都会问PCA 和 LDA 有什么区别你不需要背条文记住一个无监督、一个有监督、一个看方差、一个看类别就够了。4.2 分类任务里先降维再分类的实验为了说明两个算法对下游分类任务的影响我在鸢尾花数据上做了一个简单对比用逻辑回归做分类器分别比较不降维、PCA 降维到 2 维、LDA 降维到 2 维的效果。核心代码大致如下from sklearn.model_selection import cross_val_score from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline clf LogisticRegression(max_iter1000) pipeline_pca Pipeline([(scaler, StandardScaler()), (pca, PCA(n_components2)), (clf, clf)]) pipeline_lda Pipeline([(scaler, StandardScaler()), (lda, LDA(n_components2)), (clf, clf)]) pipeline_raw Pipeline([(scaler, StandardScaler()), (clf, clf)]) for name, pipe in [(原始, pipeline_raw), (PCA, pipeline_pca), (LDA, pipeline_lda)]: scores cross_val_score(pipe, X, y, cv5) print(f{name}: {scores.mean():.4f} ± {scores.std():.4f})在小样本、特征不复杂的数据集上三种方式通常都能得到接近 0.95 以上的准确率差别不大。但如果你把特征数扩展到几十上百个PCA 和 LDA 的降维会明显减少过拟合训练速度也会快很多。真实项目里数据量大、噪声多降维不只是为了画图而是为了让模型把注意力放在信息最密集的方向上。4.3 PCA 特征脸为什么人脸识别要用它热词里提到pca 特征脸这里详细讲一下。人脸图像通常由几千甚至几万个像素构成每个像素是一个特征如果直接把像素喂给分类器特征维度太高计算量和过拟合风险都受不了。PCA 的道理是把大量人脸图像看成高维空间中的点找一组主要变化方向——也就是特征脸Eigenface。你可以把人脸库想象成一个超大集合的点PCA 找到的前几个主成分实际上对应着人脸的整体明暗、五官位置等结构性差异。这些主成分张成的子空间就是所谓的脸空间。新来一张人脸可以投影到这个脸空间里得到一组系数再拿这些系数比相似度。ATT 人脸库上的经典实验只用几十个特征脸就能取得相当好的识别率。当然现代深度学习时代人脸识别大多用卷积神经网络了特征脸属于经典知识的范畴。但我依然建议每个人都动手实现一次特征脸因为它是理解高维数据怎么压缩、信息怎么保留的最直观案例。4.4 LDA 的一个硬限制样本数要大于特征数LDA 求解时需要求类内散度矩阵的逆如果样本数小于特征数类内散度矩阵就不是满秩的算法会退化或者报错。这在图像类的数据上特别常见图片有几千个像素样本只有几百张直接用 LDA 往往得到不稳定的结果甚至直接失败。绕开这个限制的方法主要有三种。第一种是先用 PCA 降维再做 LDA这也是先 PCA 后 LDA这种经典组合的由来第二种是给类内散度矩阵加上一个小的正则项scikit-learn 里的 solver 参数有 shrinkage 相关选项可以缓解第三种是改用更稳的求解方式比如 SVD 分解而不是直接求逆。在 scikit-learn 中LDA 默认用 svd solver很多情况下已经能处理奇异问题但理解背后的限制仍然很有用。5. 我踩过的坑和给新手的几条建议5.1 先标准化再降维顺序不能反这是我反复强调却仍然常被忽略的一条。量纲会影响 PCA 的方差排序也会影响 LDA 的散度计算。如果你手里的特征有年龄岁和收入万元不标准化的 PCA 几乎会把全部权重压在收入上因为收入的方差远大于年龄。标准化之后每个特征才有平等的发言权。实操建议把 StandardScaler 和 PCA/LDA 一起塞进 Pipeline而不是分开写好再手动调用。这样做的好处是交叉验证时每个折都能拿到独立的标准化参数不会发生数据泄露。5.2 fit 和 transform 必须分开PCA 和 LDA 拟合之后得到的是投影矩阵这个矩阵只能在训练集上学一次不能再拿测试集拟合一次。正确做法是用训练集 fit_transform用测试集只 transform。scaler StandardScaler().fit(X_train) X_train_std scaler.transform(X_train) X_test_std scaler.transform(X_test) pca PCA(n_components2).fit(X_train_std) X_train_pca pca.transform(X_train_std) X_test_pca pca.transform(X_test_std)如果你把 X_test 也拿去 fit 一次得到的不再是同一个投影空间测试结果一点参考价值都没有。这个问题在工业里叫数据泄露看着很小但影响非常大。用 Pipeline 能有效避免这种低级错误。5.3 降维不是万能的什么时候该放弃PCA 和 LDA 都是线性方法。如果数据本身是非线性流形比如环状分布、螺旋结构强行线性降维会把结构压碎。此时你需要考虑的是 t-SNE、UMAP 这类非线性方法或者直接用原始特征学习。另一个场景是特征语义本身很重要。PCA 生成的新特征是原始特征的线性组合无法解释为某个具体业务变量。在风控、医疗这类需要跟业务方解释模型的领域降维后的特征很难写进报告这时候反而应该考虑特征选择而不是特征提取。我的原则是能解释用选择只求性能用提取。5.4 一个值得尝试的组合PCA 之后再接 LDA如果类别数大于 2 但特征极多LDA 又受限于类内散度可逆性一个很经典的做法是先 PCA 降维到合适的维度再 LDA 做最终判别投影。这一步我在手写数字识别这种数据上试过很多次效果比单独用 PCA 或单独用 LDA 都好因为 PCA 去掉了原始空间的病态性LDA 则把类别信息补了回来。这里要提醒的是PCA 的中间维度选多少需要实验。选太少可能把关键的判别信息也丢掉了选太多LDA 的稳定性又不够。建议直接做一个网格搜索比如n_components从 10 到 100配合交叉验证选最优。6. 写在最后两个算法都不用怕但要理解它们的性格其实一开始我只把 PCA 当压特征的按钮按下去了事。真正开始搞懂它是有一天我对着某张图调参发现不管怎么调投影结果都像是把所有点按某一根斜线拍扁我突然意识到 PCA 不是随便选方向而是在解一个哪个方向最值得保留的优化问题。LDA 也一样它每一次投影都在回答按哪个角度来看类别最分得开。如果你把这两个算法理解成两个性格不同的人PCA 像是个只关注数据分布形状的观察者LDA 像是个时刻惦记着分类目标的判官。实战中先用 PCA 摸清数据的大致结构再用 LDA 或者别的模型去做判别往往能少走很多弯路。拿着这篇文章里的例子跑一遍再挑一个自己的数据集做一次对比应该比看十遍公式都管用。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →