MATLAB手写数字识别:贝叶斯与距离测度分类器实现指南
发布时间:2026/10/2 8:28:46来源:尧图网络
简介《模式识别算法MATLAB实现》是以模式识别与智能计算为主题的PDF电子书融合统计学、神经网络、数据挖掘、机器学习、人工智能、群智能计算等学科思想适合计算机、信息工程、生物医学工程、智能机器人学等专业的研究生、本科生及工程技术人员。全书14章首先介绍特征选择与优化、模式相似性测度然后系统讲解贝叶斯分类器、判别函数分类器以及BP神经网络、径向基函数、自组织竞争网络、概率神经网络等各类神经网络分类器并覆盖决策树、粗糙集、聚类分析、模糊聚类以及禁忌搜索、遗传算法、蚁群算法、粒子群算法等聚类方法。书中以手写数字识别为贯穿实例给出详细实现步骤和MATLAB代码读者可对照练习并迁移到文字识别、字符识别、图形识别等实际项目。压缩包内仅含1个PDF文件大小17.87MB轻量便于下载学习。目前已有311人浏览学习对于希望系统掌握模式识别主流算法并提升MATLAB编程实现能力的读者具有较好的参考价值。1. 手写数字识别与贝叶斯、距离测度分类先让 MATLAB 跑起来做模式识别算法复现最怕的不是原理看不懂而是对着书敲了一下午代码一运行满屏红色报错。手写数字识别正好是这类资料的理想载体——样本好获取、类别清楚、特征提取直观而且它能把贝叶斯分类、距离测度、判别函数这些经典算法串成一条完整的落地链路。我拆过不少 MATLAB 模式识别资源这套《模式识别与智能计算——MATLAB 技术实现》第 3 版的实际价值在于每个算法都配了能直接跑的代码和手写数字实例从特征选择到分类器设计到聚类分析一共 14 章覆盖了贝叶斯、神经网络、决策树、粗糙集、遗传算法聚类、蚁群聚类、粒子群聚类等主流方法。适合正在做课程设计、本科毕设或者刚转算法岗需要快速补经典模型代码的从业者。这篇笔记我就按自己的复现顺序来讲先说识别效果最好的贝叶斯分类器再对比三种距离测度最后给出参数设置和踩坑记录。2. 贝叶斯分类器实现最小错误率、最小风险与二值数据的三种写法2.1 贝叶斯决策的核心概念与在 MATLAB 里的落地思路贝叶斯分类器解决的是一个带不确定性的决策问题给定一个手写数字的特征向量 X它到底属于 0~9 中的哪一类书上推导了贝叶斯公式但落到 MATLAB 代码里重点其实是三件事先验概率怎么估、类条件概率密度怎么建模、决策规则怎么选。先验概率 P(ωi) 的估计最直接——统计训练样本里每个数字出现的频率就行。类条件概率密度 P(X|ωi) 才是麻烦的地方特征维度一高直接估计联合密度几乎不可能。常见做法是假设每一类服从多元正态分布用训练样本估计每类的均值向量和协方差矩阵这样 P(X|ωi) 就变成了一个可计算的解析式。这个假设在工程上是妥协但对 MNIST 这类干净的手写数字数据集效果已经很能打。决策规则有两种最小错误率贝叶斯看的是后验概率最大最小风险贝叶斯则在损失矩阵里引入把 A 错分到 B 的代价。数字识别场景里最小错误率通常够用但如果你做的是医学影像或金融风控这类错分代价不对称的任务就得换最小风险那套。书上这两节各给了独立实现我先说最小错误率的写法。2.2 最小错误率贝叶斯分类器的 MATLAB 实现这里给出一个可以直接跑通的功能函数输入是训练样本、训练标签和待测样本输出是预测标签。核心逻辑是先按类别统计均值和协方差再对每个待测样本计算各类的高斯密度最后取后验概率最大的一类。function predict_label bayes_min_error(train_data, train_label, test_data) % 最小错误率贝叶斯分类器假设各类服从多元正态分布 % train_data: 训练特征矩阵每列一个样本train_label: 行向量标签 % test_data: 待测特征矩阵每列一个样本 classes unique(train_label); % 类别列表这里是0~9 n_class length(classes); % 逐类估计均值和协方差 for k 1:n_class idx (train_label classes(k)); mu{k} mean(train_data(:, idx), 2); % 每类的均值向量 sigma{k} cov(train_data(:, idx)); % 每类的协方差矩阵 prior(k) sum(idx) / length(train_label); % 先验频率 % 防止协方差奇异加一个小的单位阵扰动 sigma{k} sigma{k} 1e-6 * eye(size(sigma{k}, 1)); end n_test size(test_data, 2); predict_label zeros(1, n_test); for i 1:n_test x test_data(:, i); log_posterior zeros(1, n_class); for k 1:n_class % 用对数形式计算高斯密度避免数值下溢 diff_x x - mu{k}; log_posterior(k) log(prior(k)) - 0.5 * diff_x / sigma{k} * diff_x ... - 0.5 * log(det(sigma{k})) - 0.5 * size(x, 1) * log(2*pi); end [~, best] max(log_posterior); predict_label(i) classes(best); end end这段代码我一般会配合手写数字特征向量直接调用。几个关键参数说明协方差矩阵用cov估计时如果特征维度高于该类的训练样本数矩阵必然奇异这时候加1e-6 * eye的单位阵扰动是必须的否则det(sigma{k})直接算出 0 或 NaN。对数似然比直接乘diff_x / sigma{k} * diff_x要快得多而且避免了指数运算下溢——如果哪天真遇到了log(0)的报错十有八九就是没加扰动。训练集和测试集的特征必须用同一套提取流程这一点最容易出问题。我见过有人训练时用 8x8 网格特征测试时用了 16x16 网格维度对不上代码直接崩。书上第 2 章的手写数字特征提取部分已经给了完整的投影特征代码建议先跑通那个再喂给这个分类器。2.3 最小风险贝叶斯与二值数据的差别最小风险贝叶斯改的是决策规则不是密度估计部分。代码里需要额外传入一个损失矩阵 lambda其中 lambda(i,j) 表示真实类别是 i、判成 j 的代价。决策时不再选后验概率最大的类而是选期望风险最小的类。期望风险的计算方式是 sum(后验概率 .* 损失矩阵的对应行)这个改动很小但语义差别很大。我一般会在代码里单独写一个函数方便和最小错误率版本做对比实验。二值数据的贝叶斯分类就是另一套写法了。手写数字二值化之后每个像素只有 0 和 1 两种取值这时候再用高斯密度去拟合就很不自然。书上提出的做法是统计每类样本中每个特征位取 1 的概率得到一个概率矩阵识别时对待测样本逐位求概率乘积。因为特征维度高、概率乘积会下溢通常要对数化处理。这个实现我后面单独展开先记住一条特征是连续值用高斯假设特征是二值直接用伯努利假设别混用。2.4 贝叶斯分类器在数字识别上的效果与参数选择我用 500 个训练样本、200 个测试样本跑过一轮特征取的是 8x8 网格归一化后的灰度值。结果贝叶斯分类器准确率在 88% 左右和二值贝叶斯的结果差不多但明显优于第三章的欧式距离模板匹配约 82%。原因是贝叶斯把每一类的方差信息用上了而欧式距离只用了均值对数字这种类内差异不小的对象方差信息很关键。参数上最值得调的是协方差扰动的系数1e-6。训练样本越少协方差越容易病态扰动系数要适当加大到1e-4甚至1e-2但太大会把真实方差信息抹掉准确率反而下降。建议做一组系数扫描实验画出准确率随扰动系数的变化曲线挑一个平台期的值。另一个参数是先验概率小数据集上如果各类样本数不均衡prior(k) sum(idx) / length(train_label)这个频率估计会放大不均衡的影响。样本数差距超过 5 倍时我一般会改用各类样本数的倒数做权重让分类器对少数类更友好。3. 避坑指南贝叶斯分类器常见的四个坑3.1 协方差矩阵奇异的报错与处理现象运行det(sigma{k})时提示矩阵接近奇异或结果为 NaN分类准确率骤降。原因特征维度大于该类训练样本数。手写数字特征动辄几十维而某些数字比如 8在训练集里可能只有十几个样本协方差矩阵必然不满秩。解决训练前检查每类样本数低于特征维度时强制加对角扰动。我习惯写一个regularize_cov函数计算lambda max(trace(sigma), 1e-6) / d然后做sigma sigma lambda * eye(d)比固定加1e-6更稳因为它是按矩阵的尺度动态调整的。3.2 特征值连续与二值混用导致的概率计算错误现象二值化数据用高斯密度拟合准确率反而低于随机猜测。原因二值数据是离散分布高斯密度的假设完全不成立算出来的对数似然没有区分度。解决数据先统计每个特征位取 1 的概率再用伯努利模型。代码上注意概率矩阵会有大量 0 和 1 的极端值直接对概率求乘积会得到 0。我一般会对概率做平滑处理训练样本中出现过 1 的位置概率取(count1)/(n2)全部为 0 的位置取1/(n2)避免概率为 0 导致整个对数似然变负无穷。3.3 先验概率不均衡导致的识别偏向现象训练集里数字 0 有 200 个样本数字 5 只有 20 个分类器把大量输入判成 0。原因最大后验概率决策天然偏向先验大的类别样本数差距悬殊时尤其严重。解决两类策略一是按各类样本数倒数加权先验二是做数据增强对少数类样本做小角度旋转、平移加噪凑到和多数类相近的样本量。第二种方法对最终模型的泛化能力提升更大因为本质上是增加了训练数据多样性。3.4 训练集与测试集特征提取流程不一致现象训练时准确率 95%换测试数据直接崩报错维度对不上。原因训练和测试用了两套特征提取代码或者图像归一化方法不同导致特征分布完全不一致。解决特征提取统一封装成一个函数训练和测试都调用同一入口。我习惯把特征提取、归一化、分类器三个环节分别写成独立函数用脚本串起来这样任何一步改动都能保证训练和测试走同一条链路。这也是全书项目里最值得养成的代码组织习惯。4. 距离测度分类法对比模板匹配、马氏距离与夹角余弦的实现和选型4.1 模板匹配法和基于 PCA 的模板匹配第三章的距离测度分类是模式识别里最直观的一类方法算待测样本和各类模板的距离距离最近就是哪类。最基础的模板匹配法模板就是每类训练样本的均值向量距离用欧式距离。代码很短function label template_matching(train_data, train_label, test_data) % 模板匹配法欧式距离最小即为所属类别 classes unique(train_label); n_class length(classes); template zeros(size(train_data, 1), n_class); for k 1:n_class idx (train_label classes(k)); template(:, k) mean(train_data(:, idx), 2); % 每类模板取均值 end n_test size(test_data, 2); label zeros(1, n_test, like, classes); for i 1:n_test dist sum((test_data(:, i) - template).^2, 1); % 欧式距离 [~, best] min(dist); label(i) classes(best); end end这个代码简单到几乎没有可调参数但效果一般我实测约 80%~82%。问题出在欧式距离对每个特征维度一视同仁而手写数字特征里有些维度方差大、区分度低有些维度相反。PCA 做模板匹配的思路是先把特征空间降维去掉方差小但噪声大的维度再用降维后的主成分空间算距离。书上给的实现是先用pca函数求出主成分变换矩阵把训练和测试数据都投影到前 k 个主成分方向再套用模板匹配。k 的取值一般是保留累计方差贡献率 90% 以上经验上 8x8 的特征降到 15~20 维效果最好再降反而丢信息。4.2 马氏距离分类引入协方差的距离测度马氏距离和欧式距离的本质区别在于马氏距离用协方差矩阵的逆对特征做了白化处理相当于先对每个特征维度做去相关和方差归一化再算距离。对特征维度存在强相关性的数据马氏距离比欧式距离合理得多。function label mahalanobis_classify(train_data, train_label, test_data) classes unique(train_label); n_class length(classes); % 全局协方差矩阵各类共享一个 sigma_global cov(train_data); sigma_global sigma_global 1e-4 * eye(size(sigma_global, 1)); inv_sigma inv(sigma_global); n_test size(test_data, 2); label zeros(1, n_test, like, classes); for i 1:n_test best_dist inf; for k 1:n_class idx (train_label classes(k)); mu_k mean(train_data(:, idx), 2); diff_x test_data(:, i) - mu_k; dist_k diff_x * inv_sigma * diff_x; if dist_k best_dist best_dist dist_k; label(i) classes(k); end end end end这段代码用的是全局协方差也就是所有类别共享同一个协方差结构。另一种做法是每类算自己的协方差效果理论上更好但需要更多训练样本否则协方差估计不稳定。注意这里同样要加对角扰动原因和贝叶斯分类器里一样。马氏距离在数字识别上我实测能到 86%左右比欧式距离模板匹配高了 4 个点但比贝叶斯分类器略低一点而且计算量大了不少——每个待测样本都要对所有类别算一次矩阵乘向量。4.3 夹角余弦与二值化 Tanimoto 测度夹角余弦测的是两个向量方向上的相似度对向量的绝对大小不敏感。这对灰度受光照影响的数据很有用但手写数字归一化后灰度范围基本一致所以夹角余弦的优势不明显。二值化的夹角余弦把特征先二值化阈值化后转成 0/1再算余弦相当于只比较哪些像素亮了的一致性对笔画的整体形状更敏感。Tanimoto 测度和 Jaccard 系数类似适合二值特征的集合相似性比较公式是交集大小除以并集大小。这几个二值测度我实测效果差别不大准确率都在 83%~85% 之间。区别主要在于对噪声的敏感度Tanimoto 对加性噪声更稳夹角余弦对笔画粗细变化更稳。选择依据是特征本身的性质——二值特征优先 Tanimoto灰度特征优先普通余弦或者马氏距离。如果任务里待识别对象有旋转和尺度变化这几个测度都不够用要考虑傅里叶描述子或者矩特征那是特征工程层面的问题了。4.4 距离测度选型的判断依据给我的判断经验欧式距离只适合特征维度之间相互独立、方差相近的情况这在真实数据里几乎不存在马氏距离是最稳的通用选择它自动处理了相关性和量纲问题代价是需要足够样本估计协方差夹角余弦适合特征受尺度影响大的场景比如语音特征、文本 TF-IDF 向量。在数字识别这个具体任务上我的建议是先用模板匹配法跑通全流程确认数据没问题再换马氏距离或者贝叶斯分类器提精度。不要一上来就上复杂算法因为特征提取和预处理的坑比分类器本身的坑多得多。分类器换起来容易特征如果提坏了换什么算法都救不回来。5. 把多个分类器串成对比实验识别率对比与参数影响分析5.1 实验流程设计书里的特色是同一套手写数字数据用不同算法各跑一遍最后对比识别率。这个思路非常适合做课程设计或者毕设的实验章节。标准流程是先做特征提取得到特征矩阵把样本按 7:3 或 8:2 划分训练集和测试集分别训练贝叶斯、模板匹配、马氏距离、神经网络等分类器计算各自的测试准确率画出对比表格。我一般会在每个分类器外面套一层交叉验证用 5 折交叉验证的平均准确率作为最终指标避免单次划分的偶然性。5.2 制作识别率对比表对比表建议按行放算法按列放准确率、训练时间、测试时间、可调参数个数这几个维度。训练时间和测试时间用tic/toc统计可调参数个数这个指标能直观反映算法的手工调参成本——贝叶斯只有一个扰动系数BP 神经网络则有隐含层节点数、学习率、动量项、迭代次数好几个调起来费时费力。5.3 用可视化辅助判断错误类型光看准确率还不够我还习惯画混淆矩阵。MATLAB 里可以用confusionchart函数R2018b 以后才有没有的话自己用histcounts2画热力图。混淆矩阵能告诉你哪些数字容易被混淆——比如手写数字 4 和 9、7 和 1 在特征空间里确实靠得很近如果混淆集中在这几对说明特征提取环节需要改进而不是分类器的问题。这个判断对论文里写错误分析部分非常有用能把准确率 90%这句话扩展成一段有说服力的分析。6. 验证模型是否靠谱交叉验证和混淆矩阵两个必做动作模型跑出 88% 的准确率后下一步不是急着调参数而是先验证这个数字的可信度。单次划分训练集和测试集的偶然性很大换一批测试样本准确率可能波动 3~5 个百分点。我习惯用 5 折交叉验证把数据集随机分成 5 份轮流拿 4 份训练、1 份测试最终准确率取 5 次平均并记录标准差。标准差大于 2 个百分点说明数据划分对结果影响显著这时候要把目光放回特征提取而不是继续抠分类器参数。% 5折交叉验证框架 rng(42); % 固定随机种子保证结果可复现 n size(features, 2); cv_idx crossvalind(Kfold, n, 5); % 生成5折索引 acc_list zeros(5, 1); for fold 1:5 train_idx (cv_idx ~ fold); test_idx (cv_idx fold); pred bayes_min_error(features(:, train_idx), labels(train_idx), features(:, test_idx)); acc_list(fold) mean(pred labels(test_idx)); end fprintf(5折平均准确率: %.2f%% ± %.2f%%\n, mean(acc_list)*100, std(acc_list)*100);交叉验证之后画混淆矩阵。这里要看的不是对角线上的数字而是对角线以外的错分对是否集中在特定的两个数字上。如果 4 和 9 互相混淆严重我一般会回去看这两类的均值向量差异——如果差异最大的几个特征维度数值也很接近说明特征本身区分度不足此时换分类器不会有本质提升应该考虑补充新特征比如网格方向特征、笔画密度特征。从那以后我每次跑分类实验都强制走一遍交叉验证加混淆矩阵的诊断流程即使没发现问题这个动作也能让论文里的实验数据站得住脚。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网