我一直觉得,手写数字识别这个题目被深度学习“宠坏”了。一提到它,大家都默认要上CNN、要搭LeNet,仿佛不用神经网络就不够高级。但真到了课程设计、论文里做对照实验,或者只是想完整跑通“图像预处理→特征提取→降维→分类→评估”这条机器学习流水线时,决策树+MATLAB反而是更快出活、更容易讲清楚的选择。
这个项目我用MATLAB完整实现了一遍,涵盖了图片二值化、数字定位、5×5网格分割、主成分分析法降维以及交叉验证评估。整条链路下来,不用任何深度学习框架,只靠MATLAB自带的图像处理和统计学习工具箱,就能把识别精度做到88%到90%这个区间。而且每一步都能画图给老师看、能解释为什么这么做,这在教学演示和论文写作场景里,比一个黑盒CNN要有说服力得多。
1. 为什么在深度学习时代仍要拿决策树做手写数字识别
1.1 手写数字识别的问题本质
手写数字识别本质上是一个十类分类问题:给定一张包含数字的图像,判断它是0到9中的哪一个。这个问题的难点集中在两个地方:一是手写体本身存在巨大的形变差异,不同人写的同一个数字,倾斜角度、笔画粗细、位置偏移都可能不一样;二是图像输入通常是高维数据,一张28×28的灰度图直接展开就是784维特征,直接扔给分类器很容易出现维度灾难和过拟合。
这类问题的常规解法其实分成两条路线。一条是深度学习路线,靠卷积神经网络自动提取层次化特征,在MNIST上可以做到99%以上的准确率;另一条是经典机器学习路线,先通过图像处理手段人工设计特征,再用决策树、SVM、KNN这类模型做分类,这条路线通常能到85%到93%的准确率,取决于特征工程做得好不好。
我做这个项目时选择的是第二条路线,原因很实际:深度学习需要调的网络结构和超参数太多,而且出问题的时候很难定位是数据有问题还是网络有问题。经典路线每个环节都可以拆开检查,图像预处理阶段的结果可以直接可视化,特征提取之后还能用PCA看一下数据分布,整个流程更透明。
1.2 决策树在这个场景下的定位
决策树的核心逻辑是按特征逐层划分样本空间,每次选择一个特征作为分裂节点,把数据分成不同分支,递归进行直到叶子节点代表一个类别或达到停止条件。它跟SVM、KNN这些模型最大的区别在于可解释性,训练完成后可以导出完整的树结构,直接看到模型是根据哪些特征作出判断的。
在手写数字识别这个任务里,决策树有一个很明显的优势:对特征尺度不敏感。图像分块特征本身就是像素密度,所有维度量纲一致,不需要像SVM那样精心调核函数和惩罚参数。另外决策树训练速度非常快,几千个样本几十秒就能训练完成,交叉验证跑十轮也花不了多少时间,这在需要反复调参验证的场景下非常实用。
当然决策树也有它的短板,单棵树容易过拟合,在不限制深度的情况下,训练集上可能拿到99%的准确率,但测试集上掉到80%左右。这个问题需要通过限制树深、设置叶子节点最小样本数、或者配合剪枝策略来解决。我在这个项目里用的是fitctree函数,通过交叉验证来选参数,效果稳定在88%以上。
1.3 技术选型的取舍逻辑
有人可能会问,既然有KNN这种简单实现、效果也不差的方法,为什么偏要用决策树?我是这么看的:KNN虽然实现简单,但每次预测都要计算与所有训练样本的距离,存储和计算开销都比较大,而且K值的选择对结果影响很敏感。决策树不同,训练一次之后预测就是走树结构做判断,速度快得多。
还有个原因是决策树可以作为后续集成学习的基础。如果项目要扩展,把单棵决策树换成随机森林或者提升树,代码改动量非常小,效果还能再往上走一截。这意味着这个项目的技术路线有扩展空间,不是做一次就废掉的“死项目”。
我把决策树、SVM、KNN三者在相同特征下的表现做了个简要对比,供大家参考:
| 模型 | 可解释性 | 训练速度 | 预测速度 | 对特征尺度敏感度 | 调参复杂度 |
|---|---|---|---|---|---|
| 决策树 | 高 | 快 | 快 | 不敏感 | 低 |
| SVM | 低 | 中 | 中 | 敏感 | 中 |
| KNN | 无 | 无训练 | 慢 | 敏感 | 低 |
从表格能看出来,决策树在综合指标上最均衡,特别适合那种既需要解释模型行为、又需要快速出结果的场景。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 图像预处理链路:二值化、定位到5×5分块的具体实现
2.1 二值化:为什么用Otsu而不是固定阈值
二值化的目的是把灰度图像转换成只有黑白两种像素的图像,数字区域为黑色(像素值0),背景为白色(像素值255),或者反过来。这一步是整个识别流程的起点,直接影响后面所有环节的质量。
很多教程会直接告诉你“设阈值为128,大于128的变为白,小于128的变为黑”,但实际做的时候你会发现固定阈值很容易翻车。如果拍照时环境光不均匀,或者扫描时纸张泛黄,同一个数字在不同位置的灰度值差异会很大,一个固定阈值处理完,数字可能断成一截一截的。
这个项目里我用的是MATLAB的graythresh函数,它实现的是Otsu大津法。大津法的思路是遍历所有可能的灰度阈值,找到那个能让前景和背景两类的类间方差最大的值。说得通俗一点,就是找一个阈值,让分割之后的两组像素“各自尽可能紧凑,组间尽可能分开”。这样做的好处是阈值会根据每张图片的灰度分布自动调整,适应性很强。
核心代码如下:
matlab复制% 读取灰度图
img = imread('digit_sample.jpg');
if size(img, 3) == 3
img_gray = rgb2gray(img);
else
img_gray = img;
end
% Otsu自
