1. 一个二维数据,凭什么值得压成一维
二维数据为什么要降成一维?最直接的理由是可视化。人眼对二维散点图还能勉强看出聚集关系,但三维以上就得靠旋转、着色、降维映射帮着猜。而把二维压到一维,等于把散点“拍”到一条直线上,每个点在这条直线上的坐标就是降维后的数值,看一张一维分布图就能判断两个类别分不分开。这个场景特别适合用来验证线性判别分析(LDA)到底在干什么。
LDA 的核心不是普通降维,而是“带着分类任务去降维”。它找一个投影方向,让两类样本在投影后尽可能离得远,同时每一类内部又尽可能聚得拢。这样一来,降维后的每个样本既保留了足够的判别信息,又可以直接用于画图、分类、构建特征,甚至用一维阈值做决策树或逻辑回归的输入。特别适合用来理解:为什么有的投影方向能把两类分开,而另一些方向会把两类揉成一团。
我建议想入门机器学习、或者正在做特征工程的开发者,都可以拿“随机生成数”来做这个实验。随机数据的好处是可控、干净、能复现,不会出现真实数据里各种脏乱差带来的干扰。你用正态分布生成两类点,分别贴好标签,然后实现 LDA 从二维到一维的完整流程,你会发现代码量不大,但每个步骤背后的几何直觉非常清晰。
1.1 为什么拿随机数据先跑一遍
真实数据集往往伴随着量纲不一致、缺失值、离群点、标签噪声,这些都会让算法调试变得复杂。用随机生成的数据,第一优势是真值已知:你知道这些点是按哪两个均值、哪两个协方差矩阵生成的,所以投影方向、分类准确率都有“标准答案”可以做交叉验证。第二优势是可复现性:只要固定随机种子,每次跑的类内散度矩阵、类间散度矩阵完全一致,方便排查问题。
我在实际项目中见过不少团队直接拿生产数据调降维参数,结果发现投影方向总是被某些特征的大数值主导,最后花了很长时间才意识到是数据没标准化。换成随机数据之后,问题本质立刻暴露出来——LDA 用的是散度矩阵,散度跟量纲是绑定的。先用随机数据把算法逻辑跑通,再迁移到真实数据,这种“先易后难”的习惯很值得养成。
1.2 LDA 和 PCA 到底哪里不一样
很多人一提到“降维”就条件反射想到 PCA,但 PCA 和 LDA 做的事情有本质区别。PCA 是无监督方法,它只关心所有样本的方差最大方向,完全忽略样本标签。LDA 是有监督方法,它必须用到标签信息,目标是“每一类的均值尽量分开,每一类内部的散度尽量小”。
我用一个生活类比来说明:假设你在学校操场上要找一个方向,把操场上的同学投到墙上影子。PCA 找的方向是影子拉得最长、方差最大,不管学生是哪个班的;LDA 找的方向是各班影子重心离得最远,同时每个班的影子内部不要挤得太散。判断谁是谁,当然用 LDA 更合理。下面这个表可以更直观地对比两种方法的差异:
| 对比维度 | PCA | LDA |
|---|---|---|
| 是否需要标签 | 不需要 | 需要 |
| 优化目标 | 最大化整体方差 | 最大化类间散度与类内散度之比 |
| 输出维度上限 | 可以等于原始特征数 | 二分类通常只有 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。把两个类内散度矩阵加起来就得到总的类内散度矩阵Sw:
Sw = 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 = ((1+2+2+3)/4, (2+3+4+3)/4) = (2, 3) m2 = ((6+7+6+8)/4, (5+6+7+6)/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.36,w.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]], size=100) X2 = rng.multivariate_normal([7.0, 6.0], [[1.2, -0.2], [-0.2, 0.8]], size=120) 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, axis=0) m2 = np.mean(X2, axis=0) 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 类别重叠到什么程度就该放弃普通 LDA
LDA 本质上假设两类样本服从正态分布,并且协方差矩阵大致相同。如果实际数据的类内结构差异很大,比如一类很扁、一类很圆,那么 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 大概率是值得第一个尝试的工具。