做时序预测的人,应该都经历过一个纠结:单变量序列到底该用LSTM、Transformer,还是干脆堆个混合模型?我今年在Matlab里把DE(差分进化)和Transformer、BiLSTM三个东西揉到了一起,做了一个单输入单输出的单变量时序预测模型,实测下来比单独用LSTM或者单独用Transformer都要稳。这篇就把整个实现思路、代码细节、调参过程,还有我踩过的坑完整梳理一遍,适合正在做毕业论文、时序预测相关课题,或者想给工业预测场景换一套更稳方案的朋友参考。
这个项目最大的特点是把DE算法当作“超参数自动搜索器”,去自动寻找Transformer和BiLSTM堆叠时的最优超参数组合。因为混合模型效果好归好,但参数一多,手动调参就成了噩梦。DE的好处是不需要梯度信息,实现简单,收敛快,在Matlab里几行就能写好,而且能并行计算,实测跑起来比网格搜索省太多时间。
1. 项目整体设计与思路拆解
1.1 单变量时序预测的基本任务定义
单变量时序预测,说白了就是只有一列数值,比如某台设备的温度、某个城市的日电量、某只股票的收盘价,要用过去一段时间的观测值去预测未来一个时刻或者多个时刻的值。常见的做法是“滑动窗口”,也就是把数据切成固定长度的输入段和输出段,喂给模型做有监督回归训练。
本项目是单输入单输出结构,输入是过去连续N个时刻的值,输出是未来一个时刻的预测值。比如用前24个小时的负荷数据,预测下一个小时的电负荷。这种设定在Matlab里数据组织非常直接:输入矩阵的每一行是一个窗口的样本,每一列是一个时间步的特征。因为特征维度是1,所以数据形状是“样本数×时间步数×1”,非常干净。
很多人在做单变量预测时容易犯一个错误,就是把输出也做成多步。实际上如果你的任务只需要下一步预测,做成多步反而会引入额外的累积误差。我建议刚开始做,先把单步预测做扎实,再考虑多步迭代或seq2seq结构。
1.2 为什么把Transformer和BiLSTM放在一起
LSTM这类循环网络处理序列的天生优势是能建模时间顺序依赖,尤其是BiLSTM,双向的结构能同时看到过去和未来的上下文信息。这对时序预测特别重要,因为某些模式可能只靠“之前的值”判断不出来,还需要结合“之后一段时间的走势”来确认当前时刻是否处于异常点或者拐点。
但BiLSTM的缺点是:它处理长序列时需要逐步传递信息,距离太远的信息容易衰减,训练速度也慢。Transformer的注意力机制则完全不同,它可以在任意两个位置之间直接建立依赖,路径长度是1。这意味着它能抓住非常远的历史信息对当前预测的影响,而且并行度高,训练速度快很多。
那为什么不直接只用Transformer?因为Transformer对局部时序模式(比如短期趋势、周期性波动的细微变化)的敏感度往往不如循环网络。它会倾向于关注全局的相似性,而对近期窗口内连续变化的“语序感”把握得不够细。把两者结合起来,相当于一边用注意力抓长程相关性,一边用BiLSTM抓局部时序特征,互补性很强。
我的设计是:输入先过一层位置编码和Transformer编码器,得到带全局上下文信息的特征序列,再送入BiLSTM做双向时序特征提取,最后拼接两者的特征输出,通过全连接层映射到预测值。这个结构在Matlab里实现并没有想象中那么复杂,关键是每一步输出的维度要对齐。
1.3 DE算法解决的是混合模型的“调参灾难”
Transformer和BiLSTM混合之后,需要确定的超参数包括:Transformer编码器的层数、注意力头数、隐藏维度、位置编码类型、BiLSTM的隐藏单元数、dropout比例、正则化系数、初始学习率、批大小等等。这些参数之间是相互影响的,不是一个个单独调就能调好。
比如把注意力头数从4改到8,能提升短期趋势捕捉能力,但可能同时造成过拟合,需要dropout配合调整。如果手工去试,一组参数训练一次可能就要几分钟,完整的参数空间至少有十几种组合,累死人的。
DE之所以适合做这件事,是因为它本质上是一个基于种群的进化优化算法,完全不需要目标函数的梯度信息。它的适应度函数可以是“某个超参数组合下,模型在验证集上的RMSE”。每次迭代,DE只评估几十组参数,然后通过变异、交叉、选择不断生成更优的候选解。实现起来非常简单,Matlab基础语法就能写完,不需要任何额外的优化工具箱。
我在实际项目中把DE的种群设置成20,迭代25轮,也就是最多跑500次模型训练。听起来很多,但在并行环境下,一次完整搜索通常几小时就能完成,比手动调参靠谱得多。后面会详细讲DE的参数设置和代码实现。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据预处理与实验配置
2.1 数据归一化必须只用训练集的统计量
时序预测里数据归一化几乎是必须的,尤其是当输入数据的量级差异大的时候。本项目用的单变量序列一般波动范围不固定,直接用原始数值训练会导致梯度不稳定,模型很难收敛。常见做法是把数据缩放到[0, 1]或者[-1, 1]区间。
这里有一个非常重要的细节:计算归一化用的min和max只能从训练集上获得,然后原样应用到验证集和测试集。很多人图省事,把全部数据一起做归一化再切分,这会造成数据泄漏。后果是验证集和测试集的信息在训练阶段就被模型“偷看”到了,实验结果虚高,放到真实预测场景立刻现原形。
Matlab里面用mapminmax函数做归一化很方便,但默认是行处理,记得先确认数据的方向。我一般习惯这样处理:
matlab复制% data是列向量,dataMin和dataMax只从训练集计算
dataMin = min(trainData);
dataMax = max(trainData);
trainNorm = (trainData - dataMin) / (dataMax - dataMin + eps);
testNor
