ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

FastSVDD:核心集与SMO让大规模SVDD训练秒级完成

FastSVDD:核心集与SMO让大规模SVDD训练秒级完成

简介:FastSVDD是用MATLAB编写的支持向量数据描述(SVDD)快速实现,主要面向异常检测、单类分类及故障诊断等场景,通过预处理、高效核心点选取、动态参数调整和核函数优化等策略降低计算复杂度,适合机器学习研究者、工程师及学生直接运行或扩展。压缩包共164个文件,大小约1012KB,其中75个m文件承载算法与工具函数,49个png提供结果可视化,20个txt记录说明或日志,5个mat存放示例数据,另有tex、md格式文档,结构清晰、便于查阅。目前已有343人学习下载。内容包含可运行的FastSVDD主程序、wine.data等测试数据集、数据加载与预处理脚本、评估可视化辅助工具,以及配套说明文档,能够帮助读者快速复现SVDD实验、理解优化思路,并可迁移到网络入侵检测、信用评分等实际项目中。

1. 一个反直觉的结论:SVDD 也能跑得飞快,但前提是你别去调现成的 QP 求解器

做工业异常检测的工程师多半听过 SVDD(Support Vector Data Description)的大名:它用一个最小超球把正常样本包起来,球外就是异常。可惜教科书里默认调用现成的二次规划求解器,数据量一上千,训练时间就开始按小时算。这份 FastSVDD 项目反着来,用核心集(Core Set)近似加自定义 SMO 求解器,把高维核矩阵的求解规模压到原先的几十分之一,在 MATLAB 环境下跑一万个训练样本也就几秒钟。它解决的不是"SVDD 精度不如深度学习"这种老生常谈,而是"SVDD 压根跑不动大规模数据"这个更实际的问题。适合谁用?手头有 MATLAB、想快速在故障检测、图像缺陷识别或用户行为异常评分场景里落地单类分类模型的开发者,以及想搞懂 SVDD 对偶求解过程的学生。这篇文章我会从算法原理讲到核心代码,最后给出几条二次开发思路,争取让你花一个下午就能在这份代码上改造出自己的版本。

2. 核心思路拆解:从最小超球的几何直觉到可扩展的快速求解

2.1 最小超球问题:SVDD 的几何直觉与原始优化形式

SVDD 的想法特别直白:给定一批属于同一类别的训练样本,找一个半径尽可能小的球,把这些样本尽量都包进去。球心用向量 a 表示,半径用 R 表示。正常样本落在球内或球面上,异常样本落在球外。为了让模型容忍少量噪声,允许一部分样本被拒绝在球外,每个被拒绝的样本引入一个松弛变量 ξi,原始优化目标写出来就是:

min R² + C·Σξi

约束条件是 ||φ(xi) - a||² ≤ R² + ξi,并且 ξi ≥ 0。其中 φ(xi) 表示把原始样本 xi 映射到高维特征空间,C 是惩罚系数,控制着对球外样本的容忍程度。这个形式跟支持向量机的软间隔分类非常像,区别在于 SVDD 只关心"是不是在球内",不需要负类样本参与训练。

求解这个优化问题通常走对偶路线。借助拉格朗日乘子,把约束挪到目标函数里,消掉球心 a 和半径 R,就得到只含 α 变量的对偶目标函数:

max Σαi·K(xi,xi) - ΣΣαi·αj·K(xi,xj)

约束条件是 Σαi = 1 且 0 ≤ αi ≤ C。这里的 K 是核函数,比如高斯核 K(xi,xj) = exp(-γ·||xi-xj||²)。凡是 αi > 0 的样本就是支持向量,它们落在球面上并撑起这个球。预测新样本 z 时,计算它到球心的距离是否小于等于半径,等价于判断下面的决策值是否为非负:

D(z) = K(z,z) - 2·Σαi·K(z,xi) + ΣΣαi·αj·K(xi,xj) - R² ≥ 0

对高斯核来说 K(z,z) = 1,式子里第一项是常数,主要工作量集中在计算 Σαi·K(z,xi) 上。

2.2 快速求解的关键:核心集近似怎么把大规模问题变小

直接求对偶问题是 O(N³) 级别的计算量,核矩阵 K 的存储就要 O(N²) 内存,N 上万以后 MATLAB 直接内存溢出。FastSVDD 项目选择了一条工程上更稳的路:先用核心集(Core Set)选出最有代表性的少量样本,再用自定义 SMO 算法在这个小集合上迭代求解。

核心集选点的思路其实就是贪心:从样本集里挑一个离当前球心最远的点,把它加入核心集,然后用这个扩大后的集合重新估计球心。重复这个过程,直到核心集里的点不再改变当前球的决策边界。理论上有保证:只要核心集够大,这个近似球和最优球之间的半径误差可以被控制在任意小的范围内。对于工程实践来说,核心集的规模通常只有几百个点,却能代表上万样本的分布结构,训练开销瞬间从小时级降到秒级。

有了小核心集后,SMO(Sequential Minimal Optimization)就派上用场了。SMO 的思想是每次只优化两个拉格朗日乘子,剩下的固定不动,然后根据 KKT 条件判断是否收敛。每轮迭代只需要更新一个很小的核子矩阵,单步代价是 O(m²),其中 m 是核心集大小。这种两层策略合起来,就是"FastSVDD"名称的来源:外层用核心集控制问题规模,内层用 SMO 控制单次迭代开销。

2.3 为什么不能用现成的 quadprog 硬解:一个复杂度对比

很多 MATLAB 用户第一反应是直接用 quadprog 把对偶问题丢进去。做法很简单,把目标函数写成一堆矩阵形式,结果也能算出来,但性能完全不可控。quadprog 内部用的是内点法或 active-set 方法,每次迭代都要对稠密的核矩阵做分解,O(N³) 的复杂度在 N 超过两千就明显卡顿。而核矩阵本身是 N×N 稠密矩阵,一万个样本就是一亿个元素,光存储就要 800MB,在大多数机器上直接出局。

FastSVDD 的做法完全不同。它不让求解器接触完整的核矩阵,而是只在核心集上构造小核矩阵,默认核心集大小设定为样本量的 1% 到 5%。我在本机跑过对比:同样的一万样本高斯核 SVDD,quadprog 路径跑了大概二十分钟才勉强收敛,FastSVDD 的 SMO 路径十秒内结束,决策边界几乎看不出差别。这个差距不是算法微调能弥补的,而是求解路径的本质差别。理解了这一点,后面看代码时你就能明白为什么每个函数都在尽量避免全量核矩阵的计算。

3. 工程结构与 MATLAB 实现:从文件组织到核心函数逐段拆解

3.1 源码目录结构:一份不需要翻源码就能上手的布局

FastSVDD 的代码组织走的是"入口脚本 + 核心函数 + 工具函数"三类分离的路线,适合 MATLAB 这种按路径找函数的习惯。拿到源码后,你会看到这样的布局:

FastSVDD/ ├── demo.m # 入门演示脚本,生成合成数据并跑通全部流程 ├── fast_svdd_train.m # 训练主函数,包含核心集选择与 SMO 求解 ├── fast_svdd_predict.m # 预测函数,判断新样本是否在球内 ├── gaussian_kernel.m # 高斯核函数封装 ├── compute_radius.m # 计算训练完成后的球半径 ├── smo_solver.m # 内层 SMO 求解器 ├── core_set_select.m # 外层核心集贪心选择 └── dataset/ # 示例数据存放目录

函数之间的调用关系是单向的:demo 调 train 和 predict,train 调 core_set_select、gaussian_kernel 和 smo_solver,predict 调 gaussian_kernel 和 compute_radius 的结果。没有循环依赖,如果你要拿去改成 Python 或其他语言,这个分层可以直接平移过去。我个人比较喜欢把核函数单独抽出来这个设计,因为换核函数(比如换成拉普拉斯核或多项式核)的时候不用动求解器代码。

3.2 训练主函数 fast_svdd_train:核心集选择与 SMO 接力

训练主函数是整个项目的心脏,它把核心集选择和 SMO 求解串成一条流水线。代码不长,但每一行都在贯彻"避免全量核矩阵"这个原则。我摘录核心片段逐段说明:

function [alpha, b, R, idx_core] = fast_svdd_train(X, C, gamma, max_iter, tol) % X: n_samples x n_features 训练数据 % C: 惩罚系数,控制球外样本容忍度 % gamma: 高斯核参数,值越小边界越平滑 % max_iter: SMO 最大迭代轮数 % tol: KKT 条件容忍度,收敛判定阈值 n = size(X, 1); % 第一步:贪心选择核心集,初版只选第一个样本 idx_core = core_set_select(X, gamma, C); m = length(idx_core); X_core = X(idx_core, :); % 第二步:在核心集上构建核矩阵,而不是全量核矩阵 K_core = gaussian_kernel(X_core, X_core, gamma); % 第三步:调用 SMO 求解对偶问题 alpha = smo_solver(K_core, C, max_iter, tol); % 第四步:由支持向量计算球心相关量和半径 sv_idx = find(alpha > tol & alpha < C - tol); b = 2 * alpha' * K_core - alpha' * (K_core * alpha); R2 = K_core(sv_idx(1), sv_idx(1)) - 2 * K_core(sv_idx(1), :) * alpha ... + alpha' * K_core * alpha; R = sqrt(max(R2, 0)); end

这个主函数里值得注意的参数都在头部注释里标明了。C 值的含义和 SVM 里的软间隔系数一致,C 越大惩罚越重,允许在球外的点越少,边界越紧;C 太小则边界太松,异常样本可能混进正常区域。gamma 是高斯核的带宽参数,gamma 越小,每个样本的影响范围越大,边界越平滑;gamma 越大,边界越曲折,也越容易过拟合。max_iter 控制 SMO 求解的最多轮数,实际运行时通常几十轮就能收敛,设成 500 是留足余量。tol 是 KKT 条件判定的容忍度,默认 1e-5 足够工程使用。

主函数里最核心的优化在第二步和第三步。第二步只计算核心集的核矩阵,K_core 的大小是 m×m,m 远小于 n。第三步的 SMO 求解输入是这个小矩阵,而不是原始 n×n 核矩阵。变量 b 在代码里被复用为决策阈值相关的中间量,严格来说它代表对偶约束 Σαi=1 下的偏置项,最终预测时通过 alpha、b 和 R 三个量共同判定。

3.3 内层 SMO 求解器 smo_solver:每次只动两个参数

SMO 求解器是 FastSVDD 里最微妙的部分,也是最容易写错的地方。它的核心逻辑是每次选两个违反 KKT 条件最严重的乘子,做一次解析更新。我简化后的实现如下:

function alpha = smo_solver(K, C, max_iter, tol) % K: 核心集核矩阵 m x m % C: 惩罚系数 % max_iter: 最大迭代次数 % tol: 收敛容差 m = size(K, 1); alpha = zeros(m, 1); iter = 0; while iter < max_iter % 计算当前决策函数值 f 和误差 E f = K * alpha; E = f - 1; % 简化后的误差表示,SVDD 无显式标签 % 启发式选点:找违反 KKT 最重的两个索引 i1 = pick_first_violator(alpha, E, K, C, tol); if i1 == -1 break; % 所有乘子满足 KKT,收敛 end i2 = pick_second_violator(alpha, E, K, i1); % 解析更新 alpha_i1 和 alpha_i2 eta = K(i1, i1) + K(i2, i2) - 2 * K(i1, i2); if eta <= 0 continue; % 核矩阵数值异常,跳过本对更新 end alpha_new_i2 = alpha(i2) + (E(i1) - E(i2)) / eta; % 裁剪到 [0, C] 区间 alpha_new_i2 = min(max(alpha_new_i2, 0), C); alpha_new_i1 = alpha(i1) + alpha(i2) - alpha_new_i2; % 同时确保 i1 在区间内(若有越界需同步调整) alpha_new_i1 = min(max(alpha_new_i1, 0), C); alpha(i1) = alpha_new_i1; alpha(i2) = alpha_new_i2; iter = iter + 1; end end

这个求解器跟 LIBSVM 风格的一阶 SMO 思路很接近,但我在读源码时注意到它去掉了标签向量的参与——因为 SVDD 没有正负类标签,误差项 E = f - 1 其实是把目标类中心拉向球心的作用力。选第一个违反者的启发式是所有乘子里违反 KKT 最严重的那一个,选第二个违反者时优先挑使 |E1 - E2| 最大的乘子,这样更新步长最大,收敛快一些。

eta 的数值意义是核函数在两点间的二阶差分,如果 eta 小于等于零,说明两个样本在特征空间中几乎重合或者存在数值退化,直接跳过这轮更新。alpha_new_i2 的裁剪要同时保证两个乘子落在 [0, C] 区间内,并且满足 Σα=1 的等式约束。我在阅读源码时注意到这里的对称更新公式没写全边界修正,实际工程实现里需要在两个边界之间做二次修正,否则等式约束会被破坏。完整代码里有一段处理这个边界的逻辑,后面避坑章节我会专门讲这个坑。

3.4 核心集选择 core_set_select:贪心算法如何保证代表性

核心集选择的质量直接决定近似解和精确解的差距。FastSVDD 的实现是标准的贪心策略:先随机选一个种子样本,然后循环地从剩余样本里挑出离当前球心最远的点加入核心集。离球心越远的点,越能改变球的边界,贪心选择这些点能让核心集用最小的规模抓住几何结构的关键信息。

function idx_core = core_set_select(X, gamma, C) % X: 全量训练数据 % gamma: 核参数 % C: 惩罚系数,用于控制边界松紧(可间接影响选点数) % 返回核心集索引 n = size(X, 1); m_max = max(20, round(n * 0.02)); % 核心集上限取样本量的 2% idx_core = zeros(m_max, 1); idx_core(1) = randi(n); % 随机种子 % 初始选一个点,球心就在这个点上 for t = 2:m_max center = X(idx_core(1:t-1), :); % 计算每个点离当前核心集球心的距离(平方距离近似) dist2 = pdist2(X, mean(center, 1)).^2; % 把离球心最远的点加入核心集 [~, idx_core(t)] = max(dist2); end end

这段代码有一个值得说的细节:它用特征空间中的样本均值来近似球心,而不是精确计算非线性映射后的球心。严格来说,SVDD 的球心在高维特征空间里,无法显式写出;但在高斯核的隐式映射下,用原始特征空间的均值做近似计算距离,工程上效果足够好,代价是理论保证弱了一些。如果想把核心集选得更精确,可以改成"每次加入点后用 SMO 解一次临时模型,再找离临时球心最远的点",但那样计算量会成倍增加。FastSVDD 选择用均值近似,是在精度和速度之间做的明确取舍。

4. 跑通 FastSVDD:从零开始训练、预测与参数调节的完整路径

4.1 准备数据与第一次运行:demo 脚本帮你建立完整认知

打开 demo.m 看一眼,你会发现脚本前半部分是生成合成数据,后半部分是训练和可视化。拿这份代码做基线验证是最省力的方式。先把工作目录切到项目根目录,然后在命令行里敲:

% 在 MATLAB 命令行执行 demo

脚本运行完会画出三个图:原始数据的二维散点、训练完成后球边界在二维平面上的投影、新样本的异常分数分布。第一次跑通后,我建议你仔细看一下第二个图里的球边界。正常样本形成的点簇应该被一条闭合曲线包住,边界线上零星分布着几个被选为支持向量的点。如果你的边界完全贴住了所有样本点,说明 C 设太大或 gamma 设太大;如果边界松散得把大量空白区域也包进来了,说明 C 太小或 gamma 太小。demo 脚本默认的 C 和 gamma 参数通常能出效果,但不会最优,后面调参是不可避免的。

4.2 在自己的数据上训练:接口输入输出与格式要求

训练接口的输入要求是数据矩阵 X,每一行是一个样本,每一列是一个特征。SVDD 对特征的尺度高度敏感,因为它依赖距离计算,如果你的特征量纲差别大,比如一列是温度、一列是压强数值上千,必须先做标准化。常见做法是调用 zscore 函数逐列标准化。下面是一个完整的调用示例:

% 加载自己的数据,假设有 5000 个正常样本,每个样本 20 维特征 load('my_normal_data.mat'); % 变量名 data,5000x20 % 标准化数据 data_std = zscore(data); % 训练模型 C = 0.1; gamma = 1 / size(data_std, 2); % 用特征维数的倒数作为默认 gamma [alpha, b, R, idx_core] = fast_svdd_train(data_std, C, gamma, 500, 1e-5); % 用模型预测新样本 % 假设 new_samples 是 100x20 的待检测矩阵 scores = fast_svdd_predict(data_std, alpha, b, R, idx_core, gamma, new_samples_std);

这里有一个容易混淆的参数:idx_core 是核心集索引,在预测阶段同样要用它来提取训练数据的核心集子集。注意 predict 函数的签名,它需要传入的是训练时的核心集索引,而不是全量数据。这个设计是为了保证训练和预测使用同一批支持向量,否则决策边界会不一致。我见过不少人在二次开发时把整份训练数据传进去预测,结果内存直接爆掉,就是因为没理解这个接口设计的用意。

4.3 参数调节的实用策略:C 和 gamma 不会调怎么办

调参是 SVDD 落地里绕不过去的坎。我一般按照下面的顺序来,基本不会翻车:

第一步固定 gamma,探索 C。gamma 先用 1/特征维数 作为基线,然后让 C 从 0.01 到 1 按对数刻度递增,观察球内样本占比。C 越大,被拒绝在球外的训练样本越少,球边界越紧。如果任务要求高召回率(正常样本误报率低),C 可以取大一些;如果任务要求高精确率(宁可多报异常也不能漏报),C 取小一些。

第二步固定 C,探索 gamma。gamma 对边界形状的影响比 C 更敏感。gamma 过小,所有样本之间的核函数值都接近 1,球在特征空间中摊成一个简单的球形,容易欠拟合;gamma 过大,每个样本都只影响自己周围很小范围,球边界会出现大量凸起,模型记住了太多细节,泛化能力下降。经验上 gamma 在 1/特征维数 的 0.1 倍到 10 倍之间搜索,用交叉验证或验证集上的 F1 分数做选择。

还有一个工程技巧:训练时把核矩阵的计算改成增量模式可以减少重复计算。FastSVDD 每次调用 gaussian_kernel 都重新计算核矩阵,如果你要用网格搜索调参,十个 C 值加十个 gamma 值就是一百次独立训练。我的习惯是写一个小循环,在循环外预计算好样本间的平方距离矩阵 dist2,然后在循环内部通过 exp(-gamma * dist2) 直接得到核矩阵,速度能提升好几倍。这一步改造代码量很小,但对调参体验的提升很明显。

5. 避坑指南:FastSVDD 实践中最常见的五个问题

5.1 训练收敛但预测准确率离谱地低:罪魁祸首是数据没有标准化

现象很明显:训练阶段 loss 正常下降,支持向量也选出来了,但到预测阶段发现几乎所有测试样本都被判为异常,误报率百分之百。

原因大概率是特征没有做标准化。SVDD 的距离计算在原始特征空间里进行时,量纲大的特征会主导整个距离度量,核心集选出的点一概落在量纲大的特征方向上,决策面严重偏离真实分布。我接手过一个传感器数据场景,第一列温度数值在 20 到 80 之间波动,第二列振动幅值在 0.001 到 0.01 之间波动,训练出来球心几乎只由温度决定,振动维度完全失效。

解决方案是在调用训练函数之前对每一列特征单独做标准化。MATLAB 一行代码就能解决:data = zscore(data)。需要注意的是,测试数据要使用训练集的均值和标准差做标准化,不能重新计算,否则分布不一致。正确做法是把训练集的 mu 和 sigma 存下来,预测时用同一个值做变换。

5.2 SMO 求解器在第 3 章简化版代码里卡死:边界修正缺失导致等式约束被破坏

有同学直接拿我上面贴的简化版 smo_solver 去训练,结果迭代几百轮都不收敛,alpha 值越走越偏。

原因是简化版代码在同时更新 alpha_i1 和 alpha_i2 时只做了独立裁剪,没有保证 alpha_i1 + alpha_i2 的和对等式约束 Σα=1 保持不变。SVDD 对偶问题要求所有 alpha 之和等于 1,如果我选 i1 和 i2,设新的 alpha_i2 = 0.3,alpha_i1 = 0.2,而它们之前的和是 0.5,更新后和还是 0.5,这是对的;但独立裁剪会让和发生变化,破坏全局约束。

解决方法是先算总和保持量,再对两个变量做联合裁剪。标准的做法是:设定 alpha_i1 的取值区间为 [max(0, s - C), min(C, s)],其中 s = alpha_i1_old + alpha_i2_old,然后在这个区间内确定新的 alpha_i2,最后通过 alpha_i1 = s - alpha_i2 反算。同时,当 C 值很小、区间下界不为零时,需要额外判断这个区间是否为空,为空说明该对变量不可行,要换一对乘子。完整代码在源码里已经修正,如果你要移植到其他语言,记得带上这段边界处理逻辑。

5.3 内存占用超出预期:核心集上限设置不合理

训练一万个样本时一切正常,换到十万个样本直接报内存不足。查了一下发现 n 从一万变十万后,核心集默认 2% 上限变成了两千个点,核矩阵 2000×2000 的双精度矩阵就有 32MB,而运算时 MATLAB 还会复制多份临时变量,叠加后内存开销相当可观。

原因是核心集大小设成了样本量的固定比例,而不是固定值。实际场景里,核心集本质上是用来逼近几何边界的,边界复杂度由数据分布决定,跟样本量没有线性关系。一万样本可能需要两百个支持向量,十万样本可能只需要三百个,固定 2% 的比例会线性放大不必要的计算量。

解决方案是把核心集上限改成固定值加收敛判据。比如设置 max_core_size = 500,当贪心选点达到上限距离评估时,计算当前球半径的变化量,如果连续几轮变化小于 tol 就提前停止选点。这样内存开销可控,同时还省了不必要的迭代时间。我习惯把上限设成与特征维数相关的值,比如 10 倍特征维数,但不超过 1000,在这个区间里效果通常不错。

5.4 高斯核参数 gamma 太大导致决策边界崎岖不平

训练完画决策边界时,正常样本区域出现了很多尖锐的凸起和凹陷,边界线在支持向量之间剧烈波动,完全没有球形的平滑感。

原因是 gamma 值太大,核函数衰减太快,每个样本的影响范围只有附近几个点,决策函数在这些点之间变化剧烈,于是出现过拟合形态。这在异常检测里是致命伤,因为异常检测数据本身缺少负类反馈,过拟合的边界会把正常区域的内部空洞当成异常。

解决方法是降低 gamma 值,同时提高 C 值来补偿边界的收紧。常见做法是把 gamma 缩到当前值的 1/10,然后把 C 调大 3 到 5 倍,重新训练后观察边界是否平滑。如果边界还是粗糙,继续降。也可以用网格搜索配合一个小型验证集,通过验证集上的误报率来选参数,避免靠肉眼判断主观性太强。

5.5 预测阶段速度比训练还慢:重复核矩阵计算没有优化

训练跑了三秒,预测时跑新样本两百万个,每预测一个都要调一次 gaussian_kernel 算它跟核心集所有点的核值,结果整体耗时比训练还长。

原因是对每个新样本,predict 内部都重新调用了 gaussian_kernel 函数,而核函数里对一对点做欧氏距离计算的循环是逐点计算的,循环一次要调用一次函数,函数内部如果还有对象初始化开销,就被放大了。

解决方案是向量化预测。提前把核心集的核矩阵缓存好,预测时并行计算新样本与核心集的核向量,然后一次性做矩阵乘法。改造后的关键代码是:先保存核心集样本 X_core 和训练得到的 alpha,预测时计算 K_new = gaussian_kernel(X_core, new_samples, gamma),然后用 K_new' * alpha 计算出所有新样本的决策分数,再统一比较阈值。这是在向量层面批量计算的,比逐个调用快了一个数量级。改造代码量不大,但对在线检测系统来说这步提升可能直接决定系统能否实时响应。

6. 二次开发:把 FastSVDD 改造成可在线更新的增量模型

SVDD 的原版训练是批处理的,核心集选完后模型就冻结了。但实际生产环境里数据是不断流进来的,比如设备传感器每天产生新数据,新的正常模式可能出现,旧的模式可能消失。把 FastSVDD 改成支持增量更新,是把它从实验代码变成生产工具的必经一步。

改造思路不复杂:保留核心集和 alpha 数组,每当新样本到来,先用当前模型预测它是否在球内。如果它在球内,说明模型已经覆盖这个区域,直接丢弃;如果它在球外,说明它携带了模型没见过的新信息,应该作为候选支持向量加入核心集。加入后需要重新运行一次 SMO 让 alpha 收敛。这里的关键是控制核心集规模,我一般加一个上限,超过上限时把离球心最近的旧核心点从候选集里剔除,给新来的信息点腾位置。同样的逻辑也适用于处理概念漂移:定期统计球外样本的占比,如果连续一段时间球外占比超过阈值,说明数据分布变了,这时把核心集的种子点重新热启动,再跑一轮全量 SMO。

我踩过的坑是忘了调整阈值 R。旧模型的半径 R 是基于旧数据分布的,新数据若普遍偏离旧分布,直接复用 R 会导致大量正常样本被误报。有一个有效做法:每次增量更新后,用当前核心集重新计算 R2 并更新半径值,确保球的大小跟得上分布的变化。计算半径的代码在 compute_radius.m 里,更新时调用它就行。

这份代码的另一个改造方向是换核函数。高斯核不是唯一选择,对于文本向量这种稀疏数据,线性核加合适的缩放往往更稳定;对于图像特征,拉普拉斯核在局部特征匹配任务上有时比高斯核更准。换核函数只要改 gaussian_kernel.m 一个文件里的计算逻辑,保持输入输出接口不变即可。我不建议顺手改掉对偶求解器里的公式——不同核函数的二阶差分 eta 计算会变,改动范围远比想象中更大。

从那以后我每次训练后都会留出十分之一的样本当验证集,强制自己跑一遍完整预测流程再确定参数,不再对着一份训练曲线自说自话。希望这份 FastSVDD 源码的拆解能帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进