ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Matlab贝叶斯分类实战:从先验到后验的小样本分类指南

Matlab贝叶斯分类实战:从先验到后验的小样本分类指南 简介基于Matlab的贝叶斯分类完整源码包面向机器学习初学者与算法实现者解决分类任务中从模型训练到GUI交互的落地问题。资源包含28个文件核心为17个m脚本实现朴素贝叶斯训练、预测、概率计算等功能附带8个txt数据文件覆盖多列属性、概率相等及原始测试数据等以及2个prj工程文件和1个fig图形界面文件整体仅37KB结构清晰易读。目前已有3681人学习浏览适合需要快速上手贝叶斯分类Matlab实现的用户。通过该压缩包可获取带操作界面的完整分类流程不仅支持数据导入、参数设置、模型训练与结果展示还提供不同属性规模的测试数据便于对照验证。深入研究源码可理解先验/后验概率计算与分类器构建细节亦可基于工程文件二次开发适配自身数据集。1. 贝叶斯分类的 Matlab 落地小样本分类问题的第一个选择传感器数据只有两百个样本却要求输出一个概率而不是一个打死结的标签——这是我处理过最多的分类需求。贝叶斯分类不绕弯子把类别的先验概率和样本的条件概率乘起来后验谁大谁赢。放在 Matlab 环境里它不需要搭建神经网络也不需要 GPU统计与机器学习工具箱里的 fitcnb 一行建模型、predict 一行出后验概率适合数据量小、特征带噪声、模型还要能解释的工程场景。这篇文章适合手里有标注数据、想快速迭代出第一个能用的分类器的人你也别指望它包打天下特征之间强相关、类别严重不平衡的时候它有自己的脾气。我会从公式压缩版讲起直接落到最小可运行代码、参数调整和踩坑现场。2. 从先验到后验贝叶斯分类器在 Matlab 里到底算了什么先说公式。朴素贝叶斯要算的是 P(类别|特征) 这个后验概率贝叶斯公式把它拆成 P(类别) 乘上“每个特征在这个类别下的概率”再除以 P(特征)。分类时只比大小分母对所有类别是同一个常数所以代码里很少真的算它。真正干活的是两部分先验 P(Yk) 和数据似然 P(X_j|Yk)。先验来自训练集里每个类的样本占比fitcnb 默认按empirical算似然则由每个特征的分布假设决定。Matlab 里 fitcnb 把这套东西封装成对象训练完模型的属性里直接能看到每类均值和方差这也是我喜欢它的原因——它不是黑匣子。2.1 先挑工具统计工具箱里够用的三个入口做贝叶斯分类Matlab 市面上常见入口其实是三个fitcnb 朴素贝叶斯、fitcdiscr 线性/二次判别分析、以及 ClassificationNaiveBayes 这个老式类。三者都在 Statistics and Machine Learning Toolbox 里如果装的 matlab 版本没勾这个工具箱跑 fitcnb 会直接报Undefined function。我习惯先跑一句ver(stats)确认工具箱在位再动手见过不少同事在 matlab 下载装完后卡在工具箱缺失换了个版本重装才解决。函数接口在 2023b 和 2026b 之间基本没变老代码直接挪过来能跑真正棘手的往往不是函数名而是对象版本和工具箱许可。入口核心假设适合场景输出fitcnb特征条件独立逐特征分布建模小样本、混合类型特征、要概率输出类别、后验概率、期望损失fitcdiscr所有特征联合高斯特征相关性较强且近似正态线性或二次判别边界ClassificationNaiveBayes同 fitcnb老代码兼容新代码别用同 fitcnbfitcdiscr 和 fitcnb 的区别值得多说一句fitcdiscr 假设整个特征向量服从多元高斯要估计特征间的协方差矩阵样本量上千时才稳。小样本下协方差估计极易失真这是它经常翻车的地方。fitcnb 不估计特征间协方差所以参数数量少数据少的时候反而稳。我自己的选型习惯是先看样本量少于 500 直接走 fitcnb样本够多、特征又明显相关才把 fitcdiscr 拉出来对比。2.2 朴素贝叶斯为什么敢假设“特征独立”条件独立是指给定类别后每个特征单独建模特征之间不再搭桥。比如诊断设备故障温度高和高频振动同时出现朴素贝叶斯会认为这两个现象各自独立地指向故障类别而不会去建模“温度高时振动也更容易高”这种关联。代价是如果两个传感器读数其实来自同一个物理量模型会把这条信息重复计权概率输出会偏向 0 或 1。好处是模型参数数量少不容易过拟合对缺失特征也更宽容某列特征暂时没采到其他特征的计算还能继续走。这个假设听起来理想化但在故障检测、文本分类这类特征往往由人工挑选的场景里现实效果比想象的靠谱。最典型的例子是 matlab 图像处理大作业里把像素的 RGB 三通道当特征三个通道本身高度相关直接送进朴素贝叶斯会出问题但先做 PCA 去相关再进模型结果立刻正常。独立假设不是用来迷信的而是用来理解模型为什么在某些数据上“概率极端”的钥匙。2.3 选高斯还是核密度分布假设决定模型形状fitcnb 里最常用的两个分布假设是 normal 和 kernel。normal 给每个类、每个特征各估一个均值和方差参数少数据量小时最稳kernel 用核密度估计去贴合任意形状能处理多峰分布但要额外调带宽 Width数据少时容易过拟合。连续特征我一般先全上 normal如果验证分数明显偏低再挑可疑特征换 kernel。离散特征比如开关状态、计数项用 mvmn 做多项分布建模特别是图像直方图这类计数特征mvmn 比硬套 normal 合理得多。分布适用特征参数特点主要风险normal连续、单峰每类一均值一方差多峰特征会失真kernel连续、任意形状需要调带宽小样本易过拟合mvmn离散、计数按类别分布建模取值稀疏时概率为 0对比深度学习的 matlab 实现贝叶斯分类不需要选网络结构、不需要配训练轮次数据量 500 以下我几乎不犹豫选它。深度学习要在几百个样本上跑出稳定结果说实话主要靠运气加数据增强贝叶斯分类至少给你一个可解释的均值和方差你知道模型为什么这么判断。接下来直接写代码把流程跑通。3. 跑通第一个贝叶斯分类器最小可复现的 Matlab 流程这一章的所有代码都可以直接复制到脚本里跑。我会故意不用内置数据集因为自己生成数据时你知道真实的类别分布更容易判断模型输出是否合理。3.1 生成一个有标签的二维数据集我习惯先造一个完全可控的二维两类数据来验证流程再去碰真实数据。mvnrnd是统计工具箱里生成多元高斯样本的函数第一个参数是均值向量第二个参数是协方差矩阵第三个参数是样本数。代码故意把两类分布压得有一点重叠这样后验概率不会总是 0.99才能看到贝叶斯分类器的判断底气。rng(42); % 固定随机种子保证可复现 % 第一类80 个样本均值 [1 1]协方差矩阵给出轻微相关性 X1 mvnrnd([1 1], [0.5 0.2; 0.2 0.4], 80); % 第二类80 个样本均值 [3 3]分布稍微更散 X2 mvnrnd([3 3], [0.7 0.1; 0.1 0.3], 80); X [X1; X2]; % 160 x 2 的特征矩阵 Y [ones(80,1); 2*ones(80,1)]; % 类别标签1 和 2rng(42)是整段代码里最容易漏掉但最影响复现的一行。没有固定随机种子每次跑出来的数据集都不同后面调参时你会分不清分数变化是参数引起的还是数据波动引起的。协方差矩阵里的0.2和0.1是刻意引入的弱相关让两个类都不是完美的圆形分布。3.2 fitcnb 建模型与 predict 出概率模型训练只调用一行 fitcnb。默认情况下它对每个连续特征都用高斯分布建模这里特征只有两列所以不需要额外指定分布类型。Mdl fitcnb(X, Y); % 训练朴素贝叶斯分类器 newSample [2.1 2.4]; % 模拟一个落在两类的重叠区的新样本 [label, posterior] predict(Mdl, newSample); disp(label); disp(posterior);predict 有两个输出label是硬标签posterior是后验概率矩阵每一列对应一个类别。对工程使用我只看 posterior 的最大列大于 0.8 算是敢下手0.5 到 0.8 之间一律当成“需要人工复核”。predict 还有第三个输出是期望损失实际项目里我会用它做拒绝决策比单纯设阈值灵活。3.3 用 HoldOut 验证别拿训练集自己骗自己如果直接把 X 和 Y 都喂给 fitcnb然后再对同一批 X 做 predict准确率会虚高因为模型见过这些样本。正确的做法是用 cvpartition 留出一部分样本当测试集模型只碰训练集测试集从头到尾不参与拟合。cv cvpartition(Y, HoldOut, 0.3); % 留出 30% 当测试集 trainIdx training(cv); % 训练集逻辑索引 testIdx test(cv); % 测试集逻辑索引 Mdl fitcnb(X(trainIdx, :), Y(trainIdx)); Ypred predict(Mdl, X(testIdx, :)); acc sum(Ypred Y(testIdx)) / sum(testIdx); % 准确率 C confusionmat(Y(testIdx), Ypred); % 混淆矩阵 disp(acc); disp(C);HoldOut, 0.3表示把 30% 数据留作测试。样本量只有 160 时测试集大约 48 个样本准确率会有一定波动这是正常的不必因为一次 0.93 一次 0.88 就怀疑代码。混淆矩阵的行是真实类别列是预测类别对角线是判对的。我每次都会打印它准确率会掩盖“某一类全错”的问题。3.4 从二类扩展成多类fitcnb 原生支持多类类别数大于 2 不需要额外包装。只需要在 Y 里加入第三个类别模型会自动为每个类估计一组分布参数。% 新增第三类60 个样本均值 [5 5]用单位矩阵缩放作为协方差 X3 mvnrnd([5 5], eye(2) * 0.3, 60); Xall [X; X3]; Yall [Y; ones(60,1) * 3]; % 类别 3 Mdl3 fitcnb(Xall, Yall); testSample [4.5 4.8]; [label3, posterior3] predict(Mdl3, testSample); disp(label3); disp(posterior3);多类情况下 posterior 矩阵变成 1 行 3 列每一列对应 classes(Mdl3) 里的一个类别。这里有个细节classes 属性的顺序不一定是 1、2、3它按 Y 里第一次出现的顺序排列。我在写循环评估多类模型时从来不用 “第几列就是类别几” 的假设一律现查 classes() 再对应。贝叶斯分类器在多类场景的分数通常比二类低一点因为类别重叠区变多这不算模型缺陷是数据本身的可分性问题。4. 调参前先看懂状态Prior、DistributionNames 与交叉验证fitcnb 最常被调的参数就三个DistributionNames、Prior、交叉验证方式。前两个决定模型的数学形态交叉验证决定你敢不敢信这个模型。其他地方像 Kernel 带宽属于分布参数的下级配置等前三个稳定后再碰。4.1 DistributionNames连续、离散、混合特征怎么声明默认情况下 fitcnb 把所有特征当连续高斯建模。如果数据里有离散计数特征必须显式声明。DistributionNames 支持两种写法一个字符串表示所有特征用同一种分布一个 cell 数组按特征维度逐个指定。% 假设 X 有两列第一列温度(连续)第二列开关状态(0/1 离散) MdlMix fitcnb(X, Y, DistributionNames, {normal, mvmn});上面代码表示第一列特征按高斯建模第二列按多变量多项式分布建模。mvmn 要求输入是非负整数比如开关状态 0/1、档位 1/2/3。如果往里传小数拟合结果会变得不可理喻。我见过一个案例同事把温度的小数值传进 mvmn后验概率直接变成 NaN查了半天才发现是分布类型和数据类型不匹配。kernel 分布则适合那些“看起来不服从高斯”的连续特征比如有明显双峰的数据。写法是把对应位置的分布名改成kernel再用Width参数控制平滑度MdlKernel fitcnb(X, Y, DistributionNames, {kernel, normal}, Width, 0.5);Width是核函数的带宽值越小拟合越尖锐值越大曲线越平。默认情况下 Matlab 按数据自适应选一个值但我习惯从 0.5 开始手动扫因为自适应结果在小样本上经常偏小造成过拟合。4.2 Prior不平衡数据集的第一个后悔药fitcnb 默认按样本占比估计先验。类别 0 有 90% 样本、类别 1 只有 10% 时模型预测天然偏向多数类。处理方式先把测试集混淆矩阵打出来看召回率别只看准确率如果少数类几乎全被吃掉就手动把先验拉平。% 强制两个类别的先验概率相同 MdlBalanced fitcnb(X, Y, Prior, [0.5 0.5]);注意Prior 向量顺序要和 classes(Mdl) 一致。我一般先跑一次默认模型打印 classes(Mdl) 确认顺序防止把两个类的先验写反。写反之后模型会把少数类当成多数类分数更难看。先验不是玄学它把“两个类别出现概率相等”这种领域知识硬写进模型。如果业务上确实是少数类发生概率低那先验维持默认也合理但如果你想训练一个能识别少数类的模型拉平先验是最快的干预手段。4.3 用 KFold 交叉验证确认模型不是昙花一现HoldOut 只跑一次随机分割带来的运气成分大。KFold 5 折把数据切成五份轮流当验证集最后平均损失。交叉验证的意义不在调参而在给你一个置信区间如果两次随机种子跑出来的交叉验证分数差超过 5%说明数据量不够或模型不稳定。cvMdl crossval(Mdl, KFold, 5); avgLoss kfoldLoss(cvMdl); accuracy 1 - avgLoss; disp(accuracy);样本量少于 200 时我一般用 5 折多于 500 才用 10 折。K 越大训练集越大偏差越小但计算量涨了不算过拟合风险也在涨。LeaveOneOut 在小样本上结果方差大得吓人我基本不碰。4.4 保存与加载模型别每次重训模型训练完直接保存成 mat 文件部署时加载再用。训练和预测分离这是工程化最基本的一步。save(bayesModel.mat, Mdl); % 保存模型对象 load(bayesModel.mat, Mdl); % 加载模型对象 label predict(Mdl, newData); % 直接用加载的模型预测注意换环境加载模型前先查ver(stats)确认对方装了同名或向后兼容的统计工具箱否则 predict 会因为对象版本不一致报错。我在不同年份的 matlab 版本之间来回切时踩过这个坑模型文件本身小但版本敏感度不低。5. Matlab 贝叶斯分类实战避坑五个反复出现的翻车现场以下五条都是我在真实项目里见过或踩过的坑每条按现象、原因、解决的顺序写。前三条和贝叶斯模型的数学假设直接相关后两条是数据质量问题但都能让模型输出集体失真。5.1 特征强相关导致后验概率接近 0 或 1现象predict 输出的后验概率要么 0.999要么 0.001几乎没有中间值。模型在验证集上的准确率不低但你要拿概率做风险决策时完全没法用。原因朴素贝叶斯的条件独立假设被强相关特征击穿。两个传感器读数来自同一物理量时模型把这条信息当成两个独立证据重复计权证据翻倍后验概率自然冲到极端。解决先做主成分分析去掉特征相关性再喂给 fitcnb。降维不是必须追求信息保留率压到能消掉相关性的维度即可。[coeff, score] pca(X); Xpca score(:, 1:2); % 取前两个主成分 MdlPca fitcnb(Xpca, Y);如果不想 PCA也可以在特征工程层面保留一个代表直接删掉其余冗余特征。比如温度和温控器反馈两个特征保留温度即可。5.2 类别样本太少导致 predict 给出异常后验现象某类别的后验概率永远显示 0或者干脆是 NaN。训练过程不报错predict 时突然崩。原因某个类别只有几个样本高斯模型估计出来的该类别方差接近 0密度函数除零mvmn 分布下某个取值从来没有在该类别出现过概率连乘后直接归零。解决先看模型对象里的分布参数Mdl.DistributionParameters如果某列某类的方差比其他类小几个数量级对这个特征改用核密度估计或者往数据里加极小的 jitter。临时处理可以这样% 给特征加一个极小的噪声避免方差为 0 Xjitter X 1e-6 * randn(size(X)); MdlSafe fitcnb(Xjitter, Y, DistributionNames, kernel);长期做法还是收集更多样本或把样本量过小的类别跟相邻类别合并。这个坑在小样本故障数据里最常出现。人类别本来就少还要按型号细分结果每个类只剩个位数样本。5.3 混合数据类型没声明模型直接打偏现象连续特征和计数特征放一起训练validation 分数看起来很糟有时候甚至会出现复数或 NaN 概率。原因默认的 normal 分布要求连续实数特征计数特征取值是 0、1、2 这种非负整数硬套高斯分布后模型对这类特征会估计出奇怪的均值和方差。解决用 cell 数组按特征维度指定分布类型。把离散列写进mvmn连续列保持normal。这个改完后验概率会立刻回归合理范围。检查方法也简单打印Mdl.DistributionParameters看每个特征的分布名是否和你预期一致。5.4 类别不平衡被整体吞掉现象准确率停在 95% 以上看起来很漂亮但看混淆矩阵时少数类那一行的召回率接近 0。所有测试样本都被判成多数类。原因默认先验empirical按样本比例估计少数类占比太低时后验最大的永远是多数类而且根本轮不到少数类参与竞争。解决至少做两步。第一步把Prior设成uniform或手动[0.5 0.5]第二步评估指标从 accuracy 换成召回率、F1 或混淆矩阵。必要时对少数类做简单重复采样让模型在训练时多看到几个样本。注意先调先验再采样先验都不用改就直接上采样属于白费功夫。5.5 NaN 或方差为 0 的特征混入训练集现象fitcnb 训练不报错但 predict 出来的概率里有 NaN或者准确率异常低。原因某列特征存在 NaNMatlab 默认按行删除如果删除后某类样本太少后续参数估计出问题或者某列特征在所有该类样本里取值恒定方差为 0高斯密度除零。解决训练前先检查数据完整性。我习惯先删掉全为 NaN 的列用中位数填充零散缺失值再检查每列的方差方差为 0 的情况直接删除该列或加 jitter 破零。% 删除全为 NaN 的列 X(:, all(isnan(X))) []; % 用列中位数填充零散 NaN X fillmissing(X, constant, 0);填充值用中位数比用均值更稳因为中位数对异常值不敏感。为 0 的特征如果业务上确实有区分意义加1e-6 * randn纯粹为了数值稳定不影响分类逻辑。6. 让结果可见混淆矩阵、ROC 与决策边界可视化模型训练完第一件事不是看参数而是把评价指标和决策边界画出来。贝叶斯分类有个天然优势——它输出后验概率所以 ROC 曲线比只给标签的模型更顺滑。6.1 不要只盯 accuracy准确率会掩盖两类错误的不对称性。我一般把混淆矩阵和 ROC 一起打印。perfcurve 是最常用的 ROC 函数输入真实标签、后验概率矩阵中某一列、以及该列的类别编号。[fp, tp, ~, auc] perfcurve(Y(testIdx), posterior(:, 2), 2); plot(fp, tp); grid on; xlabel(False positive rate); ylabel(True positive rate); title([ROC, AUC , num2str(auc)]);AUC 大于 0.9 说明模型有区分度介于 0.7 到 0.9 之间说明特征还有优化空间低于 0.7 基本可以换特征工程思路了再调贝叶斯参数也救不回来。新版 matlab 推荐用 rocmetrics但 perfcurve 在老项目里依然稳定二选一即可。6.2 决策边界可视化贝叶斯分类器的决策边界不总是直线尤其是用了 kernel 分布之后。画边界能帮你快速发现两类是否真的可分也能直观看到模型的置信度变化。x1g linspace(min(X(:,1))-1, max(X(:,1))1, 200); x2g linspace(min(X(:,2))-1, max(X(:,2))1, 200); [G1, G2] meshgrid(x1g, x2g); labelGrid predict(Mdl, [G1(:), G2(:)]); labelGrid reshape(labelGrid, size(G1)); imagesc(x1g, x2g, labelGrid); set(gca, YDir, normal); % 防止 y 轴倒置 hold on; scatter(X(Y1,1), X(Y1,2), b); scatter(X(Y2,1), X(Y2,2), r);imagesc 默认 y 轴方向是反的不写set(gca,YDir,normal)画出来的图和实际数据坐标对不上这个细节我至少翻车过两次每次都以为是模型错了结果只是显示问题。边界图上如果两类交界带很宽说明模型在这个区域的后验概率在 0.5 附近徘徊实际业务里遇到这种样本就直接标“待复核”不要硬给结论。最后一个忠告来自我自己的经验最早我把高度相关的信号特征直接送进朴素贝叶斯概率输出几乎只有 0 和 1模型看似确定实则盲目后来先做了 PCA 再去相关性模型才真正可信。贝叶斯分类的好处是透明、快速数学假设也清清楚楚代价是这些假设不满足时它不会主动告诉你全靠你拿验证结果去反推。模型对象是黑的还是白的取决于你愿不愿意把参数和可视化都打出来看一遍。希望帮到你。本文还有配套的精品资源点击获取
返回列表