想象你是一位篮球教练,新赛季要排出全队最优的攻防阵型。24 名球员的站位、跑位、防守默契互相影响,直接“一次性设计所有人的完美站位”几乎不可能——变量太多、约束太杂,算到天荒地老也解不开。但经验丰富的老教练有自己的诀窍:每一次训练只挑两名球员,根据他们之间的对位关系微调跑位,改完一个组合再换下一组。几十轮下来,全队阵型居然不知不觉逼近了最优。 支持向量机(SVM)的训练,本质上就是“排阵型”——求解一个带约束的优化问题。SMO(序列最小优化)算法就是那位老教练:它不一次性求解所有参数,而是每次只调整两个拉格朗日乘子,绕开了大规模矩阵运算,让 SVM 的训练从“实验室里的数学品”变成了“工业界的日用品”。本文就用这支球队的故事,带你看懂 SMO 的每一步。
掌握 SMO 的思想,你就拿到了解读 scikit-learn、libsvm 等主流 SVM 库加速内核的钥匙,也理解了大规模优化“分而治之”的通用智慧。
可视化演示
展示AI驱动的晶体结构搜索流程,从候选结构生成、能量计算到稳定结构筛选,直观理解材料发现的核心方法。
本节配有交互动效(进入视口后自动加载,约 3KB)。
晶体结构搜索与筛选 候选生成:通过启发式或生成模型产生候选晶体构型 能量计算:DFT或ML势函数评估每个构型的形成能 稳定性筛选:与凸包比较,筛选出热力学稳定的新结构 Stable = E_hull < 0 (eV/atom) 结构搜索是材料发现的核心步骤
对偶问题引入
教练直接设计全队 24 人的跑位图,要同时满足体能、轮换、防守补位等几十条规则——这就是 SVM 的原始问题:找到权重向量 \(\mathbf{w}\) 和偏置 \(b\),让两类样本的间隔最大,同时所有样本满足 \(y_i(\mathbf{w}\cdot\mathbf{x}_i+b)\ge 1\)。直接在成千上万个特征上求解这个大优化,计算量惊人。
聪明的数学家发现:与其死磕“每个人的站位”,不如把注意力转向“球员之间的对位关系”。他们用拉格朗日乘子把原始约束吸收进目标函数,把问题转化为对偶问题——决策变量从高维的 \(\mathbf{w}\) 变成每个样本对应的乘子 \(\alpha_i\)。对偶问题的约束简单得只剩上下界和一个等式,还能顺手引入核函数把样本映射到高维空间。
如此,原始问题变成“给每个球员分配合理的发言权”。而 SMO 的使命,就是在 \(n\) 个 \(\alpha_i\) 组成的大棋盘上,一步步走出最优解。
- 乘子的含义\(\alpha_i\) 是第 \(i\) 个样本的拉格朗日乘子,它衡量该样本对决策边界的“发言权”。
- 目标的两部分\(\sum\alpha_i\) 奖励每个样本的宽松度,二重和 \(\frac{1}{2}\sum\sum\alpha_i\alpha_j y_i y_j K_{ij}\) 惩罚样本两两之间的“对位冲突”。
- 核函数的角色\(K(\mathbf{x}_i,\mathbf{x}_j)\) 计算两个样本的心理相似度,相似度越高,乘积贡献越大。
- 平衡约束\(\sum\alpha_i y_i=0\) 就像正负球员总贡献必须抵消,这个等式是后面一切推导的锚点。
拉格朗日乘子
每个球员都有一个“上场额度”\(\alpha_i\)。额度为 0 的球员坐在板凳上,对比赛毫无影响;额度大于 0 的球员才真正参与攻防,他们就是支持向量;还有一类球员额度被顶到上限 \(C\),像是被对方犯规动作压制住的球员——他们要么贴着边界,要么已经越到了错误一侧。
KKT 条件就是教练的“上岗资格检查表”。对每个样本 \(i\),根据 \(\alpha_i\) 的取值,检查它和间隔边界的位置关系是否匹配:
训练完成后,绝大多数样本的 \(\alpha_i\) 都是 0,只有少数支持向量留下。这正是 SVM 预测速度快的秘诀:决策只由少数“关键球员”决定,与全队板凳无关。
- 函数值\(f(\mathbf{x}_i)=\mathbf{w}\cdot\mathbf{x}_i+b\) 是当前模型对样本 \(\mathbf{x}_i\) 的打分。
- 第一分支\(\alpha_i=0\) 时要求 \(y_i f(\mathbf{x}_i)\ge 1\),即该样本被充分正确分类且远离边界——板凳球员不添乱。
- 第二分支\(0<\alpha_i<C\) 时要求 \(y_i f(\mathbf{x}_i)=1\),样本恰好卡在间隔边界上——核心球员站在最前线。
- 第三分支\(\alpha_i=C\) 时允许 \(y_i f(\mathbf{x}_i)\le 1\),样本可能在间隔内甚至错侧——满额球员正在“被惩罚”。
两变量优化
老教练知道:一次调整一名球员会导致全队阵型失衡。因为球员们被一个看不见的规则焊在一起——正样本总贡献与负样本总贡献必须相等,即 \(\sum\alpha_i y_i=0\)。改动任何一个 \(\alpha_i\),等式立刻被破坏。所以最少也得同时调整两个乘子,一个动,另一个反向补偿,天平才能保持平衡。
这就是 SMO 的核心思想:每次只取两个乘子 \((\alpha_1,\alpha_2)\) 联动优化,其余 \(n-2\) 个乘子全部固定。固定住其它乘子后,等式约束把 \(\alpha_1\) 和 \(\alpha_2\) 绑定成一个线性关系:
每一轮“双人配合”都在二维空间里完成一次精确的小幅调整。全局大棋盘上的几十步,就这样被拆成几十次简单的一维寻优。
- 常数部分\(\zeta=-\sum_{i=3}^{n}\alpha_i y_i\) 是其余 \(n-2\) 个乘子贡献的总和,本轮内它保持不变。
- 消去 alpha1由等式 \(\alpha_1 y_1+\alpha_2 y_2=\zeta\) 可得 \(\alpha_1=y_1(\zeta-\alpha_2 y_2)\)。
- 降维优化把 \(\alpha_1\) 的表达式代入对偶目标 \(W(\alpha)\),目标函数变成只关于 \(\alpha_2\) 的一元二次函数,可直接求解析解。
乘子更新
确定了要调整的两个人,接下来就是“怎么调”。第一步先不管任何限制,在理想情况下算出 \(\alpha_2\) 的新位置——这叫未剪辑更新。它利用两个样本的预测误差 \(E_1,E_2\) 和核函数组合 \(\eta\) 决定移动的方向和步长:
理论位置算好后,还要检查它是否越界。\(\alpha_2\) 的合法活动区间是 \([L,H]\),由两个样本的旧乘子、类别异同以及正则化参数 \(C\) 共同决定。如果算出的新值越界,就把它剪辑回区间边界:
这一轮调整是否有效,取决于 \(\alpha_2\) 是否真正移动了足够距离。如果移动量小于容差,这对组合就被判定为“没有配合价值”,跳过。
- 预测误差\(E_i=f(\mathbf{x}_i)-y_i\),正值说明模型打分偏高,负值说明偏低。
- 配合曲率\(\eta=K_{11}+K_{22}-2K_{12}\),\(\eta\) 越大,两个样本在特征空间里越“彼此独立”,一步能跨出的距离越有把握。
- 同号与异号当 \(y_1\ne y_2\) 时 \(L=\max(0,\alpha_2^{\text{old}}-\alpha_1^{\text{old}})\),\(H=\min(C,C+\alpha_2^{\text{old}}-\alpha_1^{\text{old}})\);当 \(y_1=y_2\) 时 \(L=\max(0,\alpha_1^{\text{old}}+\alpha_2^{\text{old}}-C)\),\(H=\min(C,\alpha_1^{\text{old}}+\alpha_2^{\text{old}})\)。
- 联动更新\(\alpha_2\) 落定后,\(\alpha_1\) 由等式约束推出:\(\alpha_1^{\text{new}}=\alpha_1^{\text{old}}+y_1y_2(\alpha_2^{\text{old}}-\alpha_2^{\text{new}})\)。
变量选择
教练不会闭眼乱点两个人——他先找全场最不守规矩的球员,再给他搭配一个“一推就动”的搭档。SMO 的变量选择正是这个套路:外层循环负责抓人,内层循环负责配对。
外层循环遍历所有样本,优先扫描 \(0<\alpha_i<C\) 的支持向量集合(这些是站在边界线上的核心球员,最容易违背 KKT),检查它们是否违反 KKT 条件。如果这批人全部“守规矩”,再扩大到全体样本扫描一遍。判断是否违规的公式是:
启发式选择让收敛速度提升了几个数量级。它不追求理论上最完美的搭档,而是用“最大步长”这种又快又稳的经验法则,配合兜底扫描保证不遗漏任何机会。
- 打分与标签\(r_i=y_i f(\mathbf{x}_i)\),若 \(r_i\ge 1\) 表示样本在自己一侧且离边界够远。
- 上调条件\(r_i<1\) 且 \(\alpha_i<C\),说明样本被错误分类或太靠近边界,但它的额度还没用满,应该增加 \(\alpha_i\) 给它更多发言权。
- 下调条件\(r_i>1\) 且 \(\alpha_i>0\),说明样本分类得很安全,却还占着发言权,应该减少 \(\alpha_i\) 让它“回到板凳”。
- 双循环结构外层循环负责找违规者,内层循环从其余样本中挑选使 \(|E_1-E_2|\) 最大的搭档,保证每步移动尽量大。
阈值更新
两个人的跑位调整完毕后,全队的“中线”也就该重新画了。在 SVM 里,这条中线就是偏置 \(b\):它决定决策边界整体平移的位置。如果 \(b\) 不更新,前面的努力会被整体带偏。
更新规则是:分别用两个被调整的样本计算候选阈值 \(b_1^{\text{new}}\) 和 \(b_2^{\text{new}}\),再根据新的乘子落点挑选最终值。单个候选的计算式为:
这一收一放之间,决策边界被精确校准,下一轮“抓违规者”时判断依据才可靠。
- 旧哨位修正\(b^{\text{old}}-E_1\) 先用第一个样本的预测误差修正旧哨位。
- 乘子变化补偿\(y_1(\alpha_1^{\text{new}}-\alpha_1^{\text{old}})K_{11}\) 补偿乘子 \(\alpha_1\) 变化对样本 1 打分的影响。
- 交叉影响补偿\(-y_2(\alpha_2^{\text{new}}-\alpha_2^{\text{old}})K_{12}\) 补偿乘子 \(\alpha_2\) 变化对样本 1 打分的影响。
- 最终选择规则若 \(0<\alpha_1^{\text{new}}<C\) 则取 \(b_1^{\text{new}}\);否则若 \(0<\alpha_2^{\text{new}}<C\) 取 \(b_2^{\text{new}}\);若两者都在边界上,取 \(b_1^{\text{new}}\) 与 \(b_2^{\text{new}}\) 的区间中点。
收敛与实战
训练什么时候结束?老教练的标准是:全场球员都按规矩站位(KKT 条件在容差内满足),或者训练课达到预设上限。SMO 的收敛判据与此类似——外层循环连续若干次扫描都找不到值得调整的样本对,就宣告收敛;同时设置最大迭代次数作为保险丝,防止算法在边界上震荡不休。
训练完成后,预测新样本只需要调用决策函数:
从理论到落地,SMO 用“每次两名球员、逐步逼近”的策略,把 SVM 的训练从不可能变成了可能。今天你在 scikit-learn 里敲下 SVC(),背后默默运行的,依然是这位老教练的排兵布阵术。
- 核函数缓存\(K(\mathbf{x}_i,\mathbf{x})\) 在 SMO 每次更新中反复出现,工程实现通常把 \(n\times n\) 的核矩阵预计算缓存,避免重复计算。
- 稀疏求和绝大多数 \(\alpha_i=0\),求和项大幅减少——这正是只保留支持向量的稀疏性红利。
- 工程要点数值上要对 \(\eta\le 0\) 的退化情况做保护(直接跳过),对 \(\alpha_i\) 更新做浮点容差判断,防止累积误差导致循环无法退出。
AI 应用场景
垃圾邮件与文本分类
阶段:模型训练与优化
解决的问题:文本特征动辄数万维(词袋或 TF-IDF),直接求解 SVM 原始问题需要存储和分解高维稠密矩阵,内存与算力都吃不消。SMO 每次只更新两个乘子,核矩阵按需参与计算,让高维稀疏文本上的 SVM 训练变得可行。
具体案例:某邮箱服务商每天需拦截数亿封垃圾邮件。工程师用 TF-IDF 把邮件转为稀疏向量,选用线性核 SVM + SMO 求解器训练分类器。训练完成后模型只保留数千个支持向量,单封邮件预测仅需一次稀疏内积计算,延迟控制在毫秒级,准确率稳定在 99% 以上。
金融信用评分
阶段:特征工程与模型可解释性验证
解决的问题:信贷审批对模型可解释性要求高,黑箱模型难以过审。SVM 对偶问题的稀疏性让“哪些样本决定了决策边界”一目了然——支持向量就是最有代表性的风险案例,风控人员可以直接审查它们。
具体案例:某消费金融公司构建申请评分卡,用 SMO 求解的 SVM 识别违约风险。模型从 20 万条申请记录中筛出约 1.2 万个支持向量作为“关键风险画像”,业务团队核对这些样本的共性特征后,将模型上线并配合规则引擎做审批,坏账率较旧模型下降 18%。
基因表达谱分类
阶段:小样本高维数据的模型训练
解决的问题:基因表达数据通常只有几十到几百个样本,特征却达数万个,极易过拟合。SVM 配合核函数天然适合这种“样本少、特征多”的场景,而 SMO 避免了大矩阵分解,让非线性核 SVM 在普通工作站上也能跑完训练。
具体案例:肿瘤研究团队用 SMO 求解的 RBF 核 SVM 区分正常样本与癌症样本。训练集仅 60 例,特征 2.4 万个,SMO 在数十轮迭代内收敛,识别出 12 个关键支持向量。后续病理验证发现其中 9 个基因与已有文献报道的癌症标志物吻合。
Python 演示
import numpy as np class SimpleSMO: def __init__(self, C=1.0, tol=1e-4, max_passes=10): self.C = C self.tol = tol self.max_passes = max_passes self.alphas = None self.b = 0.0 self.K = None self.X = None self.y = None def _kernel(self, xi, xj): return np.dot(xi, xj) def _compute_K(self, X): n = X.shape[0] K = np.empty((n, n)) for i in range(n): for j in range(n): K[i, j] = self._kernel(X[i], X[j]) return K def _error(self, i): n = self.X.shape[0] fx = sum(self.alphas[j] * self.y[j] * self.K[j, i] for j in range(n)) + self.b return fx - self.y[i] def _select_second(self, i1, E1): n = self.X.shape[0] cand = [i for i in range(n) if i != i1 and 0.0 < self.alphas[i] < self.C] if not cand: cand = [i for i in range(n) if i != i1] i2 = cand[0] best = -1.0 for i in cand: Ei = self._error(i) gap = abs(E1 - Ei) if gap > best: best = gap i2 = i return i2 def _take_step(self, i1, i2): if i1 == i2: return 0 a1_old = self.alphas[i1] a2_old = self.alphas[i2] y1 = self.y[i1] y2 = self.y[i2] s = y1 * y2 E1 = self._error(i1) E2 = self._error(i2) if y1 != y2: L = max(0.0, a2_old - a1_old) H = min(self.C, self.C + a2_old - a1_old) else: L = max(0.0, a1_old + a2_old - self.C) H = min(self.C, a1_old + a2_old) if L == H: return 0 eta = self.K[i1, i1] + self.K[i2, i2] - 2.0 * self.K[i1, i2] if eta <= 0: return 0 a2_new = a2_old + y2 * (E1 - E2) / eta a2_new = min(H, max(L, a2_new)) if abs(a2_new - a2_old) < 1e-6: return 0 a1_new = a1_old + s * (a2_old - a2_new) if 0.0 < a1_new < self.C: b_new = self.b - E1 - y1 * (a1_new - a1_old) * self.K[i1, i1] \ - y2 * (a2_new - a2_old) * self.K[i1, i2] elif 0.0 < a2_new < self.C: b_new = self.b - E2 - y1 * (a1_new - a1_old) * self.K[i1, i2] \ - y2 * (a2_new - a2_old) * self.K[i2, i2] else: b1 = self.b - E1 - y1 * (a1_new - a1_old) * self.K[i1, i1] \ - y2 * (a2_new - a2_old) * self.K[i1, i2] b2 = self.b - E2 - y1 * (a1_new - a1_old) * self.K[i1, i2] \ - y2 * (a2_new - a2_old) * self.K[i2, i2] b_new = (b1 + b2) / 2.0 self.alphas[i1] = a1_new self.alphas[i2] = a2_new self.b = b_new return 1 def train(self, X, y): self.X = X self.y = y n = X.shape[0] self.alphas = np.zeros(n) self.b = 0.0 self.K = self._compute_K(X) num_changed = 0 passes = 0 while passes < self.max_passes: full_scan = (num_changed == 0) or (passes % 3 == 0) scanned = range(n) if full_scan else \ [i for i in range(n) if 0.0 < self.alphas[i] < self.C] for i1 in scanned: E1 = self._error(i1) r1 = self.y[i1] * E1 if (r1 < -self.tol and self.alphas[i1] < self.C) or \ (r1 > self.tol and self.alphas[i1] > 0.0): i2 = self._select_second(i1, E1) num_changed += self._take_step(i1, i2) if num_changed: passes = 0 else: passes += 1 else: passes += 1 return self def weights(self): w = np.zeros(self.X.shape[1]) for i in range(self.X.shape[0]): w += self.alphas[i] * self.y[i] * self.X[i] return w if __name__ == "__main__": X = np.array([[2, 3], [3, 3], [1, 1], [2, 1], [5, 6], [6, 5], [4, 8], [7, 5], [8, 6], [9, 4]]) y = np.array([1, 1, 1, 1, 1, -1, -1, -1, -1, -1]) model = SimpleSMO(C=1.0, tol=1e-4, max_passes=5).train(X, y) w = model.weights() print("支持向量数量:", int(np.sum(model.alphas > 1e-6))) print("权重 w:", w) print("偏置 b:", model.b)
self.K = self._compute_K(X)把核矩阵一次性算好缓存,SMO 每一步更新都要反复查表。full_scan = (num_changed == 0) or (passes % 3 == 0)上一轮没有有效更新时,强制全量扫描兜底。r1 = self.y[i1] * E1用误差乘标签判断样本是否违背 KKT,决定是否值得调整。i2 = self._select_second(i1, E1)内层循环挑选让 \(|E_1-E_2|\) 最大的搭档,保证步长尽量大。eta = self.K[i1, i1] + self.K[i2, i2] - 2.0 * self.K[i1, i2]计算两人配合的“曲率”,分母为零时放弃本轮。a2_new = a2_old + y2 * (E1 - E2) / eta未剪辑更新公式,理想状态下的新位置。a2_new = min(H, max(L, a2_new))把新值剪辑回合法区间,相当于把越界的球员拉回场内。a1_new = a1_old + s * (a2_old - a2_new)联动的 \(\alpha_1\) 更新,保证等式约束始终成立。
常见误区
误区SMO 名字里带“序列最小优化”,每次应该只改一个拉格朗日乘子,改完一个再改下一个。
正解SMO 每次至少同时优化两个乘子。因为对偶问题自带等式约束 \(\sum\alpha_i y_i=0\),只改一个乘子就会破坏平衡。就像一个天平两端各有一个砝码,只动一边指针立刻倾斜。两个乘子联动,一变一补,等式才能始终成立。
为何常见:日常优化问题常按“一次一个维度”做坐标下降,初学者容易把这种直觉套到 SMO 上,忽略了 SVM 对偶问题特有的等式约束。
误区SMO 的内层循环必须遍历所有样本,选出理论上让目标函数下降最多的那个搭档,否则就不精确。
正解SMO 采用启发式:优先选使 \(|E_1-E_2|\) 最大的样本做搭档,因为经验上这往往带来大步长。如果这样选出的搭档收益太小,算法会退而求其次遍历全体样本找一个“还过得去”的次优者。追求理论最优反而浪费算力,启发式已经足够保证收敛。
为何常见:教科书常强调优化问题的“最优性”,容易让人以为每一步都必须全局最优,忽略了工程实现中“足够好、快收敛”的妥协智慧。
误区SMO 是在线性可分假设下推导的,遇到非线性数据就无能为力,必须先把数据手动变换。
正解SMO 的对偶目标里只需要样本内积 \(\mathbf{x}_i\cdot\mathbf{x}_j\),把这个内积替换成核函数 \(K(\mathbf{x}_i,\mathbf{x}_j)\),就自动实现了隐式高维映射。训练流程完全不变,只是把“相似度计算”换成了核函数。真正的线性假设只存在于“未加核”的简单版本里。
为何常见:初学 SVM 时先用线性可分例子推导公式,很多人把推导过程中的简化假设当成了最终算法的局限。
误区更新偏置 \(b\) 时,随便挑一个支持向量按公式算一遍就行,反正最后模型都能用。
正解SMO 对 \(b\) 的更新有严格的分支规则:\(\alpha_1\) 在开区间 \((0,C)\) 内就用 \(b_1\);否则看 \(\alpha_2\);如果两个乘子都在边界上,取 \(b_1\) 与 \(b_2\) 之间的中点。因为 \(b\) 必须让所有“核心球员”(满足 \(0<\alpha<C\) 的样本)的 KKT 条件严格成立,选错会使下一轮的外层判断失真。
为何常见:\(b\) 只是一个标量,看起来不像 \(\alpha\) 那样重要,但它是决策边界的位置基准,细节上偷懒会让整个迭代过程“地基不正”。
误区\(\alpha\) 越大样本越重要,所以 \(\alpha=C\) 的样本是决定边界的最强支柱,必须优先保留。
正解\(\alpha=C\) 的样本实际上是“被规则压到上限”的样本,它们往往位于间隔边界内部甚至被误分类的一侧。真正把边界稳稳撑住的是 \(0<\alpha<C\) 且 \(y_i f(\mathbf{x}_i)=1\) 的样本——它们恰好站在间隔边界线上,就像精确踩在边线上的球员。\(\alpha=C\) 是“受罚者”,不是“最优者”。
为何常见:从“乘子越大权重越高”的直觉出发,想当然地把大 \(\alpha\) 等同于高重要性,忽略了 KKT 条件中不同取值区间对应的几何含义差异。
面试题
Q1. SMO 算法每次优化几个拉格朗日乘子?
- A.只固定优化一个乘子
- B.固定同时优化两个乘子← 正确答案
- C.一次优化剩余全部乘子
- D.随机挑选一批乘子更新
答案:B。SMO 的核心就是“序列最小优化”:每次只取两个乘子联动调整,其余全部固定。选项 B 正确。A 忽略了等式约束必须两个一起变;C 是全量优化的思路,正是 SMO 要避免的;D 是随机批量坐标下降,不是 SMO。
Q2. 为什么 SMO 一次至少要同时调整两个乘子?
- A.等式约束维持平衡需要← 正确答案
- B.核函数需要成对出现
- C.两个乘子更新速度最快
- D.梯度下降要求配对更新
答案:A。对偶问题带有 \(\sum\alpha_i y_i=0\) 的等式约束,只改一个乘子会让约束立刻失配,就像只动天平一端。至少两个乘子一变一补才能维持平衡,故选 A。B、C、D 都不是 SMO 设计两个变量优化机制的根本原因。
Q3. KKT 条件中,\(0<\alpha<C\) 的样本对应哪种几何位置?
- A.远离决策边界的多数点
- B.恰好位于间隔边界样本← 正确答案
- C.明显越过边界的样本点
- D.被误分类的噪声样本点
答案:B。\(0<\alpha<C\) 时 KKT 要求 \(y_i f(\mathbf{x}_i)=1\),也就是样本恰好站在间隔边界上,是名副其实的“核心球员”,选 B。A 对应 \(\alpha=0\);C 和 D 对应 \(\alpha=C\) 的受罚样本。
Q4. 关于 \(b\) 在 SVM 决策函数中的作用,下列哪个说法正确?
- A.它控制核矩阵的高位特征值
- B.它决定决策边界的平移偏移量← 正确答案
- C.它决定样本在高维空间的映射方式
- D.它控制拉格朗日乘子的初始化方式
答案:B。决策函数 \(f(\mathbf{x})=\sum\alpha_i y_i K(\mathbf{x}_i,\mathbf{x})+b\) 中,求和项确定边界的方向与形状,\(b\) 负责整体平移,就像球场的“中线位置”,故选 B。核映射由核函数决定,与 \(b\) 无关。
Q5. SMO 外层循环优先扫描哪类样本?
- A.随机从全体样本中抽取扫描
- B.扫描 \(\alpha=0\) 的闲散样本
- C.扫描 \(0<\alpha<C\) 的支持向量← 正确答案
- D.扫描 \(\alpha=C\) 的受罚样本
答案:C。\(0<\alpha<C\) 的样本站在间隔边界上,最容易被扰动而违反 KKT,所以外层循环先扫这批“核心球员”;只有他们全部合规时才扩大到全体样本。选 C。
Q6. SMO 内层循环选择第二个变量的启发式规则是?
- A.让 \(E_1-E_2\) 的差距最大← 正确答案
- B.让 \(\alpha\) 的绝对值取得最大
- C.让核函数值彼此最接近
- D.让决策间隔宽度保持最大
答案:A。内层循环遍历候选样本,挑选使 \(|E_1-E_2|\) 最大的那个,因为误差差越大,更新步长通常越大,收敛越快。这是工程上的启发式近似,并非全局最优搜索,选 A。
Q7. 剪辑区间 \([L,H]\) 的取值与哪些量直接相关?
- A.两个乘子的旧取值← 正确答案
- B.正则化参数 \(C\) 的大小← 正确答案
- C.两个样本的类别是否相同← 正确答案
- D.核矩阵的全部特征值
答案:ABC。\(L,H\) 由 \(\alpha_1^{\text{old}},\alpha_2^{\text{old}},C\) 以及 \(y_1,y_2\) 是否同号共同决定,所以 A、B、C 都正确。核函数在更新目标里出现,但不参与剪辑区间的推导,D 错误。
Q8. 若更新后 \(\alpha_1\) 与 \(\alpha_2\) 都不在 \((0,C)\) 内,\(b\) 应如何取值?
- A.任选边界端点值即可
- B.取 \(b_1\) 与 \(b_2\) 区间中点← 正确答案
- C.直接保留旧 \(b\) 值不再更新
- D.用全体样本的预测均值替代
答案:B。当两个乘子都落在边界(0 或 C)上时,没有一个样本能严格支撑 \(b\) 的唯一取值,此时取 \(b_1\) 与 \(b_2\) 的区间中点作为折中,保证两类边界都尽量合理,选 B。
Q9. 下列哪些属于 SMO 常用的收敛判据?
- A.全部样本满足 KKT 条件← 正确答案
- B.达到预设最大迭代次数← 正确答案
- C.决策间隔宽度达到最大
- D.核矩阵迹的绝对值最小
答案:AB。SMO 在无法找到任何违背 KKT 的样本对时停止,即“全体样本在容差内满足 KKT”;同时设置最大迭代次数防止死循环,A、B 正确。间隔宽度是目标函数的一部分而非停止判据,C 错误;核矩阵迹与收敛无关,D 错误。
Q10. 关于 \(\alpha=C\) 的样本,正确的理解包括哪些?
- A.它们可能位于间隔边界内侧← 正确答案
- B.它们可能落在错误分类一侧← 正确答案
- C.它们仍然属于支持向量← 正确答案
- D.它们一定不会影响决策边界
答案:ABC。\(\alpha=C\) 表示乘子被上限钳制,样本可能在间隔内部甚至错侧,但正是因为它们的存在,模型才需要付出“罚金”来换取整体间隔最大化,它们同样是支持向量并参与决策,A、B、C 正确。D 与事实相反,错误。