二次判别分析QDA:非线性分类边界背后的原理与Python实战
发布时间:2026/10/17 17:55:19 锦皓数字建站

1. 需要一个比线性边界更灵活的模型时做分类任务的朋友应该都遇到过这种情况拿逻辑回归或者线性判别分析LDA跑了一版模型测试集准确率卡在某个数值怎么都上不去。画一下决策边界发现两类数据之间那条分界线压根不是直线硬用一个线性边界去切代价就是样本在边界附近错分一大片。这时候二次判别分析Quadratic Discriminant Analysis简称QDA就可能是个值得试试的方案。QDA名字里的“二次”指的是它生成的决策边界是二次曲线在二维特征空间里是抛物线或椭圆弧在高维空间里是二次曲面核心假设比LDA放松了一步——允许每个类别拥有自己的协方差矩阵。正是这一步松动让它能拟合出非线性边界在处理异方差性数据、流形有明显曲率的分类问题时比LDA更稳。这篇文章适合两类人看一是正在学统计机器学习想把判别分析原理吃透的初学者二是已经在用scikit-learn做分类觉得线性模型上限不够、想多掌握一个工具箱的实践者。我不会只丢给你sklearn的API文档而是把QDA背后的推导逻辑、与LDA的血缘关系、Python实现细节以及调试经验一步步拆开讲争取你读完就能在真实数据集上用起来。2. QDA的核心思路拆解从贝叶斯决策到二次判别函数2.1 生成模型视角QDA到底在算什么先建立一个关键认知LDA和QDA都是生成式模型它们在做的不是直接拟合“类别边界”而是为每一个类别单独建立一个概率分布模型然后通过贝叶斯公式把新样本归到后验概率最大的那一类。具体到QDA它假设每个类别下特征向量服从多元高斯分布p(x | y k) N(μ_k, Σ_k)注意划重点每个类别有自己独立的协方差矩阵Σ_k。这是相对LDA的那个“所有类别共享同一个协方差矩阵Σ”的假设而言的。你可能觉得这一下子区别不大但数学上会产生质变。根据贝叶斯公式后验概率P(y k | x) ∝ P(y k) · p(x | y k)对两边取对数展开多元高斯密度函数的指数部分-1/2 · (x - μ_k)ᵀ Σ_k⁻¹ (x - μ_k) - 1/2 · ln|Σ_k| ln π_k在做类别两两比较的判断时只看这个判别函数值的大小。注意到关键点(x - μ_k)ᵀ Σ_k⁻¹ (x - μ_k) 这玩意儿是马氏距离的平方而且因为Σ_k随类别变化它展开后会出现x的二次项。这就是“二次”之名的数学出处——判别边界由这些二次项的等值线决定自然就是二次曲面。用生活化的类比来说LDA好比给每一类人统一买一件均码外套不管高矮胖瘦都往一个尺码上套拟合时用一个共享协方差QDA则是按各自身形分别量体裁衣每一类都得到贴合自己的那件。衣服合身了边界自然能根据各群体分布变化。2.2 分界面到底长什么样为什么QDA能出非线性边界如果把两类数据的判别函数令为相等得到决策边界方程δ_1(x) δ_2(x)这个等式重新整理后依然是一个关于x的二次方程。二维情况下解出来的边界可能是抛物线、椭圆、双曲线中的一种。很多初学者误以为“二次边界就是想怎么弯就怎么弯”其实不是。QDA的模型容量有上限边界只能是二次曲面族中的一种不能像树模型那样无限拟合。这种“半灵活”的特点在统计上反而是优点——因为它避免了完全非参数方法的过拟合同时又保留了参数模型的稳定性。我在实践中倾向于把QDA看作比LDA灵活一个级别、但依然有强结构约束的中间地带的模型。2.3 判别分析家族的小型对照模型协方差假设边界类型参数估计复杂度训练量需求LDA各类别共享Σ线性低低QDA各类别各自Σ_k二次曲面中中高朴素贝叶斯(高斯)各类别、各特征独立线性/复杂低低KNN无分布假设任意训练几乎为零高预测时高学完LDA的人很容易忘记QDA但其实两者是一条藤上结出的两个瓜。LDA是QDA的约束版本把Σ_k全部设成同一个Σ。从参数效率角度看LDA因为共享协方差需要估计的参数大幅减少在较小数据集上有优势但代价是不允许各类方差结构不同。有些实际场景里你明知道每个类别的数据离散程度差很多。比如医疗诊断里病例组和健康组的代谢指标往往方差就不同比如金融风控里正常用户的消费波动规律和套现用户完全不是一个量级。这种时候硬压成同一个协方差等于主动扔掉了信息。经验当各类别样本量的量级差距超过3倍且小类别样本量不足特征维度的5倍以上时LDA估计出的共享协方差矩阵会被大类主导小类的真实分布被抹平优先考虑QDA或降维再做判别分析。3. 深入QDA的推导细节与判别规则3.1 判别函数的完整推导过程我不想跳过推导直接给结论。漏掉推导你就只能被动接受sklearn的返回结果遇到边界怪异的情况根本不知道为什么。从贝叶斯最优判别规则出发分到第k类的条件是它产生的后验概率最大C(x) argmax_k P(y k | x)后验概率的分母对所有类别都一样可以去掉。所以只比较分子——也就是先验和似然的乘积。取对数之后得到判别函数δ_k(x) ln π_k - 1/2 · (x - μ_k)ᵀ Σ_k⁻¹ (x - μ_k) - 1/2 · ln det(Σ_k)其中π_k表示类别k的样本占比μ_k和Σ_k用极大似然估计μ_k (1/N_k) Σ_{i: y_i k} x_i Σ_k (1/(N_k - 1)) Σ_{i: y_i k} (x_i - μ_k)(x_i - μ_k)ᵀ现在再看判别规则的展开。两类情况把δ_1(x) δ_2(x)展开一下二次项为中心协方差之差的逆矩阵。想一下如果两类数据的Σ完全一样二次项直接抵消——这个规则瞬间退回LDA的线性形式。所以QDA对LDA是包含关系这个细节值得你在代码里实际验证一下。3.2 正则化与退化问题为什么小样本下QDA会翻车讲了QDA的优点也该讲它过敏的地方。每个类别单独估计Σ_k这种自由度的成本是你必须拿足够多的样本去填充每一个协方差矩阵。当特征维度p和某个类别的样本量关系失衡N_k p 1Σ_k就不是满秩矩阵了逆矩阵都不存在判别函数直接没法算。严格讲N_k可以等于p也能估但算出来几乎是病态的泛化能力极差。scikit-learn的QuadraticDiscriminantAnalysis实现里我看过源码它有一个reg_param超参数默认0本质是对每个Σ_k做正则化Σ_k (1 - reg_param) · Σ_k reg_param · I理解它的物理意义把原始估计出的协方差矩阵朝着单位矩阵方向收缩抬高特征值下限让矩阵恢复良态。这等价于在原有估计基础上加上一根“安全绳”。我在一个开源数据集上做过对比某类别只有60个样本特征维度到了27维不设reg_param时训练集准确率很高但验证集一塌糊涂把reg_param调到0.1之后虽然训练集准确率稍微下降了2个百分点但验证集准确率反而回升了约9个百分点。这是典型的“用小代价换大收益”。3.3 参数估计流程的实操细节用sklearn实现QDA只需要几行from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.metrics import classification_report # 对特征做标准化数值稳定性优先 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) qda QuadraticDiscriminantAnalysis(reg_param0.01) qda.fit(X_train_scaled, y_train) y_pred qda.predict(X_test_scaled) print(classification_report(y_test, y_pred))但我想提醒你标准化这一步对QDA不是“сенсорно必做”因为QDA用的是各个类别自身的协方差单独缩放每个特征会让协方差矩阵的对角线变得相似反而会削弱QDA对各类别方差结构差异的感知。我倒是建议如果特征的量级差异太大数值跨了好几个数量级优先用对数变换而不是StandardScaler。此外sklearn的QDA是基于cholesky分解实现求逆的特征维度过高时即使有reg_param也可能在数值上不太稳定。所以实际项目中我倾向先对高维稀疏特征做PCA降维再做QDA顺序别搞反。细心的朋友还会发现QDA对象里有决策函数值可以用# 拿到每一类的后验概率 prob qda.predict_proba(X_test_scaled) # 或拿到判别函数值 decision qda.decision_function(X_test_scaled)predict_proba直接给你每个类别的概率估计这在工程上特别有用——比如设定置信阈值只对预测概率超过0.8的样本做自动决策其余进入人工复核通道。4. 实操过程让QDA在真实业务场景中落地4.1 从数据探索到模型选型的完整决策流我在做一次客户流失预测项目时团队一开始走的是逻辑回归基线。数据显示两类用户的重度使用指标差异明显但画散点图发现边界是一条明显的弧线而非直线——逻辑回归的线性边界在测试集上表现挣扎。这时候我做了个简单实验先把同一批特征分别喂给LDA和QDA对比交叉验证分数。LDA只有约退了0.72的AUC换成QDA后提升到了0.81。这是一个很典型的信号——说明两个类别的协方差结构大概率不等。如果你更谨慎可以在做选择之前跑一个Boxs M-test检验各类协方差矩阵是否相等。但这个检验对非正态很敏感在样本量大的时候连微小的差异都会判定为显著。所以我的习惯是业务上先判断如果各类别方差有明显差异的迹象可以从各个类别的特征箱线图宽度看出来直接上QDA截图对比效果提升就留下没提升就换回LDA。4.2 超参数reg_param的调参思路与交叉验证方案QDA的超参数不多核心就是reg_param和tol那两个。reg_param我之前说过它收缩Σ_k的对角线调参逻辑大概这样如果训练集和测试集差距巨大说明过拟合reg_param调大。如果训练集就已经很差那reg_param要调小或者为0问题不在这里。如果训练集很好、测试集稍差且数据量本身不够按0.01、0.05、0.1三个档位网格搜索通常会落在0.1以下。一个实用做法是写个简单交叉验证循环from sklearn.model_selection import GridSearchCV from sklearn.pipeline import Pipeline pipe Pipeline([ (qda, QuadraticDiscriminantAnalysis()) ]) param_grid {qda__reg_param: [0.0, 0.01, 0.05, 0.1, 0.2, 0.5]} gs GridSearchCV(pipe, param_grid, cv5, scoringroc_auc) gs.fit(X_train_scaled, y_train) print(gs.best_params_)注意别盲目追求最高测试集分数那样容易把阈值定得过于适配当前这一份切分。我在一个二分类数据上跑过reg_param0时一个切分下的AUC是0.84另一个切分只有0.77reg_param0.05虽然两个切分都只有0.82/0.80但差距小得多。稳定性胜于一时的表现。4.3 多分类场景中的QDA表现与置信区间multiclass分类场景里QDA不需要任何额外改动它天然支持K类判别——算每一类的判别函数δ_k选择最大值对应的类。和多个二分类器投票的“一对其余”策略不同QDA一次拟合就得到完整模型推理时算K个判别函数值取最大就行。在执行阶段请注意类别不均衡的影响。先验π_k在判别函数里扮演的角色很直接它给样本量大的类加了一个常数偏置。如果业务里类别比例天然不均衡这个偏置是合理的但若是采样导致的比例失真比如负采样过的场景样本下次别忘了把priors手动设置成实际业务比例。qda QuadraticDiscriminantAnalysis(priors[0.9, 0.1])同时在sklearn实现里默认采用留一分位点剪枝的先验估计对少量不均衡的类别有轻微拉平效果这样能避免小样本类的判别函数被严重低估。4.4 实操中遇到的那些坑与排查方法坑一特征相关性高且各类别协方差差别大导致数值爆炸。一个包含两个强相关特征的业务数据在两个类别里它们的相关性符号恰好相反——QDA把相关结构都刻进去了看起来很美。可是新来一批数据那点微弱相关性一波动预测结果就变脸。对此我建议从业务层面审查相关性是否稳定不是光看验证集分数。坑二训练数据和预测数据分布不一致。QDA的核心是生成式描述如果生产环境的数据分布已经偏移covariate shift所有生成式模型的退化速度都比判别式模型快。应对方案是定期监控特征边缘分布一旦发现漂移重训并重估特征集。坑三在分布长尾重、离群点多的数据上生用QDA。多元高斯假设对离群点极度敏感。单个离群点可能把某一类的Σ_k对角线拉大数倍导致真正的判别信息被稀释。我处理这类数据时往往先修剪离群点再跑到几十次实验对比修剪前后分数。5. 从实战角度聊聊QDA什么时候用、什么时候别用5.1 特征维度爆炸与数据量不足时的替代方案开头说的回归QDA也不是包治百病。特征维度上去了数据量却不够QDA就会像穿了一件大一号的衣服晃荡得厉害。LDA共享协方差意味着只需要估计一个p×p矩阵QDA要估计K个训练量越大要求越低。如果样本量确实紧张可以考虑先用PCA把维度压缩到十来个主成分再上QDA。主成分提取方向和数据方差对齐的部分那正好是QDA判别关注的方差结构。这样一套流程在几个项目中都帮我挽回了不少训练集里看起来要崩的模型。另一条路是改成正则化判别分析RDA它是LDA和QDA的线性插值版把各类别协方差矩阵估计向共享协方差压缩程度由超参数γ控制。RDA在中小样本场景下常常比QDA更稳只是sklearn没直接给RDA类需要你用一点代码技巧自己包装。5.2 QDA与神经网络不是一个赛道的选手还有一点要提醒不要拿QDA和深度学习硬比。神经网络是柔性模型小微数据上容易过拟合建模周期长。QDA的参数个数是K×(p(p1)/2 p) K以十维特征二分类为例模型参数约120个但一个小型全连接网络可能在一层就上千参数。数据量没到千级样本神经网络本身的复杂度就碾压统计可辨识性了。适合QDA的场景就一个字稳。样本量几千到小几万、特征十几到几十维、数据近似单峰分布类内大致团状此时QDA性价比极高训练秒级推理轻量决策边界又有非线性表达力。5.3 和PCA结合的一个直觉理解PCA做的事是找到方差最大的方向线性变换而QDA在这些变换后的特征上计算判别函数。想象一堆团块数据方差最大方向正好区分两类。用PCA降维再进QDA其实是用数据自身结构辅助判别这比盲目的特征筛选要好用。但顺序错了就会出事。如果先PCA保留下方差最大的主成分而恰好这个方向的类间差异很小等于把判别信息都扔了。万全做法是PCA QDA做基线再和完全不做PCA的QDA对比留表现好的。6. 关于QDA应用的经验补充我在实际操作中还有几个印象比较深的地方最后一块儿说了。第一判别函数与可视化的联动。QDA的predict_proba输出对业务方展示特别友好比决策树那套if-else输出更容易讲清楚。不用讲公式对业务人员就说“系统给每个客户打了一个分分数落到这种弧形区域就判定为流失风险高这个弧形是从数据里学出来的”。第二类先验必须谨慎。有的朋友因为类别比例不平衡就在训练前做SMOTE过采样或者欠采样。QDA不依赖全局协方差采样改变的是各类别协方差估计时用的样本量新样本从哪来会直接影响Σ_k的精度。过采样生成的合成样本会增加小类别协方差的伪证据反而带偏模型。做类别重平衡时确实慎重点或者干脆用priors参数表达业务偏移。第三把QDA当异常检测器用的变式思路。QDA给出的是后验概率如果样本落在所有类别的分布尾部预测概率可能并不低——因为它是相对概率不是绝对概率。要拿它做异常检测用的是每个类别下的马氏距离本身。距离超过阈值就判为“不在任何已知分布内”这一招在一些工业质检项目中我用过效果比纯阈值还稳定。第四和集成方法配合。QDA本身方差大因为对数据变动敏感容易被过拟合。放进bagging里当基学习器反而能大幅提升稳定性这点可能在主流教材里很少提。用BaggingClassifier包QDA跑了一下在具有一定量噪声的数据集上AUC提升大概在3到5个百分点之间。思路值得记录一个高方差低偏置的模型正好适合bagging去压方差。最后再分享一个小经验模型选型时别被“非线性”三个字牵着走。QDA的非线性是有特定形状的它解决的是“各类协方差不同”的数据而不是“任意非线性关系”的数据。如果你的数据呈现出明显多模态或长尾形态数据空间里密的地方有好几团那QDA就帮不上大忙该上密度估计或者更灵活的非参数模型。使用前先搞清楚数据长什么样这比记住一堆公式实际得多。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。