简介面向机器学习初学者与模式识别方向学习者的SVM三分类实践代码包围绕支持向量机处理多类别任务这一常见难点提供可运行的MATLAB实现思路。压缩包共6个文件以5个.m脚本和1个.data数据文件为主整体约4KB脚本分别承担训练、核函数设置、分类器构建与结果可视化数据文件可用于鸢尾花三分类实验便于对照理解从特征输入到类别判定的完整流程。已有6351人学习下载说明该主题在课程实验与入门项目中具有较高关注度。代码采用一对一策略拆解三分类问题结合fitcsvm训练多个二分类器并通过predict完成新样本预测读者可据此观察核函数、正则化参数与分类边界之间的关系也可在现有脚本上替换数据、调整参数并复现可视化结果适合作为SVM多分类入门的轻量级参考。1. 三分类 SVM 的真相二分类器拼出来的决策面为什么比想象中难支持向量机SVM常被当成一个“分类函数”但把 SVM 用在三分类上时真正要处理的不是三个类别标签而是三个二分类器如何组合、如何投票、如何在没有唯一胜者时给出稳定决策。SVM 的底层仍然是最大间隔与对偶问题三分类只是在这套几何语言外搭了一层决策表。搞懂这层表才能解释为什么同样的数据换一种投票方式准确率会差几个点也才能知道调 C、gamma 时到底在动哪块骨头。三分类的坑往往不在“能不能分开”而在“分完之后怎么合并”。一对多会把某个类别推成“多数派”一对一会在三个分类器之间出现 1:1:1 的平票核函数一换决策边界曲率变了投票结果也跟着抖。把 SVM 的三分类拆成“间隔几何、对偶求解、核映射、多类组合”四层后面每一层都能单独验证才不至于把它当成黑匣子。2. 最大间隔到对偶SVM 为什么只认少数支持向量2.1 间隔的几何点到超平面的距离怎么变成约束SVM 的起点是一个很朴素的几何问题在特征空间里找一个超平面把两类样本分开并且让离它最近的样本尽量远。设超平面为 (w^T\phi(x)b0)样本点到超平面的函数间隔是 (y_i(w^T\phi(x_i)b))几何间隔则要除以 (|w|)。把最近样本的几何间隔固定为 (1/|w|)最大化间隔就等价于最小化 (\frac{1}{2}|w|^2)约束是每个样本都满足 (y_i(w^T\phi(x_i)b)\ge 1)。这个变换的关键在于“固定尺度”。如果不固定(w) 和 (b) 同时放大函数间隔可以任意大问题就没有唯一解。固定最近样本的函数间隔为 1等于给超平面定了一个标尺后面所有支持向量都落在这个标尺的边界上。支持向量不是训练前挑出来的而是优化结束后由约束是否绷紧决定的。绝大多数样本的约束是松的只有少数样本把边界“撑住”这也是 SVM 解稀疏的根源。软间隔把这套几何放宽允许部分样本越过边界但越过要付代价。约束变成 (y_i(w^T\phi(x_i)b)\ge 1-\xi_i)目标里加 (\frac{1}{2}|w|^2 C\sum\xi_i)。(\xi_i) 是样本越界的程度C 是“越界罚款”的单价。C 越大模型越不愿意容忍越界间隔被压窄C 越小越愿意牺牲个别样本换一个宽间隔。这个单价不是玄学它直接决定支持向量的数量和模型的方差。2.2 拉格朗日对偶与 KKTalpha 非零的样本长什么样带约束的优化不好直接解标准做法是引入拉格朗日乘子 (\alpha_i\ge 0)把约束乘进去再对 (w)、(b) 求偏导置零。对 (w) 求导得到 (w\sum_i\alpha_i y_i\phi(x_i))对 (b) 求导得到 (\sum_i\alpha_i y_i0)。代回原式问题变成对偶形式最大化 (\sum_i\alpha_i-\frac{1}{2}\sum_i\sum_j\alpha_i\alpha_j y_i y_j K(x_i,x_j))约束是 (0\le\alpha_i\le C) 且 (\sum_i\alpha_i y_i0)。这里 (K(x_i,x_j)\phi(x_i)^T\phi(x_j))核函数就是在这一步进来的。对偶形式最值钱的地方是决策函数只依赖 (\alpha_i) 和核函数内积不需要显式知道 (\phi(x)) 长什么样。训练完成后(f(x)\sum_i\alpha_i y_i K(x_i,x)b)只有 (\alpha_i0) 的样本参与求和。这些样本就是支持向量。KKT 条件进一步说明它们分三类(\alpha_i0) 的样本在间隔外对决策没贡献(0\alpha_iC) 的样本刚好在间隔边界上是“自由支持向量”(\alpha_iC) 的样本越过了边界是“受罚支持向量”。这套 KKT 条件不只是理论装饰它是排查训练是否收敛的硬指标。一个训练好的软间隔 SVM如果出现 (\alpha_i0) 但 (y_i f(x_i)1)说明优化没收敛或者 b 算错了如果出现 (0\alpha_iC) 但 (y_i f(x_i)) 明显不等于 1说明自由支持向量的条件被破坏。下面这段纯 Python 代码不依赖任何第三方库用来对一个小规模对偶解做 KKT 体检。def decision_value(alpha, y, K, b, idx): # f(x_idx) sum_j alpha_j * y_j * K[j][idx] b s b for j in range(len(alpha)): s alpha[j] * y[j] * K[j][idx] return s def kkt_violation(alpha, y, K, b, C, tol1e-6): bad [] for i in range(len(alpha)): f decision_value(alpha, y, K, b, i) margin y[i] * f if alpha[i] tol and margin 1 - 1e-4: bad.append((i, alpha0 但 margin1)) elif alpha[i] C - tol and margin 1 1e-4: bad.append((i, alphaC 但 margin1)) elif tol alpha[i] C - tol and abs(margin - 1) 1e-4: bad.append((i, 自由向量 margin 不在 1 上)) return bad代码里的alpha是对偶变量y是 1/-1 标签K是核矩阵b是偏置C是软间隔单价。decision_value按对偶形式算决策值kkt_violation按三条 KKT 分支逐样本检查。实际训练时 b 通常取所有自由支持向量的平均值因为浮点误差会让单个自由向量的 margin 略偏 1。如果bad列表很长先别怀疑核函数优先检查 SMO 的停止条件是不是太松、b 的更新是不是用了旧 alpha。2.3 软间隔与 hinge lossC 到底在惩罚谁把软间隔的目标改写成损失函数视角会看得更清楚。约束 (y_i f(x_i)\ge 1-\xi_i) 且 (\xi_i\ge 0)消掉 (\xi_i) 后得到 (\xi_i\max(0,1-y_i f(x_i)))这就是 hinge loss。优化目标变成 (\frac{1}{2}|w|^2 C\sum_i\max(0,1-y_i f(x_i)))。第一项控制间隔宽度第二项控制越界程度C 是两者的交换率。hinge loss 在 (y_i f(x_i)1) 时梯度为零所以间隔外的样本不再推动边界在 (y_i f(x_i)1) 时梯度非零推动边界或调整 w。C 的物理意义可以这样理解C 大模型把每个越界样本都看得很重宁愿把间隔压窄也要把它们拉回边界内支持向量数量上升模型方差变大C 小模型更愿意忽略个别越界样本间隔变宽支持向量数量下降偏差变大。三分类里 C 的影响会被投票机制放大如果某个二分类器因为 C 过大而过拟合它在自己负责的那一对类别上投票很自信但在边界附近样本上可能给出极端决策值投票表就会偏向它。hinge loss 还解释了一个常见现象SVM 对类别不平衡很敏感。多数类样本多它们在损失里占的权重大模型倾向于把边界推向少数类少数类召回率下降。三分类里这种不平衡会在 OVO 的每一对分类器里重复出现最后投票结果被多数类主导。调 C 只能缓解不能根治更直接的办法是给类别加权或者在采样阶段做分层。3. 核函数与三分类RBF 核把边界弯进高维gamma 在控制什么3.1 核技巧的本质只算内积不显式升维线性 SVM 只能画超平面遇到同心圆、异或这类分布就无能为力。核技巧的做法是找一个映射 (\phi)把样本送到高维空间在高维里画超平面再投影回原空间边界就弯了。真正省事的地方在于对偶形式和决策函数里样本只以 (K(x_i,x_j)\phi(x_i)^T\phi(x_j)) 的形式出现根本不需要写出 (\phi)。只要一个函数满足 Mercer 条件能表示成某个高维空间的内积它就能当核函数用。常用的核有线性核、多项式核、RBF 核。三分类里 RBF 核最常见因为它只依赖样本间的欧氏距离平方(K(x,z)\exp(-\gamma|x-z|^2))。这个形式有一个很好的解释两个样本越近核值越接近 1越远核值越接近 0。每个支持向量像一个“局部影响源”决策函数是这些影响源的加权和。gamma 控制影响源的半径gamma 大半径小每个支持向量只影响附近一小片gamma 小半径大影响范围广。核技巧不是免费午餐。训练时要算核矩阵样本数为 n 时矩阵是 n×n内存 O(n²)计算 O(n²d)。三分类如果按 OVO 拆每个二分类器只用两类样本核矩阵可以按子集算但三个分类器加起来仍然可能重复计算。工程上常见的做法是先算全量核矩阵再切片或者按需缓存样本上万时全量核矩阵的内存就会变成瓶颈这时候要么换线性核要么用低秩近似。3.2 RBF 核的 gamma局部性、曲率与过拟合gamma 是 RBF 核里最容易被忽略、又最容易翻车的参数。它控制的是“多远算远”。gamma 很大时(\exp(-\gamma|x-z|^2)) 衰减很快只有非常近的样本才有非零核值决策边界会围着每个支持向量画小圈训练集准确率很高测试集一塌糊涂。gamma 很小时核值衰减慢所有样本看起来都差不多近决策边界接近线性模型欠拟合。从几何上看gamma 影响的是决策边界的曲率。gamma 越大边界越弯模型容量越大gamma 越小边界越平模型容量越小。C 和 gamma 要一起看C 大 gamma 大模型非常激进支持向量多且局部化容易过拟合C 小 gamma 小模型非常保守支持向量少且平滑容易欠拟合。常见的网格搜索不是碰运气而是在这两个维度上找方差和偏差的平衡点。三分类里 gamma 还有一个隐蔽影响它改变投票的“自信程度”。RBF 核的决策函数值是核加权和gamma 大时远离所有支持向量的样本会得到接近 0 的决策值投票时容易变成随机票gamma 小时决策值普遍偏大投票更稳定但边界可能太钝。所以调 gamma 不能只看准确率还要看决策函数值的分布尤其是边界附近样本的票数差。3.3 三分类的核矩阵缓存为什么训练时间对样本数敏感核矩阵是对称的算一半就能填满这是最基本的优化。下面这段代码用纯 Python 列表实现 RBF 核矩阵只算上三角再对称赋值避免重复计算距离平方。def rbf_kernel_matrix(X, gamma): n len(X) K [[0.0] * n for _ in range(n)] for i in range(n): K[i][i] 1.0 # 自身距离为 0核值为 1 for j in range(i 1, n): d2 0.0 for k in range(len(X[i])): diff X[i][k] - X[j][k] d2 diff * diff val 2.718281828459045 ** (-gamma * d2) K[i][j] val K[j][i] val # 对称赋值 return KX是二维列表每行一个样本gamma是 RBF 核的局部性参数K返回 n×n 对称矩阵。内层循环算的是欧氏距离平方指数用常数近似自然底数避免额外导入。这个实现的时间复杂度是 O(n²d)空间 O(n²)。三分类 OVO 训练三个二分类器时如果每个分类器都重新算核矩阵时间会翻三倍更合理的做法是先算全量 K再按类别对切片传给二分类器。核矩阵还有一个容易被忽略的检查项对角线必须全为 1。如果对角线不是 1说明距离算错、gamma 为负或者数据里有 NaN。对称性也要检查K[i][j]和K[j][i]差太多通常是循环边界写错。三分类里如果某个类别的样本特征尺度特别大RBF 核值会集体趋近 0核矩阵接近单位矩阵模型退化成“每个样本自己一类”这时候准确率看着不低但泛化基本没救。4. 三分类的拆法OVO、OVR 与投票表里的平票问题4.1 OVO三个二分类器怎么覆盖三类SVM 原生是二分类器三分类最直接的拆法是一对一OVO三类两两配对训练三个二分类器分别是 A 对 B、A 对 C、B 对 C。每个分类器只使用对应两类的样本训练时把第一类标 1第二类标 -1。预测时让三个分类器各自投票票数最多的类别胜出。三分类只有三个分类器票数分布只可能是 (3,0,0)、(2,1,0) 或 (1,1,1)。前两种没有歧义第三种就是平票。OVO 的好处是每个分类器只看两类样本问题规模小类别不平衡在局部更容易处理坏处是分类器数量随类别数平方增长类别多时训练成本高。三分类只有三个分类器成本还能接受所以很多教学实现和中小规模任务都用 OVO。OVO 的投票表还有一个特点每个分类器的决策函数值不能直接跨分类器比较因为不同分类器用的样本子集不同决策值的尺度可能不一样。三分类 OVO 的平票不是理论玩具。当样本落在三个类别的交界处三个分类器都可能给出“偏向自己正类”的弱信号票数打成 1:1:1。这时候只看票数没法决策必须引入第二层规则把每个分类器的决策函数值累加到对应类别上取累加值最大的类别或者用概率校准把决策值转成概率再平均。哪种规则更好取决于决策值的可比性。4.2 OVR三个“一对多”分类器的阈值与置信度一对多OVR是另一种拆法为每个类别训练一个二分类器正类是当前类别负类是其余所有类别。三分类就有三个分类器。预测时把样本喂给三个分类器取决策函数值最大的类别。OVR 的分类器数量随类别数线性增长训练成本低但每个分类器都要面对类别不平衡正类样本只占三分之一负类占三分之二边界容易被负类主导。OVR 的决策函数值有一个天然解释它表示样本离“当前类对其他类”的超平面有多远。值越大越像当前类。三个值可以直接比较因为每个分类器都在同一个特征空间里训练虽然样本子集不同但尺度大致可比。OVR 的缺点是“多对一”的负类太杂负类内部的结构被抹平某些类别组合可能被错分。三分类里如果三个类别呈链式分布OVR 的边界会互相打架。OVR 也有平票问题但形式不同两个分类器的决策值可能非常接近取最大值时对浮点误差敏感。工程上通常加一个优先级规则或者要求最大值比次大值大一个阈值否则进入人工复核。三分类里 OVR 的决策值分布通常比 OVO 更平滑因为每个分类器用了全部样本支持向量的影响范围更广。4.3 投票、决策函数值与平票处理把 OVO 的投票逻辑写清楚平票问题就暴露了。下面这段伪代码用纯 Python 描述三个二分类器的投票表train_binary返回一个决策函数model(x)大于 0 判正类否则判负类。def train_ovo(X, y, train_binary, classes): models [] for i in range(len(classes)): for j in range(i 1, len(classes)): idx [k for k in range(len(y)) if y[k] in (classes[i], classes[j])] Xp [X[k] for k in idx] yp [1 if y[k] classes[i] else -1 for k in idx] model train_binary(Xp, yp) models.append((classes[i], classes[j], model)) return models def predict_ovo(models, x, classes): votes {c: 0 for c in classes} scores {c: 0.0 for c in classes} for ci, cj, model in models: s model(x) # 正数偏向 ci负数偏向 cj if s 0: votes[ci] 1 else: votes[cj] 1 scores[ci] s scores[cj] - s # 先比票数票数相同再用决策值累加和打破平票 best max(classes, keylambda c: (votes[c], scores[c])) return best, votes, scoresclasses是类别列表train_binary是任意二分类 SVM 训练函数models存的是三个配对分类器。predict_ovo里votes记硬票scores记软票分类器偏向ci时给ci加决策值、给cj减决策值。最后按(票数, 决策值累加和)的字典序取最大票数平局时由软票决定。这个规则不是唯一解也可以先做概率校准再平均概率但软票的好处是不需要额外训练校准器代价是决策值的尺度依赖核函数和 C。三分类的 OVO 还有一个实现细节类别顺序会影响scores的符号累加但不会影响最终排序因为每个分类器对两个类别的贡献是相反的。真正会影响结果的是平票处理规则如果两个类别的票数和软票都相同max会取列表里靠前的类别这相当于引入了一个隐式先验。要避免这种隐式先验可以在平票时返回“拒识”或者走人工复核而不是硬塞一个类别。5. 避坑与排查三分类 SVM 翻车的五条血泪经验5.1 数据与特征层的坑现象训练集准确率 95%测试集只有 60%换随机种子结果大幅波动。原因通常不是 SVM 本身而是特征没有标准化。RBF 核算的是欧氏距离平方某个特征量纲是几千另一个是 0.1距离会被大量纲特征完全主导核矩阵接近单位矩阵模型退化成最近邻。解决办法是在划分训练集之后再做标准化把均值方差或者最大最小值只从训练集学出来再应用到验证集和测试集。三分类里如果某个类别的特征分布偏移标准化参数用全量数据算还会造成验证集泄漏血泪经验是标准化必须放进 Pipeline跟着交叉验证一起走。现象核矩阵对角线不是 1或者对称位置差很多。原因可能是距离计算时用了整数除法、gamma 传了负数、数据里有缺失值。RBF 核在自身距离为 0 时核值必须是 1这是硬性检查点。三分类里如果某个类别样本的特征维度不一致距离循环会提前结束核值偏小。解决办法是先做缺失值填充和维度检查再算核矩阵训练前打印K[i][i]的前几个值不是 1 就停下来。现象类别不平衡时总体准确率很高少数类召回率接近 0。原因在于 hinge loss 对多数类样本更敏感边界被推向少数类OVO 的每一对分类器里多数类在局部仍然占优。解决办法是给类别加权让少数类的损失权重更大或者用分层采样保证每个二分类器的正负样本比例接近评估时不要只看准确率看 macro-F1 和混淆矩阵。三分类里如果少数类只占 5%投票表会系统性地把它压掉调 C 只能缓解不能替代重采样。5.2 训练与决策层的坑现象三个 OVO 分类器投票出现 1:1:1预测结果随样本顺序变化。原因是硬投票只保留胜者丢掉了决策值的大小信息。三分类恰好有三个分类器平票是真实存在的。解决办法是用决策值累加和打破平票或者做概率校准如果业务允许拒识平票时返回“不确定”比硬猜更安全。注意不同二分类器的决策值尺度可能不同累加前最好做归一化否则某个核参数大的分类器会主导软票。现象C 调到很大训练集全对支持向量比例接近 100%测试集崩了。原因是 C 太大软间隔退化成硬间隔模型把每个越界样本都当成必须纠正的错误间隔被压得很窄支持向量覆盖了大部分样本。解决办法是把 C 和 gamma 放在一起做网格搜索监控支持向量比例比例过高说明模型太激进比例过低说明模型太保守。三分类里还要看每个二分类器的支持向量比例如果某一对类别比例异常高说明这一对的边界本身模糊可能需要单独特征或者更多样本。现象在训练集上调参交叉验证分数很好上线后掉点。原因是标准化、特征选择或者类别重采样在划分之前做了验证集信息泄漏进训练过程。解决办法是把所有预处理塞进 Pipeline用分层 K 折交叉验证每一折只在训练折上 fit 预处理器。三分类里分层尤其重要少数类在某一折里可能只剩几个样本OVO 的某个分类器会直接训不出来。看到某个分类器训练时报“只有单一类别”先检查分层抽样再检查标签编码。6. 设计取舍C、gamma 与投票策略的平衡以及怎么验证三分类 SVM 的调参不是找一组“最高分”的参数而是在三个维度上做取舍C 控制对越界样本的惩罚gamma 控制核的局部性投票策略控制多分类的合并方式。C 大、gamma 大模型容量大边界弯得厉害训练集好看但方差高C 小、gamma 小模型保守偏差高但稳定。OVO 和 OVR 也不是谁绝对更好OVO 每个分类器只看两类局部边界更干净但有平票和分类器数量平方增长的问题OVR 分类器少、决策值可比但负类太杂类别不平衡更严重。取舍点偏小/偏简单偏大/偏复杂验证信号C间隔宽容忍越界偏差高间隔窄支持向量多方差高支持向量比例、训练/验证差距gamma核宽边界平滑欠拟合核窄边界局部化过拟合核矩阵有效秩、边界附近决策值投票硬投票简单平票难处理软票/概率校准稳定成本高平票率、混淆矩阵、macro-F1验证时我一般会看四样东西分层 K 折的 macro-F1 而不是准确率混淆矩阵里哪两类互相污染支持向量占训练样本的比例以及边界附近样本的决策值分布。如果某一对类别的混淆特别严重先别调 C去看这两个类别的特征是否重叠、样本量是否悬殊如果支持向量比例超过 70%先把 C 降一个数量级如果平票率超过 5%把硬投票换成软票或者概率校准。三分类的评估比二分类更容易被总体准确率骗因为一个多数类就能把分数撑起来。从那以后我每次做三分类 SVM都强制走一遍“标准化进 Pipeline、先分层划分再调参、投票表打印平票率、KKT 体检”的流程不再把调参当成玄学。这份资料里的三分类实现和投票表可以对照着逐行拆尤其是核矩阵切片和软票累加那两段改几个参数就能看到边界怎么变。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?