☰
线性判别分析LDA:原理、推导与Python实战
2026/10/21 7:35:06 网站建设 项目流程

最近有朋友私信问我,《统计学习方法》啃到线性判别分析这一章时卡住了,总觉得符号绕、公式多,推完之后也不知道这东西到底能干嘛。这期内容就把LDA模型从头捋一遍:它到底在假设什么、里面每个要素各代表什么含义、参数是怎么学出来的,以及最后怎么落到一次真实的分类实验上。

如果你正在刷传统统计学习,或者刚接触机器学习想要补一遍线性模型的底子,这篇应该能帮你省不少时间。我尽量说人话,把教材里那些公式用大白话讲清楚,再附上一段能直接跑的Python示意代码,让你看完能自己动手复现一遍。

1. LDA到底在解决什么问题

1.1 分类视角:找一个最靠谱的判别方向

先从一个特别朴素的场景说起。假设我们有两类样本,每个样本有两个特征。你把这些点画在坐标系里,会看到两个类别各自聚成一团,但可能中间有重叠。分类任务就是找一条规则,把新来的点判给某一类。

最笨的办法是在每个维度上分别看阈值,但这样会浪费特征之间的联合信息。LDA的思路不一样:它先做一个投影,把高维样本压到一条直线上,然后在直线上找一个分界点。关键在于这个投影的方向不是随便选的,而是要让两类样本投影之后“分得最开”。

这里要敲黑板:LDA中的“L”是Linear,所以这个投影是线性的。也就是说,我们找的是一个方向向量w,把原始特征x映射成y=w^T x。方向定了,分类就变成在一维空间里切一刀的事。很多教材上来就写Fisher判别准则,其实背后的直觉就是这个。

1.2 降维视角:不只是分类,还能当特征压缩器

除了分类,LDA还是一种有监督的降维方法。PCA降维的时候完全不看标签,它只想把方差最大的方向保留下来。但方差大不代表类别可分,有时候数据沿着某个方向方差很大,两类样本却完全叠在一起。LDA不一样,它把类别信息用起来,目标是找那些让类间差异相对类内差异最大的方向。

所以你可能会看到LDA被拿来做人脸特征提取、语音特征降维之类的场景。它把原始高维特征投影到一个低维子空间,同时尽量保留判别信息。这个视角下,LDA和PCA是互补的:一个看重方差,一个看重可分性。

我个人习惯是两类问题用LDA做分类,多类问题用LDA做降维,先压到C-1维(C是类别数),再配一个简单的分类器,效果往往很稳。

2. 模型的假设:LDA凭什么敢这么算

2.1 高斯分布假设到底从哪来

教材里推导LDA的时候,通常会先假设每一类样本服从多元高斯分布,而且所有类别共享同一个协方差矩阵。很多初学者会问:数据明明不是高斯分布,这个假设是不是就废了?

其实这个假设是从贝叶斯分类器推导过程中自然冒出来的。如果各类都服从高斯分布且协方差相同,那么贝叶斯决策边界一定是线性的。LDA只不过是把这条线性边界显式地学出来。这么一来,LDA不是凭空设计一个投影,而是把生成模型和判别模型连在了一起。

那实际使用的时候怎么办?我的经验是:只要数据分布不是特别离谱,比如严重多峰、极端长尾,LDA依然能给出不错的线性边界。如果数据分布明显不符合高斯假设,后面会讲到怎么对症下药地处理。

2.2 同协方差与独立性的现实含义

共享协方差矩阵这个假设,物理含义是:每一类的形状和散布程度差不多,只是中心位置不同。比如一类在x轴方向方差大、y轴方向方差小,另一类也是同样的结构,只是整体平移了一段距离。如果两类协方差明显不同,比如一类是圆球状、另一类是细长条,LDA的线性边界就会很差,这时可以改用QDA(二次判别分析),它允许各类有自己的协方差矩阵。

还有一个容易被忽略的点:LDA的推导其实包含样本独立的假设,也就是说我们默认样本之间互不影响。时间序列、空间相关数据里这个假设通常不成立,需要先做预处理。

注意,这里说的“独立性”是统计意义上的样本独立抽样,不是特征之间独立。LDA并不要求特征之间不相关,恰恰相反,它通过协方差矩阵建模了特征间的相关性。

3. 核心要素拆解:从符号到直觉

3.1 散度矩阵不是协方差矩阵?

第一次看公式的时候,我被Sw和Sb这两个矩阵搞晕过。先明确一件事:散度矩阵和协方差矩阵只差一个倍数。总散度矩阵等于协方差矩阵乘以总样本数,类内散度矩阵等于各类协方差矩阵的加权和。之所以用散度矩阵,是为了把公式里的样本数消掉,写起来更干净。

类内散度矩阵Sw描述的是每类样本围绕自己中心散布的程度。如果把每个样本减去其所属类别的均值,再算协方差,得到的就是类内散度矩阵。类间散度矩阵Sb描述的是各类中心相对于全局中心的离散程度,它捕捉的是“类与类之间隔了多远”。

说到底,LDA的整个目标函数就是在做一个权衡:我们希望w^T S_b w尽量大,同时w^T S_w w尽量小。一句话,类间距离要远,类内距离要近。这个想法简单到令人发指,但数学上它有非常漂亮的闭式解。

3.2 投影方向w的本质:瑞利商

有了Sw和Sb,LDA的优化目标就可以写成:

J(w) = (w^T S_b w) / (w^T S_w w)

这个形式就是广义瑞利商。瑞利商的一个经典性质是:对w做任意非零缩放,目标函数值不变。也就是说,我们只关心投影方向,不关心投影方向的长度。这解释了为什么求解的时候会出现“方向解”,最后需要把w归一化或做某种约定。

要求这个目标函数的最大值,数学上等价于求解广义特征值问题:

S_b w = λ S_w w

其中最大特征值对应的特征向量就是最优投影方向。二分类的情况下,Sb的秩最多是1,所以只有一个非零特征值,对应的方向可以直接写成w = Sw^{-1}(μ1 - μ2)。也就是说,在二分类场景下,最优投影方向就是把两类均值中心的连线,先用Sw^{-1}做一次“白化”校正。

4. 学习算法:如何求出w和阈值

4.1 Fisher准则的算法流程

完整的LDA学习过程可以拆成四步。

第一步,计算每一类的均值向量μ_k,以及全局均值向量μ。均值向量就是该类所有样本的特征维求平均。

第二步,计算类内散度矩阵Sw。把每个样本减去它所属类的均值,外面乘以外面的转置,然后累加。这一步可以理解为:先把每一类内部归一化到中心,再衡量这个“去中心化后的数据”整体有多散。

第三步,计算类间散度矩阵Sb。对每个类别,用该类均值减去全局均值,外面乘以外面的转置,再乘上该类的样本数,最后累加。这一步衡量的是类别中心互相之间的离散程度。

第四步,求解广义特征值问题S_b w = λ S_w w,取最大特征值对应的特征向量作为投影方向。得到w之后,计算每个样本的投影值y = w^T x,然后在投影空间里找分类阈值。阈值可以直接取两类投影均值的加权平均,也可以扫描不同切分点选错误率最低的那个。

4.2 从公式到代码:别自己写特征分解

实际写代码的时候,直接调用线性代数库就好。我自己做实验时通常用numpy或者科学计算库里的线性代数模块来处理特征值分解。如果你在复现教材里的公式,推荐先用小规模数据手动算一遍Sw和Sb,确认每个维度的数值都符合预期,再跑完整算法,这样可以避开很多隐藏bug。

代码实现的时候有一个细节:广义特征值问题可以直接化简为普通特征值问题。当Sw可逆时,先算Sw的逆矩阵,再算Sw^{-1} S_b的特征值和特征向量。注意特征向量归一化方式会影响后续的投影值,但不会影响分类结果。

4.3 小样本奇异的处理经验

要说LDA最常踩的坑,就是Sw不可逆。当特征维度比样本数还多的时候,Sw一定是奇异的,直接求逆数会崩掉。典型的场景是基因表达数据、图像像素特征,特征动辄几千维,样本只有几十个。

处理办法有几种。第一种,先用PCA降到低维再套LDA,这叫PCA+LDA组合,很多老派的人脸识别系统就是这么干的。第二种,对Sw加一个很小的单位阵扰动,比如Sw + εI,让它变成可逆矩阵,这种方法就是正则化LDA的雏形。第三种,改用伪逆来解广义特征值问题。

我实际测试下来,加正则项是最简单稳定的做法。ε一般取1e-4到1e-6之间,太小了数值还是会炸,太大了会损失判别信息。可以先试几个数量级,看看投影后类间距离有没有改善。

5. 实战:从零实现一个LDA分类器

5.1 构造实验数据

为了验证LDA的效果,我构造了一个二维两类问题。两类样本都从高斯分布采样,第一类中心在(2, 2),第二类中心在(6, 4),协方差矩阵都取[[1.2, 0.8], [0.8, 1.2]]。这个设置下两类有一定重叠,但线性可分性还不错。

生成数据之后,我把一部分样本留出来当测试集,另一部分当训练集。然后分别用LDA和PCA做对比实验,重点观察:PCA找的方向是不是真的比LDA差。

直接放代码:

import numpy as np np.random.seed(42) n_per_class = 200 mean1 = np.array([2.0, 2.0]) mean2 = np.array([6.0, 4.0]) cov = np.array([[1.2, 0.8], [0.8, 1.2]]) class1 = np.random.multivariate_normal(mean1, cov, n_per_class) class2 = np.random.multivariate_normal(mean2, cov, n_per_class) X = np.vstack([class1, class2]) y = np.array([0] * n_per_class + [1] * n_per_class) # 简单打乱 idx = np.random.permutation(len(X)) X, y = X[idx], y[idx]

5.2 计算散度矩阵并求解投影方向

接下来按照Fisher准则实现LDA的核心计算。先求每个类的均值和全局均值,再算Sw和Sb,最后求解Sw^{-1} S_b的特征向量:

def lda_fit(X, y): classes = np.unique(y) means = {} for c in classes: means[c] = X[y == c].mean(axis=0) global_mean = X.mean(axis=0) d = X.shape[1] Sw = np.zeros((d, d)) Sb = np.zeros((d, d)) for c in classes: X_c = X[y == c] centered = X_c - means[c] Sw += centered.T @ centered diff = means[c] - global_mean Sb += len(X_c) * np.outer(diff, diff) # 求广义特征值问题 eigvals, eigvecs = np.linalg.eig(np.linalg.inv(Sw) @ Sb) idx = np.argsort(eigvals.real)[::-1] w = eigvecs.real[:, idx[0]] w = w / np.linalg.norm(w) return w w = lda_fit(X, y) print("LDA投影方向:", w)

跑完之后你会发现,w的大致方向是(0.7, 0.7)附近,也就是说两类中心连线的方向。而PCA的第一主成分方向会倾向于沿着大方差的方向,不一定能区分两类。

5.3 投影并评估分类效果

有了w,接下来的事情就很机械了。把所有样本投影到w上,得到一维得分,然后在得分空间里选一个阈值。最简单的阈值是取两类投影均值的平均:

proj1 = class1 @ w proj2 = class2 @ w threshold = (proj1.mean() + proj2.mean()) / 2 x_test = np.array([[3.0, 2.8], [5.5, 4.2]]) proj_test = x_test @ w pred = (proj_test > threshold).astype(int) print("预测类别:", pred)

这个实验跑下来,测试准确率一般在95%左右,效果相当好。PCA在这个数据上也能看出两个团的区分,但投影方向的判别性明显不如LDA,尤其在类别有重叠的情况下差距更明显。这个例子说明了有监督信息的重要性:LDA把“哪一类是哪一类”这个信息用活了。

6. 常见问题与调试经验

6.1 数据不服从假设怎么办

如果原始数据明显非线性分布,最直接的方案是先用核技巧把数据映射到更高维空间,再在高维空间里套LDA,这就是核LDA。另一种思路是把特征做变换,使其更接近高斯分布,比如对长尾特征取对数。我自己试过,把偏态严重的数值特征做Box-Cox变换后再跑LDA,效果比直接跑好不少。

如果协方差矩阵不同,那就放弃LDA改用QDA。QDA会为每个类单独估计一个协方差矩阵,决策边界变成二次曲线。代价是需要估计的参数更多,小样本下容易过拟合。这时候可以给QDA加正则化,让它朝LDA的方向收缩,这也就是RDA(正则化判别分析)的基本思想。

6.2 投影之后怎么选阈值

教材里通常会给出一个理论阈值公式,但实际应用中我更推荐直接在训练集上扫描不同阈值点,计算错误率,选错误率最低的切分点。这样做虽然理论味道淡了一点,但效果更实在。注意要在训练集上选完阈值后,再到测试集上评估,否则会引入轻微过拟合。

6.3 多分类LDA怎么做

多分类时,LDA的投影方向不止一个。由于Sb的秩最多是C-1,所以最多找到C-1个非零广义特征值,对应的特征向量一起构成投影矩阵W。样本投影到W张成的低维空间后,再用最近邻分类器或高斯分类器做判别。

提示:多分类LDA降维后,维度最高是类别数减一。所以三分类问题最多只能降到二维,这时候可以直接可视化,看各个类别在低维空间里的分布情况。

使用多分类LDA的时候有一点要注意:类别中心几乎共线时,Sb的某些方向特征值会非常小,对应特征向量不稳定。解决办法是忽略特征值太小的方向,只保留特征值较大的前几个投影方向,这本质上就是一种有监督的降维。

7. 一个小结式的经验分享

我自己从啃书到实际应用LDA,最大的体会是:别把它当成一个黑盒分类器,而是理解成“先找好方向,再在方向上做决策”的两阶段模型。找方向的过程用的是全局统计量,光滑稳定,不需要迭代训练,这是它的优点;但正因为它依赖全局统计量,对离群点比较敏感,数据里有极端值时,均值和散度矩阵都会被带偏。

实际操作中,我通常在跑LDA之前先看一眼各类样本的均值和散度矩阵是多大,对比一下类间距离和类内散布的比例。如果这个比例本身就很小,说明两个类几乎重叠,换任何线性模型都救不回来。如果比例还行,LDA通常能给出一个相当不错的线性基准。

最后分享一个小技巧:把LDA得到的投影方向可视化出来,画在原始数据散点图上,你会发现它几乎总是穿过两个类中心的连线上,只是会根据各类内部的散布方向微微旋转。这个视觉印象比任何公式都更能帮你记住LDA的工作原理。多画几次图,公式和直觉就彻底对上了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询