简介:这份资源围绕拉普拉斯支持向量机(LapSVM)展开,面向机器学习初学者与需要处理非线性分类任务的研究者,帮助理解如何将SVM与图论中的拉普拉斯矩阵结合,利用数据局部几何结构提升高维复杂数据的分类表现。压缩包共38个文件,约155KB,以m脚本为主,辅以cpp、c、h等源码及mexglx、dll等编译文件,另有mat数据、fig图形和txt说明,覆盖算法实现、核函数计算与实验演示等环节。资源中提供了邻接矩阵构建、拉普拉斯矩阵计算、优化求解与核函数选择等关键步骤的代码示例,并包含流形学习相关对比内容,便于读者对照理论动手复现实验、观察分类边界与评估指标。目前已有108人学习,适合希望深入掌握LapSVM原理与工程实现、并拓展非线性数据分析能力的读者参考。
1. 拉普拉斯支持向量机:当标注样本少得可怜时,半监督学习怎么救场
手上只有几十条带标签的样本,却要训一个能上线的分类器,这种场景做工业质检、医学影像筛查或者金融风控的同行都不陌生。拉普拉斯支持向量机(Laplacian SVM,常写作 LapSVM)就是冲着这个痛点来的:它把大量无标签样本的几何结构信息,通过图拉普拉斯矩阵塞进 SVM 的正则项里,让决策边界顺着数据流形走,而不是只被那几个可怜的有标签点牵着鼻子跑。标题里的laplacian、lapsvm、svm-laplacian说的其实是同一件事——用拉普拉斯图正则约束 SVM。它适合标注成本高、无标签数据管够、且数据分布有明显簇结构的任务。如果你手上标签充足,直接上标准 SVM 或梯度提升树更省事;但当你面对的是“标注一条要几百块、无标签数据堆成山”的局面,LapSVM 值得认真试一次。
2. 拉普拉斯正则到底在优化什么:从流形假设到图矩阵
2.1 流形假设:为什么无标签点也能约束决策边界
半监督学习的底气来自两个假设:聚类假设(decision boundary 不该穿过高密度区域)和流形假设(高维数据其实躺在一个低维流形上)。LapSVM 主要吃的是流形假设。直观理解:如果两个样本在原始空间里离得很近,那它们的预测输出也应该接近。无标签样本虽然不知道类别,但它们的位置信息能告诉我们“哪些点应该被分到一起”。
把这个直觉写成数学,就是给每个样本建一个图:每个点是一个节点,近邻之间连边,边的权重反映相似度。整张图的“平滑程度”用图拉普拉斯二次型衡量:
$$\sum_{i,j} W_{ij} |f(x_i) - f(x_j)|^2 = \mathbf{f}^T L \mathbf{f}$$
其中 $L = D - W$ 是拉普拉斯矩阵,$D$ 是度矩阵,$W$ 是相似度权重矩阵。这个量越小,说明函数 $f$ 在图上越平滑,相邻点的输出越一致。LapSVM 就是把这个平滑项加到 SVM 的目标函数里,让分类器在拟合有标签数据的同时,别在无标签数据的高密度区域里乱切。
2.2 LapSVM 的目标函数:把平滑项塞进 hinge loss
标准 SVM 的优化目标是最大化间隔、最小化 hinge loss。LapSVM 在此基础上加了一项:
$$\min_{f \in \mathcal{H}K} \frac{1}{l}\sum{i=1}^{l} \max(0, 1 - y_i f(x_i)) + \gamma_A |f|_K^2 + \gamma_I \mathbf{f}^T L \mathbf{f}$$
三个部分各司其职:第一项是标准 hinge loss,只管有标签的 $l$ 个样本;第二项 $\gamma_A |f|_K^2$ 是 RKHS 范数,控制模型复杂度,防止过拟合;第三项 $\gamma_I \mathbf{f}^T L \mathbf{f}$ 就是拉普拉斯正则,让函数在整个图(含无标签点)上平滑。$\gamma_A$ 和 $\gamma_I$ 是两个关键超参,前者管“拟合有标签数据有多狠”,后者管“听无标签结构的话有多深”。
按表示定理,最优解可以写成核函数的线性组合 $f(x) = \sum_{i=1}^{n} \alpha_i K(x_i, x)$,$n$ 是全部样本数(有标签 + 无标签)。代进去之后,原问题变成一个带约束的二次规划,可以用标准的 SVM 求解器思路去解,只是核矩阵从 $l \times l$ 扩到了 $n \times n$。这也是 LapSVM 计算量比标准 SVM 大的根本原因——无标签样本越多,核矩阵越大。
2.3 图怎么建:kNN 还是全连接,权重用 RBF 还是 0/1
图的质量直接决定 LapSVM 的效果,这一步比调 $\gamma$ 还重要。常见做法有两种:
| 建图方式 | 连边规则 | 权重 | 适用场景 |
|---|---|---|---|
| kNN 图 | 每个点连最近的 k 个邻居 | 0/1 或 RBF | 数据分布不均匀、簇结构明显 |
| 全连接图 | 所有点两两相连 | RBF 核权重 | 样本量小(几千以内)、分布平滑 |
kNN 图更稀疏,计算省,但对 k 敏感;全连接图信息全,但 $n$ 大时矩阵稠密,内存吃不消。权重方面,0/1 权重最简单,RBF 权重 $W_{ij} = \exp(-|x_i - x_j|^2 / (2\sigma^2))$ 更细腻,但多了一个 $\sigma$ 要调。我一般先用 kNN + RBF 权重起步,k 取 5 到 10,$\sigma$ 取所有样本间距离的中位数,这个经验值在多数任务上不会太离谱。
注意:建图前一定要做特征标准化。不同量纲的特征直接算欧氏距离,等于让数值大的特征单方面决定邻居关系,图会建歪,后面怎么调参都救不回来。
3. 用 Python 把 LapSVM 跑起来:从建图到求解的最小实现
3.1 环境准备与数据构造
LapSVM 没有像 scikit-learn 的SVC那样开箱即用的标准库,常见做法是自己基于cvxopt或scipy.optimize写求解器,或者用现成的半监督学习包。下面给一个基于cvxopt的最小可复现实现,先造一份带两个簇的二维数据,方便可视化验证。
import numpy as np from sklearn.datasets import make_classification from sklearn.preprocessing import StandardScaler from sklearn.neighbors import kneighbors_graph # 造数据:200个样本,2类,只有20个有标签 X, y = make_classification(n_samples=200, n_features=2, n_informative=2, n_redundant=0, n_clusters_per_class=1, random_state=42) y = np.where(y == 0, -1, 1) # SVM 用 -1/+1 标签 # 标准化:建图前必做 scaler = StandardScaler() X = scaler.fit_transform(X) # 模拟标注稀缺:只保留20个有标签样本 rng = np.random.RandomState(0) labeled_idx = rng.choice(len(X), 20, replace=False) unlabeled_idx = np.setdiff1d(np.arange(len(X)), labeled_idx) y_labeled = y[labeled_idx].astype(float)这段代码做了三件事:生成两类线性不可分的数据、标准化、切出 20 个有标签样本。make_classification的参数n_clusters_per_class=1保证每类是一个紧凑簇,方便观察拉普拉斯正则是否真的让边界绕开无标签点密集区。实际任务里把X和y换成你自己的数据即可,注意标签要转成 -1/+1。
3.2 构建拉普拉斯矩阵:kNN 图与归一化
def build_laplacian(X, k=7, sigma=None): """构建归一化图拉普拉斯矩阵 L = I - D^{-1/2} W D^{-1/2}""" n = X.shape[0] # kNN 邻接(包含自身,后面减掉) A = kneighbors_graph(X, k, mode='connectivity', include_self=False).toarray() A = np.maximum(A, A.T) # 对称化 # RBF 权重 if sigma is None: dists = np.sqrt(((X[:, None, :] - X[None, :, :]) ** 2).sum(-1)) sigma = np.median(dists[dists > 0]) dists = np.sqrt(((X[:, None, :] - X[None, :, :]) ** 2).sum(-1)) W = A * np.exp(-dists ** 2 / (2 * sigma ** 2)) # 度矩阵与归一化拉普拉斯 d = W.sum(axis=1) d_inv_sqrt = np.where(d > 0, 1.0 / np.sqrt(d), 0) D_inv_sqrt = np.diag(d_inv_sqrt) L = np.eye(n) - D_inv_sqrt @ W @ D_inv_sqrt return L, W L, W = build_laplacian(X, k=7) print("L shape:", L.shape, "非零元素占比:", np.count_nonzero(L) / L.size)build_laplacian返回归一化拉普拉斯矩阵。归一化的好处是让不同度数的节点对平滑项的贡献更均衡,避免高密度区域的点主导整个正则项。k=7是邻居数,样本量几百到几千时 5 到 10 都合理;sigma默认取距离中位数,这是 RBF 核的经典启发式。A = np.maximum(A, A.T)做对称化,因为kneighbors_graph生成的邻接不一定对称——A 是 B 的邻居,B 不一定是 A 的邻居,不对称的图会让拉普拉斯矩阵失去良好性质。
3.3 求解 LapSVM:把问题写成二次规划
from cvxopt import matrix, solvers solvers.options['show_progress'] = False def lapsvm_fit(X, y_labeled, labeled_idx, L, gamma_A=0.1, gamma_I=0.05): """基于表示定理的 LapSVM 求解,返回全部样本的 alpha 系数""" n = X.shape[0] l = len(labeled_idx) # RBF 核矩阵 dists = np.sqrt(((X[:, None, :] - X[None, :, :]) ** 2).sum(-1)) sigma = np.median(dists[dists > 0]) K = np.exp(-dists ** 2 / (2 * sigma ** 2)) # 构造二次规划:min 0.5 a^T Q a + p^T a, s.t. y^T a = 0, 0 <= a <= C # 这里用简化的 hinge loss 线性规划近似,实际可用 cvxopt 的 QP # 为可读性,采用迭代重加权的最小二乘近似(等价于 hinge 的平滑版) Y = np.zeros(n) Y[labeled_idx] = y_labeled # 拉普拉斯正则项作用在全部样本上 M = K @ np.linalg.inv(2 * gamma_A * np.eye(n) + K + gamma_I * K @ L @ K) @ K # 用有标签样本做加权最小二乘(hinge loss 的一阶近似) alpha = np.zeros(n) for _ in range(20): f = K @ alpha # hinge loss 的次梯度权重 w = np.zeros(n) mask = Y != 0 margin = Y * f w[mask] = np.where(margin[mask] < 1, 1.0, 0.0) # 解线性系统 A_mat = w[:, None] * K + gamma_A * np.eye(n) + gamma_I * L @ K alpha = np.linalg.solve(A_mat + 1e-6 * np.eye(n), w * Y) return alpha, K alpha, K = lapsvm_fit(X, y_labeled, labeled_idx, L, gamma_A=0.1, gamma_I=0.05) f_pred = K @ alpha y_pred = np.sign(f_pred) acc = np.mean(y_pred == y) print(f"全样本准确率: {acc:.3f}")这段代码用迭代重加权最小二乘近似 hinge loss,避免直接调 QP 求解器的复杂度,逻辑更透明。核心是A_mat那一行:w[:, None] * K是有标签样本的拟合项,gamma_A * np.eye(n)是 RKHS 范数正则,gamma_I * L @ K是拉普拉斯正则。gamma_A越大模型越保守,gamma_I越大决策边界越平滑。迭代 20 次通常够收敛,1e-6 * np.eye(n)是数值稳定项,防止矩阵奇异。
参数说明:gamma_A=0.1、gamma_I=0.05是中等强度的正则,实际任务建议在对数尺度上网格搜索,比如gamma_A取[0.01, 0.1, 1],gamma_I取[0.001, 0.01, 0.1]。k和sigma对结果影响也很大,但优先调gamma_I,因为它直接控制无标签信息的注入强度。
3.4 验证效果:和无标签信息的 SVM 对比
from sklearn.svm import SVC # 只用有标签样本训练标准 SVM svm = SVC(kernel='rbf', C=1.0, gamma='scale') svm.fit(X[labeled_idx], y_labeled) acc_svm = np.mean(svm.predict(X) == y) print(f"标准 SVM(仅20个标签)准确率: {acc_svm:.3f}") print(f"LapSVM(20标签 + 180无标签)准确率: {acc:.3f}")在典型的两簇数据上,标准 SVM 因为只有 20 个标签,决策边界容易偏向某一侧,准确率常在 0.75 到 0.85 之间波动;LapSVM 借助 180 个无标签点的结构信息,通常能拉到 0.90 以上。差距的大小取决于无标签数据是否真的落在有意义的流形上——如果无标签数据本身就是噪声,LapSVM 反而可能被带偏,这也是下一章要重点说的坑。
4. 调参与排错:LapSVM 最容易翻车的五个地方
4.1 无标签数据里混进噪声,拉普拉斯正则变成“捣乱项”
现象:加了无标签数据后准确率不升反降,甚至比只用有标签样本还差。
原因:拉普拉斯正则假设“近邻点的输出应该一致”,但如果无标签数据里混入了标注错误、采集异常或者完全无关的样本,这些点会把错误的平滑约束强加给模型。$\gamma_I$ 越大,被带偏得越狠。
解决:先对无标签数据做一轮无监督清洗,用孤立森林或简单的密度过滤把离群点剔掉;或者把 $\gamma_I$ 调小,先观察准确率随 $\gamma_I$ 的变化曲线,找到拐点再定。我一般会做一个 sanity check:只用无标签数据跑一遍聚类,看簇结构和有标签样本的类别是否大致对齐,对不齐就说明无标签数据质量有问题。
4.2 图建得太密或太稀,平滑约束失效
现象:k 取太小(比如 2、3),图断成多个连通分量,拉普拉斯正则只在局部起作用;k 取太大(比如 50),图接近全连接,平滑项过强,决策边界被抹平。
原因:kNN 图的连通性直接决定拉普拉斯矩阵的零空间维度。图不连通时,平滑项无法跨分量传播信息;图过密时,所有点被强行拉向同一个均值,模型失去判别力。
解决:k 的经验范围是 $\log n$ 到 $\sqrt{n}$,几百个样本取 5 到 15 比较稳。更靠谱的做法是看图的连通分量数:用scipy.sparse.csgraph.connected_components检查,确保只有一个连通分量。如果数据本身分多个簇,可以考虑对每个簇分别建图再合并,但实现复杂度会上升。
4.3 核矩阵规模爆炸,内存直接吃满
现象:无标签样本加到几万条,K矩阵是 $n \times n$ 稠密矩阵,内存瞬间爆掉,程序被系统杀掉。
原因:LapSVM 的核矩阵规模是全部样本数 $n$ 的平方,$n=50000$ 时单精度浮点就要 10GB,加上拉普拉斯矩阵和中间变量,普通机器扛不住。
解决:三条路。一是降采样无标签数据,选一个能覆盖数据分布的子集,通常几千到一万条就够;二是用 Nyström 方法做核矩阵低秩近似,把 $n \times n$ 降到 $n \times m$($m \ll n$);三是换用线性核或者随机傅里叶特征,把核方法转成显式特征映射,用 SGD 类优化器求解。我一般先降采样,效果不够再上 Nyström。
4.4 标签不平衡时,hinge loss 被多数类主导
现象:两类样本比例 9:1,LapSVM 把所有样本都预测成多数类,少数类召回率接近零。
原因:hinge loss 对每个有标签样本一视同仁,多数类样本多,梯度贡献大,模型倾向于牺牲少数类来降低整体损失。拉普拉斯正则不区分类别,帮不上忙。
解决:给少数类样本的损失加权,权重取类别频率的倒数;或者在采样阶段做平衡,但有标签样本本来就少,过采样容易过拟合。更稳的做法是调 SVM 的 $C$ 参数时按类别分别设,少数类的 $C$ 设大一些。评估指标也别只看准确率,看 F1 或 AUC。
4.5 标准化没做或做错,图结构完全失真
现象:模型训练不收敛,或者准确率和随机猜差不多。
原因:建图依赖欧氏距离,如果特征量纲差异大(比如一个特征范围 0 到 1,另一个 0 到 10000),距离计算被大量纲特征主导,近邻关系完全错误。另一个常见错误是在切分有标签/无标签之前做了标准化,导致标准化参数泄露了测试集信息。
解决:标准化必须在切分之后、建图之前做,且只用训练集(含有标签和无标签)拟合 scaler,验证集和测试集用同样的参数变换。如果特征里有类别型变量,先做 one-hot 或目标编码,别直接当数值算距离。
5. 让 LapSVM 真正可用的三个进阶技巧
第一个技巧是自适应建图。固定 k 和 $\sigma$ 在不同密度区域表现差异很大,改进做法是用局部尺度:每个点的 $\sigma_i$ 取它到第 k 个邻居的距离,权重用 $\exp(-|x_i - x_j|^2 / (\sigma_i \sigma_j))$。这样稀疏区域的点不会被过度平滑,密集区域的点也不会因为 $\sigma$ 太小而失去连接。实现上只需在build_laplacian里把标量sigma换成向量,代码改动不到十行,但在密度不均的数据上提升明显。
第二个技巧是用验证集选 $\gamma_I$ 而不是拍脑袋。具体做法:把有标签样本分成训练和验证两部分,无标签样本全部参与建图,在gamma_I的网格上跑 LapSVM,看验证集准确率。注意验证集样本不能进入拉普拉斯正则的拟合项,但可以进入图——因为图只用了特征,没用标签,不算泄露。这个细节很多人搞错,把验证集完全排除在图外,导致图结构不完整,选出来的参数偏保守。
第三个技巧是监控拉普拉斯正则项的值。训练完成后算一下 $\mathbf{f}^T L \mathbf{f}$,如果这个值比初始随机猜测还大,说明模型在图上不平滑,要么是 $\gamma_I$ 太小,要么是图建错了。这个诊断指标比只看准确率更能反映 LapSVM 是否真的在利用无标签信息。我现在的习惯是每次跑 LapSVM 都打印三个数:有标签训练准确率、全样本准确率、拉普拉斯正则值。三个数一起看,基本能判断问题出在拟合、泛化还是图结构上。
这套流程我在几个标注成本高的分类任务上反复用过,最深的教训是:LapSVM 的效果上限由无标签数据的质量决定,而不是由调参决定。图建对了、无标签数据干净,默认参数就能跑出不错的结果;图建歪了,再怎么网格搜索也是白费。所以每次上手新任务,我会先花一半时间在数据清洗和建图验证上,剩下的一半才留给参数。希望帮到你。
本文还有配套的精品资源,点击获取