简介:《模式识别与智能计算——MATLAB技术实现》第3版电子书,由杨淑莹、张桦编写,是“十二五”国家级规划教材,系统讲解模式识别理论与MATLAB实现。全书共14章,在基础部分介绍模式识别概念、特征选择与相似性测度;分类器设计部分涵盖贝叶斯、判别函数、神经网络(BP、径向基、自组织竞争、概率等)、决策树、粗糙集;聚类部分包括基本聚类、模糊聚类、禁忌搜索、遗传、蚁群与粒子群算法,知识体系完整且前沿。资源为1个PDF文件,约17.87MB,目前已有311人学习。书中以手写数字识别为贯穿案例,结合实现步骤和可运行MATLAB代码,方便读者从理论过渡到实践。作为计算机、信息工程、智能科学等专业教材或研发参考,实用性强,适合需要系统掌握模式识别算法及MATLAB编程的读者。
1. 为什么模式识别课设都绕不开MATLAB:手写数字识别是最短的验证路径
每年做模式识别课设或毕设的同学,最常卡住的往往不是算法推导,而是代码跑通了、特征却不知道提什么,分类器也不确定该选哪个。拿手写数字识别来说,它只有0到9这十类,样本好采集、结果直观、出错样本能一眼看出来,于是成了这套体系里公认的验证基准。这套方案沿一条完整链路展开:先做特征选择与相似性测度,再对比贝叶斯、判别函数、神经网络、决策树、粗糙集这些分类器,最后落到K均值、模糊聚类、遗传算法、蚁群、粒子群等聚类分析。适合三类人:正在交模式识别课设或毕设的学生、需要快速复现对比实验的研究生、想把经典分类器接进项目里的工程人员。
2. 特征选择与相似性测度:识别效果的第一道关口
2.1 特征空间设计:样品筛选、特征筛选与评估的顺序
特征提取不是把原始像素直接塞给分类器就完事。原始样本库里通常混着写变形的、带噪声的、甚至写错的数字,这些异常样本如果不处理,后面算均值、算协方差全都会被带偏。通常的做法是先做样品筛选:把明显离群的样本可视化出来,人工或按距离阈值剔除,再进入特征筛选环节。
特征筛选处理的核心是去掉两类特征的干扰:一是方差接近零的“死特征”,它们对分类没有贡献;二是彼此高度相关的特征,它们会造成冗余。实操里会计算每个特征在各类别上的均值差异和方差,用类内类间距离比这类判据评分,保留得分靠前的特征子集。这一步做完,特征评估才有意义,否则排序结果只能算一个黑匣子,看不出特征到底区分了什么。
我一般会在这一步先用一个最简单的最近邻分类器做预测试,看特征子集是否已经能区分开大部分类别。如果最近邻识别率已经很高,说明特征是够用的,后面分类器设计的主要任务是控制过拟合;如果最近邻识别率很低,不要急着换复杂分类器,问题大概率出在特征没有提取到判别信息上——这是很多课设项目翻车的起点。
提示:特征工程的顺序不能颠倒。先做样品筛选再做特征筛选,否则异常样本会把特征排序结果污染成黑匣子,后面很难排查。
2.2 手写数字特征提取与PCA降维的MATLAB实现
手写数字图像最朴素的提法是把图像划分成网格,统计每个网格区域内的黑色像素占比,把二维图像压成一维特征向量。下面的函数就是这种经典做法,输入二值化、归一化到统一尺寸的数字图像,输出网格密度特征:
function fea = extractGridFeature(img, gridSize) % 输入: img 为二值化后的手写数字图像,数字区域取1 % gridSize 为网格划分参数 % 输出: fea 为 1 x (gridSize^2) 的行向量 % 取每个网格内的黑色像素占比作为特征 [rows, cols] = size(img); rBlock = floor(rows / gridSize); cBlock = floor(cols / gridSize); fea = zeros(1, gridSize * gridSize); idx = 1; for i = 1:gridSize for j = 1:gridSize block = img((i-1)*rBlock+1 : i*rBlock, ... (j-1)*cBlock+1 : j*cBlock); fea(idx) = sum(block(:)) / numel(block); % 区域密度 idx = idx + 1; end end endgridSize取4时得到16维特征,取8时得到64维,取16时得到256维。16维信息量偏少,对形变鲁棒性差;256维表达能力虽强,但特征间相关性也随之上升,分类器训练更慢。所以紧接着通常要做PCA降维,把高维特征压缩到低维空间同时保留主要判别信息。
function [Y, V, explained] = pcaReduce(X, k) % 输入: X 为 nSample x nDim 的特征矩阵,每行一个样本 % k 为目标维数 % 输出: Y 为降维后的样本矩阵,V 为投影矩阵,explained 为累计贡献率 mu = mean(X); Xc = X - repmat(mu, size(X, 1), 1); % 中心化 C = (Xc' * Xc) / (size(X, 1) - 1); % 协方差矩阵 [V, D] = eig(C); % 特征值分解 [d, idx] = sort(diag(D), 'descend'); % 按特征值降序 V = V(:, idx); explained = cumsum(d) / sum(d); % 累计贡献率 Y = Xc * V(:, 1:k); % 投影到前k个主成分 endPCA的k值不是拍脑袋定的。通常的做法是看explained累计贡献率曲线,取达到85%~95%的最小k。对64维网格特征做PCA,一般降到10~20维就能保留足够判别信息,分类器训练速度和稳定性都会有明显改善。实现时要注意中心化必须在协方差计算之前完成,否则第一主成分会被均值主导,降维结果失真。eig对对称矩阵做特征值分解是数值稳定做法,尽量避免直接用inv求逆。
2.3 模式相似性测度:模板匹配、马氏距离与夹角余弦怎么选
特征提完之后,最朴素的分法就是“看谁像谁”。这一类方法建立在相似性测度之上,方案里从距离测度分类法展开,给出了模板匹配法、基于类中心的欧式距离法、马氏距离分类、夹角余弦距离分类、二值化的夹角余弦距离法和二值化的Tanimoto测度分类六种。它们的适用场景差异很大,按使用经验整理如下:
| 测度 | 核心思路 | 适用场景 | 常见坑 |
|---|---|---|---|
| 模板匹配 | 与标准模板逐点比对 | 印刷体、规整字符 | 对手写形变敏感 |
| 欧式距离 | 特征空间中到类中心的距离 | 特征经过标准化 | 量纲不一致时失效 |
| 马氏距离 | 考虑特征相关性的距离 | 特征间强相关的场景 | 协方差矩阵奇异时求逆出错 |
| 夹角余弦 | 关注方向不关注长度 | 文本、稀疏特征 | 对特征缩放不敏感但丢幅度信息 |
| 二值化夹角余弦 | 对二值特征做夹角余弦 | 网格密度特征 | 需先做二值化,阈值靠实验定 |
| Tanimoto测度 | 交集与并集的比值 | 二值化图像匹配 | 只适合二值特征 |
距离测度分类法最大的优点是没有训练环节,拿到参考样本建好类中心或模板就能直接分类,适合做基线。缺点是决策边界固定,面对数据重叠区域时几乎没有容错能力。所以它在这套方案里的定位是“基线对比”,贝叶斯、判别函数和神经网络这些分类器才承担真正的分类任务。我一般在实验报告里都会保留这层基线数据,用来证明后面分类器的提升不是玄学,答辩时这条对比链也很能说明问题。
3. 分类器设计体系:从贝叶斯到神经网络的选型逻辑
3.1 贝叶斯分类器:最小错误率与最小风险的取舍
贝叶斯决策是整个统计模式识别的理论基石,核心是把先验概率和类条件概率密度组合成后验概率,再做决策。方案里区分了两种目标:最小错误率直接选后验概率最大的类别;最小风险则引入损失函数,把“把3误判成8”和“把8误判成3”的真实代价分别量化,再选期望损失最小的决策。前者适合各类别误判代价相等的场景,后者适合医疗诊断、故障检测这类漏报和误报代价明显不同的场景。
代码实现上,基于二值数据的贝叶斯分类实现是一个很实用的轻量版本:把每个特征的取值二值化,用频率估计条件概率,再套贝叶斯公式求后验。缺点是特征独立性假设在实际数据中很难严格成立;优点是实现简单、训练只要数频次,而且在小样本二值特征上有稳定表现。我见过不少报告在这里翻车,原因是直接用高斯分布假设拟合连续特征,却没有做方差平滑处理,导致某类样本在某个特征上方差接近零时,似然值直接溢出。处理办法是在估计类条件概率时加上一个小常数做拉普拉斯平滑,这也是基于二值数据版本天然规避的问题。
3.2 判别函数分类器:感知器、LMSE与Fisher的迭代逻辑
判别函数分类器不估计概率密度,直接构造决策边界。方案从线性判别函数讲起,依次给出感知器算法、增量校正算法、LMSE验证可分性、LMSE分类算法、Fisher分类、基于核的Fisher分类和势函数法,最后落到支持向量机。
感知器算法是最直观的梯度下降写法:初始化权向量,逐个样本检查是否被正确分类,分类错误就按学习率沿负梯度方向修正权向量,迭代到全部样本正确为止。增量校正算法是对感知器的细化,按逐个样本的误差大小调整权值更新幅度。LMSE(最小均方误差)算法值得单独说,它的价值不只是分类,还能用来验证两类样本是否线性可分——当迭代到均方误差不再下降且不等于零时,就能判断样本集不可分,这比肉眼观察散点图可靠得多,也算是给“为什么不直接用线性分类器”这个问题一个可量化的答案。
Fisher分类的思想更偏几何:把高维样本投影到一条直线上,使类间距离尽可能大、类内方差尽可能小,然后在一维空间里找阈值。基于核的Fisher通过核函数把样本隐式映射到高维空间,再做Fisher判别,本质上是把线性方法非线性化。这套递进关系在后面对比实验里很好用:线性可分用感知器或Fisher,线性不可分用核Fisher或SVM。答辩时把这条演进路径讲完整,“为什么选这个分类器”自然就有了答案。
3.3 神经网络分类器:六种结构的适用场景
神经网络部分覆盖面最广,BP、径向基函数RBF、自组织竞争、概率神经网络PNN、对向传播CPN、反馈型Hopfield各占一节。六种结构并非彼此替代关系,而是针对不同问题形态:
- BP神经网络:多层前馈结构,反向传播调整权值,最通用的监督分类器,手写数字识别的主力算法。
- RBF神经网络:隐藏层用径向基函数做局部响应,训练速度比BP快,适合函数逼近和样本量中等的分类。
- 自组织竞争神经网络:无监督训练,胜者通吃,适合类别结构不清楚时的初步探查。
- 概率神经网络PNN:基于Parzen窗估计概率密度,每个训练样本对应一个隐层节点,训练快且概率意义明确,但样本量大时模型体积膨胀。
- 对向传播CPN:结合自组织和监督训练两段式学习,适合样本间映射关系的建模。
- 反馈型Hopfield:用网络状态演化实现联想记忆,适合模式补全而不是单纯分类。
一个标准的BP分类器在MATLAB里可以这样搭:
% 构建BP神经网络分类器 hiddenNum = 20; % 隐含层神经元数,先按经验取输入维度的1.5倍 net = feedforwardnet(hiddenNum); % 单隐层前馈网络 net.layers{1}.transferFcn = 'tansig'; % 隐层激活函数,对称S型 net.layers{2}.transferFcn = 'logsig'; % 输出层用logsig,适配01标签 net.trainParam.epochs = 1000; % 最大迭代轮数 net.trainParam.goal = 1e-5; % 均方误差目标 net.trainParam.lr = 0.01; % 学习率,太大震荡太小龟速 net.divideFcn = 'dividerand'; % 默认按比例随机切分数据 net.divideParam.trainRatio = 0.7; net.divideParam.valRatio = 0.15; net.divideParam.testRatio = 0.15; net = train(net, trainFea', trainLabel'); % trainFea为特征矩阵,trainLabel为0/1编码这里有一个容易忽略的设计点:输出层的激活函数要和标签编码配套。多类问题一般把标签做成one-hot矩阵,输出层用logsig或softmax,隐层用tansig;如果标签是单列数值、输出层用purelin,网络的表达能力会打折扣。另外trainRatio、valRatio、testRatio的划分是MATLAB工具箱默认帮做的,但如果样本量小,还是要自己控制划分方式,否则验证集和测试集可能分布不一致,测试集识别率就会虚高。
3.4 决策树与粗糙集:可解释性优先的补充
决策树和粗糙集在这套方案里属于“可解释性优先”的路线。决策树按信息增益或增益率逐层选择划分特征,生成一组if-then规则,特别适合需要向非技术方解释决策逻辑的场景。手写数字这种连续特征较多的任务,决策树单独做识别率通常不如神经网络,但把它作为特征重要性分析的工具很合适——看哪几个特征先被选中,就能知道哪些网格区域对区分数字最有效。
粗糙集的思路和决策树不同,它用上近似、下近似描述数据划分的边界域,不依赖先验概率,适合处理含噪声和缺失信息的样本。实际应用重点是属性约简:在不损失分类能力的前提下剔除冗余属性,得到更精简的特征子集。我做对比实验时,经常先用粗糙集约简特征,再做神经网络分类,两个步骤拼接后识别率不降、训练时间缩短,报告里既有理论深度又有工程结论,评审往往愿意听这个方向的展开。
4. 聚类分析路径:从K均值到群智能算法的落地对比
4.1 K均值与ISODATA:迭代聚类的基础盘
聚类分析和分类器设计最大的区别在于没有标签参与。方案里聚类部分从基于试探的聚类设计讲起,先是最临近规则试探法和最大最小距离算法,再进入层次聚类(最短距离法、最长距离法、中间距离法、重心法、类平均距离法),最后是动态聚类中的K均值算法和ISODATA算法。
K均值是理解后面所有改进型聚类算法的基准。它的核心逻辑是交替执行两步:按最近中心分配样本,按类内均值更新中心。
function [label, center, iterUsed] = kmeansCluster(X, k, maxIter) % 经典K均值聚类实现 % X: nSample x nDim 特征矩阵; k: 聚类数目; maxIter: 最大迭代 [n, ~] = size(X); rng(1); % 固定随机种子保证可复现 idx = randperm(n, k); center = X(idx, :); % 随机初始化k个中心 for iterUsed = 1:maxIter dist = pdist2(X, center); % 计算所有样本到中心的距离 [~, label] = min(dist, [], 2); % 按最近中心分配样本 newCenter = zeros(k, size(X, 2)); for j = 1:k if sum(label == j) > 0 % 空簇保护 newCenter(j, :) = mean(X(label == j, :), 1); else newCenter(j, :) = center(j, :); % 空簇保留原中心 end end if norm(newCenter - center, 'fro') < 1e-4 break; % 中心不再变化则收敛 end center = newCenter; end end这段代码里有两个细节很容易被忽略但实际必踩:一是不设置rng固定种子,每次运行聚类结果都不一样,报告里的图没法复现,答辩时前后结果对不上会很难看;二是空簇保护,当某个簇没有任何样本时mean会得到NaN,不处理直接传下去,后面的pdist2全部被污染,聚类结果直接报废。
ISODATA在K均值基础上加入了合并和分裂机制,靠参数控制类别数自动调整,适合类别数不完全确定的场景。代价是参数更多:期望聚类数、最小样本数、最大标准差、最小距离阈值,调起来更繁琐。实际用它时要有心理准备,大部分时间花在参数试验上,但跑通后的自动归类能力确实比固定K灵活。
4.2 模糊聚类与模拟退火改进:当硬划分不适用时
K均值做的是硬划分,每个样本硬性属于一个簇。模糊聚类把隶属度引入划分过程,样本可以以0到1之间的程度属于多个簇,迭代过程从“更新中心”变成“更新隶属度矩阵再按隶属度加权更新中心”。这种做法对硬划分边界做了软化,让靠近多个簇边界的样本不用被迫选边站,聚类结果对噪声更稳健。
模拟退火的思想是给迭代过程一个“后悔药”:以一定概率接受使目标函数变差的解,而且这个概率随温度下降而减小。用在聚类上,一般是对K均值的初始化或迭代过程做扰动,温度高时允许中心大幅跳变,温度低时逐渐收敛到稳定划分。这个思路与遗传算法、粒子群的想法相通:与其在单个解的邻域里贪心,不如引入随机性来探索更广的解空间,避免陷在局部最优里出不来。
4.3 禁忌搜索、遗传算法与群智能聚类:全局优化的代价
群体智能聚类是这套方案后半部分的重点。禁忌搜索算法通过禁忌表记录近期搜索中移动过的解,避免短时间内走回头路,从而逃出局部最优;遗传算法把聚类方案编码成染色体,通过适应度函数评价划分好坏,靠选择、交叉、变异三类算子迭代演化;蚁群算法模拟蚂蚁用信息素标记路径的行为,聚类数目已知和未知两种版本分别对应不同应用场景;粒子群算法则让每个粒子代表一种聚类中心组合,粒子跟随自身最优和群体最优更新位置。
这几类算法的共性是全局搜索能力强,能在复杂目标函数上找到比K均值更优的聚类结果,代价也一致:计算量大、参数多。遗传算法要定种群规模、交叉概率、变异概率,粒子群要定学习因子和惯性权重,每个参数都影响收敛行为。我一般的策略是先用K均值跑一版结果做基线,再用遗传算法或粒子群聚类在同样数据上跑,对比迭代曲线和最终目标函数值。如果K均值的结果已经合理,就不必硬上群智能算法,否则调参的时间会远超收益。报告里把这条决策依据写清楚,比单纯罗列算法更有说服力。
聚类分析在图像场景里的呈现方式也很关键。方案里采用一幅需要进行聚类分析的图像演示各种聚类算法,我实践下来最直观的呈现方法是:选一幅颜色分布清晰的图片,提取像素的颜色特征,然后用不同聚类算法分簇,再把聚类标签映射回像素位置着色显示。这样算法效果一眼可见,对比实验的截图也更有冲击力,比打印一堆数值更有说服力。
各聚类算法在动手前先按问题规模选型:小规模精确解用层次聚类,中等规模效率优先用K均值或ISODATA,全局优化需求明确才上遗传/蚁群/粒子群。没有万能算法,只有匹配问题特征的算法。
5. 常见问题排查:模式识别实现中五个最容易翻车的点
5.1 特征值量纲不一致,距离测度全乱套
现象:用欧式距离或K均值聚类时,某个特征的取值范围特别大,其他特征几乎不起作用,聚类结果和直觉严重不符。
原因:网格密度、像素灰度、统计量等特征的量纲天然不同,欧式距离平方后量纲大的特征会支配整个距离值。这不是算法问题,是特征没做归一化。
解决:在进入距离测度或聚类之前,对每个特征做归一化,通常用z-score标准化或min-max归一化。MATLAB里直接用zscore(X)即可,做完后再跑一遍,聚类结果往往大变样。
5.2 神经网络训练不收敛或收敛后过拟合
现象:BP网络训练误差一直不下降,或者训练误差很快趋近于零但测试集识别率很低。
原因:不收敛通常是学习率太大导致震荡,或者特征没归一化,激活函数陷入饱和区;过拟合则是隐层神经元过多、训练轮数过长,模型把训练样本的噪声也记住了。
解决:先把学习率降到0.01以下,确认输入特征做了标准化;再看训练曲线,如果训练误差下降但验证集误差回升,就减小hiddenNum或增加早停。MATLAB训练界面里能看到训练集、验证集、测试集三条误差曲线,过拟合的迹象是验证集误差曲线在后段抬头,出现这种情况果断停止。
5.3 训练集测试集划分不当,识别率虚高
现象:测试集识别率高达99%,但换一批新样本后一塌糊涂,答辩时被追问就答不上来。
原因:数据划分时没有打乱,或者同一个手写数字的多张变体图片被同时分进了训练集和测试集,模型变相见过“答案”。
解决:划分前先randperm打乱样本顺序;如果一份样本里包含同一数字的多份变体,按数字类别分层划分,保证每一类在训练集和测试集里的比例一致。更严谨的做法是划出一个完全独立的测试集,不做任何调参,只在最终对比时使用一次。
5.4 聚类数目K定不下来
现象:跑K均值时K是随手定的,聚类结果看起来分得很碎,或者明显有一簇包含了多种类型,不知道K该取多少。
原因:K均值需要预设K,而很多场景下真实类别数未知。随手定K等于把最关键的先验当参数猜,结果自然不稳。
解决:用最大最小距离算法先估算聚类数目,或者扫描K从2到某个上限,画出目标函数随K变化的曲线找拐点。ISODATA的自动合并分裂机制也能缓解这个问题。方案里蚁群聚类专门区分了聚类数目已知和未知两种版本,未知版本就是为这种场景准备的。
5.5 MATLAB版本差异导致旧代码跑不通
现象:从教材或旧项目里拿来的.m文件直接运行报错,报错信息指向newff、net.trainFcn这类函数不存在或语法变化。
原因:MATLAB从旧版到新版,神经网络工具箱的接口变化很大,newff改成feedforwardnet,很多旧代码的调用方式已经失效。
解决:先看报错信息确定是新接口还是旧接口,新版优先用feedforwardnet、patternnet这类函数,属性名也对应调整。如果必须用旧接口,需要装对应版本的兼容工具箱。另外eig、pdist2这类基础函数各版本差异很小,优先用它们自己组装算法,对版本依赖会小很多。
注意:以上五类问题有一个共同根源——不检查中间结果直接看最终输出。特征归一化后看一眼均值和方差,训练过程中看一眼误差曲线,聚类迭代中打印中心变化量,很多问题在变严重之前就能发现。
6. 验收与答辩:用识别率之外的东西说服评审
6.1 建立一套可复现的验收基线
一份能站得住脚的模式识别方案,验收指标不能只有一个“识别率”。我一般会固定四个维度:识别率、混淆矩阵、训练时间、样本数。识别率给出总体数字,混淆矩阵指出哪两类最容易搞混,训练时间证明算法具备可用性,样本数说明实验规模合理。加一段简短的数据划分说明,写明训练集和测试集各有多少、如何分层划分,测试集只用于最终评估。这个习惯能避免答辩时被追问细节而卡壳。
6.2 答辩时把算法选型讲成一条决策链
答辩最常见的失分点是“为什么用这个算法”答成“因为大家都用”。更好的讲法是给一条决策链:数据量小且特征独立时用贝叶斯;线性可分时用感知器或Fisher;线性不可分时用核Fisher或SVM;数据量大、类别结构复杂时用神经网络。每一个环节都附上对应的一组实验数据做支撑,比如贝叶斯识别率多少、SVM提高到多少、BP又是多少,用数字说明每一步选择带来的收益。这样的逻辑闭环,评审很难挑出硬伤。
我最早做这类项目时也吃过亏,当时直接拿BP跑完识别率就开始写报告,答辩被问到特征提取这一步为什么这么提,现场支支吾吾。从那以后我每次做模式识别方案都强制走一遍完整流程:特征筛选、基线分类器、对比实验、错误样本分析,一步不省。最后准备几张错误样本的可视化图,讲清楚模型在哪里犯错、为什么犯错,这套材料的说服力远高于一个高识别率数字。希望这份方案的拆解能帮到你,至少让你少走几个我走过的弯路。
本文还有配套的精品资源,点击获取