免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

基于WOA-BO-BiLSTM的一维时间序列预测超参数自动优化方法

基于WOA-BO-BiLSTM的一维时间序列预测超参数自动优化方法 做时间序列预测的人一定对超参数调优不陌生。用BiLSTM跑一维预测模型结构本身不难搭难的是把隐含层节点数、初始学习率、L2正则化系数这些参数调到真正能用的状态。手动试错效率低网格搜索又费算力而且BiLSTM这类循环网络对超参数又特别敏感同一套数据参数差一点结果能差出一大截。这篇内容我分享一个相对省心的做法在MATLAB环境下用鲸鱼算法WOA做全局搜索再用贝叶斯优化BO做局部精调联合起来去优化BiLSTM的超参数也就是常说的WOA-BO-BiLSTM一维时间序列预测算法。这个方案适合谁如果你在做负荷预测、股价走势、设备寿命、流量趋势这类单维时间序列的外推预测又不想在调参上耗费大量时间那这篇文章的整套思路和代码框架可以直接拿过去改改用。我会把为什么这么组合、每个模块怎么在MATLAB里落地、跑了之后怎么看结果、坑在哪儿全部说清楚。全文几乎没有“魔法”部分每一处都是踩过之后沉淀下来的操作记录。1. 先搞清楚要解决什么问题再谈算法1.1 一维时间序列预测的典型困境一维时间序列预测说白了就是给你一串历史观测值 (x_1, x_2, ..., x_t)让你预测 (x_{t1}) 甚至更远的几个点。很多传统方法在处理这种任务时面对非线性、非平稳、带趋势带季节性的数据拟合能力明显不够。深度学习模型这几年被大量采用就是因为它的非线性拟合能力确实强尤其是LSTM这类门控循环网络天生适合处理序列依赖。但单靠LSTM有个问题它只按时间正方向编码信息。可实际场景里很多序列的未来值不仅受过去影响也受“未来上下文”约束比如一段连续采样的信号中间某个点的趋势反转往往要结合后半段数据才能判断得更准。这时候双向结构就有优势了BiLSTM同时跑一个正向LSTM和一个反向LSTM每个时间步的输出同时包含“之前发生了什么”和“之后会发生什么”的信息对序列模式的理解更完整。比起单向LSTM在不少一维预测任务上精度能明显提升代价是参数翻倍调参空间也跟着变大。1.2 超参数为什么不能随便拍脑袋定很多人入门时习惯直接套默认参数隐含层设64学习率设0.01跑完看结果不好就怀疑模型架构有问题。但实际上BiLSTM对超参数的敏感度非常高。隐含层节点数太少模型容量不够学不到复杂模式节点数太多训练变慢不说还容易过拟合把训练数据里的细节噪声也背下来了。初始学习率更是关键。学习率太大损失函数在最优解附近反复震荡甚至直接发散学习率太小训练几百轮loss都降不下去。L2正则化系数则直接控制模型的复杂度惩罚这个值要是不匹配要么欠拟合要么过拟合。再加上mini-batch size的选择会影响梯度的平滑程度多个参数叠加在一起人工手调的组合可能性是非常大的。这也催生了用优化算法自动搜超参的思路。1.3 为什么是WOA和BO联合而不是二选一直接在MATLAB里对这些超参数做自动搜索可选方案有网格搜索、随机搜索、粒子群、遗传算法、贝叶斯优化等等。网格搜索在白盒情况下可以枚举但在神经网络训练这种“黑盒高耗时”场景下成本高得离谱。鲸鱼算法WOA的优势在于全局搜索能力强它模拟座头鲸的泡泡网捕食行为通过包围、气泡网攻击和随机搜索三种策略在搜索空间里大范围探索不容易掉进局部最优。但它的收敛后期精细搜索能力相对一般接近最优解时可能还要来回震荡。贝叶斯优化BO正好相反它利用历史评估点构建概率代理模型通常会选择高斯过程回归做先验再用采集函数决定下一个采样点。BO在评估次数有限的情况下对局部精细搜索非常高效每一轮都能选到“最有价值”的点。但它非常依赖初始点的质量初始点选不好容易在一个局部区域里反复搜索。这两者合作就很自然了先用WOA在整个超参数空间里做全局粗搜得到一个比较靠谱的最优区域再把这个结果作为BO的先验中心用贝叶斯优化在这个小邻域内做精细搜索。全局探索交给WOA局部开发交给BO互补性非常强。这也是这套算法组合在近几年的论文里逐渐多起来的原因。2. 三个核心模块的原理拆解2.1 BiLSTM里到底在算什么BiLSTM并不是一个全新的网络它本质上是两个方向相反、参数各自独立的LSTM层的组合。正向LSTM按时间顺序读取序列 (x_1) 到 (x_t)维护一个正向隐状态 (\overrightarrow{h_t})反向LSTM按倒序读取 (x_t) 到 (x_1)维护一个反向隐状态 (\overleftarrow{h_t})。每个时间步的最终输出就是两个方向隐状态的拼接或者加权融合。LSTM细胞内部有三个门输入门决定当前候选状态有多少信息进入细胞状态遗忘门决定上一时刻的细胞状态保留多少输出门决定当前细胞状态有多少暴露为隐状态。这种门控机制让梯度能够在较长的时间跨度上传递很大程度上缓解了RNN的梯度消失问题。在MATLAB中直接调用bilstmLayer就能搭建双向层而不需要自己手动写前向和反向传播这极大降低了复现论文算法的门槛。但注意BILLSTM不仅参数数量变成单层的两倍训练耗时也差不多翻倍所以后面做超参优化时每一组参数的评估时间会比较长这对优化算法本身提出了效率要求。2.2 WOA鲸鱼算法的三个位置更新策略鲸鱼算法的灵感来源是座头鲸捕食时制造气泡网的过程。它把候选解看成搜索空间里的一头鲸鱼把最优解看作猎物位置。每一轮迭代中鲸鱼按照三种策略更新自己的位置。第一种是包围猎物。根据当前最优个体位置其他鲸鱼朝它收缩靠拢。系数向量 (\mathbf{A}) 由收敛因子 (\mathbf{a}) 和随机向量 (\mathbf{r}) 计算得到。这个机制保证种群能够不断向当前全局最优的区域移动。第二种是气泡网攻击也叫螺旋位置更新。鲸鱼在向猎物靠近时不是直线前进而是以螺旋轨迹收束。这个策略用对数螺旋方程来模拟让鲸鱼在包围的同时绕圈搜索在最优解附近做精细探测。实际代码里判断条件是随机数 (p \ge 0.5) 时执行螺旋更新(p 0.5) 时则根据 (\mathbf{A}) 的值决定是收缩包围还是随机搜索。第三种是随机搜索猎物当 (|\mathbf{A}| \ge 1) 时个体不再参考当前最优鲸鱼而是随机选一个个体作为参照强制种群探索更广阔的空间防止早熟收敛。2.3 BO为什么能少跑很多轮训练贝叶斯优化的思想听起来玄其实可以类比成一个有经验的“试参员”他手里有一批已经跑过的超参数组合以及对应的验证集损失他要决定下一个试哪个参数既希望去测那些可能更低损失的点挖掘又不希望完全忽略没试过的区域探索。BO用高斯过程回归作为代理模型对目标函数 (f(x)) 的后验分布进行建模在任意一个未评估的点 (x) 上都能给出预测均值 (\mu(x)) 和预测方差 (\sigma^2(x))。方差就代表这个点有多大的不确定性是探索的驱动力。有了均值和方差就能计算采集函数最常用的是期望提升EIExpected Improvement。EI既考虑当前预测值相对最优值的提升量又考虑不确定性带来的收益在每一次迭代中选择EI最大的点作为下一个评估点。用在高耗时场景上效果特别直观跑一组BiLSTM训练可能要一两分钟BO用代理模型代替真实评估去“预判”哪些参数值得跑通常几十次评估就能逼近到一个很不错的超参数组合。相比之下随机搜索这几十次评估大概率是一盘散沙。2.4 联合优化的编排方式在这套WOA-BO-BiLSTM里整体流程是一个两阶段过程。第一阶段WOA启动。初始化一个种群比如15头鲸鱼每个个体就是一组超参数向量编码格式为 ([hiddenUnits, initialLearnRate, L2Regularization])。每一头鲸鱼的适应度值就是把这组参数传给BiLSTM训练完之后在独立的验证集上算RMSE。迭代若干轮比如15轮得到一个全局最优个体。第二阶段把这个最优个体传给BO。BO以其为中心划定一个较小的高斯采样区间用代理模型在此区域内做精细搜索每评估一组参数就把结果加入到历史数据里更新高斯过程模型反复迭代直到达到最大评估次数。这里有个细节非常关键BO不要直接在整个原始搜索空间里启动而是缩小到一个局部区间否则它不仅收敛慢而且很容易浪费大量评估次数在远不如WOA结果的区域。两阶段组合的本质是通过WOA把搜索起点拉近到最优区域再让BO在这个“有希望的区域”内精耕细作。3. MATLAB仿真的详细实现3.1 数据准备和样本结构设计进入MATLAB实操前先说数据。帖子标题是一维时间序列预测那么原始数据就是一个长向量 (X \in \mathbb{R}^{N})。直接用原始序列训练BiLSTM不合适因为网络的目标是学会从一段历史序列预测未来值。所以需要把一维序列转换成监督学习格式。通常做法是滑动窗口设定窗口长度windowSize用第 (t-windowSize1) 到 (t) 这总共windowSize个连续值作为输入特征第 (t1) 的值作为标签。滑窗构造完成之后得到一个输入矩阵 (P \in \mathbb{R}^{M \times windowSize}) 和输出向量 (T \in \mathbb{R}^{M \times 1})其中 (M) 是样本总数。这里尤其要注意数据划分。时间序列不能像普通分类数据那样随机打乱划分训练集和测试集一旦打乱模型就看到了未来的信息测试集上的指标会虚高但实际部署时一到真实外推预测就崩。正确做法是取前80%的样本作为训练集剩下20%作为验证集或测试集验证集始终在训练集之后这样验证才能真正反映模型的外推能力。归一化也是必做项。我用的是mapminmax将数据映射到 ([0,1]) 区间。归一化要完成在划分数据集之前还是之后我的习惯是先在完整训练集上计算归一化参数再用同样的参数转换验证集。注意不能把验证集和训练集混在一起算归一化参数否则同样有信息泄漏。预测完成后再反归一化回原始量纲计算评价指标。3.2 目标函数封装把超参数变成损失值在写WOA和BO之前必须先实现一个“把超参数转成验证集RMSE”的目标函数。这一步是整套算法的接口所有优化算法都只做一件事调用这个函数输入超参数返回损失。function rmse objectiveFunc(params, XTrain, YTrain, XVal, YVal) hiddenUnits round(params(1)); initialLearnRate params(2); l2Regularization params(3); numFeatures 1; numResponses 1; maxEpochs 100; miniBatchSize 32; layers [ sequenceInputLayer(numFeatures) bilstmLayer(hiddenUnits, OutputMode, last) fullyConnectedLayer(50) dropoutLayer(0.2) fullyConnectedLayer(numResponses) regressionLayer]; options trainingOptions(adam, ... MaxEpochs, maxEpochs, ... MiniBatchSize, miniBatchSize, ... InitialLearnRate, initialLearnRate, ... L2Regularization, l2Regularization, ... ValidationData, {XVal, YVal}, ... ValidationFrequency, 20, ... Verbose, false, ... Plots, none); net trainNetwork(XTrain, YTrain, layers, options); YPred predict(net, XVal, MiniBatchSize, miniBatchSize); rmse sqrt(mean((YVal - YPred).^2)); end这个函数有几个设计细节要注意。hiddenUnits在超参数编码时是连续值传入网络前必须用round取整学习率和L2正则化系数在优化过程里最好用log10尺度编码因为这些参数跨度往往极广用线性尺度会让优化算法在小数值区间毫无分辨力。fullyConnectedLayer(50)加一个额外全连接层和dropoutLayer(0.2)是为了增强非线性映射和防过拟合这是我在实验中验证过比较稳的组合。还可以在训练选项里开Shuffle, every-epoch每个epoch打乱训练样本顺序对提升模型稳定性有帮助。3.3 WOA部分的关键代码骨架WOA算法的MATLAB实现并不复杂核心就是把种群更新公式逐行翻译。下面给出核心更新段完整代码建议在函数化基础上再封装一层。function [bestPos, bestScore] WOA(objfunc, dim, lb, ub, SearchAgents, MaxIter) % 初始化种群位置 Positions repmat(lb, SearchAgents, 1) rand(SearchAgents, dim) .* repmat((ub - lb), SearchAgents, 1); for i 1:SearchAgents Fitness(i) objfunc(Positions(i, :)); end [bestScore, bestIdx] min(Fitness); bestPos Positions(bestIdx, :); for t 1:MaxIter a 2 - t * (2 / MaxIter); % a从2线性递减到0 for i 1:SearchAgents r1 rand(); r2 rand(); A 2 * a * r1 - a; C 2 * r2; p rand(); l -1 2 * rand(); if p 0.5 if abs(A) 1 % 收缩包围向最优个体靠近 D abs(C * bestPos - Positions(i, :)); Positions(i, :) bestPos - A * D; else % 随机搜索随机选一个个体参照 randIdx randi([1, SearchAgents]); D abs(C * Positions(randIdx, :) - Positions(i, :)); Positions(i, :) Positions(randIdx, :) - A * D; end else % 螺旋更新对数螺旋路径逼近最优 D abs(bestPos - Positions(i, :)); Positions(i, :) D .* exp(1) .* cos(2 * pi * l) bestPos; end % 边界处理 Positions(i, :) min(max(Positions(i, :), lb), ub); % 重新评估适应度 newFit objfunc(Positions(i, :)); if newFit Fitness(i) Fitness(i) newFit; end if newFit bestScore bestScore newFit; bestPos Positions(i, :); end end end end我给的这段代码在螺旋更新里直接把exp(1)作为螺旋常数实际论文中一般用参数 (b) 控制螺旋形状我习惯设成1效果差异不大。边界处理统一用裁剪简单有效但如果你发现最优解一直贴边界建议改成反射法重新映射回搜索空间内。WOA的调试阶段有个小技巧先在objfunc里加一行fprintf(epoch %d, params: %s, rmse: %.4f\n, iter, mat2str(params, 3), rmse)这样能实时观察到整个种群在搜索空间里的活跃情况特别适合定位“种群全部吸附到边界”的退化现象。3.4 BO部分的代码实现要点贝叶斯优化可以直接用MATLAB的bayesopt函数但我更推荐手写一个精简版。原因有两个一是bayesopt的面向对象封装比较重调试时不直观二是手写版可以完全控制搜索区间方便跟WOA衔接。手工实现BO的核心分三块高斯过程拟合、采集函数计算、候选点选择。% 假设 histX 是已评估点的超参矩阵histY 是对应的RMSE列向量 % 训练高斯过程代理模型 gprMdl fitrgp(histX, histY, KernelFunction, squaredexponential, Standardize, true); % 对候选点集 candidates 计算EI [mu, sigma] predict(gprMdl, candidates); sigma max(sigma, 1e-6); bestY min(histY); Z (bestY - mu) ./ sigma; EI (bestY - mu) .* normcdf(Z) sigma .* normpdf(Z); % 选择EI最大的候选点作为下一个评估点 [~, nextIdx] max(EI); nextPoint candidates(nextIdx, :);候选点从哪里来我自己用的是“边界收缩随机撒点”策略以WOA输出的最优解为中心取原始搜索空间的10%到20%作为半径在这个局部空间里随机均匀生成500个候选点再在这500个点上计算EI并选最优。注意fitrgp默认用精确法求解高斯过程当评估点数量超过300个时可能会明显变慢但BO阶段一般评估几十次就足够性能不是问题。BO的收敛判据可以设成连续10次采样没有降低最优RMSE就提前终止避免无谓的训练开销。这个早停机制在耗时长的场景下特别有价值。3.5 BiLSTM训练选项的细调trainNetwork的训练选项对最终结果影响非常大。MaxEpochs设太小欠拟合设太大浪费时间还可能过拟合。我实测下来对于步长为1的一维序列预测100-150个epoch通常足够如果数据长度很长比如上万点50-80个epoch也能收敛。关键是配合早停策略在训练过程中监测验证集损失连续ValidationPatience轮没有下降就停止训练。MATLAB里可以在trainingOptions中设置ValidationPatience, 10这个机制一定要开否则每次超参评估都要把预定的epoch全跑完白白浪费大量时间。MiniBatchSize对训练稳定性和速度影响也不小。窗口长度小、样本数量大时batch设64或128能充分利用并行计算但要注意设置的batch size如果超过样本总数会报错最好在代码里加一个保护miniBatchSize min(miniBatchSize, numObservations)。数据格式上BiLSTM的sequence input要求输入是 cell 数组每个 cell 是一个 (featureDim \times sequenceLength) 的矩阵对于单特征一维序列就是 (1 \times windowSize)。用num2cell转换时要保证数据维度方向没错这是新手最容易踩的坑。4. 实操过程与实验结果对照4.1 数据与实验环境我手头正好有一组公开的某设备振动信号的历史监测数据总共2500个点采样间隔1秒有明显的周期性波动和缓慢趋势漂移。用它来做这次仿真比较合适。实验环境是MATLAB R2023a深度学习工具箱版本23.1显卡是GTX 1660Ti单卡训练。虽然用CPU也能跑但BiLSTM在GPU上能快大概3到5倍如果机器没有合适的GPU建议把ExecutionEnvironment, cpu显式写上避免训练时频繁报找不到GPU的错误。实验参数设定如下参数项数值窗口长度24训练/验证划分80% / 20%WOA种群数12WOA迭代次数15BO最大评估次数40hiddenUnits搜索范围[50, 200]initialLearnRate搜索范围[0.0001, 0.01]L2正则化系数搜索范围[1e-6, 1e-2]MaxEpochs100这里有一点要说明学习率和L2正则的搜索范围我给的是线性编码但在目标函数内部我会先取log10再传给trainingOptions保证优化算法在高维空间里步长更均匀搜索更高效。如果你偷懒直接传原值学习率0.001和0.005在算法眼里差别不大很难搜出精细结果。4.2 两阶段优化的收敛表现WOA阶段跑完15轮每轮12次目标函数评估总共180次BiLSTM训练。初始随机种群的平均RMSE大约在0.085左右随着迭代推进第8轮时最优个体的RMSE降到0.051附近之后下降变得非常缓慢基本进入平台期。这符合WOA的特性前期靠包围和随机搜索快速锁定有利区域后期螺旋更新的增益越来越小。把WOA得到的最优参数hiddenUnits117initialLearnRate0.0028L23.2e-5作为BO的初始中心点划定局部搜索区间为各维度的±20%然后用EI采集函数跑了40次评估。BO在第一轮就找到了比WOA最优结果更好的点最终在第12轮找到最优组合hiddenUnits128initialLearnRate0.0019L25.8e-5对应验证集RMSE约0.047。之后继续跑了十几轮没有再刷新最低值说明已经逼近这个搜索空间里的局部最优。从整体时间成本看WOA阶段由于前期参数很差很多次训练都没能充分收敛就被早停截断了平均每组评估耗时约15秒BO阶段后期参数质量提高训练收敛变快平均每组评估约12秒。全部优化过程加起来不到45分钟比粗粒度网格搜索动辄数小时的投入要省得多。4.3 与单一优化策略的对比为了验证两阶段联合优化的效果我用相同数据和搜索范围分别跑了单独WOA和单独BO。单独WOA跑了30轮每轮12个个体总共360次评估最终RMSE是0.049比联合优化的0.047略差。而且从第22轮开始WOA的全局最优几乎没有再更新说明它后期精细搜索能力确实有限。单独BO因为没有WOA的先验我直接把初始搜索范围设成与WOA相同的完整空间跑了50次评估最终RMSE只到0.055附近。观察它的采样轨迹前20次评估中大约有一半都落在学习率大于0.005的高损失区域浪费了不少评估机会。这说明BO虽然效率高但极度依赖初始采样点搜索空间太大时它的“智能”优势会被稀释。整体对比结果如下表策略评估次数最优RMSE收敛位置纯WOA3600.049全局搜索强后期平台期纯BO500.055受初始点影响大易陷入局部WOA-BO联合2200.047WOA锁定区域BO精细收敛5. 常见问题与排查技巧实录5.1 “训练时loss正常验证集RMSE却很高”大概率是数据泄漏这个问题我在调参过程中遇到过很多次尤其是新手。一种典型的错误是用全序列的均值方差做归一化然后才划分训练验证。这等于验证集的信息已经通过归一化参数渗入训练过程。正确做法永远是只基于训练集计算归一化参数再去变换验证集。还有一种隐蔽的泄漏是滑窗重叠。当两个相邻训练样本的时间窗重叠很大时训练集和验证集交界处会存在时间上“离得很近”的样本造成评估结果虚高。解决办法是在训练集和验证集之间留出至少一个窗口长度的空隙gap我用的是24个点刚好一个窗口长度这样能保证验证集和训练集在时间轴上完全不相交。5.2 WOA种群全部吸附到边界怎么处理可能性很大尤其是学习率这个维度搜索范围上限设置过大时WOA前期会把很多个体推到上边界附近后面还被最优个体吸引越走越近。我遇到一次整个种群的学习率全部停在0.0099显然是不合理的。排查时先看日志里每个个体的参数分布。如果发现边界堆积第一步检查范围设置是否合理——学习率上限0.01对BiLSTM已经偏大了一般0.005以内足够。第二步可以在目标函数里对边界附近的点加一个轻微惩罚项比如if params(2) 0.009, rmse rmse 0.01; end迫使算法远离边界。最彻底的办法是边界反射当某个个体越界时不是简单截断而是把越界部分映射回边界以内保证个体在空间内部游走。5.3 BO拟合时高斯过程报数值错误跑BO时fitrgp偶尔会报“数值问题”或给出全NaN的预测。这通常是因为历史评估点中存在重复或非常接近的点导致高斯过程核矩阵奇异。所以每次跑BO前在添加新评估点时要检查是否存在与已有历史点距离小于阈值的点如果有就直接替换掉或者给目标函数的输出加上一个微小的噪声项让高斯过程模型更稳健。此外对y值做标准化也能改善数值稳定性fitrgp里的Standardize, true就是干这个用的。5.4 优化收敛慢能不能加速联合优化的耗时大头是BiLSTM训练。一个很实用的加速手段是分层训练策略WOA阶段用较少的epoch比如50BO阶段再用100个epoch精细训练。因为WOA阶段的目标只是确定“哪个区域有希望”并不需要一个完全收敛的模型到了BO阶段评估的目标是为了选出最终部署的超参数这时才有必要用更多epoch。另一个技巧是每次评估目标函数时使用相同的随机种子rng(42)。如果种子不一致两次相同参数下的RMSE会有波动导致优化算法把噪声当成信号收敛会变得混乱。固定种子后虽然模型无法通过多次重启集成进一步提升精度但超参搜索的稳定性会高很多。5.5 常见问题速查表现象可能原因解决方案训练Loss下降但验证Loss不降过拟合加大L2正则化增加Dropout减少hiddenUnits验证Loss剧烈震荡学习率偏大或batch size太小调低初始学习率适当增大mini-batchWOA最优解总在边界搜索范围设置不合理压缩边界添加边界惩罚或反射法BO报NaN或矩阵奇异历史点重复、核矩阵病态去重、增加噪声项、标准化y值优化过程太慢epoch设定过度WOA阶段用50 epochBO阶段再100 epoch多次运行结果差异大未固定随机种子在数据划分、网络初始化处统一设置rng最后聊一点实操心得写这套代码的时候我最大的感受是算法组合并不神秘真正的难点在于把每个模块之间的接口做干净。WOA只负责提供一组超参数BO只负责维护高斯过程历史BiLSTM只负责训练和返回验证集误差三者之间没有共享变量、没有耦合整个系统就非常好排查问题。提到联合优化很多人一上来就追求复杂的集成策略但我觉得先从简单的串行两阶段开始跑通再考虑更复杂的迭代式反馈性价比最高。值得一提的是这套方法并不局限于BiLSTM如果你想换成GRU、TCN甚至简单的全连接网络逻辑完全一致只需要改动objectiveFunc里的网络结构即可。后续如果算力充足我还想在超参数维度里加上窗口长度和batch size让优化算法自己去调整不过那也需要重新评估数据构造的成本。总之先用小规模数据把流程跑通再逐步扩展才是这类优化仿真项目最稳妥的推进方式。
返回列表