2026/10/18 0:09:47

LDA线性判别分析:用随机数据把二维降成一维的可视化分类实战

LDA线性判别分析:用随机数据把二维降成一维的可视化分类实战 1. 一个二维数据凭什么值得压成一维二维数据为什么要降成一维最直接的理由是可视化。人眼对二维散点图还能勉强看出聚集关系但三维以上就得靠旋转、着色、降维映射帮着猜。而把二维压到一维等于把散点“拍”到一条直线上每个点在这条直线上的坐标就是降维后的数值看一张一维分布图就能判断两个类别分不分开。这个场景特别适合用来验证线性判别分析LDA到底在干什么。LDA 的核心不是普通降维而是“带着分类任务去降维”。它找一个投影方向让两类样本在投影后尽可能离得远同时每一类内部又尽可能聚得拢。这样一来降维后的每个样本既保留了足够的判别信息又可以直接用于画图、分类、构建特征甚至用一维阈值做决策树或逻辑回归的输入。特别适合用来理解为什么有的投影方向能把两类分开而另一些方向会把两类揉成一团。我建议想入门机器学习、或者正在做特征工程的开发者都可以拿“随机生成数”来做这个实验。随机数据的好处是可控、干净、能复现不会出现真实数据里各种脏乱差带来的干扰。你用正态分布生成两类点分别贴好标签然后实现 LDA 从二维到一维的完整流程你会发现代码量不大但每个步骤背后的几何直觉非常清晰。1.1 为什么拿随机数据先跑一遍真实数据集往往伴随着量纲不一致、缺失值、离群点、标签噪声这些都会让算法调试变得复杂。用随机生成的数据第一优势是真值已知你知道这些点是按哪两个均值、哪两个协方差矩阵生成的所以投影方向、分类准确率都有“标准答案”可以做交叉验证。第二优势是可复现性只要固定随机种子每次跑的类内散度矩阵、类间散度矩阵完全一致方便排查问题。我在实际项目中见过不少团队直接拿生产数据调降维参数结果发现投影方向总是被某些特征的大数值主导最后花了很长时间才意识到是数据没标准化。换成随机数据之后问题本质立刻暴露出来——LDA 用的是散度矩阵散度跟量纲是绑定的。先用随机数据把算法逻辑跑通再迁移到真实数据这种“先易后难”的习惯很值得养成。1.2 LDA 和 PCA 到底哪里不一样很多人一提到“降维”就条件反射想到 PCA但 PCA 和 LDA 做的事情有本质区别。PCA 是无监督方法它只关心所有样本的方差最大方向完全忽略样本标签。LDA 是有监督方法它必须用到标签信息目标是“每一类的均值尽量分开每一类内部的散度尽量小”。我用一个生活类比来说明假设你在学校操场上要找一个方向把操场上的同学投到墙上影子。PCA 找的方向是影子拉得最长、方差最大不管学生是哪个班的LDA 找的方向是各班影子重心离得最远同时每个班的影子内部不要挤得太散。判断谁是谁当然用 LDA 更合理。下面这个表可以更直观地对比两种方法的差异对比维度PCALDA是否需要标签不需要需要优化目标最大化整体方差最大化类间散度与类内散度之比输出维度上限可以等于原始特征数二分类通常只有 1 个方向多分类最多类别数减 1适用场景压缩存储、无监督可视化分类前特征提取、判别分析对数据分布假设无显式分布假设通常假设各类协方差相近、服从正态分布所以标题里“随机生成数的降维”本质上就是一个有标签的模拟分类问题。你问自己一个问题如果只有一条一维坐标怎么设计才能让两个类最可分这个问题的答案就是 LDA 要找的投影方向。2. 先想明白 LDA 在做什么再碰那些矩阵公式接触过 LDA 的人都会看到“类内散度矩阵”和“类间散度矩阵”这两个名词很多教材直接甩出公式读者容易劝退。我先从几何直觉讲。你要在一个平面上画一条直线然后把所有点投影到这条直线上。投影后每个样本得到一个一维数值。好投影的标准是第一类样本投影后的数值尽量集中在一个小范围内第二类样本投影后也尽量集中但这个范围跟第一类离得尽量远两类投影后中心的距离远大过各自内部的波动幅度。用公式表达就是用两个统计量来刻画这些要求。一个是类间散度衡量两个类中心在投影方向上的间隔另一个是类内散度衡量每个类在投影后内部的离散程度。LDA 最终解就是让“类间散度比类内散度”最大的那个投影向量。2.1 类内散度矩阵与类间散度矩阵到底怎么算假设现在已经有了二维数据每个样本有两个特征分别是x1和x2。类别 1 的样本集合记为X1类别 2 的样本集合记为X2。先算每一类的均值向量m1 mean(X1) m2 mean(X2)这两个均值向量是二维平面上的两个点。类别 1 的类内散度矩阵S1定义为每个样本减去自身类均值、再求各自外积的累加和S1 sum((x - m1) * (x - m1).T)这里x和m1都是二维列向量外积的结果是 2x2 矩阵。同理可以算出S2。把两个类内散度矩阵加起来就得到总的类内散度矩阵SwSw S1 S2类间散度矩阵Sb刻画的是两个类别均值之间的差异Sb (m2 - m1) * (m2 - m1).T注意Sb是两个二维向量的外积所以它的秩最多是 1。这意味着在二分类问题里类间散度只在一个方向上会产生真正的影响LDA 也只解出一个有效投影方向。这个特点在二维降一维时特别明显。2.2 优化目标一个分数叫“瑞利商”假设投影方向用列向量w表示样本点x投影后的一维坐标就是w.T * x。投影后两类均值之间的间隔可以写成w.T * Sb * w类内散度的总和可以写成w.T * Sw * w。LDA 要最大化一个分数J(w)J(w) (w.T * Sb * w) / (w.T * Sw * w)这个形式就是瑞利商Rayleigh Quotient。把方向w同时放大或缩小任意倍数分子分母一起变化分数不变所以真正有用的是方向不是向量的长度。这也是为什么很多实现会直接把解出来的w做归一化处理。求解这个最优化问题对二分类的情况有一个非常简洁的闭式解w Sw^(-1) * (m2 - m1)这个解的意思是先用类内散度矩阵的逆去缩放两个类别均值之间的差异得到最能兼顾“类间拉开、类内压缩”的投影方向。直观理解可以这样看如果某一维度上类内波动特别大Sw的逆会在那个维度上抑制贡献如果某一维度上类内波动很小w就会往那个方向多分一点权重。3. 手算一遍四行数据算出投影方向公式看再多不如手算一遍。我构造一个非常干净的两类二维数据每类 4 个点方便把每一步都列出来。类别 1 的点都在左下角类别 2 的点都在右上角肉眼已经可分但我们要验证 LDA 找到的方向是不是预期的“从左上到右下”那条斜线。类别 1 的样本(1, 2) (2, 3) (2, 4) (3, 3)类别 2 的样本(6, 5) (7, 6) (6, 7) (8, 6)这两个类别在二维平面上分布位置明显不同所以投影方向理论上应该大体指向两个类中心连线的方向再结合类内散度做一些修正。3.1 计算均值、类内散度矩阵和投影方向先算类别均值m1 ((1223)/4, (2343)/4) (2, 3) m2 ((6768)/4, (5676)/4) (6.75, 6)类间均值差异向量为m2 - m1 (4.75, 3)接着算类别 1 的类内散度。每个点减去(2, 3)后得到偏差向量再外积累加。这个过程我完整列一遍点(1,2)偏差为(-1,-1)外积矩阵是[[1, 1], [1, 1]]点(2,3)偏差为(0,0)外积矩阵是零矩阵点(2,4)偏差为(0,1)外积矩阵是[[0, 0], [0, 1]]点(3,3)偏差为(1,0)外积矩阵是[[1, 0], [0, 0]]累加得到类别 1 的散度矩阵S1 [[2, 1], [1, 2]]。同理类别 2 的每个点减去(6.75, 6)后计算外积并累加得到S2 [[2.75, 0], [0, 2]]。所以总的类内散度矩阵Sw S1 S2 [[4.75, 1], [1, 4]]两步走求出Sw的逆。二阶矩阵的逆可以直接套公式Sw^(-1) (1 / (4.75*4 - 1*1)) * [[4, -1], [-1, 4.75]]行列式是19 - 1 18因此Sw^(-1) (1/18) * [[4, -1], [-1, 4.75]]投影方向w Sw^(-1) * (m2 - m1) (1/18) * [[4, -1], [-1, 4.75]] * (4.75, 3)展开计算得到w ((16/18), (9.5/18)) (0.8889, 0.5278)这个方向大致指向右上角和两个类中心的连线方向比较接近但不是完全一致。因为类内散度矩阵并不是单位阵LDA 会在类内方差小的方向上加权让投影后的类内分布更紧凑。3.2 投影后的坐标和阈值分类把w (0.8889, 0.5278)跟每个样本点做内积就得到一维投影值。我自己算了一遍类别 1 的四个投影值大约为1.94, 3.36, 3.89, 4.25类别 2 的四个投影值大约为7.97, 9.39, 9.03, 10.28。两组数之间有一道明显的空隙间隔大概在4.25到7.97之间。二分类的阈值通常取两个类中心投影值的平均值t (w.T * m1 w.T * m2) / 2本例中w.T * m1 3.36w.T * m2 9.17于是阈值t 6.26。投影值大于6.26判为类别 2否则判为类别 1。从手算结果看所有 8 个点都被分对了准确率 100%。这个例子人为拉开了距离真实随机数据不会这么听话。但手算的价值在于你亲眼看到Sw里的非对角元素如何影响最终方向而不是直接调用一个黑盒库。4. 用 Python 把随机数据跑一遍验证公式手算只能用于理解实际验证还需要借助程序。我一般习惯按照这个流程走先生成数据再手写 LDA 的底层步骤最后用成熟库的结果做对照。这样既能确认自己对公式的理解又能保证生产环境里用库时心里有底。4.1 生成可复现的两类二维数据我用numpy生成两类二维正态分布数据。固定随机种子非常重要否则不同的人跑同一段代码可能得到完全不同的结果后面的验证也就没有意义了。import numpy as np rng np.random.default_rng(42) X1 rng.multivariate_normal([2.0, 3.0], [[1.0, 0.5], [0.5, 1.2]], size100) X2 rng.multivariate_normal([7.0, 6.0], [[1.2, -0.2], [-0.2, 0.8]], size120) X np.vstack([X1, X2]) y np.array([0] * len(X1) [1] * len(X2))这里类别 1 的中心在(2,3)附近类别 2 的中心在(7,6)附近两类中心相距超过 5 个标准差所以按道理是比较好分的。我故意让两个协方差矩阵不一样用来观察 LDA 在“同协方差假设”不成立时的行为。4.2 手写 LDA 完整代码手写代码严格照着前面推导的公式走def lda_2d_to_1d(X1, X2): m1 np.mean(X1, axis0) m2 np.mean(X2, axis0) S1 np.zeros((2, 2)) for x in X1: diff (x - m1).reshape(2, 1) S1 diff diff.T S2 np.zeros((2, 2)) for x in X2: diff (x - m2).reshape(2, 1) S2 diff diff.T Sw S1 S2 w np.linalg.inv(Sw) (m2 - m1) return w, m1, m2 w, m1, m2 lda_2d_to_1d(X1, X2) w w / np.linalg.norm(w) proj1 X1 w proj2 X2 w t (w m1 w m2) / 2 pred np.where(X w t, 1, 0) acc (pred y).mean() print(投影方向:, w) print(阈值:, t) print(分类准确率:, acc)运行这段代码投影方向大约在(0.75, 0.66)的量级附近准确率通常在 95% 以上。由于随机种子固定结果是完全可复现的。这里有一个细节值得留意代码里对w做了二范数归一化。归一化之后向量长度不影响阈值判断但做内部逻辑检查、或者把投影方向画到散点图上时会方便很多。4.3 用现成库的结果做交叉验证手写代码只用了numpy逻辑透明。但如果想快速在企业项目里落地完全可以直接用现成工具。以常用的判别分析模块为例同样的数据只要几行调用就能出结果from sklearn.discriminant_analysis import LinearDiscriminantAnalysis lda LinearDiscriminantAnalysis() lda.fit(X, y) score lda.score(X, y) print(库函数分类准确率:, score) print(带截距的线性系数:, lda.coef_, lda.intercept_)你会发现官方实现得到的决策边界和手写版本几乎一致。差异主要来自两点一是官方实现可能使用了不同的数值计算方式比如对Sw做奇异值分解而不是直接求逆二是对阈值和系数的缩放方式不同但分类结果相同。这也是我推荐“手写一遍 库函数对照”的原因能及早发现自己在公式推导里的符号错误。4.4 降维后的一维分布要怎么看投影完成后别急着只输出一个准确率。把一维投影值画成直方图或者散点分布图你会直观地看到效果。通常会出现两种情况两类投影值的直方图有明显分离中间有一段空白或者重叠很少说明这个投影方向很好两个直方图大面积重叠说明原始特征里区分度本身不够或者投影方向被异常值带偏了。在二维降到一维的场景中我还会把原始二维散点图和一维投影并排展示。散点图上只需把每个点连到数轴上的投影位置就能理解“一根坐标轴”如何承载判别信息。很多时候视觉上的判断比一个准确率数字更可靠。5. 降维之后为什么还会分错问题排查清单手工推导和随机数据都很理想但把 LDA 放到真实场景时你会遇到各种莫名其妙的问题。下面这些情况我在实际工作中都碰到过整理成一张排查表能帮你快速定位。现象可能原因解决方案投影方向完全被某个特征主导特征量纲相差太大先做标准化或归一化两类投影后仍然严重重叠原始特征可分性不足或数据本身线性不可分增加特征、尝试核方法或 QDA不同随机种子下结果剧烈波动样本量太少或类别不均衡增加样本量或使用带惩罚的 LDA训练准确率很高但测试准确率很低在投影前使用了全部数据做标签编码先划分训练测试集再单独拟合 LDA手写结果和库函数方向相反公式里m2-m1方向取反检查符号负方向不影响分类但影响系数解释矩阵求逆报错或警告特征高度相关散度矩阵近似奇异加入正则化项或先用 PCA 降维去相关5.1 特征量纲差异是一个容易被忽略的大坑LDA 对特征量纲非常敏感。假设第一个特征的取值范围是0~10第二个特征的取值范围是0~10000那么Sw矩阵中的数值会完全被第二个特征主导第一个特征对投影方向的贡献几乎可以忽略不计。这不是 LDA 的算法毛病而是数据本身的问题。我在某个项目里就踩过这个坑。当时做用户行为特征降维特征同时包含“点击次数”和“活跃时长”量纲差了好几个数量级。第一次直接跑 LDA投影系数几乎全部集中在活跃时长上类别区分效果很差。后来把每个特征做标准化同样的算法准确率立刻提升了 10 多个百分点。所以不管用哪种判别分析第一步都应该检查特征量纲必要时做标准化。5.2 类别重叠到什么程度就该放弃普通 LDALDA 本质上假设两类样本服从正态分布并且协方差矩阵大致相同。如果实际数据的类内结构差异很大比如一类很扁、一类很圆那么 LDA 找一个线性投影方向就不够用了。这时可以参考二次判别分析QDA它允许各类有不同协方差矩阵决策边界也从直线变成曲线。在随机生成的二维数据上你可以做一个简单实验把一个类别的协方差矩阵改成[[5, 0], [0, 0.2]]另一个改成[[0.2, 0], [0, 5]]跑完 LDA 你会发现准确率明显下降。原因不是投影方向算错了而是线性投影本身无法同时让两个形状迥异的类别都聚拢。5.3 数据泄漏问题归一化和 LDA 都要在训练集上做很多人在处理数据时养成一个坏习惯先对整个数据集归一化再划分训练集和测试集。这在无监督模型里问题不大但在 LDA 这类有监督方法里非常危险。因为你在划分之前已经用了所有样本的均值和方差这相当于让模型提前“偷看”了测试集的统计信息最后得到的测试准确率会虚高。正确步骤是先划分训练集、测试集然后在训练集上计算 LDA 投影方向把同样的投影公式应用到测试集上。如果要做标准化也是先在训练集上算出均值和标准差再作用到测试集上。5.4 投影方向的正负号到底有没有关系我经常看到有人在论坛上对比手写 LDA 和库函数的输出发现投影向量完全相反然后开始怀疑人生。其实投影方向的正负号只表示“取反方向”样本投影后的坐标也会统一取反类别判断依然正确。真正要关心的是相对大小和分类阈值。但有一个场景需要小心如果你要解释投影系数比如“第二个特征的贡献比第一个大”方向取反不会改变贡献的相对比值但和原始特征的语义关系会连坐。此时建议统一规定w方向为m2 - m1的方向或者直接看绝对值。6. 把二维实验延伸出去多分类、正则化与会用多久二维到一维只是 LDA 最入门的一个案例弄懂之后完全可以扩展。多分类情况下LDA 不再只求一个投影方向而是逐类计算类间散度矩阵然后对Sw^(-1) * Sb做特征分解取前若干个特征向量作为投影轴。输出维度上限是类别数减一。比如三类问题最多投影到二维这刚好又是一个非常适合可视化的场景。我建议把这个随机数据实验当成“算法练功房”。先在二维上把 LDA 的每一步都算明白再迁移到三维、多维再引入正则化项。正则化 LDA 的标准做法是在Sw的对角线上加一个小常数lambda * I避免奇异矩阵造成的不稳定这在特征数大于样本数的时候几乎是必需品。6.1 我常用的一个验证套路在正式跑真实数据之前我会先构造一组模拟数据设置不同的类别均值距离和协方差矩阵然后用 LDA 降维观察投影方向与准确率的变化。这样可以快速建立直觉类均值距离变大投影后的两类间隔变大类内散度增大投影方向会主动避开噪声维度。有了这层直觉面对真实数据时就不会只盯着准确率数字而是会主动检查投影系数是否符合业务解释。6.2 一个小技巧先画散点再决定降维第二个经验是在做任何降维之前先画二维散点图。如果你连原始二维散点里两类都分不开那任何降维算法都不可能变魔术。降维只能把信息重新组合不能凭空创造信息。很多新手一上来就大量依赖降维结果忽略了原始数据的可用性容易走弯路。6.3 结合业务解释投影系数最后提醒一点LDA 投影系数不是单纯的“权重”它代表的是“在该特征方向上类间差异乘以类内精度的校正结果”。如果你在做人脸识别、用户画像、信号处理这类场景尽量要把投影系数的正负和大小跟业务语义对照起来。系数绝对值大的特征在判别上贡献大但还要看特征本身的量纲量纲小的特征可能因为判别力强而拿到大系数。对我来说线性判别分析最迷人的地方就是把“分类”和“降维”统一进同一个目标函数。你不需要先降维再分类也不需要先分类再找判别方向而是一步到位地得到最可分的低维投影。用随机数从二维降到一维把整套逻辑亲手跑一遍之后再遇到高维数据里的判别分析问题心里就不虚了。下次如果你拿到一份高维分类数据不妨先想一个朴素问题我能不能在低维空间里画出两类样本的差异让这条线或这个面具备可解释性如果是LDA 大概率是值得第一个尝试的工具。