ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LSSVM原理与NumPy实现:核方法分类回归的调参实战指南

LSSVM原理与NumPy实现:核方法分类回归的调参实战指南 简介面向MATLAB用户的最小二乘支持向量机LSSVM实现脚本适合机器学习初学者、科研人员及工程开发者快速开展非线性回归与分类建模任务。与标准SVM相比LSSVM通过最小化平方误差构建决策边界求解过程更高效特别适合中小规模数据集。压缩包内容精简仅包含1个m文件总大小约2KB便于直接阅读、调试与集成。目前已有304人学习下载。脚本覆盖较完整的工作流程包含核函数的选择与应用、模型参数求解的优化策略、基于训练数据得到最优参数、对新样本进行预测并提供了交叉验证和调参思路帮助规避过拟合。其中对高斯核等常见核函数以及正则化参数的处理方式可平滑迁移至其他自定义数据场景。使用者只需准备输入特征矩阵和输出向量按接口调用即可完成训练与预测可作为课程设计、毕业设计或工程验证的基础工具。1. 从样本量到计算效率LSSVM 解决什么问题接手过一个几千条样本、几十个特征的软测量项目试过标准 SVM训练要等好几轮迭代。后来换成最小二乘支持向量机也就是 LSSVM直接把训练变成一个解线性方程组的问题numpy 几十行就能跑通速度差了一个量级。LSSVM 不是新东西但对中等规模数据集上的分类和回归来说它是个值得优先考虑的方案。这篇笔记讲清楚 LSSVM 的原理、实现、调参思路和常见的坑适合正在做工业数据建模、故障诊断、软测量这类任务的人。2. LSSVM 到底在改什么从稀疏支持向量到解线性方程组2.1 标准 SVM 的稀疏性是代价换来的标准 SVM 求解的是一个带不等式约束的二次规划问题。目标函数是最小化权重向量的范数同时要求每个样本的间隔不小于 1允许少量样本落在间隔内但需要付出惩罚代价。这个约束写出来是 y_i(w·φ(x_i) b) ≥ 1 - ξ_i其中 ξ_i 是松弛变量。求解这个问题的对偶形式会得到一组拉格朗日乘子 α_i并且由于不等式约束的 KKT 条件绝大多数 α_i 会退化到边界上变成 0。只有落在间隔边界上或者违反间隔的少数样本它们的 α_i 才不为 0。这些样本就是支持向量决策函数只依赖它们。这个稀疏性是标准 SVM 的核心优势——预测时不需要所有训练样本只保留支持向量就够了。但代价也很直接求解二次规划的迭代过程慢样本量一旦过万训练时间就开始让人焦虑。我早年跑 SVC 时经常等一个下午后来才意识到这个问题根源在优化算法上而不是数据量本身。2.2 LSSVM 的改法等式约束和平方损失LSSVM 的改动非常朴素把不等式约束改成等式约束把松弛变量放进目标函数里做平方惩罚。原问题变成下面这样。目标函数 min (1/2)‖w‖² (γ/2)Σe_i²约束条件 y_i(w·φ(x_i) b) 1 - e_i这里 e_i 是误差变量不再要求误差非负。也就是说每个样本都必须精确满足某个等式关系但允许有一定偏差偏差的平方被正则化。这个改动带来的连锁反应很关键不等式约束对应的二次规划变成了等式约束对应的线性方程组求解。因为约束是等式KKT 条件不再有不等式边界一说解出来的拉格朗日乘子 α_i 几乎全部不为 0。这意味着 LSSVM 失去了稀疏性每个训练样本都参与决策函数的计算。这是 LSSVM 最常被诟病的点后面我会专门讲这个坑。2.3 Ω矩阵、KKT系统与参数分工写一下推导过程。拉格朗日函数 L (1/2)‖w‖² (γ/2)Σe_i² - Σα_i[y_i(w·φ(x_i)b) - 1 e_i]对 w、b、e_i 分别求偏导并令其为 0 ∂L/∂w 0 → w Σα_i y_i φ(x_i) ∂L/∂b 0 → Σα_i y_i 0 ∂L/∂e_i 0 → α_i γe_i把 w 代回等式约束经过整理可以得到下面的线性方程组| 0 | y^T | × | b | | 0 | | y | Ω γ^(-1)I | | α | | 1 |这里的 Ω 是核矩阵的加权形式Ω_ij y_i y_j K(x_i, x_j)。整个矩阵维度是 (n1) × (n1)其中 n 是训练样本数。求解这个方程组就得到 b 和 α预测函数是 f(x) Σα_i y_i K(x_i, x) b。这个方程组的形式和岭回归高度相似。区别在于岭回归的核矩阵是直接在原始特征上计算的而 LSSVM 的核矩阵是在高维特征空间里计算的并且还乘上了 y_i y_j。这也是为什么 LSSVM 时常被归到核方法那一类里。参数方面γ 是正则化系数对应标准 SVM 里的 C控制模型复杂度σ 是 RBF 核函数的宽度参数控制样本之间的相似度衰减速度。两个参数的组合直接影响模型表现没有固定的万能取值后面第 4 章会展开讲怎么调。对比项标准 SVMLSSVM约束类型不等式约束等式约束损失函数铰链损失松弛变量平方损失求解方式二次规划SMO 等迭代算法解线性方程组稀疏性稀疏仅支持向量参与预测非稀疏所有样本参与预测计算复杂度训练约 O(n²) 到 O(n³)取决于迭代次数O(n³)但常数小典型场景中小样本高精度分类中样本回归、软测量、分类3. 用 NumPy 手写一个 LSSVM 二分类器最小可复现代码3.1 最小闭环RBF 核矩阵与 KKT 方程组先实现一个最精简的 LSSVM 二分类器。核函数用 RBF不需要任何外部机器学习库只要 numpy。import numpy as np class LSSVM: def __init__(self, gamma10.0, sigma1.0): self.gamma gamma # 正则化系数越小越平滑 self.sigma sigma # RBF 核宽度 self.X None self.alpha None # 拉格朗日乘子已经乘上 y self.b None def _rbf_kernel_matrix(self, X, YNone): if Y is None: Y X X_sq np.sum(X * X, axis1).reshape(-1, 1) Y_sq np.sum(Y * Y, axis1).reshape(1, -1) dist X_sq Y_sq - 2.0 * np.dot(X, Y.T) # 数值误差可能产生轻微负数取最大值防御一下 dist np.maximum(dist, 0) return np.exp(-dist / (2.0 * self.sigma ** 2)) def fit(self, X, y): self.X X.copy() n X.shape[0] Omega self._rbf_kernel_matrix(X) H Omega np.eye(n) / self.gamma # 构造 KKT 矩阵 [0 y^T; y H] A np.zeros((n 1, n 1)) A[0, 0] 0.0 A[1:, 1:] H A[0, 1:] y A[1:, 0] y rhs np.zeros(n 1) rhs[1:] 1.0 sol np.linalg.solve(A, rhs) self.b sol[0] self.alpha sol[1:] # 这里直接存的是 alpha * y return self def predict_proba(self, X): K self._rbf_kernel_matrix(self.X, X) return np.dot(K.T, self.alpha) self.b def predict(self, X): return np.sign(self.predict_proba(X))训练阶段的核心是构造并求解这个 (n1) 维的线性方程组。np.linalg.solve用的是 LU 分解对中小规模矩阵几千以内速度很快。alpha里直接存的是 α_i y_i这样预测时就不用再乘一次 y。gamma出现在 H 矩阵的对角线上相当于给每个样本的核自相似度加了一个正则化偏移保证矩阵可逆。3.2 标准化这一步决定了后面所有调参LSSVM 对特征尺度非常敏感。RBF 核里的距离计算是在原始特征空间做的如果某个特征取值范围是 0.001 到 0.01另一个是 0 到 10000距离会被后者完全主导核矩阵变成一列接近 1、其他接近 0 的退化结构。def standardize(X_train, X_testNone): mean np.mean(X_train, axis0) std np.std(X_train, axis0) std[std 0] 1.0 # 防止常数特征除零 X_train_norm (X_train - mean) / std if X_test is not None: X_test_norm (X_test - mean) / std return X_train_norm, X_test_norm return X_train_norm这段代码做的事情就是 z-score 标准化用训练集的均值和标准差去处理测试集。注意标准差为 0 的常数特征直接置为 1避免除零报错。标准化之后再算核矩阵每个特征对距离的贡献才是等权的。这一步不做的话σ 的取值会变得玄学同样的 σ 在标准化前后效果天差地别。3.3 在合成数据上验证训练、预测、看边界写一个具体的运行示例。生成两类同心圆数据用刚写好的 LSSVM 直接训练并查看效果。def make_circles(n_samples600, noise0.1): n_per n_samples // 2 theta np.random.uniform(0, 2 * np.pi, n_per) r1 1.0 noise * np.random.randn(n_per) X1 np.column_stack([r1 * np.cos(theta), r1 * np.sin(theta)]) y1 np.ones(n_per) r2 0.5 noise * np.random.randn(n_per) X2 np.column_stack([r2 * np.cos(theta), r2 * np.sin(theta)]) y2 -np.ones(n_per) X np.vstack([X1, X2]) y np.concatenate([y1, y2]) # 打乱顺序 idx np.random.permutation(n_samples) return X[idx], y[idx] X, y make_circles(600, noise0.08) X_train, X_test X[:450], X[450:] y_train, y_test y[:450], y[450:] X_train_norm, X_test_norm standardize(X_train, X_test) model LSSVM(gamma10.0, sigma1.0) model.fit(X_train_norm, y_train) pred model.predict(X_test_norm) acc np.mean(pred y_test) print(f测试集准确率: {acc:.3f})如果 σ 取得合适这个合成数据上准确率轻松到 0.99。构造数据时噪声要控制好噪声太大两类会重叠LSSVM 再强也很难分开。这个例子的意义是让你能看到完整的训练预测流程后续调参和踩坑都是在这个最小闭环上做文章。4. LSSVM 调参实战γ 和 σ 怎么设才能少走弯路4.1 γ 管正则、σ 管半径两个参数的独立作用先明确这两个参数各自的角色这是调参的前提。γ 是正则化系数控制模型对训练误差的容忍度。γ 越大目标函数里平方误差项的权重越高模型越倾向于精准拟合每个训练样本容易过拟合γ 越小模型越平滑偏向于用一个简单的超平面去解释数据欠拟合的风险增加。这里说的简单超平面是在核映射后的特征空间里的不是原始空间的直线。σ 是 RBF 核的宽度。它决定了两个样本的相似度衰减速度。σ 小核函数衰减快每个样本只影响它附近的很小范围模型会变得非常复杂边界曲折σ 大核函数衰减慢样本之间互相影响的范围大模型平滑。σ 取值极端大时核矩阵所有元素趋近于 1模型退化成近乎线性分类器。两个参数是联动的。实践中常见的情况是 σ 取小了导致过拟合然后去调 γ 发现怎么调都不对。我一般先固定 σ扫一遍 γ再固定 γ 扫 σ两个来回基本能定位到合理区域。4.2 网格搜索粗扫一遍、细扫一轮调参最稳的做法不是贝叶斯优化而是先做一次粗粒度的网格搜索把范围缩小再做第二轮细扫。原因是 LSSVM 训练一次很快样本量几千时解一次方程组也就是毫秒级网格搜索完全值得。def grid_search(X_train, y_train, X_val, y_val, gamma_list, sigma_list): best_acc -1.0 best_params None results {} for gamma in gamma_list: for sigma in sigma_list: model LSSVM(gammagamma, sigmasigma) model.fit(X_train, y_train) pred model.predict(X_val) acc np.mean(pred y_val) results[(gamma, sigma)] acc if acc best_acc: best_acc acc best_params (gamma, sigma) return best_params, best_acc, results gamma_list [0.01, 0.1, 1.0, 10.0, 100.0] sigma_list [0.1, 0.3, 1.0, 3.0, 10.0] best_p, best_a, res grid_search( X_train_norm, y_train, X_test_norm, y_test, gamma_list, sigma_list ) print(f最优参数: gamma{best_p[0]}, sigma{best_p[1]}, acc{best_a:.3f})网格搜索的代价是组合爆炸。5 个 γ 乘 5 个 σ 是 25 次训练完全能接受。但如果样本量上到一万以上单次训练进入秒级25 次就要等半分钟此时建议改成随机搜索或者用粗网格只扫一遍。第二轮细扫的做法是把最优值周围再取几个点步长缩小精度能上去一些。需要特别注意验证集必须是在训练中没见过的数据。拿训练集自己测自己调出来的参数结果会乐观到失真。这个教训我翻过车后面避坑章节细说。4.3 留一法在这里并不贵一个快速公式做交叉验证时K 折需要重新训练 K 次。但 LSSVM 有一个经典结论留一法交叉验证不需要重复训练 n 次只需要解一次线性方程组就能估算出每个样本的留一误差。当数据经过中心化、模型不带偏置项 b 时LSSVM 退化形式等价于核岭回归。这时 hat matrix 是 H_hat K(K γ^(-1)I)^(-1)第 i 个样本的留一残差可以写成e_i_loo (y_i - f(x_i)) / (1 - h_ii)其中 h_ii 是 H_hat 的对角线第 i 个元素。这个公式在带偏置的 LSSVM 上略有不精确但作为参数筛选的量级判断完全够用。实现代码如下。def leave_one_out_error(K, y, gamma): n len(y) H K np.eye(n) / gamma H_inv np.linalg.inv(H) # 不带 b 的近似版本 alpha np.linalg.solve(H, y) y_pred K alpha loo_errors (y - y_pred) / (1 - np.diag(K H_inv)) return np.mean(loo_errors ** 2)这个快速公式的价值在于你想在 γ 和 σ 的候选值里筛出最合适的不需要每个组合都做 K 折交叉验证跑好几轮训练。用它先把候选组合粗筛掉一大半只留几个最好的再上 K 折细验。整体调参时间能省不少。5. LSSVM 避坑与常见问题排查5.1 现象训练集准确率 100%测试集拉胯这是典型的过拟合几乎所有接触 LSSVM 的人都会遇到一次。原因通常是 γ 设得过大模型对训练样本拟合得太狠或者 σ 设得过小每个样本只影响自身附近边界变成了锯齿状。我见过最夸张的一次是训练集 0.999测试集掉到 0.72。解决方法是先看这两个参数的组合。固定 σ 在 1 到 3 之间然后把 γ 从 100 往下调观察验证集准确率的变化。如果验证集准确率开始掉说明 γ 已经小过了头。另一种做法是直接用第 4 章的网格搜索把验证集准确定义清楚让数据说话别靠感觉。5.2 现象np.linalg.solve 报奇异矩阵错误或者警告矩阵接近奇异这个坑的常见原因有两个。第一是 σ 设得过小核矩阵对角线上盼的差异巨大导致 K γ^(-1)I 的条件数爆炸。第二是训练数据里存在完全重复的样本核矩阵出现线性相关的行矩阵变成奇异矩阵。解决方法是先检查数据里有没有重复行用 np.unique 去重。然后看 σ 是否小于特征标准差的量级如果 σ 小于 0.1 就要警惕数值稳定性。最后还可以换求解器把 np.linalg.solve 换成 np.linalg.lstsq后者对奇异问题的处理更鲁棒代价是速度略慢。5.3 现象预测阶段比训练还慢LSSVM 没有稀疏性所有训练样本都是支持向量。换句话说预测时每个样本都要和全部训练样本算一遍核函数。训练集一万条预测一万条新样本那就是一亿次核函数计算第一次跑出来吓我一跳。解决思路有三条。一是对训练集做样本缩减比如用聚类中心代替原始样本能砍掉不少预测时间。二是换用近似核方法比如 Nystrom 近似。三是回到标准 SVM如果对稀疏性有硬需求LSSVM 不是合适的选择。选型阶段就要想清楚不要等部署了才发现。5.4 现象类别不平衡时模型把所有样本都判成多数类LSSVM 的平方损失函数对多数类的误差更敏感。多数类样本量大累计误差更大优化时模型优先保多数类。负样本占比 10% 的数据集LSSVM 很容易给出全正样本的预测。解决方法是给不同类别不同的正则化权重。具体做法是把 γ 换成每个样本一个权重 w_i少数类样本的权重调大。实现上不需要改太多代码在 KKT 矩阵里把对角项从 1/γ 换成 w_i/γ。少数类权重设为多数类的 N 倍N 取类别不平衡比例通常 5 到 10 倍就够。5.5 现象σ 怎么调都无效模型表现基本不变如果 σ 从 0.1 到 100 试了一遍准确率纹丝不动大概率是数据没有标准化。特征尺度不一致时RBF 核的距离计算被某个大尺度特征主导σ 对核矩阵的影响完全被淹没。我踩过这个坑花了三个小时调 σ最后发现数据里有个特征范围是 0 到 100000其他特征都是 0 到 1。解决方法是回到第 3 章的 standardize 函数做一次标准化然后重新看参数敏感度。这里有个经验标准化之后 σ 的合理范围通常在 0.5 到 5 之间如果最优 σ 落在 10 以上先怀疑数据预处理有没有问题。6. 把 LSSVM 用出经验感多分类、回归与核的取舍6.1 一对多多分类直接吃二分类器LSSVM 天然是二分类器多分类用一对多策略就够。对 K 个类别训练 K 个 LSSVM每个模型把当前类别当作正样本其余类别当作负样本。预测时比较 K 个模型的连续输出值取最大的那个作为预测类别。不要用 sign 之后的结果投票因为连续输出值携带了置信度信息直接比大小更可靠。6.2 回归任务去掉 sign、输出连续值LSSVM 做回归非常简单把第 3 章代码里的 predict 方法去掉 sign然后损失函数从准确率换成均方误差即可。训练方式完全一样y 换成连续值KKT 矩阵构造不变。我通常在数据集刚拿到手时先跑一遍 LSSVM 回归用 R² 做个基线再决定是否需要上更复杂的模型。6.3 验证习惯先线性核基线再谈 RBF我现在每拿到一个新数据集第一件事是跑一个线性核 LSSVM把核函数换成内积代码里只需替换_rbf_kernel_matrix得到一个基线指标。如果线性核的测试集指标已经够用就不折腾 RBF 了。RBF 比线性核强的场景集中在非线性关系明显的数据上而这个判断用基线结果就能做。回归任务上我会额外检查预测残差是否有明显的结构比如残差随某个特征呈周期性变化。如果有说明模型没抓住这个维度的非线性关系再考虑换核或者加特征。这个习惯帮我挡掉了很多白费功夫的调参。最后一个建议任何参数组合先在验证集上确认再上测试集。一旦测试集被用来调参它就失去了公平评估的意义。希望这些思路帮你在 LSSVM 上少走弯路一次跑通。本文还有配套的精品资源点击获取
返回列表