模式识别MATLAB实战:从特征提取到神经网络与聚类代码库
简介《模式识别与智能计算——MATLAB技术实现》第3版配套资源面向计算机、信息工程、生物医学工程、智能机器人及工业自动化等专业的学生、研究人员与工程技术人员帮助读者系统掌握模式识别主流算法的原理与MATLAB编程实现。全书共14章涵盖特征选择与优化、模式相似性测度、贝叶斯分类器、判别函数分类器、BP与RBF等神经网络分类器、决策树、粗糙集以及模糊聚类、禁忌搜索、遗传算法、蚁群与粒子群等群智能聚类方法并以手写数字识别为贯穿实例给出实现步骤、示范代码与效果图示。资源包为1个PDF文件约17.87MB内容完整、便于检索与打印学习。目前已有310人学习下载适合希望将统计学、神经网络、数据挖掘与群智能计算等理论落地到MATLAB实践、并对照不同算法效果的读者参考。1. 从一本教材到一套可跑的 MATLAB 模式识别代码库很多人第一次接触模式识别是在研究生课堂上被贝叶斯公式和 Fisher 判别推得头晕课后作业却要求用 MATLAB 把结果跑出来。杨淑莹、张桦的《模式识别与智能计算——MATLAB 技术实现第 3 版》正好卡在这个痛点上它不满足于罗列算法而是把 14 章内容全部落到手写数字识别这一个实例上每个算法都给出理论基础、实现步骤、核心代码和效果对比。全书覆盖特征选择与优化、模式相似性测度、贝叶斯分类器、判别函数、六种神经网络、决策树、粗糙集以及从 K 均值到蚁群、粒子群的完整聚类算法谱系。对做课程设计、毕设或想系统补齐模式识别工程实现的人来说这份资源的价值在于它是一套能直接跑通、能横向对比的 MATLAB 代码基线而不是又一本只讲公式的教材。2. 特征提取与相似性测度的 MATLAB 落地2.1 手写数字特征向量的构造方式模式识别的第一步永远是把图像变成特征向量。这本书以手写数字为实例常见的做法是把归一化后的数字图像按网格划分统计每个网格内的黑像素比例拼成一个固定维度的列向量。比如把 32×32 的二值图像分成 4×4 共 16 个网格每个网格统计黑像素占比就得到 16 维特征。也可以用粗网格加细网格的混合方式或者直接展平为 1024 维再做 PCA 降维。我一般会先做样本筛选去掉笔画断裂、噪声过多的样本再做特征筛选。书中第 2 章给出的思路是先计算每个特征的类间方差与类内方差之比比值低的特征直接剔除剩下的再做主成分分析。MATLAB 里用pca函数或者自己写协方差矩阵特征分解都可以。% 假设 data 是 n×d 矩阵n 个样本d 维特征 % labels 是 n×1 的类别标签0-9 % 第一步按类别计算均值和方差 classes unique(labels); d size(data, 2); class_mean zeros(length(classes), d); class_var zeros(length(classes), d); for i 1:length(classes) idx labels classes(i); class_mean(i, :) mean(data(idx, :), 1); class_var(i, :) var(data(idx, :), 0, 1); end % 第二步计算类间方差与类内方差之比 overall_mean mean(data, 1); between_var var(class_mean, 0, 1); % 类间方差 within_var mean(class_var, 1); % 类内方差均值 f_score between_var ./ (within_var eps); % 第三步按 f_score 降序排列取前 k 个特征 [~, sorted_idx] sort(f_score, descend); k 10; % 保留前 10 维 selected_features data(:, sorted_idx(1:k));这段代码的逻辑是between_var衡量不同类别均值之间的分散程度within_var衡量同类样本内部的紧凑程度两者比值越大说明该特征越有区分力。eps防止除零。sorted_idx给出特征重要性排序k根据实际识别率调整一般从 8 到 20 之间试。2.2 距离测度与模板匹配的代码实现特征有了接下来要度量样本之间的相似性。书中第 3 章系统介绍了欧氏距离、马氏距离、夹角余弦、Tanimoto 测度等。模板匹配法是最直观的把每个类别的训练样本取平均得到模板测试样本与哪个模板距离最近就判为哪一类。% train_data: 训练特征矩阵train_labels: 训练标签 % test_data: 测试特征矩阵 % 计算类模板 classes unique(train_labels); num_classes length(classes); templates zeros(num_classes, size(train_data, 2)); for i 1:num_classes templates(i, :) mean(train_data(train_labels classes(i), :), 1); end % 欧氏距离分类 dist_matrix pdist2(test_data, templates, euclidean); [~, min_idx] min(dist_matrix, [], 2); pred_labels classes(min_idx); % 计算识别率 accuracy sum(pred_labels test_labels) / length(test_labels); fprintf(欧氏距离模板匹配识别率: %.2f%%\n, accuracy * 100);pdist2是 MATLAB 统计工具箱里的函数直接算测试样本到每个模板的欧氏距离。min_idx取每行最小值的列索引对应最近的模板类别。如果换成马氏距离把euclidean改成mahalanobis即可但要注意协方差矩阵的估计需要足够多的样本否则容易奇异。提示模板匹配法在特征维度高、样本少的时候容易过拟合建议先做 PCA 降到 10 到 20 维再匹配书中第 3 章也给出了基于 PCA 的模板匹配法。2.3 不同测度的对比与选择依据测度方法适用场景MATLAB 调用方式注意事项欧氏距离特征量纲一致、分布均匀pdist2(X,Y,euclidean)对量纲敏感需先归一化马氏距离特征间有相关性pdist2(X,Y,mahalanobis)需足够样本估计协方差夹角余弦关注方向而非大小手动计算dot(a,b)/(norm(a)*norm(b))适合文本、直方图特征Tanimoto二值特征手动计算交集比并集手写数字二值化后可用选择测度的原则很简单特征已经归一化且各维独立用欧氏距离特征之间有强相关用马氏距离特征向量长度不重要而方向重要用夹角余弦。书中对手写数字的对比结果是PCA 降维后欧氏距离和夹角余弦的识别率接近马氏距离在样本充足时略优。3. 贝叶斯分类器与判别函数的 MATLAB 实现3.1 基于最小错误率的贝叶斯决策贝叶斯分类器的核心是后验概率最大化。对于手写数字识别假设每个类别的特征服从多元高斯分布用训练样本估计每个类别的均值向量和协方差矩阵测试时计算后验概率。% 训练阶段估计每个类别的均值和协方差 classes unique(train_labels); num_classes length(classes); mu cell(num_classes, 1); sigma cell(num_classes, 1); prior zeros(num_classes, 1); for i 1:num_classes idx train_labels classes(i); mu{i} mean(train_data(idx, :), 1); sigma{i} cov(train_data(idx, :)); prior(i) sum(idx) / length(train_labels); end % 测试阶段计算后验概率取最大 pred_labels zeros(size(test_data, 1), 1); for j 1:size(test_data, 1) posterior zeros(num_classes, 1); for i 1:num_classes % 多元高斯概率密度 diff test_data(j, :) - mu{i}; posterior(i) log(prior(i)) - 0.5 * log(det(sigma{i})) ... - 0.5 * diff * inv(sigma{i}) * diff; end [~, max_idx] max(posterior); pred_labels(j) classes(max_idx); end这里用对数后验概率避免数值下溢。log(prior(i))是先验概率的对数-0.5*log(det(sigma{i}))是协方差行列式的贡献最后一项是马氏距离的负半。取最大值对应的类别即为判决结果。如果协方差矩阵接近奇异可以加一个小的对角正则项sigma{i} 1e-6*eye(d)。3.2 判别函数分类器的训练流程判别函数分类器不估计概率分布直接构造一个函数把特征空间划分成不同区域。书中第 5 章覆盖了感知器算法、增量校正、LMSE、Fisher 分类、势函数法和支持向量机。以 Fisher 线性判别为例二分类时找一个投影方向使类间散度与类内散度之比最大。% 二分类 Fisher 线性判别 % 取数字 0 和 1 两类 idx0 train_labels 0; idx1 train_labels 1; X0 train_data(idx0, :); X1 train_data(idx1, :); % 计算类内散度矩阵 mean0 mean(X0, 1); mean1 mean(X1, 1); S0 (X0 - mean0) * (X0 - mean0); S1 (X1 - mean1) * (X1 - mean1); Sw S0 S1; % 投影方向 w Sw \ (mean0 - mean1); % 投影后计算阈值 proj0 X0 * w; proj1 X1 * w; threshold (mean(proj0) mean(proj1)) / 2; % 测试 proj_test test_data * w; pred proj_test threshold; % 1 表示判为类 1Sw \ (mean0 - mean1)是 Fisher 准则的解等价于inv(Sw)*(mean0-mean1)但用左除更稳定。threshold取两类投影均值的中点也可以按先验概率加权。多分类时可以用一对多策略每个类别训练一个 Fisher 判别器。3.3 分类器参数调优与交叉验证贝叶斯分类器的关键参数是协方差矩阵的估计方式。样本少时用对角协方差假设特征独立更稳样本多时用全协方差。判别函数分类器里感知器算法的学习率、LMSE 的迭代次数、SVM 的核函数和惩罚系数 C 都需要调。我一般用 5 折交叉验证来选参数。MATLAB 里可以手写循环也可以用cvpartitioncv cvpartition(train_labels, KFold, 5); acc zeros(5, 1); for fold 1:5 train_idx training(cv, fold); val_idx test(cv, fold); % 在 train_idx 上训练在 val_idx 上验证 % ... 训练和预测代码 ... acc(fold) sum(pred_val train_labels(val_idx)) / sum(val_idx); end fprintf(交叉验证平均识别率: %.2f%%\n, mean(acc) * 100);cvpartition保证每折中各类别比例与整体一致。training和test分别取出训练和验证索引。5 折是常用折中样本极少时可以用留一法。注意手写数字识别中数字 1 和 7、3 和 8、4 和 9 容易混淆调参时重点看这些类别的混淆矩阵而不是只看整体识别率。4. 神经网络与聚类算法的 MATLAB 实战4.1 BP 与 RBF 神经网络的训练配置书中第 6 章覆盖了六种神经网络BP 和 RBF 是最常用的两种。BP 网络用误差反向传播调整权重RBF 网络用径向基函数作为隐层激活训练分两步先确定隐层中心再求输出权重。% BP 神经网络分类 hidden_size 20; % 隐层神经元数 net feedforwardnet(hidden_size); net.trainFcn trainscg; % 量化共轭梯度比默认的 trainlm 省内存 net.trainParam.epochs 1000; net.trainParam.goal 1e-4; net.trainParam.lr 0.01; % 标签需要转成 one-hot 编码 num_classes 10; T zeros(num_classes, length(train_labels)); for i 1:length(train_labels) T(train_labels(i) 1, i) 1; % 假设标签是 0-9 end net train(net, train_data, T); % 测试 Y net(test_data); [~, pred_idx] max(Y, [], 1); pred_labels pred_idx - 1;feedforwardnet创建一个前馈网络trainscg是量化共轭梯度算法适合中小规模数据。epochs是最大迭代次数goal是误差目标lr是学习率。标签转 one-hot 是因为 MATLAB 神经网络工具箱的分类输出是概率向量。RBF 网络可以用newrb函数它自动增加隐层神经元直到误差满足要求goal 1e-3; % 误差目标 spread 1.0; % 径向基扩展速度 MN 50; % 最大神经元数 net newrb(train_data, T, goal, spread, MN);spread越大径向基函数越平缓泛化能力越强但拟合精度下降。MN限制网络规模防止过拟合。4.2 K 均值与 ISODATA 聚类的实现差异聚类和分类的区别在于没有标签。K 均值是最基础的聚类算法需要预先指定聚类数 K。ISODATA 是 K 均值的改进版可以自动合并和分裂聚类。% K 均值聚类 K 10; [cluster_idx, centers] kmeans(train_data, K, Replicates, 5, ... Distance, sqeuclidean, MaxIter, 500); % 计算聚类纯度 % 对每个聚类找出现最多的真实标签 purity 0; for k 1:K idx cluster_idx k; if sum(idx) 0 true_labels_in_cluster train_labels(idx); purity purity max(histcounts(true_labels_in_cluster, 0:10)); end end purity purity / length(train_labels); fprintf(聚类纯度: %.2f%%\n, purity * 100);Replicates设为 5 表示用不同初始中心跑 5 次取最优避免局部极小。sqeuclidean是平方欧氏距离比欧氏距离计算快。聚类纯度衡量每个簇中多数类样本的占比是评估聚类质量的常用指标。ISODATA 在 MATLAB 中没有内置函数需要自己实现。核心逻辑是每次迭代后如果某个簇的样本数太少就删除如果某个簇的类内方差太大就分裂成两个。书中第 9 章给出了完整代码。4.3 群体智能聚类算法的参数设置书中第 11 到 14 章介绍了禁忌搜索、遗传算法、蚁群算法和粒子群算法在聚类中的应用。这些算法的共同点是用启发式搜索代替梯度下降适合目标函数不连续或有很多局部极值的情况。以粒子群聚类为例每个粒子代表一组聚类中心适应度函数用类内距离之和% 粒子群聚类简化框架 num_particles 30; max_iter 100; w 0.7; % 惯性权重 c1 1.5; % 个体学习因子 c2 1.5; % 社会学习因子 % 初始化粒子位置每个粒子是一组聚类中心 K 10; d size(train_data, 2); positions rand(num_particles, K * d); velocities zeros(num_particles, K * d); % 计算初始适应度 pbest positions; pbest_fitness zeros(num_particles, 1); for i 1:num_particles centers reshape(positions(i, :), K, d); pbest_fitness(i) compute_fitness(train_data, centers); end [gbest_fitness, gbest_idx] min(pbest_fitness); gbest pbest(gbest_idx, :); % 迭代 for iter 1:max_iter for i 1:num_particles r1 rand(1, K * d); r2 rand(1, K * d); velocities(i, :) w * velocities(i, :) ... c1 * r1 .* (pbest(i, :) - positions(i, :)) ... c2 * r2 .* (gbest - positions(i, :)); positions(i, :) positions(i, :) velocities(i, :); centers reshape(positions(i, :), K, d); fitness compute_fitness(train_data, centers); if fitness pbest_fitness(i) pbest(i, :) positions(i, :); pbest_fitness(i) fitness; end end [min_fitness, min_idx] min(pbest_fitness); if min_fitness gbest_fitness gbest_fitness min_fitness; gbest pbest(min_idx, :); end endw控制惯性越大越偏向全局搜索c1和c2分别控制向个体最优和全局最优靠拢的程度。compute_fitness需要自己写通常是所有样本到最近聚类中心的距离平方和。粒子群聚类的缺点是计算量大每次迭代都要重新分配样本到最近中心适合样本量几千以内的场景。提示群体智能算法对参数敏感建议先用 K 均值得到一个较好的初始聚类中心再作为粒子群的一个初始粒子能显著加快收敛。5. 识别率上不去时的排查路径与加速技巧5.1 混淆矩阵定位问题类别整体识别率 85% 看起来还行但可能数字 1 的识别率是 98%数字 8 只有 60%。用混淆矩阵能快速定位问题C confusionmat(test_labels, pred_labels); % 可视化 confusionchart(C); % 计算每个类别的识别率 for i 1:10 if sum(C(i, :)) 0 fprintf(数字 %d 识别率: %.2f%%\n, i-1, ... C(i, i) / sum(C(i, :)) * 100); end endconfusionmat返回的矩阵行是真实类别列是预测类别对角线是正确分类。confusionchart画出热力图一眼能看出哪些类别互相混淆。如果 3 和 8 混淆严重说明特征对这两个类的区分力不够需要增加刻画笔画曲率的特征或者换用对形状更敏感的测度。5.2 特征归一化与降维的先后顺序一个常见错误是先降维再归一化。PCA 对特征的尺度敏感如果各维量纲差异大主成分会被大量纲特征主导。正确顺序是先归一化到零均值单位方差再做 PCA。% 正确顺序 data_norm (data - mean(data)) ./ std(data); [coeff, score, latent] pca(data_norm); % 取累计贡献率 95% 的主成分 cum_var cumsum(latent) / sum(latent); k find(cum_var 0.95, 1); data_pca score(:, 1:k);latent是特征值cum_var是累计贡献率。k取第一个使累计贡献率超过 95% 的索引。这样既降了维又保留了大部分信息。如果先 PCA 再归一化主成分的物理意义会被破坏识别率通常下降 3 到 5 个百分点。5.3 利用并行计算加速交叉验证MATLAB 的parfor可以把交叉验证的每折并行跑。前提是安装了 Parallel Computing Toolbox 并且开了并行池。if isempty(gcp(nocreate)) parpool(local, 4); % 开 4 个 worker end acc zeros(5, 1); parfor fold 1:5 % 每折的训练和验证代码 % ... 注意parfor 内不能有依赖顺序的操作 ... acc(fold) ...; endgcp(nocreate)检查是否已有并行池没有就创建一个。parpool(local, 4)开 4 个本地 worker数量一般设为 CPU 核心数。parfor要求循环体之间独立交叉验证的每折正好满足。对于 5 折交叉验证4 个 worker 能把时间降到原来的三分之一左右。注意parfor里不能直接写fprintf输出到控制台调试信息可以先存到数组里循环结束后统一打印。另外并行池的启动本身有开销如果每折计算量很小并行反而更慢建议单折超过 10 秒再用。本文还有配套的精品资源点击获取