ARTICLE DETAIL

资讯详情

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

LSSVM:用线性方程组替代二次规划的快速SVM实现与避坑指南

LSSVM:用线性方程组替代二次规划的快速SVM实现与避坑指南 简介最小二乘支持向量机LSSVM的MATLAB实现脚本面向机器学习与数据挖掘方向的算法学习者、科研人员及工程实践者主要解决非线性回归与分类问题。该脚本以平方误差最小化为核心完整实现从模型定义、核函数选择线性核、多项式核、RBF核、拉格朗日乘子求解到凸二次规划优化的训练流程同时集成交叉验证与参数调优功能用户可修改正则化参数C与核参数γ以提升模型泛化能力。压缩包内共1个m脚本文件整体体积仅2KB结构精炼易于阅读和二次开发。目前已有304人学习浏览适合作为快速上手LSSVM算法的入门参考资料。通过这份代码使用者能够直观理解LSSVM与传统SVM在决策边界构建思路上的区别并能够直接在自有数据集上完成数据读取、训练、评估与预测的全过程减少从零编码的时间成本。1. 最小二乘支持向量机把 SVM 的二次规划换成线性方程组我在一个回归任务上试过 LSSVM(最小二乘支持向量机当时特征 12 维、样本 3000 条sklearn 的 SVR 用 RBF 核跑一次交叉验证要三分钟换成 LSSVM 后同样的预测精度训练时间降到几十毫秒。这个差距的来源不是优化库多强而是 LSSVM 把标准支持向量机的二次规划换成了求解一个线性方程组免掉了 SMO 的反复迭代。它适合中小规模数据集上的快速原型也适合做回归、分类和时间序列预测只要你不介意模型完全失去稀疏性、所有训练样本都成了支持向量。这篇文章我就按自己动手实现的路径把原理、代码、参数和坑一次讲完。2. LSSVM 的数学原理从不等式约束到等式约束再到只解一个线性方程组2.1 标准 SVM 与 LSSVM 的区别松弛变量从一次方变成平方标准 SVM 分类器找这样一条超平面最小化 1/2||w||^2 C Σ ξ_i同时要求每个样本满足 y_i(w·φ(x_i)b) ≥ 1 - ξ_i且 ξ_i ≥ 0。这里的 ξ_i 是铰链损失允许个别点越过边界但要让越界量最小。因为约束是不等式最优点通常落在边界上那些满足等号的样本就是支持向量其余样本的 ξ_i0对模型没有贡献这就带来了稀疏性。LSSVM 把问题改成了最小化 1/2||w||^2 (γ/2) Σ e_i^2约束是 y_i(w·φ(x_i)b) 1 - e_i。注意两点变化第一不等式变成等式每个样本必须满足一个等式偏差由 e_i 体现第二误差惩罚从 Σ ξ_i 变成 Σ e_i^2也就是用平方误差代替铰链损失。这样一来最优解不再需要迭代寻找边界上的支持向量而是通过求导直接得到一组线性方程。为什么这个改动影响巨大因为平方误差是可导的等式约束求 KKT 条件后每个变量都能用拉格朗日乘子表示最后消元得到一个维度为 N1 的线性系统。求解它只需要直接法或共轭梯度没有迭代所以训练快。具体推导如下定义核矩阵 Ω其中 Ω_ij y_i y_j K(x_i,x_j)K 是核函数。引入拉格朗日乘子 α_i 后KKT 条件给出 w Σ α_i y_i φ(x_i)、Σ α_i y_i 0、α_i γ e_i。把这些代进约束得到[ 0 Y^T ] [b] [0] [ Y Ω γ^(-1) I ] [α] [1]这里的 I 是单位阵1 是长度为 N 的全 1 向量。这个线性系统是 LSSVM 分类的求解核心。矩阵大小是 (N1)*(N1)直接求解复杂度 O(N^3)所以样本量超过一万时内存和耗时都会明显上涨这就是 LSSVM 的适用边界。如果你正在调参时遇到速度突然变慢不用怀疑问题多半出在这个稠密矩阵上。2.2 回归形式把分类的标签乘子去掉右端换成 yLSSVM 做回归时约束改成了 y_i w·φ(x_i) b e_ie_i 是预测残差目标函数仍然是 1/2||w||^2 (γ/2) Σ e_i^2。这里的平方残差直接对应目标函数所以从原理上就是在做一个带正则化的最小二乘回归只不过把输入特征用核函数映射到了高维空间。求解系统变成[ 0 1^T ] [b] [0] [ 1 Ω γ^(-1) I ] [α] [y]注意 Ω_ij K(x_i,x_j)不再乘 y_i y_j右端是 y。这个差异看起来很小但代码写错就是整体偏移。我当年手写实现时在这里浪费了大半天最后发现是把分类版本的 y_i y_j 直接搬到了回归里导致训练集拟合得很好、测试集预测乱成一团。回归形式实际上可以看作核岭回归的一个变体只是多了一个偏置 b 的显式求解。回归问题为什么也愿意用 LSSVM因为标准 SVR 有 ε-insensitive 损失也需要迭代求解LSSVM 回归把损失改成最小二乘一步出结果。代价是对异常值敏感一个离群点可能把模型拉偏。如果你的数据里噪声分布比较干净LSSVM 回归非常顺手如果离群点多还是老老实实用 SVR 或随机森林。2.3 核函数选择与模型存储RBF 为什么是默认选项稀疏性去哪了LSSVM 的常见核函数有线性核、多项式核和 RBF 径向基核。RBF 是默认选择原因是它只有一个宽度参数 sigma能平滑地逼近非线性函数sigma 很大时RBF 核矩阵接近全 1模型退化为线性sigma 很小时核函数只对极近的样本敏感模型容易过拟合。所以调参时 sigma 的网格通常跨度很大从 2^-15 到 2^5 都是常见范围。关键问题是LSSVM 没有稀疏性。标准 SVM 中大部分 α_i 为 0只有边界上的支持向量保留LSSVM 中 α_i γ e_i只要 e_i 不为 0α_i 就不为 0而平方损失意味着几乎每个样本都有点残差所以几乎所有 α_i 都非零。这意味着预测时要算 N 个核函数模型存储规模是 N。这就是 LSSVM 的最大短板后面我会单独讲剪枝。选型理由也很明确如果你想在几百到几千条样本上快速验证非线性模型LSSVM 比标准 SVM 省事如果训练数据几万条且预测速度要求高标准 SVM 的稀疏性更有优势或者直接改用随机森林、GBDT 这类不依赖核函数的模型。没有哪个模型是银弹LSSVM 的好用建立在数据量中等、噪声可控的前提下。3. 用 Python 从零实现 LSSVM 回归最小可复现代码我不用任何 LSSVM 专用库只用 numpy因为这样可以看清矩阵在哪一步出问题。完整实现分三块核矩阵、训练、预测。下面代码基于回归版本分类版本的差异在 3.3 单独说明。3.1 核矩阵计算距离平方的展开别写错import numpy as np def rbf_kernel(X1, X2, sigma): # X1, X2: 形状分别为 (n1, d) 和 (n2, d) # 返回形状 (n1, n2) 的高斯核矩阵 dist2 (np.sum(X1**2, axis1)[:, None] np.sum(X2**2, axis1)[None, :] - 2 * np.dot(X1, X2.T)) return np.exp(-dist2 / (2 * sigma**2))逻辑说明高斯核公式是 exp(-||x_i - x_j||^2 / (2σ^2))。我用了等价展开把距离平方写成平方和与内积的组合避免双重循环。这里有一个容易写错的地方dist2 必须用[:, None]和[None, :]把维度广播开否则两个一维数组相加会得到错误形状。如果你的特征已经标准化sigma 的含义更直观如果不标准化核函数会被数值较大的特征主导。参数说明sigma 越小核函数只对很近的样本敏感模型越容易过拟合sigma 越大核矩阵越接近全 1模型趋向线性。初始值可以取样本特征标准差的 1 到 2 倍再画网格精调。这个函数同样用于预测阶段所以新样本和训练样本都会调用它。3.2 训练与预测解线性方程组def lssvm_fit(X, y, gamma1.0, sigma1.0): # X: (N, d), y: (N,)回归标签是浮点数 N X.shape[0] K rbf_kernel(X, X, sigma) # 构造分块矩阵 H对应 [K I/gamma, 1; 1^T, 0] H np.ones((N 1, N 1)) H[:N, :N] K np.eye(N) / gamma H[:N, N] 1.0 H[N, :N] 1.0 H[N, N] 0.0 rhs np.concatenate([y, [0.0]]) sol np.linalg.solve(H, rhs) alpha sol[:N] b sol[N] return alpha, b def lssvm_predict(X_new, X_train, alpha, b, sigma1.0): # 预测新样本返回连续值 K rbf_kernel(X_new, X_train, sigma) return np.dot(K, alpha) b逻辑说明训练函数把线性方程组写成 H·[alpha; b] rhs。H 的左上角是核矩阵 K 加上 1/gamma 的单位阵最后一行和一列是 1右下角是 0。这个结构对应回归系统的 [1^T] 约束。np.linalg.solve 直接解稠密线性方程组。预测时新样本要计算与所有训练样本的核函数再和 alpha 做内积最后加 b。参数说明gamma 是误差惩罚权重对应目标函数里的 γ。gamma 越大训练集上的残差越小但可能过拟合。代码里取 1/gamma 加在对角线上所以 gamma 无穷大时对角线加的量趋近 0矩阵更容易奇异这是后一节避坑的重点。b 的存在让预测多了一步如果你预测结果整体偏移先检查 b 有没有加回来。3.3 分类版本与验证两处小改动即可切换def lssvm_fit_clf(X, y, gamma1.0, sigma1.0): # y 必须是 ±1二分类专用 N X.shape[0] K0 rbf_kernel(X, X, sigma) K np.outer(y, y) * K0 # 分类核矩阵要乘标签外积 H np.ones((N 1, N 1)) H[:N, :N] K np.eye(N) / gamma H[:N, N] 1.0 H[N, :N] 1.0 H[N, N] 0.0 rhs np.concatenate([np.ones(N), [0.0]]) sol np.linalg.solve(H, rhs) alpha sol[:N] b sol[N] return alpha, b def lssvm_predict_clf(X_new, X_train, y_train, alpha, b, sigma1.0): # 返回 ±1 K rbf_kernel(X_new, X_train, sigma) raw np.dot(K, alpha * y_train) b return np.sign(raw)逻辑说明分类版本只改了两处一是核矩阵变成 y_i y_j K(x_i,x_j)用 np.outer(y, y) 和核矩阵逐元素相乘二是右端从 y 变成全 1 向量。预测时内积是 alpha * y_train不是 alpha因为原推导里 w 的展开带了 y_i。这个细节我不止一次写错分类的 w 展开式里带 y_i所以预测时要乘回去。验证时用一个二维随机数据集比如 sklearn 的 make_moons常见做法是画决策边界看分类效果但至少要对比训练集和测试集准确率。注意 LSSVM 只做二分类多分类需要自己写一对余one-vs-rest或一对一把每个类的二分类模型组合起来不要直接在一个多分类数据集上调用上面这个函数。另外分类概率需要额外映射。LSSVM 的输出没有概率含义如果你需要概率用 Platt scaling 或者拟合一个逻辑回归映射原始输出到 [0,1]。这不是必须的但在金融风控、医学预测这类场景里很常见。很多新手在这里翻车拿 LSSVM 的 raw score 直接当概率用结果阈值怎么调都不对。4. LSSVM 参数避坑指南gamma、sigma 与稀疏性缺失4.1 网格搜索gamma 和 sigma 的粗扫范围与交叉验证LSSVM 只有两个核心超参数gamma 和 sigma。gamma 控制正则化强度sigma 控制核函数宽度。常见做法是用 2 的幂做等比网格搜索再在最优附近加密。下面给一个可以直接跑的交叉验证框架from sklearn.model_selection import KFold from sklearn.preprocessing import StandardScaler def cv_lssvm(X, y, gamma, sigma, k5): # 手写 LSSVM 的 K 折交叉验证 rng np.random.default_rng(0) idx rng.permutation(len(X)) fold_size len(X) // k scores [] for f in range(k): val_idx idx[f*fold_size:(f1)*fold_size] train_idx np.setdiff1d(idx, val_idx) alpha, b lssvm_fit(X[train_idx], y[train_idx], gamma, sigma) pred lssvm_predict(X[val_idx], X[train_idx], alpha, b, sigma) scores.append(np.mean((pred - y[val_idx])**2)) return np.mean(scores) # 特征先做标准化再搜参 scaler StandardScaler() X_scaled scaler.fit_transform(X) gamma_list [2**i for i in range(-5, 16, 2)] sigma_list [2**i for i in range(-15, 6, 2)] best None for gamma in gamma_list: for sigma in sigma_list: score cv_lssvm(X_scaled, y, gamma, sigma) if best is None or score best[0]: best (score, gamma, sigma) print(best MSE:, best)逻辑说明网格用的步长是 2 倍gamma 范围 2^-5 到 2^15sigma 范围 2^-15 到 2^5。为什么用 2 的幂因为核函数和正则化对参数是指数敏感等比网格比等差网格覆盖更合理。这个循环在 10*10100 组参数上做 5 折交叉验证对几千条样本来说完全跑得动。如果 gamma 落在最优范围的边界继续往大搜如果 sigma 落在最小边界说明数据本身接近线性考虑直接用线性核。参数说明交叉验证分数用 MSE分类就用错误率。注意标准化必须在交叉验证内做否则会有数据泄露我见过很多人在外面对全量数据标准化后再折交叉验证分数虚高。正确做法是对每个训练折单独 fit scaler再 transform 验证折。上面的简化代码用了全量标准化仅适合快速粗扫不要把它当成最终评估流程。4.2 典型坑一矩阵奇异或求解失败现象np.linalg.solve 抛 LinAlgError: Singular matrix或者结果 alpha 数值巨大比如 1e12。原因当 gamma 很大时对角线上加的 1/gamma 接近 0如果样本里存在重复或近似重复的点核矩阵会接近奇异数据未标准化时某些特征尺度太大核函数计算出的距离超过浮点范围。解决先标准化再在对角线上额外加一个小正数 jitter比如 1e-8如果还不行调大 sigma 或调小 gamma。另一个可选做法是用 np.linalg.lstsq 代替 solve至少在奇异时不崩但解不稳定根源还是参数或数据问题。4.3 典型坑二预测整体偏移训练集拟合很好测试集偏差明显现象训练集 R^2 很高但测试集预测值整体比真实值高或低一个常数。原因b 的计算或预测时漏加了 b。常见错误是在构造 H 时把最后一维设置错或者回归版本错误使用了分类版本的核矩阵导致偏置估计错误。解决用一个 3 样本的小数据手算一遍打印 H、rhs、sol人工验算结果。或者拿 sklearn 的 KernelRidge 做对照如果你实现的预测结果和它差一个常数优先怀疑 b 的处理。这个坑隐蔽在代码细节里调参永远救不了逻辑错误。4.4 典型坑三分类标签用 0/1 导致约束失效现象训练集准确率 90%但预测时所有样本都被判负类。原因LSSVM 分类的约束是 y_i(w·φ(x_i)b) 1 - e_iy 必须取 ±1。如果你把标签设成 0/1那么 y_i0 的样本约束变成 0 1 - e_ie_i 永远被拉向 1模型的超平面被严重扭曲。解决把标签映射成 [-1, 1]训练完用 np.sign 输出再在评估时映射回原始标签。这个错误非常低级但很多人都会踩一次因为我看到不少代码直接把原始 0/1 标签传进去然后说 LSSVM 效果不行。4.5 稀疏性缺失所有样本都是支持向量怎么办LSSVM 的内存占用是 O(N^2) 的核矩阵训练 O(N^3)预测 O(N)N 到一万就很吃力。标准 SVM 只有少量支持向量LSSVM 几乎全有。缓解办法是剪枝pruning训练完看 |alpha_i|把绝对值小的样本删掉重新训练。因为 alpha_i 和误差 e_i 成正比删掉误差小的样本对模型影响小。def prune_lssvm(X, y, gamma, sigma, keep_ratio0.5): # 剪掉 50% 的“小支持向量” alpha, b lssvm_fit(X, y, gamma, sigma) n_keep int(len(alpha) * keep_ratio) keep_idx np.argsort(np.abs(alpha))[-n_keep:] # 保留绝对值最大的 n_keep 个 return lssvm_fit(X[keep_idx], y[keep_idx], gamma, sigma)逻辑说明剪枝后必须重新训练不能直接保留 alpha 子集因为 b 和 alpha 的联合分布变了。剪枝到 10% 性能还一样说明冗余样本多如果明显下降说明 LSSVM 的密度支持是你需要的不如趁早换稀疏方法或随机森林。另一个思路是用增量学习新样本到来时用 Sherman-Morrison 更新逆矩阵避免重训全部数据这放到最后一章讲。5. 进阶验证、在线更新与过拟合防御很多人在小数据集上一上来就用高斯核我也不例外翻过一次车。那次样本只有 80 条LSSVM 训练集拟合得近乎完美测试集一塌糊涂。后来我先跑了一个线性回归发现线性模型在测试集上反而更好。原因很简单数据量太小核模型容量太大平方损失又让模型对每个点都在意结果就是过拟合。所以我现在拿到数据第一步是设一个很大的 sigma比如 10 倍特征标准差跑一个线性基线。如果线性已经够好就不上 RBF如果线性不够好再考虑高斯核。这个验证方法成本极低却能在前期排除大量无效调参。如果你确实需要在线使用 LSSVM一个实用技巧是保存训练结束后线性系统的逆矩阵。LSSVM 的训练等价于求解 (H I/gamma) 的解当新样本到达时把原矩阵扩展一行一列用区块求逆公式做增量更新不必全量重算。这里要注意数值漂移连续更新几十次后建议全量重训一次。我一般会把新样本的贡献做一个衰减系数让太老的数据权重逐步降低本质上是给在线学习加遗忘机制。这个做法比每次重训快几个数量级实现时多花半天但长期运行稳很多。LSSVM 值得做但前提是数据量中等、特征经过标准化、你愿意为快速迭代接受非稀疏模型。我的习惯是样本几百到几千条做回归优先 LSSVM样本过万或者在线预测内存紧张转标准 SVM 稀疏版本或树模型。参数搜索先粗后细交叉验证别犯数据泄露预测时记得加 b分类标签改成 ±1这几点做到位LSSVM 基本不会让你翻车。希望帮到你。本文还有配套的精品资源点击获取
返回列表