资讯动态

MATLAB双向LSTM物料需求预测:从数据预处理到结果解读

发布时间:2026/10/21 14:08:21 来源:尧图企业网站定制
前阵子手头接了一个物料需求预测的活数据拿到手里第一反应是想用ARIMA这类统计模型先兜底。但看了几轮历史曲线发现需求量受季节性促销、库存策略、甚至随机备件波动影响非线性特征特别明显传统模型跑出来的残差始终压不下去。最后在MATLAB里搭了一个双向LSTM网络来接手这个任务训练集、测试集的结果都做了逐项输出包括对比曲线、误差曲线和量化指标。这篇就完整记录一下整个实现过程从数据预处理、网络搭建、参数配置到结果解读把踩过的坑也一并写出来给同样在做需求预测的读者一个能直接参考的样例。1. 需求预测问题与方案选型1.1 传统统计模型在需求预测上的局限需求预测本质上是时序回归问题。手里有一段时间的历史需求量目标是预测未来某一周或者某个月的需求。传统做法分几派但都有各自的短板。移动平均和指数平滑这类方法简单可靠可是对趋势和季节性的适应能力很弱遇到突发波动基本只能跟在真实曲线后面追。ARIMA和SARIMA处理线性和周期性数据效果好但对长序列依赖、多因素非线性耦合就力不从心。关键是ARIMA需要数据平稳性做差分、定阶、白噪声检验一套流程下来调参成本并不低而且不是所有需求数据都能通过差分变成平稳序列。机器学习里的随机森林、XGBoost可以拼特征但需求数据是时间序列如果只用静态特征模型很难感知“顺序”和“上下文”而LSTM这类循环网络天生就是为序列设计的。我这次做的项目历史数据是某制造企业仓库A类物料的周需求量一共108周。看曲线明显带有周期性而且某些周会出现两三倍的突发脉冲。这种数据用传统方法非常痛苦预测结果会被脉冲拉偏或者干脆滞后一拍。这时候就值得上LSTM了。1.2 为什么用双向LSTMLSTM解决了基础RNN的梯度消失问题通过输入门、遗忘门、输出门和细胞状态把有用信息长期保留把无关信息丢弃。在需求预测里表现不错但单向LSTM有一个特征信息只能从过去流向未来。双向LSTM在结构上等于把两个独立的LSTM叠在一个序列上一个按正序读序列一个按逆序读序列每个时刻的输出由两个方向的隐藏状态拼接而成。这样在训练阶段模型在每个时间步不仅能利用该时刻之前的信息也能利用该时刻之后的整体序列形态。有些读者会疑惑预测未来需求用未来信息不是作弊吗这里要澄清一下。我们在构造训练样本时每个样本的输入都是一个固定的历史窗口比如过去4周的需求量。双向LSTM是在这个窗口内部做正序和逆序的信息融合推理阶段两个方向的输入都来自同一个已知窗口并不会跨到窗口以外的未来。真正的好处是反向分支相当于给模型提供了“从结果倒推成因”的视角对周期性、上升段和回落段的模式识别有一定帮助。我在开发时同时跑了一个单向LSTM作为对照。同样的数据和超参数双向LSTM在测试集上的MAPE比单向低约5个百分点而且在脉冲周附近的预测偏差明显更小。用MATLAB实现时一条bilstmLayer指令就够了底层会自动管理正向层、反向层和拼接操作不需要自己处理双向逻辑。1.3 MATLAB在时序预测里的独特优势有一说一单论深度学习生态MATLAB肯定不如Python丰富。但在需求预测这种偏工程落地、快速验证的场景MATLAB深度学习工具箱有几点非常省心。数据处理和画图一体化plot、hold on这些指令随时可以查结果。trainNetwork封装度高训练选项用trainingOptions配置几行就能跑起来不需要自己写反向传播。调试也直观维度错误、NaN损失这类问题MATLAB的报错信息相对友好定位速度快。对工程人员来说也不需要额外配置Python环境、装CUDA和一堆依赖库只要装了深度学习工具箱直接就能跑GPU。这个项目的定位就是在MATLAB环境中验证双向LSTM对需求数据的建模能力把训练集拟合效果、训练集误差、测试集预测效果完整输出形成一个可复查、可复现的预测流程。2. 数据准备与样本构造2.1 原始数据的形式与导入数据是从某企业ERP系统导出的Excel表格字段只有两个周序号、需求量。第一列是时间第二列是销量或领用量。实际项目里字段名五花八门导入之前一定要先确认时间粒度、缺失值和异常值。我在MATLAB里用的是% 读取Excel数据 dataTable readtable(demand_record.xlsx); weeklyDemand dataTable.DemandQty; % 列名按实际修改拿到数据后先做可视化看看有没有明显缺口、异常尖峰。这一步不能省直接拿原始数据喂网络尖峰和缺失值会一起被当成规律学进去。处理原则可以简单记一下缺失值对需求数据用前后周均值填充比插值更稳能避免引入人为波动。异常尖峰如果确认是记录错误比如单周数量超过均值十倍又很快回落可以修正。如果尖峰是真实业务波动就保留并在后面评估指标时留意它对RMSE的拉高。2.2 归一化必须做但要注意泄漏LSTM类网络对输入尺度非常敏感。需求量小到几十、大到几千直接输入会让激活函数饱和训练不稳。归一化最常用Z-scoremu mean(weeklyDemand); sigma std(weeklyDemand); demandNorm (weeklyDemand - mu) / sigma;这里有一个特别关键的坑归一化的均值和标准差必须只用训练集计算。等训练完做测试时测试集数据要用同一组mu和sigma去归一化不能把测试集也掺进来重新算。否则等于训练时偷看了测试集的信息测试误差会被低估部署时一上线真实误差就现原形。所以正确做法是先按时间顺序切出训练段和测试段再用训练段的均值方差计算mu和sigma。我用的是前75周作为训练集后33周作为测试集。2.3 滑窗构造输入输出样本序列预测的标准做法是滑动窗口。我设定lookback 4即用连续4周的需求量预测第5周的需求量。窗口太小模型学不到周期窗口太大样本数量减少而且可能引入过多无关历史。这里选4是从业务角度看的这个物料的订货周期大约是3到4周前4周的量能覆盖一张订货单的完整周期。构造训练集时以第i周到第i3周作为输入第i4周作为输出lookback 4; numTrain 75; XTrain {}; YTrain []; for i 1:(numTrain - lookback) XTrain{end1, 1} demandNorm(i : ilookback-1); YTrain(end1, 1) demandNorm(i lookback); end注意几个细节demandNorm是列向量转置成行向量后存入cell数组这是sequenceInputLayer要求的格式每个cell代表一个样本序列序列维度是1×lookback。YTrain是列向量因为OutputMode取last时网络输出的是每个样本的一个标量。循环要从1到numTrain - lookback保证最后一个训练样本的输入和输出都在训练段内不越界。测试集同理用第numTrain - lookback 1周开始的数据构造numTest 33; XTest {}; YTest []; for i 1:numTest idx numTrain - lookback i; XTest{end1, 1} demandNorm(idx : idxlookback-1); YTest(end1, 1) demandNorm(idx lookback); end这里idx的范围是从72到104输入窗口结束于第104周输出对应第105周到第108周。测试集预测结果反归一化后就能和真实需求量直接对比。3. 双向LSTM网络构建与参数配置3.1 网络结构逐层拆解在MATLAB里按顺序声明层次numFeatures 1; numResponses 1; numHiddenUnits 16; layers [ sequenceInputLayer(numFeatures) bilstmLayer(numHiddenUnits, OutputMode, last) fullyConnectedLayer(numResponses) regressionLayer ];逐层说明sequenceInputLayer(1)输入特征维度是1也就是只有“需求量”这一个特征。如果后面加入价格、促销标记等外生变量这里就改成对应特征数。bilstmLayer(16, OutputMode, last)16个隐藏单元的双向LSTM。因为OutputMode是last网络只在序列最后一步输出隐藏状态这个状态会送到全连接层转换成一个预测值。双向结构让这个最后的输出同时携带了正序和逆序的信息。fullyConnectedLayer(1)把隐藏状态映射到1维得到预测的需求量归一化后的。regressionLayer回归层计算预测值与真实值之间的损失同时指导梯度回传。这里有一个取舍OutputMode也可以选sequence每个时间步都输出。但我的任务是从4周窗口预测下1个值属于sequence-to-one用last更合适参数更少训练也更快。3.2 训练选项的关键参数trainingOptions里的参数直接影响训练效果我用的是options trainingOptions(adam, ... MaxEpochs, 200, ... MiniBatchSize, 16, ... InitialLearnRate, 0.01, ... GradientThreshold, 1, ... Shuffle, every-epoch, ... Verbose, 1, ... Plots, training-progress);逐个说我的理解solver选adam自适应矩估计适合需求数据这种小样本、非平稳信号收敛稳定对初始学习率的敏感度低于sgdm。如果数据噪声特别大选sgdm配合合适动量有时更稳但需要多试几组学习率。MaxEpochs200训练轮数。需求量样本不算多200轮足够再多容易过拟合。如果损失曲线还有明显下降趋势可以加大到500但要配合验证集监控。MiniBatchSize16每次迭代用16个样本。样本总量71batch太大则每轮迭代次数太少batch太小则梯度噪声大。16在这个规模下是比较平衡的取值。InitialLearnRate0.01学习率。双向LSTM参数比单向多一倍太大会震荡太小训练慢。0.01对这个数据量级是安全的也可以试0.005。GradientThreshold1梯度裁剪阈值。LSTM训练最怕梯度爆炸尤其需求数据里突然出现的尖峰样本。设1梯度范数超过1就缩放回1训练稳定很多。Shuffleevery-epoch每个epoch打乱样本顺序避免模型记住样本的固定顺序。Verbose1配合Plotstraining-progress可以实时看到损失曲线和训练进度。3.3 隐藏单元数和层数的经验值隐藏单元数量是双向LSTM最重要的超参数之一。太少模型容量不足拟合不了需求的周期模式太多容易过拟合训练时间也成倍增加。我实验过8、16、32三组8个单元损失下降慢测试集MAPE接近50%明显欠拟合。16个单元训练集和测试集误差都比较均衡MAPE降到20%上下。32个单元训练集误差继续下降但测试集MAPE没有改善反而略有上升开始出现过拟合苗头。16对这个样本量是比较合适的值。对于更大规模的数据集可以按数据长度估算比如1000个样本用64到128个单元再结合验证集监控。层数方面我强烈建议先用单层。需求预测属于中等复杂度的时序回归单层双向LSTM已经包含了正向、反向两条信息通路容量不小。堆两层双向LSTM只会让参数量翻倍数据量不够就会陷入过拟合。如果后面数据量上来了再尝试每层减小隐藏单元数、增加层数的组合。另外可以在bilstmLayer后面接一个dropoutLayer(0.2)来抑制过拟合。我这里没有加因为测试集误差和训练集误差差距还不大加了dropout反而可能让测试集误差上升。判断依据是如果训练集MAPE明显低于测试集MAPE超过一倍就要考虑加dropout或减小隐藏单元数。4. 训练过程与结果输出实现4.1 训练过程的观测要点训练开始后MATLAB弹出的training-progress窗口会显示损失曲线。我看曲线的习惯是关注三件事刚开始几轮损失是否快速下降如果前20轮损失纹丝不动多半是学习率太小或梯度被裁剪得过于激进。中后期是否震荡损失在某个水平反复跳动说明学习率偏大或batch太小。最终收敛水平归一化后数据的MSE降到0.1左右就已经是很不错的表现换算回原始需求量的均方根误差才能解释业务意义。训练代码net trainNetwork(XTrain, YTrain, layers, options);训练结束后模型保存在net里。后面所有预测都用predict函数而不是再走一遍训练。4.2 训练集结果与训练集误差训练集上的结果目的是验证模型有没有学到东西而不是衡量泛化能力。用训练好的网络对训练样本做预测predTrainNorm predict(net, XTrain, MiniBatchSize, 16); predTrain predTrainNorm * sigma mu; actualTrain YTrain * sigma mu;predict返回的是归一化值要还原成原始需求量再对比。反归一化这一步是最容易漏的漏掉后对比图上的量级完全对不上影响对模型的判断。画出训练集对比曲线figure; plot(actualTrain, b-, LineWidth, 1.5); hold on; plot(predTrain, r--, LineWidth, 1.5); legend(真实需求量, 训练集预测值); xlabel(样本序号); ylabel(需求量); title(训练集拟合结果对比); grid on;训练集误差可以直接用残差和相对误差描述trainError actualTrain - predTrain; figure; plot(trainError, k-, LineWidth, 1); xlabel(样本序号); ylabel(预测误差); title(训练集误差曲线); grid on;如果训练集上误差普遍很小而测试集误差很大那就是典型的过拟合。如果训练集本身误差就很大说明模型容量不够或没训练到位需要回溯超参数。4.3 测试集结果与量化指标测试集是真正检验模型泛化能力的地方。测试集的数据在训练阶段完全没出现过所以测试集误差能够代表模型在真实新数据上的表现。predTestNorm predict(net, XTest, MiniBatchSize, 16); predTest predTestNorm * sigma mu; actualTest YTest * sigma mu; figure; plot(actualTest, b-, LineWidth, 1.5); hold on; plot(predTest, r--, LineWidth, 1.5); legend(真实需求量, 测试集预测值); xlabel(样本序号); ylabel(需求量); title(测试集预测结果对比); grid on;量化指标我计算三个rmse sqrt(mean((actualTest - predTest).^2)); mae mean(abs(actualTest - predTest)); mape 100 * mean(abs((actualTest - predTest) ./ actualTest));三个指标各有侧重RMSE对较大误差更敏感能暴露个别周预测严重偏离的问题。实测RMSE大约在90件左右而需求均值在400件上下相对可控。MAE反映平均绝对偏差不会因为个别尖峰被拉得太高。MAPE是百分比误差适合向业务方汇报直观说明预测偏差大约是百分之多少。这个项目里测试集MAPE在20%左右对于存在脉冲波动的A类物料业务上已经可以接受但还有优化空间。测试集误差曲线与误差分布直方图也可以一并输出testError actualTest - predTest; figure; histogram(testError, 10); xlabel(预测误差); ylabel(频数); title(测试集误差分布);为了向评审或业务方交付我通常把输出整理成一张指标表指标数值范围含义RMSE约90件平均平方误差的根号放大较大偏差的权重MAE约65件平均绝对偏差直观反映平均差多少MAPE约20%平均百分比误差业务汇报常用4.4 结果输出清单整理交付评审时我习惯把输出整合成四类训练集拟合对比图与误差曲线证明模型的学习能力。测试集预测对比图与误差曲线证明模型的部署能力。量化指标表RMSE、MAE、MAPE附上需求均值和样本数方便对照。模型文件保存训练好的net后续做月度数据更新时可以增量再训练或直接调用。模型保存很简单save(bilstm_demand_model.mat, net, mu, sigma, lookback);注意把归一化参数和窗口长度一并保存。实际使用时模型加载回来如果没有配套的mu和sigma输入数据就无法正确归一化这是部署时最常见的隐患。5. 常见问题与排查技巧实录5.1 cell数组维度不匹配构造XTrain时最容易出现维度问题的是cell里的每个数组形状不对。sequenceInputLayer要求每个cell是“特征维度 × 序列长度”的矩阵。这里的行向量本质上是1 × lookback符合要求。如果报错说输入尺寸不对第一件事就是检查size(XTrain{1}) % 期望是 [1, 4] size(YTrain) % 期望是 [71, 1]然后看是不是混入了尺寸不一致的cell尤其循环边界写错时会出现一个cell是4维、另一个是3维那代码必然报错。5.2 归一化泄漏这真的是高频事故。新手常常把整个数据集一次性zscore再划分训练和测试。表面看没问题测试误差也确实低但那是假象。因为测试集的均值和标准差已经参与过全局归一化相当于模型在训练时通过统计量间接看到了测试集的分布。我的检查习惯训练完成后随便挑一个测试样本手动用训练集的mu和sigma归一化然后喂给模型对比结果是否与训练程序输出一致。如果不一致说明归一化流程有待清洗。5.3 预测结果像“平移复制”单步预测经常出现一个现象预测曲线和真实曲线形状高度一致但整体滞后了一到两周看起来像把上涨和下跌都推迟了。这是自回归模型没有捕捉到突变信号时的典型表现本质上是模型把上一周期的需求当作最大的预测因子一旦遇到脉冲只能等脉冲进入了历史窗口才能反应过来。缓解手段可以从三方面入手增加外生特征加入是否促销、是否有备件领用、有无库存告急等标记让模型提前得到触发信号。改用一步滚动预测并实时更新状态用predictAndUpdateState把每一步预测回填到网络中能稍微改善对近期趋势的跟随能力。增加窗口长度让模型在更长的上下文里看到类似的脉冲历史有助于建立“脉冲后的回落模式”。5.4 训练很慢或GPU内存不足双向LSTM的参数和计算量大约是单向的两倍训练到后面几轮如果明显变慢优先检查是否真的在用GPU。MATLAB可以在trainingOptions里通过ExecutionEnvironment,auto自动选择或者用multi-gpu。如果显存不够把MiniBatchSize减半比如从16降到8样本总量不大的情况下影响很小。再不行把MaxEpochs适当减小并配合GradientThreshold控制稳定性而不是一味增加迭代次数。5.5 损失曲线出现NaN训练过程中如果损失变成NaN常见原因有三个学习率太大、数据里有NaN值、双向LSTM的梯度爆炸。排查顺序是先检查数据里有没有NaN再降低学习率到0.001试一次最后确认梯度阈值是否设置。这三个排查一圈绝大多数NaN问题都能解决。到这里基于MATLAB的双向LSTM需求预测项目就算完整落地了。说实话这个模型结构不算复杂真正的工程难点反而在数据清洗、归一化边界、时间窗构造这些容易被忽略的细节上。我个人的体会是做需求预测不要一开始就追求复杂的网络结构先把单层双向LSTM在一个干净的数据流程上跑通拿到训练集误差和测试集误差的完整输出再考虑加特征、加注意力机制或者换成多步滚动预测这样每一步的改动效果都可以被量化比较不会出现改了一堆配置却不知道问题出在哪里的被动局面。希望这份记录能帮你少走一些弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑