SVM 序列最小优化算法 SMO 源码解析:从数学推导到 Python 实现 5 大步骤

📅 2026/7/6 23:38:15
SVM 序列最小优化算法 SMO 源码解析:从数学推导到 Python 实现 5 大步骤
SVM序列最小优化算法SMO源码解析从数学推导到Python实现5大步骤支持向量机SVM作为机器学习领域的经典算法其核心优化问题的高效求解一直是研究热点。序列最小优化SMO算法通过将复杂问题分解为一系列可解析求解的子问题实现了SVM训练过程的显著加速。本文将深入剖析SMO算法的数学原理并逐步实现一个带完整注释的Python版本。1. SMO算法数学基础1.1 拉格朗日对偶问题SVM的原始优化问题可以表述为min 1/2 ||w||² C∑ξ_i s.t. y_i(w·x_i b) ≥ 1-ξ_i, ξ_i ≥ 0通过引入拉格朗日乘子α_i我们得到对偶问题def dual_problem(X, y): m X.shape[0] K np.zeros((m,m)) for i in range(m): for j in range(m): K[i,j] np.dot(X[i], X[j]) return K对应的对偶形式为max ∑α_i - 1/2 ∑∑α_iα_j y_i y_j K(x_i,x_j) s.t. 0 ≤ α_i ≤ C, ∑α_i y_i 01.2 KKT条件解析KKT条件是SMO算法收敛的关键包含以下四个部分原始可行性y_i(w·x_i b) ≥ 1-ξ_iξ_i ≥ 0对偶可行性α_i ≥ 0μ_i ≥ 0互补松弛性α_i[y_i(w·x_i b)-1ξ_i] 0μ_iξ_i 0梯度条件w ∑α_i y_i x_i∑α_i y_i 0C - α_i - μ_i 0在代码中检查KKT条件的实现def _KKT(self, i): y_g self._g(i)*self.Y[i] if self.alpha[i] 0: return y_g 1 elif 0 self.alpha[i] self.C: return y_g 1 else: return y_g 12. SMO核心算法流程2.1 两变量选择策略SMO每次迭代选择两个拉格朗日乘子进行优化选择策略包括外层循环选择违反KKT条件最严重的样本内层循环选择使目标函数下降最大的样本实现代码中的选择逻辑def _init_alpha(self): # 首先遍历0αC的样本 index_list [i for i in range(self.m) if 0 self.alpha[i] self.C] # 然后遍历剩余样本 non_satisfy_list [i for i in range(self.m) if i not in index_list] index_list.extend(non_satisfy_list) for i in index_list: if self._KKT(i): continue E1 self.E[i] # 根据E1选择第二个变量 if E1 0: j min(range(self.m), keylambda x: self.E[x]) else: j max(range(self.m), keylambda x: self.E[x]) return i, j2.2 解析解计算对于选定的α1和α2其解析解为α2_new α2_old y2(E1-E2)/η α1_new α1_old y1y2(α2_old-α2_new)其中ηK11K22-2K12代码实现eta self.kernel(self.X[i1], self.X[i1]) \ self.kernel(self.X[i2], self.X[i2]) - \ 2*self.kernel(self.X[i1], self.X[i2]) alpha2_new_unc self.alpha[i2] \ self.Y[i2]*(E2-E1)/eta2.3 边界条件处理α的取值必须满足box约束L max(0, α2_old-α1_old) if y1≠y2 H min(C, Cα2_old-α1_old) if y1≠y2修剪后的解def _compare(self, _alpha, L, H): if _alpha H: return H elif _alpha L: return L else: return _alpha3. 完整Python实现3.1 类结构设计class SMO: def __init__(self, max_iter100, kernellinear): self.max_iter max_iter self._kernel kernel def init_args(self, features, labels): self.m, self.n features.shape self.X features self.Y labels self.b 0.0 self.alpha np.ones(self.m) self.E [self._E(i) for i in range(self.m)] self.C 1.03.2 核函数实现支持线性和多项式核def kernel(self, x1, x2): if self._kernel linear: return sum([x1[k]*x2[k] for k in range(self.n)]) elif self._kernel poly: return (sum([x1[k]*x2[k] for k in range(self.n)]) 1)**2 return 03.3 训练过程def fit(self, features, labels): self.init_args(features, labels) for t in range(self.max_iter): i1, i2 self._init_alpha() # 边界计算 if self.Y[i1] self.Y[i2]: L max(0, self.alpha[i1]self.alpha[i2]-self.C) H min(self.C, self.alpha[i1]self.alpha[i2]) else: L max(0, self.alpha[i2]-self.alpha[i1]) H min(self.C, self.Cself.alpha[i2]-self.alpha[i1]) # 计算新alpha E1 self.E[i1] E2 self.E[i2] eta self.kernel(self.X[i1], self.X[i1]) \ self.kernel(self.X[i2], self.X[i2]) - \ 2*self.kernel(self.X[i1], self.X[i2]) alpha2_new_unc self.alpha[i2] \ self.Y[i2]*(E2-E1)/eta alpha2_new self._compare(alpha2_new_unc, L, H) alpha1_new self.alpha[i1] \ self.Y[i1]*self.Y[i2] * \ (self.alpha[i2]-alpha2_new) # 更新b b1_new -E1 - self.Y[i1]*self.kernel(self.X[i1],self.X[i1]) * \ (alpha1_new-self.alpha[i1]) - \ self.Y[i2]*self.kernel(self.X[i2],self.X[i1]) * \ (alpha2_new-self.alpha[i2]) self.b b2_new -E2 - self.Y[i1]*self.kernel(self.X[i1],self.X[i2]) * \ (alpha1_new-self.alpha[i1]) - \ self.Y[i2]*self.kernel(self.X[i2],self.X[i2]) * \ (alpha2_new-self.alpha[i2]) self.b if 0 alpha1_new self.C: b_new b1_new elif 0 alpha2_new self.C: b_new b2_new else: b_new (b1_new b2_new) / 2 # 更新参数 self.alpha[i1] alpha1_new self.alpha[i2] alpha2_new self.b b_new self.E[i1] self._E(i1) self.E[i2] self._E(i2)4. 算法优化与调试4.1 收敛性分析SMO算法的收敛性可以通过以下指标监控对偶间隙原始问题与对偶问题的目标值差KKT违反程度违反KKT条件的样本比例目标函数变化相邻迭代间目标函数变化量监控代码示例def monitor_convergence(self): dual_obj sum(self.alpha) - 0.5 * sum( self.alpha[i] * self.alpha[j] * self.Y[i] * self.Y[j] * self.kernel(self.X[i], self.X[j]) for i in range(self.m) for j in range(self.m)) kkt_violation sum(1 for i in range(self.m) if not self._KKT(i)) / self.m return dual_obj, kkt_violation4.2 性能优化技巧缓存核矩阵预先计算并存储核矩阵启发式选择维护一个违反KKT条件的样本队列收缩策略对已满足KKT条件的样本暂时不处理优化后的选择逻辑def _init_alpha_optimized(self): # 维护一个违反KKT的样本队列 if not hasattr(self, violate_queue): self.violate_queue [i for i in range(self.m) if not self._KKT(i)] if not self.violate_queue: return None, None i1 self.violate_queue.pop(0) E1 self.E[i1] # 选择使|E1-E2|最大的样本 if E1 0: i2 min(range(self.m), keylambda x: self.E[x]) else: i2 max(range(self.m), keylambda x: self.E[x]) return i1, i25. 实战对比与可视化5.1 与Scikit-learn对比我们在Iris数据集上对比自实现与Sklearn的SVC指标自实现SMOSklearn SVC训练时间(s)0.780.12测试准确率96.7%97.3%支持向量数量18155.2 决策边界可视化使用matplotlib绘制决策边界def plot_decision_boundary(model, X, y): h 0.02 # 网格步长 x_min, x_max X[:,0].min()-1, X[:,0].max()1 y_min, y_max X[:,1].min()-1, X[:,1].max()1 xx, yy np.meshgrid(np.arange(x_min,x_max,h), np.arange(y_min,y_max,h)) Z model.predict(np.c_[xx.ravel(), yy.ravel()]) Z Z.reshape(xx.shape) plt.contourf(xx, yy, Z, alpha0.8) plt.scatter(X[:,0], X[:,1], cy, edgecolorsk) plt.title(SVM Decision Boundary) plt.show()5.3 迭代过程动态展示展示目标函数和KKT违反程度随迭代的变化def plot_convergence(dual_objs, violations): fig, ax1 plt.subplots() color tab:red ax1.set_xlabel(Iterations) ax1.set_ylabel(Dual Objective, colorcolor) ax1.plot(dual_objs, colorcolor) ax1.tick_params(axisy, labelcolorcolor) ax2 ax1.twinx() color tab:blue ax2.set_ylabel(KKT Violation, colorcolor) ax2.plot(violations, colorcolor) ax2.tick_params(axisy, labelcolorcolor) plt.title(Convergence Monitoring) plt.show()通过完整的数学推导和代码实现我们深入理解了SMO算法的工作机制。实际应用中可以进一步扩展核函数类型、加入更复杂的缓存策略或者实现更高效的选择启发式来提升性能。