Matlab贝叶斯分类实战:朴素贝叶斯与判别式分析完整指南
发布时间:2026/9/29 5:07:16 锦皓数字建站

简介一套基于 Matlab 编写的贝叶斯分类完整实现配备图形用户界面适合机器学习初学者、算法研究人员以及需要快速完成特征分类任务的工程人员使用内容涵盖朴素贝叶斯分类器核心代码覆盖数据预处理、训练集与测试集划分、模型训练、参数估计、分类预测和性能评估等完整流程。压缩包共 28 个文件以 17 个 .m 源码文件为核心配合 8 个 txt 数据与说明文件、2 个 prj 工程文件、1 个 fig 界面布局文件整体仅 37KB轻量且便于部署其中 fig 与 prj 让不熟悉编程的用户也能通过界面导入数据、调整参数并直接查看分类结果。目前已有 3681 人浏览学习附带多组不同格式的测试数据包括六列属性以空格或逗号分隔的样本、概率相等数据等能够验证算法在不同输入场景下的鲁棒性。深入阅读源码可以学习 fitcnb、predict 等工具箱函数在贝叶斯建模中的实际用法也能掌握如何将算法封装成 GUI 程序对后续开展分类实验或二次开发都有参考价值。1. 贝叶斯分类在 Matlab 里是常被低估的基线凭什么先做它拿到一份带标签的表格数据很多人第一反应是直接上随机森林或 XGBoost但在 Matlab 这个环境里贝叶斯分类才是那个最容易被低估的起点。它代码量小、训练秒级完成、直接输出每个类别的后验概率在特征几百个、样本几万行的中小规模数据上表现常常不比复杂模型差而这个结论很多工程师是在复杂模型调不动之后才回头验证的。这篇笔记把贝叶斯分类在 Matlab 里的两条路线都讲透fitcnb 的朴素贝叶斯和 fitcdiscr 的判别式贝叶斯分类并给出能直接复现的最小代码、关键参数和踩坑记录。适合正在做分类任务的工程师也适合赶图像处理大作业、需要一个可靠基线模型的学生。2. 贝叶斯分类的理论先立住朴素假设、MAP 与判别式分类器2.1 从后验最大化出发为什么朴素贝叶斯“朴素”还能打对一个样本 x贝叶斯决策要找的是让后验概率 P(y|x) 最大的类别 y。用贝叶斯公式展开P(y|x) P(x|y) * P(y) / P(x)分母 P(x) 与类别无关实际比较时直接约掉。为了防止概率连乘产生下溢实现里通常取对数把乘法变成加法log P(y|x) 正比于 log P(x|y) log P(y)朴素贝叶斯的“朴素”在第二步它假设在给定类别 y 的条件下各个特征条件独立于是P(x|y) P(x1|y) * P(x2|y) * ... * P(xd|y)每个特征的分布单独估计互不干扰。这里的“条件独立假设”绝大多数真实数据都不满足但注意我们做的是 argmax不是估计绝对概率。只要每个类别的似然估计偏差方向一致、后验排序不乱分类结果依然正确。很多资料只讲“朴素所以天真”没讲它错得稳定也是一种能力——这正是贝叶斯分类能当基线、甚至在一些文本分类任务上压制复杂模型的主要原因。先验 P(y) 的设定也要说清楚。如果先验完全从数据里统计贝叶斯分类等价于最大似然估计如果业务上已知某个类别更容易出现就把这个知识通过 P(y) 注入。这就是 MAP 和 MLE 的区分MLE 不考虑先验MAP 考虑。在 Matlab 的 fitcnb 里这个参数叫 Prior下面第 4 章会专门讲怎么调。2.2 判别式贝叶斯分类LDA/QDA 与朴素贝叶斯的分水岭朴素贝叶斯不建模特征之间的相关性而判别式贝叶斯分类直接在多元高斯假设下建模整个特征向量。LDA线性判别假设所有类别的协方差矩阵相同所以决策边界是线性的QDA二次判别允许每个类别有自己的协方差矩阵决策边界是二次曲面。Matlab 里对应函数是 fitcdiscr通过 DiscrimType 参数切换。如果把高斯朴素贝叶斯写成矩阵形式它等于假设特征协方差矩阵是对角阵即特征之间在类别内部完全无关。这个认识很有用当你的特征明显相关时朴素贝叶斯会吃亏而 fitcdiscr 能利用这部分信息。代价是参数数量上升LDA 要估计一个特征维度平方量级的共享协方差矩阵QDA 则对每个类别都估计一个。每类样本数小于特征数时协方差矩阵奇异表现就是 Matlab 直接报错后面避坑章节会专门讲。选型的经验是特征数量少、样本量中等、各类协方差结构相似优先 LDA样本量大、各类数据形态差异明显试 QDA特征之间相关性弱、想快速拿基线朴素贝叶斯就够了。实际项目中我不只一次看到有人把三种模型都跑一遍最后发现 fitcnb 和 LDA 结果几乎一样——那说明你的特征相关性没有想象中严重用简单的那个就行。3. 用 Matlab 跑通贝叶斯分类fitcnb 最小可复现流程只要装好的 Matlab 自带 Statistics and Machine Learning Toolbox本文全部代码都能跑不用额外装任何包。下面从最小流程开始先跑通再讲参数。3.1 最小训练与预测fitcnb cvpartition 的最小流程clear; clc; close all; rng(2024); % 固定随机种子保证结果可复现 % 自带鸢尾花数据集meas 是 150x4 的特征矩阵 load fisheriris Y categorical(species); % 将类别转为 categorical X meas; % 按 7:3 划分训练/测试集cvpartition 对分类标签默认分层 cv cvpartition(Y, HoldOut, 0.3); idxTrain training(cv); idxTest test(cv); XTrain X(idxTrain, :); YTrain Y(idxTrain); XTest X(idxTest, :); YTest Y(idxTest); % 训练朴素贝叶斯分类器 mdl fitcnb(XTrain, YTrain, ... ClassNames, {setosa, versicolor, virginica}, ... DistributionNames, normal); % 预测第一个输出是硬标签第二个是后验概率 [predLabel, posterior] predict(mdl, XTest); % 混淆矩阵看错在哪里 figure confusionchart(YTest, predLabel); acc mean(predLabel YTest); fprintf(测试集准确率: %.2f%%\n, acc * 100);rng(2024) 的作用是把随机划分固定住否则每次跑结果都有细微差异排错时很难判断是代码问题还是数据划分运气问题。cvpartition 的 HoldOut 参数指定测试集比例0.3 表示测试集占三成它对分类标签默认做分层划分类别不平衡时不会把某一类全抽到测试集里。ClassNames 必须显式写全这是新手最容易漏的如果训练集切分后恰好缺失某个类别predict 阶段类别映射会错位甚至报错。DistributionNames 先用 normal也就是每个类、每个特征单独估计均值和方差这是最稳的默认选择。跑完看混淆矩阵如果对角线上的数字明显占优说明模型没有翻车可以从这里继续往下调。3.2 判别式贝叶斯分类fitcdiscr 的 LDA 与 QDA 模板% LDA要求各类协方差相同边界是线性的 ldaMdl fitcdiscr(XTrain, YTrain, ... DiscrimType, linear); % QDA每类一个协方差矩阵边界是二次的 qdaMdl fitcdiscr(XTrain, YTrain, ... DiscrimType, quadratic); % 样本量不足或协方差奇异时先用 pseudo 版本不报错但会损失部分精度 pseudoMdl fitcdiscr(XTrain, YTrain, ... DiscrimType, pseudoLinear); predLDA predict(ldaMdl, XTest); predQDA predict(qdaMdl, XTest);fitcdiscr 和 fitcnb 的差别在 DiscrimType。linear 用所有类别共享的协方差矩阵参数少、鲁棒性高但假设各类数据形状一样quadratic 每类各自估计协方差矩阵能刻画形状差异但每类需要的样本量也更大经验值是每类样本数至少超过特征数。pseudoLinear 和 pseudoQuadratic 是协方差奇异时的“后悔药”用伪逆或其他修正手段让训练不崩精度会有损失但至少能出结果。选型判断就一句话先跑 linear看混淆矩阵里哪些类容易混如果混的类在特征图里形态差异明显再换成 quadratic 比较。注意 QDA 不是免费午餐每类样本太少时换上 quadratic 反而比 linear 更容易过拟合。3.3 拿回后验概率predict 的第二个返回值怎么用% predict 有三个输出硬标签、后验概率、期望代价 [predLabel, posterior, cost] predict(mdl, XTest); % 后验概率的列顺序就是 ClassNames 里的顺序 [maxScore, idx] max(posterior, [], 2); predByProb mdl.ClassNames(idx); % 与 predLabel 应该一致 % 低置信样本单独挑出来看 lowConf find(maxScore 0.7); fprintf(低置信样本数: %d\n, length(lowConf));predict 的第一个输出是模型替你做的取舍第二个输出才是核心资产每一行是样本对每个类别的后验概率。第三个输出 cost 是期望误分类代价模型选标签时实际比较的是 cost不是直接取后验最大的类——这一点很容易误解。后验概率的用处很多。做图像分割大作业时可以把每个像素的颜色值当特征喂给 fitcnb输出一张和原图同尺寸的置信度图阈值一调就能做前景背景分离。这个用法比单纯看准确率有价值得多后面第 6 章展开讲。4. 把模型参数调明白DistributionNames、Prior 与误分类代价fitcnb 的参数不多但每个都对结果有实质影响。我常用的调参顺序是先定分布假设再看先验最后用代价矩阵做业务修正。4.1 连续特征用 normal 还是 kernel分布设定的选型逻辑分布设置适用特征使用场景注意点normal连续、近似正态多数表格数据的默认选择特征严重偏态时概率估计会失真kernel连续、偏态/多峰/重尾数据分布形态明显非高斯时样本少时带宽估计不稳高维下内存压力大mvmn离散/名义变量类别型特征训练集没见过的取值直接报错DistributionNames 默认是 normal它对每个类、每个特征单独估计均值和方差。这个假设在特征近似单峰、对称时没问题但真实数据经常是偏态或多峰的比如传感器振动数据、收入分布这类长尾特征。此时 normal 会把一个双峰分布硬拟合成一个高斯概率密度估计失真后验排序就可能乱。kernel 选项会换成核密度估计Matlab 对每个特征自动估计带宽不需要你手工调。它的拟合能力强但样本量少时带宽估计会不稳出现过拟合特征维度高时每个类都要拟合 d 个核密度训练时间会明显上涨。我的习惯是先用 normal 跑通如果交叉验证分数不理想再挑几个偏态明显的特征换成 kernel不要一上来就全局 kernel。mvmn 是给离散特征用的多项式分布模型直接统计每个取值在各类里出现的频次。它简单直观但有一个硬伤预测时遇到训练集没出现过的取值后验概率直接变成 0Matlab 会报错。解决办法见第 5 章。4.2 类别不平衡时修正先验Prior 参数背后的逻辑% 默认按样本比例估计先验 mdlDefault fitcnb(XTrain, YTrain); % 认为三个类别应当等权重 mdlUniform fitcnb(XTrain, YTrain, Prior, uniform); % 自定义先验按业务经验给出 mdlCustom fitcnb(XTrain, YTrain, Prior, [0.7 0.2 0.1]);Prior 参数的默认值是 empirical就是从训练集里统计类别频率。当类别分布均衡时没问题但数据不平衡时会出大问题比如故障样本只占 1%默认先验会让模型倾向于把一切都预测为正常类因为先验概率已经把正常类推到了压倒性位置。设成 uniform 会让模型回到“只看似然”的状态少数类召回率会提上来但代价是多数类准确率下降。更专业的做法是自定义先验如果业务上知道故障发生率就是 5%就把 [0.95, 0.05] 直接传给 Prior。注意 Prior 对 trained model 的 predict 阶段同样生效改先验等于同时改了训练和推理时的决策偏向。这里要纠正一个常见误区类别不平衡本身不是问题代价不对称才是。如果漏报故障和误报正常的损失一样那按频率做先验其实合理。先想清楚业务上哪种错误更贵再决定 Prior 怎么设。4.3 误分类代价与阈值贝叶斯决策业务化% 两类1正常2故障漏报故障的代价设成误报的 5 倍 Cost [0 1; 5 0]; mdlCost fitcnb(XTrain, YTrain, Cost, Cost);Cost 矩阵是让贝叶斯分类器“业务化”的接口。Cost(i,j) 表示真实类别 i 被预测成 j 的代价默认全是 1。上面这个例子中把故障漏报的代价设为 5模型在两类后验接近时会更偏向预测为故障因为误报的期望代价比漏报小。要注意 Cost 影响的是 predict 阶段选标签的策略不影响后验概率本身。换句话说训练出来的概率是一样的只是决策线被移动了。这点和直接对 posterior 设阈值本质等价二分类时 Cost 非对称就等价于把决策阈值从 0.5 挪到一个更保守的位置。选一种方式实现即可不要两个同时调否则你自己都说不清模型在做什么。5. 贝叶斯分类常见问题与避坑5 个翻车现场还原5.1 mvmn 遇到未见取值离散特征带来的零概率现象预测时报错提示训练数据里没有出现过的 level后验计算直接失败。原因mvmn 分布下模型记录的是每个离散取值在各类里的出现频次。新取值没有任何统计记录条件概率 P(x|y) 算出来是 0连乘后整行后验都为 0分类无从谈起。这是朴素贝叶斯在离散特征上最经典的翻车点fitcnb 没有开箱即用的拉普拉斯平滑参数不能指望模型自己兜底。解决训练前做取值归并把频次低于阈值的离散取值统一合并成 Other 类预测前对新数据做同样的映射保证训练和预测走同一个预处理函数。如果离散特征取值天生就是开放集合比如文本分类里的词建议改用 kernel 或者干脆换成别的模型。5.2 后验概率全压成 0 或 1别把概率当校准值用现象预测结果看着不错准确率很高但 posterior 输出大量 0.99 甚至 1.0看起来很“自信”。原因朴素贝叶斯把弱相关的特征当成独立证据累乘重复信息被反复计票概率估计的绝对值会被推向极端。分类决策通常不受影响因为相对排序仍然正确但后验的绝对值已经不能当作真实置信度来读。解决不要把后验当校准概率用。需要给业务方解释“置信度”时优先展示排序关系如果非要校准单独留一个验证集做 Platt 缩放或保序回归。更简单的方式是只取后验做阈值筛选不纠结它是不是真实概率。5.3 特征强相关时朴素假设失效重复特征等于翻倍投票现象把两个强相关特征同时放进模型训练精度不但没提升反而下降或者把同一个特征复制一份放进特征矩阵模型立刻变“自信”了。原因朴素贝叶斯的条件独立假设下特征被当成独立证据。两个强相关特征提供的是同一份信息却被计了两票相当于给决策边界加了错误权重。解决先做相关性筛查相关系数超过 0.8 的特征只留一个或者用 PCA 降维后再喂给 fitcnb。如果项目里特征相关性强直接用 fitcdiscr 的 linear 模式它能建模协方差天然规避这个问题。5.4 kernel 密度在过拟合与速度之间摇摆带宽的玄学现象训练集上准确率接近满分交叉验证分数波动很大特征一多训练时间从秒级变成分钟级。原因kernel 的带宽是 Matlab 基于数据自动估计的。样本量少时带宽估计会偏小核密度曲线跟着每个样本走过拟合随之而来。高维场景下每个类、每个特征都要拟合一个密度估计计算量和内存都上去了。解决kernel 慎用在高维数据上。可以先跑 normal 看基线只在明显偏态的单特征上换成 kernel。如果必须用 kernel先做特征选择把维度压到十几个以内。带宽自动化对多数场景够用不建议手工去碰。5.5 fitcdiscr 的奇异协方差pseudo 是最后的后悔药现象fitcdiscr 训练直接报错提示协方差矩阵奇异或非正定。原因LDA 和 QDA 都要估计协方差矩阵。特征数接近或超过每类样本数时矩阵不满秩求逆失败。QDA 更敏感因为每个类别都要单独估一个矩阵只要有某一类样本偏少就会整场崩溃。解决把 DiscrimType 换成 pseudoLinear 或 pseudoQuadratic让 Matlab 用修正手段强行训练。更稳的路线是先做 PCA 把特征维度压到样本量以下再上 fitcdiscr。如果是 QDA 崩了先检查是不是某个类别的样本数太少优先补样本而不是换模型。6. 进阶把贝叶斯分类器用作不确定性估计与集成基模型6.1 阈值拒识让低置信预测留在流程外thr 0.6; [maxPost, idxPred] max(posterior, [], 2); accept maxPost thr; % accept 为 0 的样本不进自动流程转人工复核或标记待定 rejectIdx find(~accept);这个技巧在业务落地里比调任何参数都实用。分类器不需要对所有样本都硬给一个标签低置信样本直接踢出自动流程准确率立刻提升。thr 的取值通过验证集上的 ROC 曲线来定横轴是误报率纵轴是召回率找到业务能接受的点再反查对应的后验阈值。6.2 随机投影 朴素贝叶斯集成打破特征相关的一种低成本做法% 用两个随机子空间训练两个朴素贝叶斯测试时投票 rng(7) proj1 randn(size(X, 2), 8); proj2 randn(size(X, 2), 8); mdl1 fitcnb(X * proj1, Y, DistributionNames, kernel); mdl2 fitcnb(X * proj2, Y, DistributionNames, kernel); p1 predict(mdl1, X * proj1); p2 predict(mdl2, X * proj2); % 示例两票一致才通过不一致标记待定 finalPred p1; finalPred(p1 ~ p2) 待定;随机投影把原始特征映射到低维子空间相当于每次只保留一部分混合信息天然打断特征间的强相关。多个投影子空间训练多个朴素贝叶斯投票时模型之间差异越大集成的提升越明显。这个做法不追求单个模型精度追求的是“不同视角下的分歧”和随机森林的随机特征选择思路一致。特征多、样本不太少时值得一试样本量小时直接降维更稳。6.3 验证习惯交叉验证与 ROC 一起看% 5 折交叉验证看整体损失 cvm crossval(mdl, KFold, 5); loss kfoldLoss(cvm); fprintf(交叉验证损失: %.4f\n, loss); % 新版本用 rocmetrics 画 ROC旧版本用 perfcurve目的一样 scores posterior(:, 2); rocObj rocmetrics(YTest, scores, mdl.ClassNames(2)); plot(rocObj);只看测试集准确率是不够的尤其在类别不平衡数据上准确率天然虚高。交叉验证损失给出的是模型稳定性的判断ROC 曲线给出的是决策阈值可调空间的判断。两个一起看才能决定贝叶斯分类器是作为最终方案留下还是只当基线。我个人的习惯是任何分类项目都先花五分钟跑一个 fitcnb 基线把训练耗时、混淆矩阵、ROC 三个东西放在桌面上再决定要不要上复杂模型。以前做图像分割大作业时用颜色直方图加 fitcnb输出后验置信度图效果比预想的好后期把阈值调高做拒识就交了差。贝叶斯分类不是万能的但它是验证“数据里到底有没有信号”的最快手段省下来的时间值得花在特征和业务规则上。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。