
简介这份PDF面向Python机器学习初学者与需要快速上手中最小二乘支持向量机LSSVM的开发者以清晰可运行的Python代码为线索系统演示了数据导入、核函数定义、optStruct类初始化以及最小二乘法求解参数alphas与b的完整流程。文档对代码中的关键步骤给出了专门解释包括方程求解时的矩阵构造、hstack与vstack的拼接逻辑以及核函数计算的内在含义便于读者边看边练。资源为单个PDF文件共248KB内容来自CSDN下载频道当前已有2172人学习浏览。与单纯代码片段不同这份材料将LSSVM从loadDataSet读取数据、kernelTrans核函数计算到最终预测的各个环节都串联起来并针对最小二乘法求解的细节做拆解能帮助读者避开常见实现误区快速将方法迁移到自己的分类或回归任务中。 做机器学习这些年踩过不少优化算法的坑。标准支持向量机SVM分类效果不错但一旦样本量上千甚至上万那个二次规划求解过程真的能把人急死。后来我接触到LSSVMLeast Squares Support Vector Machine最小二乘支持向量机发现它把SVM的约束条件从不等式改成等式损失函数直接用平方误差最后只需要解一个线性方程组训练速度提升非常明显。在Python里虽然scikit-learn没有直接提供LSSVM类但核心代码其实很短用numpy几十行就能写一个能用的版本回归、分类都能做。这篇文章就围绕LSSVM的Python代码实例来写包含原理推导、完整可运行代码、调参经验和踩坑记录适合已经懂一点SVM概念、想快速上手LSSVM的朋友。1. LSSVM原理与参数含义1.1 从标准SVM到LSSVM到底改了什么标准SVM的思路是找一个超平面让样本间隔最大同时允许少量样本偏离间隔。这个“少量偏离”通过松弛变量ξ_i来控制求解时是一个不等式约束的凸二次规划问题。Python里常用的SVC、SVR底层用的是SMO算法每次迭代挑两个拉格朗日乘子更新数据量一上去迭代次数就会明显增加。LSSVM是Suykens在1999年提出的改进方案。它把优化目标从“最大化间隔 加权误差”改成“最小化权重范数 加权误差平方和”约束条件也变成严格的等式y_i w^T φ(x_i) b e_i这里的e_i不再是松弛变量而是训练误差。这个改动最大的好处是原来需要求解二次规划现在只需要求解一个线性方程组。代价是失去了SVM的稀疏性——训练完以后几乎所有样本对应的α_i都不为0所以预测阶段需要用到全部训练样本的核函数值。对于中小规模数据这个代价完全能接受。1.2 回归目标函数和KKT条件LSSVM回归的原始优化问题可以写成min J(w, e) 1/2 w^T w γ/2 Σ e_i²约束条件是 y_i w^T φ(x_i) b e_ii 1,2,...,n。构造拉格朗日函数以后对w、b、e_i、α_i分别求偏导并令其为零得到KKT条件w Σ α_i φ(x_i)Σ α_i 0α_i γ e_iw^T φ(x_i) b e_i y_i把第一式和第三式代入第四式消去w和e_i整理后就得到一组关于b和α的线性方程[ 0 1^T ] [ b ] [ 0 ] [ 1 ΩI/γ] [ α ] [ y ]其中Ω是核矩阵Ω_ij K(x_i, x_j)I是单位矩阵。方程组的规模是(n1)×(n1)直接用numpy求解即可。一旦解出α和b预测函数就是f(x) Σ α_i K(x, x_i) b从公式可以看出LSSVM整个训练过程就是一次矩阵求解没有任何迭代这是它速度快的主要原因。1.3 核函数和两个关键参数核函数在LSSVM里的作用和SVM完全一样把低维线性不可分的数据映射到高维空间。最常用的是RBF核也叫高斯核K(x_i, x_j) exp(-||x_i - x_j||² / (2σ²))RBF核有两个特点一是只有一个尺度参数σ调起来方便二是只要σ不是特别小核矩阵基本是正定的求解比较稳定。使用RBF核的LSSVM其实只有两个超参数需要调γ和σ。γ是惩罚系数对应误差平方项的权重γ越大模型越不允许训练误差存在越容易过拟合γ越小模型越平滑但可能欠拟合。σ控制核宽度σ越小核函数只对很近的样本有响应模型越复杂容易把噪声也学进去σ越大核函数越平缓模型越简单。实际调参时这两个参数通常按数量级搜索比如0.01、0.1、1、10、100这样的序列。2. Python手写LSSVM回归器2.1 用numpy实现核心代码既然原理已经清楚写代码就顺理成章了。我习惯把LSSVM封装成一个类和sklearn的风格保持一致fit方法负责训练predict方法负责预测。下面这个LSSVR类实现了RBF核的LSSVM回归代码尽量精简方便阅读。import numpy as np class LSSVR: def __init__(self, gamma10.0, sigma1.0): self.gamma gamma self.sigma sigma self.alpha None self.b None self.X_train None def _rbf_kernel(self, X1, X2): # 计算两个样本集之间的RBF核矩阵 # X1: (m, d), X2: (n, d) - K: (m, n) XX np.sum(X1 ** 2, axis1).reshape(-1, 1) YY np.sum(X2 ** 2, axis1).reshape(1, -1) XY np.dot(X1, X2.T) sq_dists XX YY - 2 * XY return np.exp(-sq_dists / (2 * self.sigma ** 2)) def fit(self, X, y): self.X_train np.asarray(X) n len(self.X_train) K self._rbf_kernel(self.X_train, self.X_train) # 构造线性方程组 H np.zeros((n 1, n 1)) H[0, 1:] 1.0 H[1:, 0] 1.0 H[1:, 1:] K np.eye(n) / self.gamma rhs np.zeros(n 1) rhs[1:] y sol np.linalg.solve(H, rhs) self.b sol[0] self.alpha sol[1:] return self def predict(self, X_test): X_test np.asarray(X_test) K_test self._rbf_kernel(X_test, self.X_train) return K_test.dot(self.alpha) self.b代码里有一处细节需要说明核矩阵对角线上加了np.eye(n) / self.gamma对应公式里的I/γ。γ越大加的对角线元素越小矩阵越接近原始核矩阵。如果γ设得特别大比如10000以上再加上数值问题H矩阵可能接近奇异后面说排查时会专门讲。2.2 用sinc函数测试拟合效果代码写完之后最好用一个已知规律的数据集验证。sinc函数是经典的回归测试函数公式是y sin(x)/x在x0处需要特殊处理。加一点高斯噪声用来模拟真实数据。import numpy as np import matplotlib.pyplot as plt np.random.seed(42) def sinc(x): with np.errstate(divideignore, invalidignore): y np.sin(x) / x y[np.abs(x) 1e-8] 1.0 return y X np.linspace(-10, 10, 200).reshape(-1, 1) y_true sinc(X.ravel()) y y_true np.random.normal(0, 0.1, sizelen(X)) model LSSVR(gamma10.0, sigma1.0) model.fit(X, y) X_test np.linspace(-10, 10, 300).reshape(-1, 1) y_pred model.predict(X_test) plt.scatter(X, y, s10, alpha0.5, labelnoisy data) plt.plot(X_test, sinc(X_test.ravel()), g--, labeltrue function) plt.plot(X_test, y_pred, r-, labelLSSVR prediction) plt.legend() plt.savefig(lssvm_sinc.png, dpi150)实测下来γ10、σ1时预测曲线和真实函数贴合得很好边界处略有一点抖动但整体趋势没问题。如果把γ调到100以上训练点附近几乎完全穿过噪声点曲线会变得毛糙把σ调到0.1预测曲线会明显过拟合。这个现象后面调参会再提。2.3 和sklearn KernelRidge的关系有朋友问scikit-learn明明没有LSSVM为什么非要自己写我一般会回答你可以用KernelRidge近似替代。从数学上看RBF核的KernelRidge是在求解min ||K α - y||² (1/γ) ||α||²这和LSSVM回归的线性方程组非常接近只是在截距项b的处理上略有差异。如果你不想自己维护代码可以直接用from sklearn.kernel_ridge import KernelRidge model KernelRidge(alpha1.0 / 10.0, kernelrbf, gamma1.0 / (2 * 1.0 ** 2)) model.fit(X, y)这里的alpha对应LSSVM里的1/γgamma对应RBF核公式里的1/(2σ²)。实测效果和我手写的LSSVR几乎一样区别在于sklearn的KernelRidge默认不求解截距b不过使用前对y做中心化通常就够了。我的建议是学习原理时一定要自己写一遍生产环境图省事可以用KernelRidge。3. 实操过程真实数据调参与评估3.1 数据标准化不能省略LSSVM对特征的尺度非常敏感因为RBF核里的距离计算直接使用了原始数值。假如一个特征是0到1另一个特征是0到10000第二个特征会完全主导核函数输出第一个特征等于白给。所以训练前必须做标准化常见做法是零均值单位方差from sklearn.preprocessing import StandardScaler scaler StandardScaler() X_scaled scaler.fit_transform(X) y y.ravel()标准化完之后σ的默认值1通常就比较合理了。如果不做标准化σ需要跟着特征量级调整调参会变得很痛苦。还在用真实业务数据的朋友特别要注意先拆分训练集和测试集再对训练集做fit_transform对测试集只做transform避免把测试集的信息提前泄露到训练过程中。3.2 用交叉验证选择γ和σLSSVM只有两个超参数网格搜索完全可行。我常用的方式是把训练集划分成5折遍历γ和σ的候选值对每个组合计算5次验证集的R²均值取最高分对应的参数。核心代码逻辑如下from sklearn.model_selection import KFold from sklearn.metrics import r2_score gamma_list [0.1, 1, 10, 100, 1000] sigma_list [0.1, 0.5, 1.0, 2.0, 5.0] best_score -np.inf best_params None kf KFold(n_splits5, shuffleTrue, random_state42) for gamma in gamma_list: for sigma in sigma_list: scores [] for train_idx, val_idx in kf.split(X_scaled): X_train_fold X_scaled[train_idx] y_train_fold y[train_idx] X_val_fold X_scaled[val_idx] y_val_fold y[val_idx] model LSSVR(gammagamma, sigmasigma) model.fit(X_train_fold, y_train_fold) y_pred_fold model.predict(X_val_fold) scores.append(r2_score(y_val_fold, y_pred_fold)) avg_score np.mean(scores) if avg_score best_score: best_score avg_score best_params (gamma, sigma) print(best R2:, best_score) print(best gamma:, best_params[0], best sigma:, best_params[1])需要提醒一点交叉验证的折数不宜太少数据量不大时5折是比较稳的。另外LSSVM每次训练都要求解一个(n1)阶线性方程组如果样本量上万网格搜索会有点慢。可以先粗粒度搜索定位一个大致区间再在小区间内加密搜索能省不少时间。我常用的扫描结果可以整理成一张表类似下面这样gammasigma5折R²均值备注0.10.10.84欠拟合11.00.93一般101.00.97推荐区间1000.50.95过拟合倾向10000.10.71严重过拟合这张表的数值不是固定的但它反映的趋势很有代表性γ和σ需要一起考虑不能只看单个参数。3.3 预测效果怎么看评估回归模型不能只看一个指标。我一般同时看R²和RMSE。R²反映模型解释了多少方差RMSE反映绝对误差大小。比如在某个数据集上R²0.97看起来不错但如果RMSE也很大说明数据本身量纲大不能简单说模型好。正确做法是把RMSE和y的标准差做对比RMSE明显小于标准差才算有实际预测力。from sklearn.metrics import mean_squared_error rmse np.sqrt(mean_squared_error(y_test, y_pred))还有一个容易被忽略的问题LSSVM由于失去了稀疏性预测速度取决于训练样本数n。当n5000时每次预测都要计算5000次核函数在高频调用场景可能变成瓶颈。解决办法是尽量减少冗余样本或者改用原始SVM以保留稀疏性这也是设计模型时需要权衡的地方。4. 常见问题与排查技巧4.1 矩阵奇异或求解报错使用np.linalg.solve时如果H矩阵接近奇异会抛出LinAlgError: Singular matrix。常见原因有三个。第一γ设置过大导致np.eye(n) / self.gamma几乎为零核矩阵本身又不满秩H就奇异了。第二训练样本中有重复样本导致核矩阵的行线性相关。第三σ设置过小核矩阵对角线接近1非对角线接近0条件数很大。我的排查顺序一般是先看有没有重复样本再把γ降到100以内试试最后检查σ是否小于0.01。如果问题依旧可以把np.linalg.solve换成np.linalg.lstsq或者手动在对角线上加一个很小的正则项比如1e-8。这个正则项相当于给每个样本的误差再加一点权重不会对结果造成明显影响。4.2 参数过拟合与模型选择很多新手调LSSVM时喜欢把γ往大了调因为训练集误差能降到非常好。我踩过这个坑某个小数据集上γ10000时训练R²接近0.999但验证集R²只有0.6典型的过拟合。后来用交叉验证搜索才发现γ10、σ1.0的组合验证分数最高。这里分享一个判断过拟合的土办法把训练误差和验证误差随参数变化的曲线画出来两条曲线距离越大过拟合越严重两条都在高位说明欠拟合两条都低且接近才是比较理想的区域。LSSVM参数少这个办法非常直观。4.3 环境配置和包安装有读者私信问过“python环境配置”“numpy库怎么装”这类问题。其实LSSVM代码依赖的库很少只要安装numpy、scikit-learn、matplotlib就够。命令行执行pip install numpy scikit-learn matplotlib如果提示pip: command not found说明Python环境变量没配好Windows用户可以在安装Python时勾选“Add Python to PATH”。在VSCode里跑代码时要确保右下角选中的解释器是安装了这些包的同一个环境否则会报ModuleNotFoundError。这也是我经常遇到的情况代码明明没问题就是解释器选错了。4.4 分类问题怎么扩展这篇主要写了回归但LSSVM也常用于分类。思路是把分类标签变成1和-1用回归的方式拟合一个连续值然后用符号判断类别。如果要得到一个和SVM分类器类似的置信度可以把模型输出经过一个sigmoid函数映射到[0,1]。多分类则可以使用一对多或一对一策略。我实际测试过手写LSSVR扩展到二分类效果和sklearn的SVC很像训练速度快不少但缺少概率校准需要额外处理。如果项目对概率输出有硬性要求建议还是用专门的分类器。5. 写在最后的调试心得5.1 从手写代码到成熟库我最初上手LSSVM时觉得公式多、代码绕后来自己推导了一遍KKT条件再写代码思路就清晰了。LSSVM最大的价值在于“简单有效”不需要复杂的优化器一个线性方程组就能完成训练特别适合中小规模回归任务。如果你正在用SVR但数据量让你头疼或者刚接触核方法想加深理解我强烈建议照着上面的代码自己敲一遍改改σ和γ看看曲线变化比死记公式有用得多。5.2 给新手的三个建议如果你刚接触LSSVM我有三个具体建议。第一先用sinc这种已知函数测试代码确认模型实现没问题再上自己的数据。第二参数搜索一定要用交叉验证别只看训练集表现否则一定会被过拟合坑一次。第三数据标准化别偷懒这步能帮你省掉大量调参时间。做到这三点LSSVM基本就能在真实任务里稳定工作了。遇到调参或运行代码的问题欢迎在评论区留言我看到会回复。本文还有配套的精品资源点击获取