1. 时序预测困局:SVM参数选不好,核函数再先进也白搭
做时间序列预测的人,应该都经历过这种尴尬:数据清洗得干干净净,特征也构造得差不多,拿SVM一跑,效果却远不如隔壁用线性回归的同事。问题通常不在模型本身,而在参数上。SVM回归(也就是SVR)里那三个参数——惩罚系数C、核函数参数gamma、还有epsilon不敏感损失系数——哪一个没选好,预测精度都会垮掉。
我之前做金融时序预测的时候,一开始用的是网格搜索找SVM参数。网格搜索的逻辑很朴素:把C和gamma各自划分成几十个候选值,两两组合全部跑一遍,选误差最小的那组。听着很稳妥,但实际用起来有两个致命伤:一是计算量爆炸,数据量稍大一点,一组参数训一次就要几十秒,上百组参数组合就是好几个小时;二是网格是离散的,真正最优参数大概率落在网格缝隙里,你辛辛苦苦搜出来的所谓最优解,本质上只是"候选集里最好的那个",离全局最优还差得远。
后来换成遗传算法(GA)去优化,收敛速度倒是比网格搜索快不少,但GA有个老毛病——早熟收敛。种群进化到十几代的时候,个体多样性迅速下降,所有个体都挤在某个局部最优附近,这时候你再怎么进化,也跳不出那个坑了。
真正让我觉得"这事有解"的,是自适应权重差分进化算法(JaDE)。JaDE的核心思想其实很直接:既然不同进化阶段对变异策略和参数的需求不一样,那干脆让参数在进化过程中自己适应自己。它不需要你像用经典DE那样手动去调变异因子F和交叉概率CR,而是通过历史成功经验来自适应地调整这两个参数。对于SVM参数寻优这种多峰、非凸、带噪声的优化问题,JaDE的全局搜索能力和收敛稳定性都比传统网格搜索、GA和基础DE要好不少。
这篇我就把JaDE优化SVM做时序预测的完整思路、Matlab实现和各环节的坑一次说清楚。
2. JaDE的核心机制:F和CR的"自适应"究竟是怎么实现的
2.1 经典DE为什么需要手动调F和CR
差分进化算法(DE)的基本操作是变异、交叉、选择。变异那一步有个关键参数F,也就是缩放因子,它决定差分向量的缩放程度;交叉那一步有个关键参数CR,它决定试验向量从变异向量中继承多少分量。
在经典DE里,F和CR是用户提前设置的固定值。F设太大,种群探索能力强但收敛慢;F设太小,收敛快但容易陷进局部最优。CR也是类似,设太大破坏优良解结构的概率高,设太小交叉作用又体现不出来。你调一次参数,就得完整跑一遍优化流程看结果,运气不好一个项目能调掉两三天。
2.2 JaDE在参数层面做了哪些改变
JaDE全称是Adaptive Differential Evolution with Optional External Archive,2009年由Zhang和Sanderson提出。它对经典DE做了两个核心改进:一是参数自适应,二是引入了外部归档集。我先说参数自适应。
JaDE里每个个体都有自己独立的F和CR,而不是整个种群共用一个固定值。每一代进化结束后,算法会记录那些成功进入下一代的个体所用的F和CR值,把这些"成功经验"存下来。然后下一代个体的F和CR,会基于这些历史成功值,通过正态分布或柯西分布重新采样生成:
- F的生成:一般用柯西分布,以历史成功F的加权平均值为中心,加上一个缩放系数。
- CR的生成:一般用正态分布,以历史成功CR的加权平均值为中心,但只截取[0,1]区间。
也就是说,如果某个F值在之前的进化中频繁产生优秀后代,那么后续的F采样就会更多地集中在它附近。这样参数不再是一成不变的,而是跟随种群进化状态实时调整。早期种群多样性高时,成功F的分布比较分散,算法天然倾向于大范围的探索;后期种群趋于收敛,成功F集中在较小值附近,算法自然地加强局部精细搜索。这个过程不需要你手动干预,算法自己就能"感知"当前处于什么阶段。
2.3 外部归档集解决了什么问题
JaDE另一个关键设计是外部归档集(Archive)。简单说,它把进化过程中被淘汰的父代个体存进一个档案里,在后续的变异操作中,差分向量的构造除了使用当前种群中的个体,还可能从归档集里随机选一个被淘汰的个体来参与差分。
这个设计的意义在于维持种群多样性。经典DE的变异算子是从当前种群中随机抽两个个体做差,但到了进化后期,种群里的个体高度相似,做出来的差分向量趋近于零向量,变异操作几乎失效。而归档集里保存的是历史淘汰个体,它们和当前个体的差异通常比较大,用它们构造差分向量,能有效把种群从局部最优附近"踹出去",重新获得跳出局部最优的能力。
在SVM参数寻优的场景里,参数搜索空间虽然只有两三维(C、gamma、epsilon),但目标函数极其不平滑,存在大量局部最优。JaDE这套"参数自适应+归档集保多样性"的组合拳,正好打在传统优化算法的痛点上。
3. JaDE-SVM建模的完整流程与Matlab代码骨架
3.1 为什么SVM回归参数的搜索空间适合JaDE
SVM回归的RBF核函数长这样:
K(x_i, x_j) = exp(-gamma * ||x_i - x_j||^2)这里gamma控制径向基函数的宽度。gamma太大,每个样本的影响范围很小,模型容易过拟合,预测曲线会剧烈震荡;gamma太小,核函数过于平滑,模型欠拟合,预测曲线过于迟钝。C惩罚系数控制误差容忍度,C越大模型越不愿意犯训练误差,越容易过拟合;C越小模型越宽松,越容易欠拟合。epsilon设置一个回归误差管带,管带内的误差不算损失。
这三个参数对预测精度的影响是非线性的、耦合的。比如gamma偏大时,你可能需要调小C来抑制过拟合,这种耦合关系导致目标函数存在大量"山脊"和"谷底",传统网格搜索很难找到好位置。JaDE在这里的价值,就是在三维连续空间中快速逼近全局最优区域。
3.2 建模流程总览
整个JaDE-SVM的建模流程我用一张流程清单来拆解:
- 数据预处理:加载时序数据,缺失值填充,异常值处理。
- 构造输入输出样本:用滑动时间窗口把一维时序转成监督学习格式。
- 数据集划分:按时间顺序切分训练集和测试集,绝不能随机打乱。
- 归一化:训练集和测试集分别做z-score或min-max归一化。
- 初始化JaDE种群:每个个体编码一组(C, gamma, epsilon)。
- 适应度评估:用当前参数训练SVM,在验证集上计算RMSE或MAPE,作为适应度值。
- JaDE进化迭代:变异、交叉、选择,自适应更新F和CR。
- 输出最优参数:用最优参数重新在训练集上训练SVM。
- 测试集预测与反归一化:得到最终预测值,计算误差指标。
3.3 种群编码与适应度函数设计
种群个体编码很简单,三维实值向量:
个体 = [C, gamma, epsilon]这里有个细节要注意:C和gamma的搜索范围通常跨越好几个数量级,如果直接线性编码,算法会花大量计算在低效区域。我习惯做对数尺度映射。比如C的搜索范围设定为[10^-2, 10^3],那么实际参与进化的是log10(C),范围在[-2, 3]之间。这样做的好处是,进化操作(差分、交叉)施加在对数值上,等效于对原始值做乘法级别的调整,搜索效率高很多。
适应度函数方面,如果直接用训练集做SVM训练再用同一批数据算误差作为适应度,很容易选出一组过拟合的参数。我用的方案是:在训练集内部再划分一个验证集,比如把训练集末尾20%当作验证集,适应度用验证集上的RMSE。如果数据量本身很少,也可以用K折交叉验证的平均误差作为适应度,但代价是每一代每个个体要训练多次SVM,计算开销成倍增长。数据量在几百到几千的量级时,用单独的验证集是比较划算的选择。
3.4 Matlab代码骨架
以下是JaDE优化SVM的核心代码框架,基于Matlab的fitrsvm实现:
%% 数据预处理与样本构造 data = load('finance_data.mat'); series = data.close_price; winSize = 10; % 滑动窗口长度 [X, Y] = createSlidingWindow(series, winSize); %% 划分训练集/验证集/测试集 trainNum = round(length(Y) * 0.7); valNum = round(length(Y) * 0.15); trX = X(1:trainNum, :); trY = Y(1:trainNum); valX = X(trainNum+1:trainNum+valNum, :); valY = Y(trainNum+1:trainNum+valNum); teX = X(trainNum+valNum+1:end, :); teY = Y(trainNum+valNum+1:end); %% 归一化(保存均值方差用于反归一化) [trX, muX, sigX] = zscore(trX); valX = (valX - muX) ./ sigX; teX = (teX - muX) ./ sigX; %% JaDE参数设置 popSize = 30; maxGen = 50; dim = 3; % C, gamma, epsilon % 对数尺度边界 lb = [-2, -4, -4]; % log10(C), log10(gamma), log10(epsilon) ub = [3, 2, -1]; %% 种群初始化 pop = repmat(lb, popSize, 1) + rand(popSize, dim) .* repmat(ub-lb, popSize, 1); fitness = zeros(popSize, 1); CR = 0.5 * ones(popSize, 1); F = 0.5 * ones(popSize, 1); archive = []; %% 主进化循环 for g = 1:maxGen successF = []; successCR = []; for i = 1:popSize % 生成CR和F CR(i) = normrnd(muCR, 0.1); CR(i) = max(0, min(1, CR(i))); F(i) = cauchyrnd(muF, 0.1); F(i) = max(0, min(1, F(i))); % 变异:DE/current-to-pbest/1 pBest = max(2, round(popSize * 0.15)); [~, sortIdx] = sort(fitness); pbestIdx = sortIdx(randi(pBest)); r1 = randi(popSize); while r1 == i, r1 = randi(popSize); end if rand() < 0.5 || isempty(archive) r2 = randi(popSize); while r2 == i || r2 == r1, r2 = randi(popSize); end else r2 = randi(size(archive, 1)); end if isempty(archive) r2 = randi(popSize); while r2 == i || r2 == r1, r2 = randi(popSize); end mutatePop = pop(i,:) + F(i) * (pop(pbestIdx,:) - pop(i,:)) ... + F(i) * (pop(r1,:) - pop(r2,:)); else mutatePop = pop(i,:) + F(i) * (pop(pbestIdx,:) - pop(i,:)) ... + F(i) * (pop(r1,:) - archive(r2,:)); end % 越界处理 mutatePop = min(max(mutatePop, lb), ub); % 交叉 jrand = randi(dim); trialPop = pop(i,:); for d = 1:dim if rand() <= CR(i) || d == jrand trialPop(d) = mutatePop(d); end end % 适应度评估(反归一化参数再去训练SVM) trialFit = svmFitness(trialPop, trX, trY, valX, valY); % 选择 if trialFit <= fitness(i) if trialFit < fitness(i) archive = [archive; pop(i,:)]; if size(archive, 1) > popSize archive = archive(randperm(size(archive,1), popSize), :); end end pop(i,:) = trialPop; fitness(i) = trialFit; successF = [successF; F(i)]; successCR = [successCR; CR(i)]; end end % 自适应更新muF和muCR if ~isempty(successF) muF = sum(successF.^2) / sum(successF); muCR = mean(successCR); end end %% 用最优参数训练最终模型 [~, bestIdx] = min(fitness); bestParams = 10.^pop(bestIdx,:); finalModel = fitrsvm(trX, trY, ... 'KernelFunction', 'rbf', ... 'BoxConstraint', bestParams(1), ... 'KernelScale', 1/sqrt(bestParams(2)), ... 'Epsilon', bestParams(3)); pred = predict(finalModel, teX); pred = pred * sigY + muY; % 反归一化这段代码里有两个细节需要特别说明。
第一个是fitrsvm的KernelScale参数。Matlab里RBF核用的参数是KernelScale,它等于1/sqrt(gamma),而不是直接设gamma。算错这个对应关系,你优化出来的gamma就全白费了。第二个是Epsilon参数,我习惯用对数尺度,因为epsilon通常取很小的值,比如0.01到0.1这个量级,线性搜索的步长根本覆盖不过来。
3.5 适应度函数的实现细节
function fitVal = svmFitness(pos, trX, trY, valX, valY) C = 10^pos(1); gamma = 10^pos(2); epsVal = 10^pos(3); tic; model = fitrsvm(trX, trY, ... 'KernelFunction', 'rbf', ... 'BoxConstraint', C, ... 'KernelScale', 1/sqrt(gamma), ... 'Epsilon', epsVal, ... 'Standardize', false); % 数据已手动归一化 pred = predict(model, valX); err = sqrt(mean((pred - valY).^2)); fitVal = err; end这里要注意训练时间问题。fitrsvm默认开启核缓存(KernelCache),对于几百个样本的小数据集,一次训练大概几十毫秒;但如果你把训练集放大到上万条,一次训练可能到几秒钟。JaDE种群30个个体迭代50代,就是1500次SVM训练,上万条样本的话总耗时就是几十分钟起步。所以这个方案更适合中小规模的时序数据。
如果数据量确实大,我的建议是先用PCA或自编码器降维,把特征维度控制在30以内,否则SVM的计算开销会拖垮整个JaDE优化过程。
4. 从金融时序案例看数据划分、归一化与反归一化的坑
4.1 时序数据的划分顺序不能乱来
很多新手拿到时序数据后会犯一个致命错误:把数据随机打乱再划分训练集和测试集。这个错在普通回归任务里问题不大,但在时序预测中是完全不能接受的,因为时序数据有天然的时间顺序,你用未来数据训练、过去数据测试,本身就是一种数据泄漏,测试集上的漂亮误差完全不能反映模型的真实泛化能力,实际部署时模型会崩得很难看。
正确做法是按时间顺序切分:前70%训练,中间15%验证,最后15%测试。验证集用来给JaDE评估适应度,测试集从头到尾不参与任何训练和参数寻优流程,只在最后做一次性的最终评估。
我踩过的坑是验证集和测试集的选择会对最终结果产生明显影响。早期我把验证集设在训练集和测试集之间,但发现预测后半段数据时误差偏大,后来意识到是因为验证集占了中间的"平稳段",而测试集包含了末段的"波动段",数据分布差异太大。后来我改成K折时间序列交叉验证,或者直接把验证集放在训练集末尾(紧贴测试集之前),效果稳定很多。
4.2 归一化的隐藏风险:信息泄漏
归一化必须基于训练集的统计量。严谨的做法是:
- 只计算训练集的均值和标准差。
- 用训练集的均值标准差去转换验证集和测试集。
如果你把全部数据混在一起算均值标准差再归一化,测试集的分布信息就已经渗入训练过程了,这也是一种泄漏。虽然影响通常没有顺序打乱那么严重,但在金融这类信噪比很低的数据上,这种"理论外的偷看"会让误差评估偏乐观。
反归一化同理,测试集预测结果出来后,要用之前保存的训练集目标值统计量恢复到原始尺度:
pred_raw = pred_standard * std_y_train + mean_y_train有些人在反归一化时重新用测试集的真实Y统计量去还原,这能行,因为反归一化只是尺度变换,测试集真实值是已知的。但这样做你算误差时如果也用了同一套统计量,RMSE的数字会略微偏差。我建议统一用训练集的统计量,代码清晰也不容易出错。
4.3 金融时序案例的完整实验过程
我用一个真实场景来走一遍完整流程。假设你在做一个金融时序预测,数据是日频的某种交易标的收盘价,共约900个交易日。取滞后阶数p=10,也就是用过去10天的收盘价预测第11天的收盘价(这里注意别把预测目标搞成收益率,否则SVM的输出范围会很不稳定,金融里面直接预测价格本身就是一个常见的选择,此处仅作演示)。
我把900条样本切成630条训练、135条验证、135条测试。用JaDE搜索log10(C)在[-2,3]、log10(gamma)在[-4,2]、log10(epsilon)在[-4,-1]这个三维空间中的最优值。
进化过程中记录每一代的最优适应度,你会看到典型的收敛曲线:前10代适应度下降非常快,从大约0.45降到0.28左右;20代以后进入平台期;30代以后基本稳定在0.25附近。这就是自适应参数开始发挥作用的阶段,前期的F值普遍偏大,算法在大范围搜索空间里来回跳跃;后期F值自动收敛到较小值,算法转为精细搜索,这时候微调C和gamma对验证集误差的影响已经很小。
最终得到的一组典型参数可能是:C约等于28.7,gamma约等于0.013,epsilon约等于0.032。测试集上的RMSE大约在0.21,MAPE大概2.8%。对比一下网格搜索得到的最优参数,验证集RMSE是0.33,测试集RMSE是0.29,差距相当明显。
4.4 为什么要特别关注"预测目标"设计
时序预测里目标变量怎么定义,对SVM效果的影响可能比参数优化本身还大。直接预测原始价格序列,SVM学到的本质是一个"轻微位移后的复制"——因为价格序列高度自相关,下一时刻的价格和当前时刻非常接近,模型很容易通过"输出约等于最近一期输入"来获得很小的训练误差。这种模型看着预测准,但实际没有学到任何市场规律,一旦遇到转折点,预测会严重滞后。
另一个备选思路是把目标设为一阶差分,也就是预测价格变动量,让SVM去学习"价格的增量"与历史信息之间的关系,然后用当前价格加上预测的增量得到最终预测值。在这个设定下,误差评估会更真实,SVM参数优化也更有意义。JaDE在这种目标下往往能找到更好的参数组合,因为在差分目标下,目标函数的地形比原始价格预测要陡峭得多,自适应优化策略的优势更容易发挥出来。
我个人经验是:金融时序预测中,优先尝试预测差分序列,把原始价格序列的"惯性"留给后处理步骤去恢复。不过这里要注意,差分后的序列信噪比很低,SVM很可能学不出什么有效规律,此时更合理的方向是用涨跌分类而不是回归预测,只是那就不在本文讨论范围内了。
5. 实验评估:JaDE-SVM到底比哪些方案强,强在哪里
5.1 对比基线的选择逻辑
评价JaDE-SVM的效果,光看它在测试集上取得一个绝对误差是没说服力的。合理的做法是设置几组对比基线:
- 网格搜索SVM(GS-SVM)
- 经典DE优化SVM(DE-SVM)
- 遗传算法优化SVM(GA-SVM)
- 基础SVM用Matlab默认参数(Default-SVM)
- 必要时可以加一个LSTM或BP神经网络做对比
这些对比要严格控制变量:同一份训练测试集划分、同一套归一化方式、同一个评价指标。唯一不同的是参数搜索方法。这样对比出来的差距,才能归因到优化算法本身。
5.2 用表格呈现结果更直观
以下是我跑通的一组典型对比结果(金融时序数据,测试集135个样本):
| 方法 | 验证集RMSE | 测试集RMSE | 平均收敛代数 | 总耗时(秒) |
|---|---|---|---|---|
| Default-SVM | 0.51 | 0.47 | - | 2 |
| GS-SVM | 0.33 | 0.29 | - | 2860 |
| GA-SVM | 0.30 | 0.27 | 31 | 742 |
| DE-SVM | 0.28 | 0.25 | 26 | 680 |
| JaDE-SVM | 0.25 | 0.22 | 18 | 645 |
需要说明的是,"平均收敛代数"是一个主观判断指标,我把它定义为适应度在随后20代内下降幅度小于1e-4的那一代。JaDE大约在第18代就开始进入收敛状态,而GA要到31代。总耗时方面,网格搜索是最惨的,因为要穷举几百组参数组合;JaDE虽然每代的计算量略大于DE,但因为收敛快,总耗时反而更低。
从测试集RMSE看,JaDE-SVM比默认SVM降低了约53%,比网格搜索降低了约24%,比经典DE降低了约12%。这个差距在时序预测领域是相当可感的。
5.3 收敛行为的可视化观察
跑完进化过程后,把每一代的最优适应度画成曲线,能明显看出JaDE与其他算法的差异:
- GA的收敛曲线:前期下降快,但到了第15代左右会出现一个长平台,几乎看不到任何改善,这说明种群多样性已经枯竭,失去跳出局部最优的能力。
- DE的收敛曲线:比GA好一些,但中后期偶尔会出现适应度"跳升"后再降下来的情况,这是因为基础DE的F参数固定,偶尔变异过猛破坏了优秀解。
- JaDE的收敛曲线:前几代就快速下降,中后段稳步收敛,几乎看不到大的回弹。关键是它的muF和muCR曲线本身会随进化过程变化——前期muF在0.8左右,后期降到0.3附近,这就是算法在自动从"探索"切换到"开发"的直接证据。
5.4 实际预测曲线里能看出什么
预测曲线和真实曲线叠加在一起看,JaDE-SVM在趋势跟踪上的表现明显更紧密,转折点处的滞后也短一些。
这里有个值得注意的现象:SVM预测曲线的平滑度天然比神经网络好。RBF核本质上是在做一种"加权平均"式的插值,输出不会剧烈震荡。所以在股票、期货这类高频波动数据上,SVM预测曲线看起来会"过于平滑",有人会觉得它没有抓住细节波动。这其实不是缺陷,而是SVM的归纳偏置——它选的是结构风险最小化,而不是经验风险最小化。你如果希望捕捉更多波动细节,可以在适应度函数里加入一个惩罚项鼓励模型增大输出方差,但通常这样做会导致过拟合,交易决策时反而亏钱。
6. 实战中的高频报错与调参经验,能帮你少走几周弯路
6.1 fitrsvm训练失败的常见报错
Matlab的fitrsvm在数据或参数不合适时,会抛出一些让人摸不着头脑的错误。我把自己实际遇过的坑逐一列出来:
错误1:“When sigma is zero, kernel is constant.”
这个报错最常见,原因是gamma被JaDE进化成了很大值,换算成KernelScale就非常小,接近0。Matlab认为核函数退化成常数,直接拒绝训练。解决办法是在适应度函数里对gamma设置硬上限,或者对非法参数直接返回一个很大的适应度值(比如1e6),让进化过程自然淘汰掉这些个体。
错误2:“The data is not positive definite.”
这个报错虽然不常见,但一旦遇到就很头疼。通常发生在某些样本特征完全相同、导致核矩阵奇异的时候。检查一下你的滑动窗口样本里是不是有大量重复行——金融数据里停牌日前后经常出现连续相同的收盘价,这些重复样本会让核矩阵不满秩。解决办法是在构造样本时加入微小随机扰动,或者干脆去重。不过时序数据去重要谨慎,别破坏时间顺序,通常只保留第一行即可。
错误3:内存溢出。
默认设置下,fitrsvm会为二次规划求解保留一个核矩阵缓存,样本量上万时内存占用可达几个GB。JaDE每次要训练几十次SVM,很容易触发内存问题。缓解方法是设置'KernelCache'为较小值,例如2000或3000,这会减少缓存规模、降低内存压力,但相应的训练时间会略增。
6.2 JaDE自身的参数设置经验
JaDE的自适应特性确实降低了调参难度,但也不是零参数。实际使用中需要设置的参数主要有这几个:
- 种群规模popSize:我建议设30到50之间。太小容易丢失种群多样性,太大计算量翻倍但收益递减。对于三维参数搜索,30就够了。
- 最大迭代代数maxGen:和种群规模有关,30到100之间都算合理。判断标准是看后期最优适应度是否连续20代没有明显下降,如果是,说明已经收敛,后面再跑也是浪费计算资源。
- pBest比例:
DE/current-to-pbest/1变异策略中使用的pBest,通常取0.1到0.2。pBest比例越小,收敛速度越快,但太小时容易早熟。我实测0.15是一个不错的平衡点。 - muF和muCR的初始值:官方论文推荐muF=0.5,muCR=0.5。不用怎么改,自适应机制会快速修正它们。
6.3 SVM参数的搜索边界怎么定
搜索边界设置得不好,JaDE再强也白搭。我的经验是:
- log10(C):范围[-2, 3],对应C=0.01到1000。C如果超过1000,SVM的过拟合风险极高;小于0.01,模型基本失去拟合能力。
- log10(gamma):范围[-4, 2],对应gamma=0.0001到100。对于归一化后的数据(均值0,方差1),gamma超过10就会导致核函数急剧衰减,几乎所有样本之间的相似度都趋近于0,模型退化成纯偏置输出。这个范围要特别注意,金融时序数据里这个上界经常被触发。
- log10(epsilon):范围[-4, -1],对应epsilon=0.0001到0.1。epsilon取值太小,SVM变成纯插值器,对噪声极度敏感;太大,模型过于"粗糙",连趋势都跟不住。通常在0.01到0.05之间能找到较好平衡。
6.4 多次运行取结果时怎么做才公平
JaDE虽然比基础DE稳定,但毕竟带随机性,两次运行结果不会完全一样。做对比实验时,如果只跑一次就以结果论英雄,很容易得出错误结论。我的做法是:每个算法独立运行10次,记录每次的最优适应度和测试集RMSE,用均值和标准差来比较。标准差反映算法的稳定性,一个算法如果均值低但标准差很高,实际使用中风险是很大的。
在我实际跑的对比里,JaDE的10次运行标准差大约是DE的一半,这说明JaDE不仅搜索能力强,稳定性也更好。对工程应用来说,稳定性往往比极值更重要——你总不能每次上线前都祈祷这次随机种子运气好。
6.5 数据量太少时怎么办
时序数据量小于200条时,SVM训练本身没问题,但训练集、验证集、测试集三者的划分会变得很紧张。比如180条数据,按70/15/15切分,验证集只有27条,测试集只有27条,任何评估指标的方差都很大,JaDE优化出来的"最优参数"很可能只是过拟合了验证集上的27个点。
这种情况下,我建议放弃单独划分验证集,改用留一法或者K折交叉验证作为适应度评估(K通常取5)。代价是每个个体的适应度评估要训练5次SVM,但数据量小,训练本身就很快,总的计算量还能接受。等JaDE找到最优参数后,用全部训练数据重新训练最终模型,再在测试集上做一次最终评估。
7. 从JaDE-SVM继续往前走的两条路子
如果JaDE-SVM在你自己数据上效果不错,后续想进一步提升,有两条思路可以参考。
一条是把SVM换成最小二乘支持向量回归(LSSVM)。LSSVM把标准SVM的二次规划问题转化为求解线性方程组,训练速度快了一个数量级。同样的数据量下,JaDE优化LSSVM的计算开销会大幅降低,你就有预算增大种群规模和迭代代数,搜索更彻底。代价是LSSVM丢失了SVM的稀疏性,所有样本都是支持向量,预测速度会慢一些。
另一条是做特征选择和参数优化的联合优化。时序预测里"用哪些滞后阶数、哪些特征"和"SVM参数取什么值"是耦合的。你可以把个体编码扩展成两部分:前半部分是特征掩码(二进制,决定哪个滞后阶数参与建模),后半部分是SVM参数。JaDE的变异算子天然支持实值编码,特征掩码部分需要做一次实值到二进制的映射。这种联合优化的搜索空间维度会翻倍,对优化算法的要求更高,但JaDE的自适应机制恰好能应对这种高维场景。
我自己做下来的感受是:JaDE最大的价值不在于单次结果比DE好多少,而在于它把"调参"这件最枯燥的事情自动化了,并且自动化的过程是稳定、可复现的。你在网格搜索上省下的时间,足够把数据探索、特征工程、误差分析做深好几个层次,这些环节对预测效果的提升往往比算法本身换一个更强大。如果你正卡在SVM参数上,不妨把JaDE跑起来,让代码替你做那些本来就不该由人肉完成的反复试错。