29 经典分类器
识别篇走到这里,我们其实一直在做分类,只是形式越来越自由。条码识别本质上是”查表”——符号系统把答案预先编进了黑白条空,算法只需解码;OCR 把范围放宽到几十个字符类别,但字符集是已知且封闭的。本章面对的是通用分类问题本身:给定一个目标,判断它属于事先定义的哪一类——药片上的污点是黑点、纤维还是碎屑团?这类问题没有编码规则可依赖,只能从样本中学习统计规律。在深度学习席卷视觉领域之前,K 近邻(k-nearest neighbors, KNN)、多层感知机(multilayer perceptron, MLP)与支持向量机(support vector machine, SVM)是模式识别的”三大件”;时至今日,在特征维度低、样本量小、需要可解释性的工业任务里,它们依然是性价比之王——训练以秒计,预测以微秒计,不需要 GPU,错判时还能查出原因。本章用一个异物分选任务贯穿三种分类器:在亮背景上区分三类暗色目标——圆点(dot)、细长纤维(fiber)与不规则团簇(cluster),如 图 29.1 前三行所示;第四行那些”胖胶囊”是故意制造的边界样本,我们留到后面制造麻烦。
29.1 特征空间与可分性
分类器不直接吃图像。第一步是把每幅图变成一个特征向量(feature vector):阈值分割得到目标区域后,用 章节 23 介绍过的区域特征把它压缩成 4 个数——面积(area)、各向异性(anisometry)、圆形度(circularity)与紧凑度(compactness)。每个样本于是成为四维特征空间(feature space)中的一个点,“分类”就变成了在这个空间里划分领地的几何问题。
真实产线的异物长什么样?图 29.2 取自奥普特 Smart3 深度学习示例方案的一帧——一支半透明软管表面散落着多颗暗色异物。我们用与上文完全相同的流程处理它:在异物聚集区划定 ROI、阈值分割(管体亮、异物近黑)、blob 分析滤除印刷网点,再对每颗异物提取同一套 4 维区域特征。分割出的 6 颗异物及其真实测量值如下:
| 异物 | 面积 | 各向异性 | 圆形度 | 紧凑度 | 形态 |
|---|---|---|---|---|---|
| P1 | 1143 | 1.67 | 0.500 | 1.40 | 块状碎屑(团簇形) |
| P2 | 902 | 1.64 | 0.478 | 2.14 | 块状碎屑(团簇形) |
| P3 | 1180 | 2.29 | 0.358 | 2.21 | 块状碎屑(团簇形) |
| P4 | 562 | 1.14 | 0.753 | 1.09 | 紧凑暗点(圆点形) |
| P5 | 294 | 1.33 | 0.571 | 1.20 | 紧凑暗点(圆点形) |
| P6 | 322 | 3.28 | 0.280 | 1.73 | 细长碎屑(纤维形) |
这组真实测量印证了合成任务的设定并非凭空构造:P4、P5 是紧凑暗点(圆形度 0.57~0.75、各向异性近 1),对应”圆点”;P1~P3 是面积更大、边缘破碎的块状碎屑(圆形度 0.36~0.50),对应”团簇”;P6 是一条细长碎屑(各向异性 3.28、圆形度仅 0.28),形态偏向”纤维”。真实异物确实分布在圆点—纤维—团簇这同一条形态谱上,本章的特征设计因此有据可依。
但正因为它”真实”,分类器的训练与评估反而无法建立在它之上,原因有三:其一,单帧仅 6 颗异物、且无逐颗类别真值标注,凑不出每类 40/20 的训练—测试切分;其二,真实异物的形态谱是连续的——P6 的各向异性 3.28 正贴着我们后面特意合成的”胖胶囊”边界样本区间(3.33~5.16)的下沿,类与类之间没有干净的间隔,无法用来公平地比较三种分类器的边界行为;其三,受控合成集能精确摆布可分性、样本量与边界难度,正是后续实验所需的”实验台”。因此本章的分工是:真实样图负责验证特征提取与形态谱的真实性,分类器的训练与对照实验则在可标注、可切分、可调难度的受控合成集上进行(合成样本见 图 29.1)。
图 29.3 把这套受控合成数据集投影到各向异性 × 圆形度平面上。三类目标的格局一目了然:纤维(十字)孤悬右下——各向异性 7.15~18.0、圆形度仅 0.07~0.15,与谁都不挨着;而圆点(实心圆盘)与团簇(空心方框)却在左上角挤成一团——圆点的各向异性 1.00~1.48 与团簇的 1.09~2.17 几乎重合,圆形度区间(0.50~0.79 对 0.41~0.68)也大面积交叠。仅凭这两维,没有任何分类器能把它们分开。救兵在另外两维:圆点面积 54~340,团簇面积 373~807,一刀两断;紧凑度(0.90~1.09 对 1.17~1.66)同样泾渭分明。灰色菱形是那 10 个边界胶囊,各向异性 3.33~5.16,恰好架在圆点与纤维之间的真空地带。
这张图给出本章第一条、也可能是最重要的一条结论:可分性(separability)首先取决于特征选择,其次才轮到分类器。特征选对了,三类样本在四维空间里彼此远离,连最朴素的分类器都能全对;特征选错了——比如只给前两维——再精巧的模型也无米下锅。工程上排查分类效果差的问题,应当先画特征散点图检查可分性,而不是急着换模型调参数。
特征工程与端到端学习的分工:深度网络把”提特征”也交给学习,代价是需要大量标注样本;经典分类器把特征交给领域知识(形状、纹理、颜色统计),学习只负责低维空间里的划界。工业任务常常样本少而机理清楚,恰好落在后者的舒适区。
29.2 三种分类器的几何
三种分类器都在特征空间里学一个从向量到类别的映射 \(f:\mathbb{R}^4 \to \{\text{dot},\text{fiber},\text{cluster}\}\),但它们的”世界观”截然不同。
KNN 不学习,只记忆。训练阶段仅仅把全部样本存起来(实践中建一棵 kd 树加速检索);预测时找出与查询点最近的 \(K\) 个训练样本,让它们投票。它的决策完全是局部的:边界由零星几个样本的位置决定,样本带噪声,边界就跟着斑驳起伏。优点同样源于此——无训练成本、增删样本即时生效、每个判决都能指出”是哪几个邻居投的票”。不同特征量纲悬殊(面积以百计、圆形度不足 1),距离计算前必须做归一化,否则面积一维独霸距离。
MLP 学一个平滑函数。输入层 4 个节点对应特征,隐层做非线性变换,输出层 3 个节点经归一化后给出各类的软分数(soft score),取最大者为判决。训练用反向传播迭代拟合,是三者中唯一需要”慢慢学”的。它的决策是全局的:所有训练样本共同塑造一个连续曲面,个别噪声样本被平均掉,边界因此平滑。代价是训练有随机性、隐层节点数要人为指定,而且软分数只是拟合的副产品,并非校准过的概率——这一点后面有实例。
SVM 只关心边界附近。它寻找把两类分开且间隔(margin)最大的超平面,超平面只由贴着间隔的少数支持向量(support vector)决定,远处的样本再多也不参与;配合核技巧(kernel trick)——本章用径向基函数(radial basis function, RBF)核——可以在隐式的高维空间里划线性边界,回到原空间就成了弯曲边界。决策函数形如
\[ f(\mathbf{x}) = \operatorname{sign}\Big(\sum_{i} \alpha_i y_i\, k(\mathbf{x}_i, \mathbf{x}) + b\Big), \]
求和只跑遍支持向量。多分类由多个两类判决组合而成。
\(K\) 的选择是 KNN 唯一的超参数:\(K=1\) 边界最碎、对噪声最敏感;\(K\) 增大边界变平滑,但小类会被大类”淹没”。本章用 \(K=3\)——既能投票(避免单个噪声邻居一票定音),又不至于在 40 个样本/类的规模上抹平局部结构。
三种世界观投射到平面上就是 图 29.4。我们把面积与紧凑度固定在训练集均值(301.5 与 2.15),在各向异性 × 圆形度平面上以 72×72 网格逐点询问三个训练好的模型。KNN(左)的上半区一片斑驳——固定的面积值恰好落在圆点与团簇之间,这片区域距离两类样本都不近,判决在邻居的拉锯中来回翻转,右侧还散落着孤立的”飞地”;MLP(中)给出教科书般干净的平滑边界;SVM(右)的边界由几段近似直线的弧拼接而成——每一段都是某几个支持向量的 RBF 影响范围的交界。三幅图分类同一份数据、准确率不相上下,但边界形态忠实暴露了各自的归纳偏置(inductive bias):局部记忆、全局拟合、最大间隔。
29.3 实验:准确率与样本量
每类 60 个样本按 40/20 切分训练与测试,再把训练集砍到每类 5 个、甚至每类 2 个,考察样本量的影响。全部结果如下(测试集恒为 3×20):
| 分类器 | 训练 120(40/类) | 训练 15(5/类) | 训练 6(2/类) |
|---|---|---|---|
| KNN(\(K=3\),z-score,kd 树) | 100% | 98.3%1 | 100% |
| MLP(4-6-3,500 次迭代) | 100% | 100% | 100% |
| SVM(NU-SVC,RBF 核) | 100% | 100% | 100% |
诚实的结论是:没有谁崩溃。每类只剩 2 个训练样本时,三个分类器仍然几乎全对——因为特征空间里三类本来就离得远,2 个点也足以标定各自的领地。这正是上一节论断的反面印证:可分性好的任务对分类器选择和样本量都不敏感;分类器之间的差异,要到边界地带和真正的小样本难题上才会显形。指望用一个”易分”的验收数据集比出三种分类器的高下,是产线选型时常见的方法论错误。
表中结果有一处须如实交代:KNN 的准确率跨运行不稳定——SDK 的 kd 树构建存在非确定性,同一份数据重跑,KNN 的判定会出现偶发翻转——实测 5 次重跑准确率在 91.7%~100% 之间波动,最多一次翻转 5 个样本,且并不固定出现在哪个训练规模档。MLP 与 SVM 的结果则完全可复现。
29.4 模糊地带:置信度的三种语义
现在请出第四行那 10 个胖胶囊。它们的各向异性 3.33~5.16,落在圆点(≤1.48)与纤维(≥7.15)之间的无人区,没有标准答案——我们感兴趣的不是”对错”,而是三个用同一份数据训练出来的分类器会怎么判、以及它们各自如何表达”有多确定”。
结果出现了真实的三方分歧:KNN 把 8 个判为纤维、2 个判为圆点;MLP 把 10 个全部判为圆点;SVM 把 9 个判为纤维、1 个判为团簇。同样的训练集,同样的查询点,三种归纳偏置给出三种答案:KNN 看局部——胶囊离纤维样本群更近,邻居投票就倒向纤维;MLP 的全局平滑曲面把圆点的领地一路外推过了无人区;SVM 的间隔中线把多数胶囊划给纤维一侧,而最接近圆形的那个(各向异性 3.33)竟被推给了第三方团簇。分类器在边界处的分歧,暴露的不是谁对谁错,而是它们各自的归纳偏置——训练数据没有覆盖的地方,模型只能按自己的世界观脑补。
更有工程价值的是三者表达置信度的方式。KNN 给出邻居的票数与相似度:8 个判纤维的样本是 3 票全票,而某个判圆点的样本输出”dot 得票 0.667(相似度 0.613)、fiber 得票 0.333(相似度 0.635)“——票数与相似度甚至互相矛盾,犹豫写在脸上。MLP 给出软分数:近半数胶囊给出 dot=1.000 的满分,其余多在 0.95 以上(最低 0.703)——再次提醒,软分数是曲面外推的副产品,高分不等于高可靠,距离训练数据越远越要打折扣。SVM 在本 SDK 中最干脆:只给硬标签,没有分数、没有间隔距离,连”犹豫”的渠道都没有。
拒识(rejection)是工业分类的标配需求:与其错判流出,不如挑出存疑样本转人工复核。实现拒识的前提是分类器输出可比较的置信度并支持阈值化——这与 章节 28 中用 minScore 卡掉低分字符是同一个机制。
这就引出选型铁律:需要拒识或人工复核兜底的产线,必须选置信度输出可用的分类器。本 SDK 的 SVM 输出硬标签,意味着它在这条产线上无法支持”低置信度送人工”的流程——无论它的准确率多漂亮。
29.5 SciVision 实现
三个分类器分别由 SciSvKNNClassifier、SciSvMLPClassifier、SciSvSVMClassifier 提供,调用骨架一致:初始化 → 逐样本 AddSample → Train → Classify。
// KNN:无训练过程,Train 只是建索引
SCIMV::SciSvKNNClassifier knn;
knn.InitializeModel(4, featNames); // 特征维数 + 特征名数组
knn.AddSample(featVec, className); // 逐样本加入(4 维 SciVarArray + 类名)
knn.Train(SCI_KNN_Z_SCORE, SCI_KNN_KDTREE); // z-score 归一化, kd 树索引
knn.Classify(3, SCI_KNN_MAX_COUNT, fv, // K=3, 多数票
&cls, &score, &names, &votes, &sims); // 输出邻居票数与相似度
// MLP:4-6-3 全连接网络
SCIMV::SciSvMLPClassifier mlp;
mlp.LoadModel(path); // 必须先调用!文件不存在也行(见下)
mlp.InitializeModel(0, 4, 6, 3); // 模式 0=特征分类; 输入/隐层/输出节点数
mlp.SetNames(featNames, classNames);
mlp.Train(500, 1e-6f, 0.0f, 1, 0); // 最大迭代, 误差容限, 无正则, 标准化
mlp.Classify(fv, &cls, &score, &names, &scores); // 输出全类软分数
// SVM:NU-SVC + RBF 核
SCIMV::SciSvSVMClassifier svm;
svm.LoadModel(path); // 同样必须先调用
svm.InitializeModel(featNames, 4, SCI_SVM_NU_SVC, 0.01, SCI_SVM_RBF, 1.0, 1.0, 0.0);
svm.Train(1000, 1e-6f, 1); // min-max 标准化
svm.Classify(fv, &cls, &names); // 只有硬标签,无分数/间隔输出API 本身都能跑通,但这组模块存在本书遇到的最严重的一批 SDK 缺陷——多条跨模块的进程级状态污染,全部经过反复实测定位,原样记录:
ManualThreshold之后 KNNTrain必崩溃(0xC0000005):同一进程里只要调用过阈值分割,后续 KNN 训练就访问违例。这迫使本章示例拆成两次进程运行:demo.exe extract负责合成数据、阈值分割、特征提取并写出features.csv;demo.exe classify另起进程读 CSV 完成训练与评估。特征与分类之间用纯文本文件交接,是当前唯一稳妥的隔离方式。- MLP 与 SVM 都训练过之后,再训练 KNN 得到静默坏模型:不报错、不崩溃,但团簇类测试 0/20 全错。规避方法是按”KNN 块 → MLP 块 → SVM 块”组织代码,一个分类器的全部训练与预测做完再碰下一个。
- 约 100 次 KNN
Classify之后,同进程的下一次 KNNTrain崩溃或产出坏模型(实测 10 次安全、100 次崩溃):每个块内必须先把所有模型训练完,再开始批量预测。 - KNN
Train的 treeType=1(暴力搜索)训练后Classify崩溃——只能用 kd 树(0);而 kd 树又有跨运行非确定性(上节 ±1 翻转的根源)。 - MLP/SVM 的
InitializeModel之前必须先调一次LoadModel,否则报 122706011/122707003——传一个不存在的文件路径也行,返回”已新建空模型”码即可继续。 - KNN 归一化模式 0/1 下
Classify返回的类名字符串偶发乱码——用 z-score(模式 2)未观察到此问题。
这份清单的方法论意义不亚于其内容:商用库的稳定性边界不会写在手册里,只能用系统化的实验探明——固定其余变量、逐一引入模块、用探针程序定位崩溃阈值,与 章节 5 用黄金实验反推接口真实行为是同一套思路。把绕行方案连同来龙去脉写进代码注释(见 code/classical_classifiers/main.cpp 文件头),后来者才不必再踩一遍。
工业案例:人造革纹理分选的特征之争
某人造革产线需按纹理粗细把成品分为三个等级。初版方案把整幅原始图像缩放后直接喂给 MLP,“端到端”的准确率勉强过八成,且对光照漂移极其敏感,每次换灯重训一次。复盘后改走经典路线:每幅图先计算 GLCM 的能量、对比度、相关性、同质性四个纹理特征(见 章节 25),再用 KNN 在这个四维空间里分类。准确率反超原方案十多个百分点,对光照漂移也稳定——纹理统计量对整体亮度变化天然不敏感。更受质检员欢迎的是可解释性:每个错判样本都能调出它的 3 个近邻图像人工比对,边界等级的革面”像谁”一目了然。教训:小数据的工业任务里,好特征 + 简单分类器通常胜过原始数据 + 复杂模型。
29.6 小结
- 可分性先于分类器:分类问题的成败大半在特征选择阶段就已决定。本章任务里圆点与团簇在各向异性 × 圆形度投影中完全重叠,靠面积与紧凑度才分开——排查分类问题先画特征散点图。
- 三种归纳偏置:KNN 局部记忆(边界斑驳、判决可溯源到邻居)、MLP 全局平滑函数(软分数、需迭代训练)、SVM 最大间隔 + 核技巧(边界只由支持向量决定)。易分任务上三者难分高下——每类 2 个训练样本都能近乎全对;差异在边界地带才显形。
- 置信度语义各不相同:邻居票数 + 相似度、软分数、硬标签——10 个边界胶囊让三个 100% 准确率的分类器给出 8:2、10:0、9:1 三种判决。需要拒识与人工复核的产线,必须选置信度输出可用、可阈值化的分类器。
- 商用库的稳定性边界要用实验探明:本章 SDK 的分类器模块存在跨模块内存破坏,规避方案(特征提取与分类分两个进程、先全部训练再批量预测)来自系统化的崩溃定位实验,而非手册。
- 更系统的分类器理论(贝叶斯决策、特征选择与新颖性检测)可阅读 Steger 等人著作的分类章节 (Steger, Ulrich, 和 Wiedemann 2018),以及模式识别的两部标准教材 (Duda, Hart, 和 Stork 2001; Bishop 2006)。本章三种分类器各自的奠基文献都值得回到原文:KNN 的渐近误差界由 Cover 与 Hart 给出 (Cover 和 Hart 1967),MLP 训练所依赖的反向传播由 Rumelhart、Hinton 与 Williams 确立 (Rumelhart, Hinton, 和 Williams 1986),SVM(含最大间隔与核技巧)则源自 Cortes 与 Vapnik 的支持向量网络 (Cortes 和 Vapnik 1995)。
识别篇至此收束。从条码的确定性解码,到 OCR 的封闭集字符分类,再到本章面向开放特征空间的统计决策——三章走完了”从查表到学习”的谱系:编码规则能管住的交给解码器,规则管不住的交给特征与分类器,而分类器管不住的边界地带,留给置信度与人工复核。
单次代表值;实测 5 次重跑范围 91.7%~100%,非确定性不限于此训练规模档↩︎



