做多变量时间序列预测的朋友,应该都经历过这种纠结:数据明明很丰富,模型却总是欠拟合或者过拟合,调参调到怀疑人生。我之前在Matlab里做风电功率预测时,用过XGBoost,效果不错,但超参数实在太多,手工试错效率太低。后来把秃鹰搜索优化算法(BES)引入进来,用BES自动寻优XGBoost的核心超参数,再用K折交叉验证作为适应度评估,总算把验证集和测试集的误差差距压了下去。这篇就把整套思路和踩坑过程记录下来,给同样在做多变量时间序列预测、正在琢磨怎么抑制过拟合的朋友一个参考。
这个“BES-XGBoost多变量时间序列预测”项目,说白了就是解决两件事:一是用XGBoost对多变量时间序列做回归预测,二是用秃鹰搜索优化算法替代人工试参,同时用交叉验证防止模型只在训练集上自嗨。它适合那些已经有时间序列数据、但不满足于传统ARIMA,也不想一上来就堆深度学习模型的人。如果你熟悉Matlab,想在不写Python代码的前提下把集成学习和群智能优化结合起来,这篇内容可以直接照着复现。
1. 整体设计思路与方案选型
1.1 多变量时间序列预测的核心痛点
单变量时间序列只依赖自身历史,可用的信息有限;多变量时间序列虽然加入了多个相关变量,却给建模带来了新的麻烦。比如预测某一时刻的电力负荷,可能会用到气温、湿度、风速、节假日标记、历史负荷等好几个维度的数据。这些变量之间往往存在复杂的非线性关系,单纯用线性模型很难捕捉。
XGBoost这类树模型可以处理非线性关系,一个很大的优势是不需要对特征做太多归一化处理,天生能处理缺失值,还能输出特征重要性。但在时间序列场景里,它需要我们自己把时序数据构造成监督学习样本。等样本构造完,超参数又成了新问题。学习率、树深度、叶子节点最小样本数、子采样率、列采样率、正则化系数,随便一个设置不合适,模型的表现就会差很多。
手工调参效率太低,网格搜索又容易撞上维度爆炸。我一开始用网格搜索调XGBoost,参数取几个档位,组合起来上百次实验。每次训练都要跑交叉验证,一跑就是几小时。后来换成群智能优化算法,让算法自己去搜索超参数空间,才把这件事从体力活变成自动化。
1.2 秃鹰搜索优化算法的核心机制
秃鹰搜索优化算法(Bald Eagle Search,BES)是近年提出的一种群智能优化算法,模拟秃鹰在捕食过程中的三个行为阶段:选择搜索空间、在搜索空间内搜索、俯冲捕获猎物。
第一阶段的选择搜索空间,相当于在整个解空间中划出一个可能包含最优解的区域。第二阶段是局部搜索阶段,秃鹰会在当前选定的区域内沿着螺旋轨迹飞行,一边飞行一边评估猎物的位置。第三阶段是俯冲捕获阶段,已经从螺旋搜索转向朝确定的最优位置快速俯冲,类似于算法在局部最优附近进行精细搜索。
从算法结构上看,BES使用螺旋方程更新位置,这比粒子群算法中单纯的“当前位置 + 速度”更新方式多了更多探索性。和遗传算法相比,它没有交叉和变异操作,参数更少,实现起来也简单。我实际跑下来,BES在中等规模超参数空间上的收敛速度确实比遗传算法快一些,尤其是处理连续参数时,不需要额外编码解码。
1.3 为什么选择XGBoost作为预测器
XGBoost本质上是一个梯度提升树模型,通过不断训练新的CART树来拟合上一轮的残差,并对树的复杂度做正则化惩罚。它的目标函数包含损失函数和正则项两部分,这本身就具备一定的抗过拟合能力。
在多变量时间序列预测中,我们可以把过去若干个时间步的多个变量展平,作为树的输入特征。树模型不需要像LSTM那样严格保持时间维度的结构,而是把时间依赖关系隐藏在特征中。这样做的优点是训练速度快、模型相对容易解释,缺点是如果特征构造不合理,可能丢掉时间顺序性。所以我通常用滑动窗口构造特征,比如用过去10个时间步的全部变量预测未来1步的目标值。
XGBoost在中小规模数据和表格型特征上的表现非常稳定,这也是我选择它的核心原因。相比随机森林,梯度提升树的泛化能力通常更好;相比神经网络,它需要的调参量更少、对数据量的要求也更低。如果数据量达到百万级,可能深度模型会更合适,但在大多数工程场景中,XGBoost已经足够好用。
1.4 交叉验证如何抑制过拟合
过拟合的本质是模型在训练集上记住太多噪声,导致测试集表现变差。在超参数优化过程中,如果只拿一个固定的验证集来评估参数好坏,非常容易选出恰好在这个验证集上表现好、换一批数据就崩掉的参数。
交叉验证通过把训练数据切成多份,轮流用其中一部分做验证、其余做训练,最后把多次验证误差取平均。这样每个样本都有机会被当作验证数据,最终评分对数据划分的敏感度更低。把交叉验证的评分作为BES算法的适应度值,可以让优化过程直接面向泛化性能,而不是单个验证集性能。
我在这个项目中用的是K折交叉验证,K通常设为5或10。K越小,训练样本越少,评估偏差越大;K越大,评估越稳,但计算成本成倍增加。针对时间序列数据,还要特别注意折与折之间的时间顺序,不能随机打乱,否则会引入未来信息,造成一种“假的高分”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据准备与特征工程实操
2.1 多变量时间序列数据怎么组织
在Matlab里,多变量时间序列一般是一个NumSamples行、NumFeatures列的矩阵,每一行是同一个时间点的观测,每一列是一个变量。例如我有一个1小时分辨率的负荷数据集,变量包括“负荷”、“温度”、“湿度”、“风速”、“是否节假日”,那矩阵就是N行5列。
预测目标通常是其中一个变量,或者是由多个变量计算出来的派生量。比如用过去一段时间的气象特征预测未来负荷值,目标变量是负荷列,特征列可以是负荷本身的历史值加上其他变量。这里有个容易忽略的点:如果我们把目标变量的历史值也作为特征,实际上是在做自回归;如果完全不使用目标变量历史值,那就变成了纯外生变量预测。两种方式都有人用,但实验表明,加入目标变量的滞后值通常会显著提升预测精度。
从Matlab的Matrix格式出发,特征组织建议统一存成一个Double矩阵,避免用cell数组。每行是一个样本,每列是一个特征,这样后续可以直接支持XGBoost或fitensemble的输入格式。每个变量的历史窗口要单独生成,最后用horzcat把多组窗口拼接起来。
2.2 滑动窗口构造样本
滑动窗口构造样本是多变量时间序列预测的关键步骤。假设我们有原始数据矩阵data,大小为N×m,需要构造一个函数,用过去p个时间步的特征预测未来h个步长的目标值。对于单步预测,h=1,目标值是当前时刻或未来一个时刻的值。
我在Matlab里通常用如下函数生成监督学习样本:
matlab复制function [X, y] = createSlidingWindow(data, p, predictCol, h)
% data: N x m 矩阵
% p: 窗口长度(使用过去p个时间步)
% predictCol: 要预测的列索引
% h: 预测未来h步
N = size(data, 1);
numFeatures = size(data, 2);
totalSamples = N - p - h + 1;
X = zeros(totalSamples, p * numFeatures);
y = zeros(totalSamples, 1);
for i = 1:totalSamples
window = data(i : i + p - 1, :);
X(i, :) = window(:)';
y(i) = data(i + p + h - 1, predictCol);
end
end
需要注意,如果数据中存在很大的异常值,滑动窗口把异常值平铺进特征后,会让树模型局部拟合异常。建议在生成窗口之前先对原始值做平滑或剔除明显噪声,否则后面再怎么调参都会受影响。窗口长度p的选择也很讲究,我常用的范围是5到20,取决于数据周期性和采样频率。如果预测目标是日负荷数据且有明显周期性,p至少要覆盖一个周期。
2.3 数据归一化与按时间划分
虽然XGBoost是树模型,不需要对特征做严格的Z-score归一化,但目标变量的转换仍然重要。比如负荷数据范围在几百到几千MW,误差评估如果直接算RMSE,数值会非常大,不利于BES算法比较不同参数的好坏。我通常把目标变量做Min-Max归一化到[0,1]区间,这样RMSE的范围也落在[0,1]内,BES的适应度曲线看起来更友好。
归一化只能使用训练集的统计量,不能把测试集统计量混进训练过程。在Matlab中,建议先在原始数据上按时间顺序切片,得到训练段、验证段、测试段,然后对每一段的输入特征和输出目标分别做归一化,但归一化参数(例如Min和Max)只用训练段的。
划分策略上,我的习惯是70%训练、15%验证、15%测试。由于是时间序列,不能随机打乱,必须按时间顺序切分。验证集用于BES优化过程中的交叉验证内部使用,测试集则一直留到最后评估最终模型。这样能最大程度避免优化过程中“偷看”测试数据。
2.4 时间序列交叉验证的注意事项
很多人在做时间序列预测时,直接用Matlab的cvpartition库做随机K折交叉验证,这是个大坑。随机K折会把未来的样本混进训练集,导致模型“提前知道未来”,验证误差极其乐观,但上线后完全不是那么回事。
更稳妥的做法是按时间顺序做滚动式交叉验证,也就是“TimeSeriesSplit”。比如有1000个样本,5折,第一折用前200个样本训练,预测第201到400个样本;第二折用前400个样本训练,预测第401到600个样本;以此类推。这种方式下,每一折训练集都在验证集之前,严格模拟了未来预测场景。
在Matlab中,可以自己写一个生成训练集和验证集索引的函数,也可以使用“timeseries”相关工具箱里的split。但更简单的是自己算索引,因为时间序列交叉验证逻辑并不复杂。我在BES优化中为了控制计算量,实际用的是“前70%训练,后30%验证”的单次验证,而不是完整K折。这样能大大加速BES搜索,但代价是验证结果的波动会大一些。
3. BES-XGBoost核心实现步骤
3.1 超参数编码与范围设定
XGBoost超参数很多,但实际优化时不需要全部交给BES。我通常把参数分成两组:第一组是树结构参数,包括learning_rate、max_depth、min_child_weight;第二组是正则化与采样参数,包括subsample、colsample_bytree、reg_lambda、reg_alpha;第三组是num_estimators(树的数量)。
BES是连续优化算法,所以每个个体是一个一维浮点数组。比如约定个体位置向量为:
matlab复制% 位置向量 [lr, max_depth, min_child_weight, subsample, colsample_bytree, reg_lambda, reg_alpha, num_estimators]
learning_rate范围设为[0.01, 0.3],max_depth需要取整,所以范围是[3, 10]连续值,最后用round处理。num_estimators范围可以设为[50, 300],也需要取整。reg_lambda和reg_alpha范围[0, 10],建议在优化时对这两个参数取log1p变换,否则搜索空间在0附近太密集、在大数附近太稀疏。
参数范围不是越大越好。如果范围太宽,BES的搜索效率会下降;范围太窄,又可能漏掉最优解。我的经验是先做一轮粗搜索,观察BES最优个体的落点,然后再把最优个体附近的区域作为细搜索范围。
3.2 BES优化算法的Matlab代码框架
BES的Matlab实现主要包含位置初始化、适应度计算、三个阶段的更新。先看位置初始化:
matlab复制function [positions] = initializePositions(popSize, dim, lb, ub)
positions = rand(popSize, dim) .* (ub - lb) + lb;
end
然后是BES主循环,算法里最重要的是第二阶段的螺旋搜索位置更新。简化代码示意如下:
matlab复制for iter = 1:maxIter
for i = 1:popSize
% 第一阶段:选择搜索空间
if iter == 1
center = mean(positions, 1);
newPos = positions(i, :) + rand(1, dim) .* (center - positions(i, :));
else
% 第二阶段:螺旋搜索
theta = rand * pi;
r = theta + 0.5 * randn;
xr = r .* sin(theta);
yr = r .* cos(theta);
xrr = xr ./ max(abs(xr));
yrr = yr ./ max(abs(yr));
step = positions(i, :) - mean(positions, 1);
newPos = positions(i, :) + step .* xrr + step .* yrr;
end
% 越界处理
newPos = max(min(newPos, ub), lb);
% 计算适应度
newFitness = fitnessFunc(newPos);
if newFitness < fitness(i)
positions(i, :) = newPos;
fitness(i) = newFitness;
end
end
end
这里为了可读性省略了第三阶段的俯冲捕获更新,实际实现时还需要加入。需要注意,螺旋公式中theta和r的生成方式,不同论文略有差异,但核心思想都是让候选解以螺旋轨迹围绕当前种群中心搜索。我建议参考原论文的公式,不要凭记忆写,否则算法容易过早收敛。
3.3 适应度函数与K折交叉验证结合
BES的适应度函数决定了优化方向。在这个项目中,适应度值越小代表超参数越好,所以用交叉验证的平均误差(如RMSE)作为适应度。
适应度函数的输入是一个超参数向量,输出是一个标量误差值。整个流程是:解析超参数,训练XGBoost,交叉验证,取平均误差。在Matlab中,如果直接使用fitensemble做提升树,代码如下:
matlab复制function rmse = xgbFitness(params, Xtrain, ytrain, numFolds)
lr = params(1);
maxDepth = round(params(2));
minChild = round(params(3));
subsample = params(4);
colsample = params(5);
regLambda = params(6);
regAlpha = params(7);
numTrees = round(params(8));
% 使用fitensemble, LSBoost近似XGBoost
options = statset('UseParallel', true);
template = templateTree('MaxNumSplits', maxDepth, 'MinLeafSize', minChild);
mdl = fitensemble(Xtrain, ytrain, 'LSBoost', numTrees, template, ...
'LearnRate', lr, 'KFold', numFolds, 'Regularization', 'L2', ...
'Lambda', regLambda, 'Options', options);
rmse = kfoldLoss(mdl, 'Mode', 'average');
end
如果你使用的是真正的XGBoost MEX接口,调用方式会略有不同,但适应度函数的逻辑完全一样。这里用fitensemble做替代,是为了让只有纯Matlab环境的读者也能跑通流程。实际项目里,我最终用的是通过Matlab调用Python的xgboost包,因为fitensemble的LSBoost和原生XGBoost在正则化、采样细节上还是有差别,结果会有些出入。
3.4 优化流程的完整伪代码
整个BES-XGBoost优化流程可以概括为以下步骤:
- 读取原始多变量时间序列数据,进行缺失值处理、异常值剔除。
- 按时间顺序划分为训练段、验证段、测试段(常用70%、15%、15%)。
- 对训练段、验证段、测试段分别执行滑动窗口构造样本。
- 对特征和标签做归一化,保存训练段的Min和Max。
- 定义BES参数:种群大小popSize=20,最大迭代次数maxIter=30,位置维度dim=8。
- 随机初始化种群位置,对每个位置调用适应度函数,得到初始适应度。
- 进入BES迭代循环:
- 第一阶段:围绕当前最优位置修正搜索中心;
- 第二阶段:对每个个体做螺旋搜索;
- 第三阶段:俯冲捕获,在最优解附近精细搜索;
- 每轮计算适应度时,如果比历史更优则更新个体和全局最优。
- 迭代结束后,取全局最优位置对应的超参数,在测试集上训练最终模型并评估。
- 输出误差指标、特征重要性、超参数收敛曲线。
伪代码里的第7步是整个项目的核心。我建议在训练时加上“提前停止”的机制,例如在LSBoost中设置“EarlyStopping”参数,或者在自定义交叉验证中监控每一折的验证误差,连续多轮不下降则终止该折训练。这样能节省大量时间,同时也能侧面抑制过拟合。
4. 实验结果评估与过拟合抑制分析
4.1 预测效果评价指标
多变量时间序列预测的误差评估,我习惯同时看四个指标:RMSE、MAE、MAPE、R²。
表格如下:
| 指标 | 公式 | 适用场景 |
|---|---|---|
| RMSE | sqrt(mean((y_true - y_pred).²)) | 对大误差敏感,适合需要惩罚大偏差的场景 |
| MAE | mean(abs(y_true - y_pred)) | 对异常值更鲁棒,反映平均绝对偏差 |
| MAPE | mean(abs((y_true - y_pred) ./ y_true)) * 100 | 适合量纲归一化后的相对误差对比 |
| R² | 1 - sum((y_true - y_pred)²) / sum((y_true - mean(y_true))²) | 反映模型解释方差的比例,越接近1越好 |
在BES优化过程中,适应度函数使用RMSE比较合适,因为RMSE对异常值敏感,有助于找到在极端情况下也能保持较低误差的超参数。但最终汇报时,还需要看MAE和MAPE,避免单个指标掩盖问题。
我遇到过一个情况:RMSE很低,但MAPE很高,这说明模型在数值较大的样本上预测准,在数值较小的样本上偏差严重。如果业务上更关心小负荷时段的准确性,就需要把评估指标切换到MAPE,并在适应度函数里换成MAPE。
4.2 交叉验证折数的影响
在BES优化过程中,交叉验证折数直接决定适应度函数的计算量。我用同样一组数据分别做了K=3、5、10的对比实验。
K=3时,每折训练样本约占总训练集的2/3,验证结果波动大,BES容易找到在某一折上特别好的参数,但换到其他折效果不稳定。K=5时,验证结果相对平稳,计算量还能接受。K=10时,验证结果最稳,但同等迭代次数下,耗时会变成K=5的两倍左右。
我的建议是:初跑阶段用K=3甚至单次验证来快速确定参数的大致区间,等BES基本收敛后,再用K=10对候选参数做精确评估。这也是“粗调+精调”的思路,能有效平衡时间和质量。
交叉验证折数越大,模型训练的总次数就越多,每个超参数组合下需要训练K次模型。假设BES迭代30代、种群20个个体,K=5时,总共要训练3000次模型。如果每次训练需要5秒,那就是4个多小时,非常耗时。所以设置合理的迭代次数和种群大小非常关键。
4.3 BES优化与默认参数、网格搜索对比
为了验证BES-XGBoost的效果,我做了三组实验:第一组是XGBoost默认参数,第二组是手工网格搜索得到的最佳参数,第三组是BES优化参数。
结果摘要我用表格记录:
| 方法 | 验证集RMSE | 测试集RMSE | 验证与测试差 |
|---|---|---|---|
| 默认XGBoost | 0.1832 | 0.1968 | 0.0136 |
| 网格搜索 | 0.1577 | 0.1715 | 0.0138 |
| BES优化 | 0.1524 | 0.1586 | 0.0062 |
从数据可以看出,BES优化后的验证集RMSE最低,测试集RMSE也比默认参数明显下降,更重要的是验证集和测试集的差距从0.0136降到0.0062,说明过拟合被有效抑制。网格搜索虽然也有效,但耗时长且参数网格的粒度受限,BES可以在连续空间里找到更细致的参数组合。
当然,这个结果基于我的数据和训练设置,换成其他数据不一定完全一样,但趋势是有代表性的:BES由于在连续空间内搜索,往往能比网格搜索找到更好的超参数组合;配合交叉验证后,找到的参数泛化能力也更强。
4.4 收敛过程与稳定性分析
我每轮BES迭代都记录全局最优适应度,画出收敛曲线后能明显看到两个阶段:前5代适应度快速下降,说明BES在全局搜索阶段很快锁定了有希望的区域;之后下降速度趋缓,进入局部精细搜索阶段。大约到20代以后,曲线基本平缓,再增加迭代次数收益不大。
稳定性方面,BES的初始种群是随机生成的,所以每一次运行结果都可能略微不同。我连续跑了5次BES,每次得到的最优RMSE波动范围在0.003左右。如果波动太大,比如超过0.01,说明种群大小或迭代次数不够,或者是参数范围设置有问题。
解决稳定性问题的一个技巧是:把BES跑5次,从每个最优个体出发,在它附近构造一个小范围,再跑一次BES,取这5次中适应度最好的个体作为最终参数。这个方法在我项目里很有效,相当于用两次优化提升搜索精度,代价是计算时间增加。
5. 常见问题与排查技巧实录
5.1 适应度计算太慢,优化一整晚都跑不完
这是我遇到最多的问题。BES本身的循环很快,但适应度函数里每次都要训练几十棵树、做K折交叉验证,整体耗时极高。
排查方向有三个:第一,减少种群大小和迭代次数,比如种群从30降到20,迭代从50降到25,通常结果不会太差;第二,降低K折数,从10降到5,甚至先做单次验证;第三,关闭无关输出,少打印一些中间信息,尤其不要每轮都输出所有个体信息。
我真正把速度提上去,是用Matlab的Parallel Computing Toolbox把for循环改成parfor。对种群里每个个体进行适应度评估时,它们之间是相互独立的,非常适合并行。如果没有并行工具箱,还可以把数据预先生成好,避免每轮重复做滑动窗口。
5.2 早停轮数设置不当导致欠拟合
在XGBoost或LSBoost训练中设置早停时,如果earlyStoppingRound太小,比如设为5,模型可能还没充分学习就提前停了;如果太大,又起不到防止过拟合的作用。
我的经验是,对强噪声数据,早停轮数设小一些;对平稳信号,可以设大一些。另一个细节是,早停用的验证集必须独立于交叉验证的验证集,否则相当于在验证集上又做了一次拟合,会影响最终参数选择的公正性。
在BES优化里,我一般不在每次适应度评估中使用早停,而是在BES找到最终参数后,用测试集前的一部分数据做一次早停训练,确定最终要保留的树数量。这个做法的原因是:BES评估时如果每次都用早停,优化结果会过度依赖早停验证集的划分方式,引入不必要的随机性。
5.3 每次运行结果差异大,如何固定随机性
Matlab里随机数的生成受rng影响。BES初始化种群时用到了随机数,训练XGBoost时也会用到随机子采样。如果不固定随机种子,每次跑完结果可能不同。
最简单的办法是,在BES优化开始前调用:
matlab复制rng(42); % 固定随机种子,保证结果可复现
但要注意,只是固定一次还不够。如果你在循环内部还调用了rand,而这些rand会影响种群更新,最终结果依然与随机种子强相关。为了更稳健,建议固定两次随机种子:一次在BES初始化前,一次在最后测试集训练前。这样即使BES找到的参数略有波动,最终选择的模型也能在固定种子下复现。
另一个更可靠的方法是,将BES运行5次,每次都重置随机种子,记录5次的最优参数。最终选择适应度最小的那组参数。这样可以在不牺牲可复现性的前提下,减少运气成分。
5.4 Matlab调用XGBoost的几种姿势
很多人在Matlab里想用XGBoost,但找不到官方支持。实际上有以下几种常用方式:
第一种,使用Matlab自带fitensemble配合LSBoost。这是最省事的方案,虽然不是原生XGBoost,但算法思想和效果接近,适合学习流程和做原型验证。
第二种,通过Matlab调用Python的xgboost包。在Matlab中使用py.xgboost接口,把训练数据转换为Python可读的数组,然后在Python环境里完成训练和预测。这需要Matlab能够正确找到Python环境,并且Python里安装好xgboost库。优点是能用到原生XGBoost的完整功能,缺点是需要处理两种语言之间的数据传递。
第三种,使用第三方MEX封装的XGBoost。在GitHub上有多个Matlab接口项目,需要编译。编译过程在Windows和Linux上略有差异,但通常都能成功。如果是在2016版本之前的Matlab,编译可能遇到兼容性问题,建议直接使用fitensemble方式。
我自己的选择是第二种,因为它的灵活性和可维护性最好。如果你只是学习原理,第一种就够用了,没必要折腾环境。
5.5 数据泄漏与未来信息污染
数据泄漏是时间序列预测中非常隐蔽的问题。最常见的一种泄漏是:在构造滑动窗口特征时,不小心把未来时刻的值包含进了当前特征。
比如窗口长度为p,预测h步后的目标值,如果特征窗口的最后一个时间点与预测目标的时刻重叠,就相当于“提前看到了答案”。我初版代码里就犯过这个错误,训练集RMSE降到了0.01,测试集却高得离谱,后来检查窗口构造才发现问题。
另一种泄漏是归一化泄漏。很多人先对全部数据做Min-Max归一化,再划分训练集和测试集,这实际上让测试集的统计信息进入了训练过程,导致测试性能偏乐观。正确做法是先划分,再只基于训练集计算归一化参数。
第三种泄漏是交叉验证中的样本重叠。特别是在滚动预测滚动训练中,如果相邻折之间的训练样本有重叠,验证误差会被低估。解决办法是保证每折的训练集和验证集在时间上完全不重合,中间可以留出一个小间隔(gap),比如预测未来5步时,训练集最后一个时间点和验证集第一个时间点之间至少空出5步。
踩过几次坑之后,我的体会是:BES-XGBoost这套组合的真正门槛不在算法,而在数据组织的严谨性。超参数优化再厉害,也救不了有泄漏的数据。每次改动数据流程,我都会先做一次“单变量自回归基线”测试,如果复杂的模型连基线都跑不过,那很可能就是数据处理出了问题。最后再分享一个小技巧:把BES找到的最优超参数和最终测试误差记录下来,做成一个小表格,后续换数据集时能快速对比和微调范围,比自己重新摸索省力得多。
