尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

基于Matlab的LSTM时间序列预测实战:从数据预处理到滚动预测

发布时间:2026/9/26 16:43:09

资讯中心
01
ARTICLE

基于Matlab的LSTM时间序列预测实战:从数据预处理到滚动预测

基于Matlab的LSTM时间序列预测实战:从数据预处理到滚动预测
1. 为什么在这个Python统治的领域我仍然选择用Matlab做LSTM预测先交代一下背景。最近接手了一个设备寿命预测的活儿甲方要求在一周内给出初步预测结果而且整个团队的技术栈锁定在Matlab上——传感器数据已经在Matlab里做完了信号预处理模型再切到Python意味着两套工具链、两套数据格式、两拨人来回对接。当时第一反应是LSTM 时间序列预测不是 Python 的天下吗但翻完 2023b 的文档之后发现Matlab 的 Deep Learning Toolbox 早就把 LSTM 的 API 封装得很成熟了从sequenceInputLayer到lstmLayer再到trainNetwork一条龙根本不需要自己手搭反向传播。很多人一提 LSTM 就默认用 Python 的 Keras 或 PyTorch这没错但如果你手里本来就是传感器、信号、控制类项目Matlab 的好处是数据流转成本极低。我在这次实践中最大的感受是预测这件事本身不难难的是和上下游数据管道的耦合。Matlab 里timeseries对象、timetable、fillmissing、normalize这些函数直接写进脚本信号去噪、异常值剔除、归一化一气呵成然后无缝丢给trainNetwork。换 Python 的话scipy.signal和sklearn.preprocessing也能做但每次跨环境导.mat文件、转 DataFrame、再转 NumPy 数组光是类型检查就能磨掉半天。这篇文章不是要论证 Matlab 比 Python 好而是把我这次用 Matlab 完整实现 LSTM 时间序列预测的代码、参数调优过程和踩过的坑全部摊开。适合谁看第一是工程师设备预测性维护、能耗预测、库存预测这类场景你们手里的历史和实时数据十有八九已经跑在 Matlab 里了第二是学生和研究人员需要快速出基线结果、画漂亮图、写论文的Matlab 的代码量比手写 PyTorch 循环小一个量级第三是那些被网上零星博客坑过的朋友我这次把训练收敛但预测跟着上一时刻走这种经典翻车现场也会一并复盘。先说结论整个流程就四步数据准备 → 网络定义 → 训练调参 → 滚动预测。每一步都有坑每一步我都会给出我实际测试过的参数和备用方案。整个项目跑通的代码在最后按模块拆开可以自己拼。2. 数据准备时间序列建模最容易翻车的环节LSTM 再厉害喂进去的是垃圾出来的一定是垃圾。而时间序列的数据准备和普通监督学习有个本质区别你不能随机打乱样本。我见过不少从图像识别转过来的人习惯用randperm把数据洗一遍再切训练集测试集这在 LSTM 里直接凉——时间顺序就是信息的载体打乱了等于把因果律撕碎了。2.1 原始数据检查与异常处理我这次用的是某个设备的关键监测参数采样间隔 1 分钟连续采集了 30 天总共 43200 个点。拿到数据的第一件事不是建模而是画图。plot一眼扫过去能看出三个问题偶尔有突变尖峰传感器瞬时干扰、中间有一段 3 小时的连续掉线NaN、以及数据整体量级跨了几个数量级最大值数千、最小值接近 0。处理顺序很重要先处理尖峰和数据缺失再做归一化。我在脚本里用了这么一段% 原始数据加载data 为列向量 raw load(sensor_data.mat).sensor; % 1) 用中位数滤波去掉孤立尖峰窗口大小选 7效果比较稳 filtered medfilt1(raw, 7, omitnan); % 2) 连续缺失段用 pchip 插值比 linear 更平滑 idx_nan isnan(filtered); t_all (1:length(filtered)); filtered(idx_nan) pchip(t_all(~idx_nan), filtered(~idx_nan), t_all(idx_nan));这里有个细节medfilt1处理完以后 NaN 邻域会被进一步污染所以插值要放在滤波之后而不是之前。窗口 7 对应大约 7 分钟的滑动窗口能滤掉单点尖峰但不会把真实趋势磨平。你要是数据采样率高或者场景更敏感可以试试窗口 3 到 15看滤波后曲线和原始趋势的重合度过度平滑会让 LSTM 学到钝化的规律预测结果永远慢半拍。2.2 归一化策略min-max 还是 z-scoreLSTM 内部用的是 tanh 和 sigmoid 激活输入范围太大会让梯度一开始就饱和。我做了对比实验同样的网络结构、同样的数据minmax归一化比zscore收敛快不少最终验证集 RMSE 也略低。原因不复杂设备传感器这类数据经常是偏态分布zscore会把大部分点压到正负 3 以内但尾部极值依旧很大而minmax把整个分布牢固地锁在 [0,1]。代价是对噪声敏感所以异常值必须先清干净——这就是为什么我把去尖峰放在归一化前面。% 2) min-max 归一化保存最大值最小值用于反归一化 data_min min(filtered); data_max max(filtered); data_norm (filtered - data_min) / (data_max - data_min);2.3 构造监督学习样本滑窗法详解LSTM 不是把一整条序列扔进去就能学会预测的你需要定义用过去多少步预测未来多少步。这里的设计直接影响模型上限。numSteps输入序列长度也就是回溯窗口。我试过 10、20、50。数据本身有约 2 小时准周期特性窗口 50对应 50 分钟效果明显好了因为它让网络至少看到 1 个完整周期以上的上下文窗口太大比如 200训练时间暴涨但精度提升有限。numFutureSteps预测步数单步预测时设为 1多步预测设为对应的步数。第一步先把单步预测跑通再往多步扩展。构造样本矩阵的经典写法numSteps 50; % 输入序列长度 numFuture 1; % 预测未来 1 步 n_total length(data_norm); n_samples n_total - numSteps - numFuture 1; % 预分配矩阵Matlab 里不要边循环边增长数组太慢 X zeros(numSteps, 1, n_samples); Y zeros(1, n_samples); for i 1:n_samples X(:, 1, i) data_norm(i : i numSteps - 1); Y(1, i) data_norm(i numSteps : i numSteps numFuture - 1); end注意X的维度Deep Learning Toolbox 中包含序列的单个观测是一个numFeatures × numTimeSteps的矩阵因此单个样本是50 × 1所有样本放在第三维上形成50 × 1 × N。你的数据如果是多维特征比如多个传感器就变成numFeatures × numSteps × N第一维放特征数。2.4 训练集测试集划分别把时间顺序切碎了我用前 80% 的样本做训练、后 20% 做测试切分点直接选在样本索引中间。这样测试集恰好是时间轴上最后 20% 的真实未来——这正是预测场景最关心的我没见过的未来表现如何。ratio 0.8; n_train floor(n_samples * ratio); X_train X(:, :, 1:n_train); Y_train Y(:, 1:n_train); X_test X(:, :, n_train1:end); Y_test Y(:, n_train1:end);这里有个必须强调的坑不要在训练集内部用交叉验证做随机划分保持连续性。如果你强制要求验证集应该顺着时间轴把训练集结尾切出一小段而不是打乱重选。LSTM 的记忆是时序相关的随机抽取很轻率。3. LSTM 网络架构设计层数和神经元数量到底怎么定模型结构这块我经历了从随便堆到有章法的过程。网上最常见的模板是一个 sequenceInputLayer 一个 lstmLayer(100) 一个 fullyConnectedLayer(1) regressionLayer确实能跑但是否合理要看你的数据复杂度。我这次测试了几组结构直接贴对比。3.1 最简结构先跑通layers [ ... sequenceInputLayer(1) lstmLayer(50, OutputMode, last) fullyConnectedLayer(1) regressionLayer];OutputMode选last是因为我们只取最后一个时间步的输出做预测回归任务不是对每个时间步都输出。这和序列到序列的任务模式不同。第一版用 50 个隐层单元结果训练集误差降得还行测试集 RMSE 大约 0.042归一化后可以接受但总觉得欠拟合。3.2 加深 vs 加宽把隐层单元加到 200训练时间翻了将近 2 倍测试集精度只提升了 0.002改成双层 LSTM——第一层 100 单元、第二层 50 单元、中间OutputMode第一层设为sequence第二层设为last——测试集 RMSE 立刻掉到 0.031。这说明对该数据来说深度比宽度更有效因为数据里有明显的周期性成分两级 LSTM 像两级滤波器低层捕捉局部形态、高层捕捉长程依赖。layers [ ... sequenceInputLayer(1) lstmLayer(100, OutputMode, sequence) lstmLayer(50, OutputMode, last) fullyConnectedLayer(1) regressionLayer];当然这不是绝对的。数据简单、样本量小的时候双层 LSTM 反而容易过拟合。判断标准很简单训练集误差低但测试集误差高就是过拟合这时候退回单层或者加 Dropout。Matlab 里dropoutLayer(0.2)是放在 LSTM 层之间的我试过加在第二层前面泛化确实变好了大约提升 5% 的测试集 RMSE。3.3 训练选项的关键参数trainingOptions里面埋着无数个坑。我第一版就用默认参数结果 loss 曲线一直在高位震荡。下面是我最后一版用起来很稳的配置options trainingOptions(adam, ... MaxEpochs, 300, ... MiniBatchSize, 64, ... InitialLearnRate, 0.005, ... GradientThreshold, 1, ... Shuffle, never, ... ValidationData, {X_val, Y_val}, ... ValidationFrequency, 20, ... Plots, training-progress, ... Verbose, true);逐个解释我为什么这么选Shuffle 必须 never时间序列样本打乱了会让每个 batch 里的时间上下文断裂。很多人不知道trainNetwork默认会打乱数据忘了设Shuffle,never结果模型训练不稳定时好时坏。GradientThreshold 设 1LSTM 训练中最经典的毛病是梯度爆炸梯度裁剪能救命。默认是[]也就是不裁剪第一次跑长序列时 loss 变成 NaN 基本都是这个原因。MiniBatchSize 64 不大不小太小收敛噪声大太大内存吃紧而且泛化差。我的数据量约 34 万样本用 64 合适如果你数据少几千条用 16 或 32。InitialLearnRate 0.005比默认的 0.001 大一些收敛快但配合梯度裁剪不至于发散。你要是发现 loss 曲线震荡严重降回 0.001。4. 完整代码实现从训练到滚动预测的逐段解读现在把整套流程串起来。我按模块拆分每段都能单独跑拼起来就是完整项目。这段代码我实测过,在 Matlab 2023b 上跑通Deep Learning Toolbox 版本要 R2021a 以上低版本有些参数名不一样比如老版本的Shuffle,never写法和新版本保持一致,但ValidationData的格式有变化。4.1 主脚本数据加载到训练%% LSTM 时间序列预测 - 主脚本 clear; clc; close all; rng(42); % 固定随机种子保证可复现 % 1. 加载数据 load(sensor_data.mat); % 2. 预处理按第 2 节的方法 filtered medfilt1(sensor, 7, omitnan); filtered fillmissing(filtered, pchip); data_min min(filtered); data_max max(filtered); data_norm (filtered - data_min) / (data_max - data_min); % 3. 构造滑窗样本 numSteps 50; numFuture 1; n_total length(data_norm); n_samples n_total - numSteps - numFuture 1; X zeros(numSteps, 1, n_samples); Y zeros(1, n_samples); for i 1:n_samples X(:, 1, i) data_norm(i : i numSteps - 1); Y(1, i) data_norm(i numSteps : i numSteps numFuture - 1); end % 4. 划分训练/验证/测试集 ratio_train 0.75; ratio_val 0.05; n_train floor(n_samples * ratio_train); n_val floor(n_samples * ratio_val); X_train X(:, :, 1:n_train); Y_train Y(:, 1:n_train); X_val X(:, :, n_train1 : n_trainn_val); Y_val Y(:, n_train1 : n_trainn_val); X_test X(:, :, n_trainn_val1 : end); Y_test Y(:, n_trainn_val1 : end); % 5. 定义网络 layers [ ... sequenceInputLayer(1) lstmLayer(100, OutputMode, sequence) dropoutLayer(0.2) lstmLayer(50, OutputMode, last) fullyConnectedLayer(1) regressionLayer]; % 6. 训练选项 options trainingOptions(adam, ... MaxEpochs, 300, ... MiniBatchSize, 64, ... InitialLearnRate, 0.005, ... GradientThreshold, 1, ... Shuffle, never, ... ValidationData, {X_val, Y_val}, ... ValidationFrequency, 20, ... Plots, training-progress, ... Verbose, true); % 7. 训练 net trainNetwork(X_train, Y_train, layers, options);4.2 预测的艺术一次性预测 vs 滚动预测训练完以后最关键的决策点来了怎么做预测。两种方式一次性预测直接把测试集所有分片喂给网络每个分片独立出一个预测值因为每个分片有各自的 50 步历史所以不存在误差积累。速度快适合验证模型本身。滚动预测只给网络第一个分片开头 50 步真实数据让它预测下一步然后把真实历史去掉最前面一步 预测值接在末尾作为输入再预测下下一步……每一步的输入都包含了之前预测出来的值误差会像滚雪球一样累积。真实业务永远需要滚动预测因为未来没有真实值给你更新输入。所以我两个都写了。%% 一次性预测用于评估模型精度 Y_pred_once predict(net, X_test); Y_pred_once reshape(Y_pred_once, [], 1); % 反归一化 Y_test_denorm Y_test * (data_max - data_min) data_min; Y_pred_once_denorm Y_pred_once * (data_max - data_min) data_min; % 计算 RMSE rmse_once sqrt(mean((Y_test_denorm - Y_pred_once_denorm).^2)); fprintf(一次性预测 RMSE: %.4f\n, rmse_once);滚动预测要维护一个状态向量每次只预测一个点然后滚动更新输入序列%% 滚动预测更贴近真实业务 num_test size(X_test, 3); rolling_input X_test(:, :, 1); % 用第一个测试样本作为种子 Y_pred_roll zeros(num_test, 1); for t 1:num_test % 用当前输入预测下一个点 y_hat predict(net, rolling_input); Y_pred_roll(t) y_hat; % 把预测值接在输入序列末尾并去掉最前面的一个点 new_seq [rolling_input(2:end, 1); y_hat]; % 注意形状是 numSteps x 1 rolling_input reshape(new_seq, numSteps, 1, 1); end Y_pred_roll_denorm Y_pred_roll * (data_max - data_min) data_min; rmse_roll sqrt(mean((Y_test_denorm - Y_pred_roll_denorm).^2)); fprintf(滚动预测 RMSE: %.4f\n, rmse_roll);这里有个维度细节坑死过人predict输入要求是numTimeSteps × numFeatures × numObservations的三维数组但单个观测时第三个维度的尺寸是 1绝对不能写成二维矩阵。我第一次写的时候直接把rolling_input用成numSteps × 1的二维结果报维度错误报错信息还很抽象找了半天。滚动预测的 RMSE 通常比一次性高不少这是正常的。我实测一次性 RMSE 0.031滚动预测 100 步之后 RMSE 涨到 0.085——误差确实在积累但至少没有发散到不可用。要是预测序列开始震荡成白噪声多半是前面那个OutputMode配错了或者隐层单元太少。4.3 多步预测和特征扩展如果目标不是单步而是未来 10 步、30 步我把numFuture改成对应数值然后Y的维度变成numFuture × n_samplesnumFuture 10; Y zeros(numFuture, n_samples); for i 1:n_samples Y(:, i) data_norm(i numSteps : i numSteps numFuture - 1); end最后一层全连接要改成fullyConnectedLayer(numFuture)。多步预测的误差会随预测长度快速增大这是时间序列预测的固有限制不是模型 bug。对这类任务滚动预测反而更实用——只预测一步然后更新输入循环几十次。如果你手里有多个相关变量比如温度、振动、压力多个传感器可以堆成多特征输入X变成numSteps × numFeatures × n_samples第一维是时间步第二维是特征数量。我试过加了一个相关变量之后RMSE 又降了 18%——多特征带来的信息增益在设备预测里很明显。5. 训练过程诊断别让 loss 曲线骗了你training-progress那个图不是摆设但大部分人并不会正确解读。我这次截了三次实验的曲线并记录下来给你当参照。5.1 典型正常曲线长什么样正常训练时训练 RMSE 和验证 RMSE 一起下降但验证会偶尔抖动尤其是ValidationFrequency设得小的时候。最后验证 RMSE 会进入平台期不再明显下降——这才是你该停止加 epoch 的信号。我第一版跑了 500 epoch实际上 250 左右就已经收敛了后面的 epoch 纯属浪费 GPU。5.2 三个经典异常曲线和修正策略我整理成一张表方便你对照自己的训练图排查现象可能原因修正方式loss 直接是 NaN学习率过大InitialLearnRate降到 0.001或减小MiniBatchSize训练 loss 降验证 loss 一路升过拟合加dropoutLayer减小隐层单元数增大训练数据训练 loss 降得很慢且震荡学习率偏大或数据没归一化确认数据已归一化到 [0,1]学习率改用 0.001GradientThreshold调小为 0.5loss 一直高位不降网络结构太浅或OutputMode错误检查lstmLayer的OutputMode尝试加深到双层最迷惑人的是第三种。我有一个试验版本用的GradientThreshold默认配置loss 曲线在 0.8 附近死命震荡我还以为是学习率问题调了好几天。后来把梯度阈值设为 110 个 epoch 内就拐头下降了。经验是任何 Nan 或剧烈震荡第一个检查项永远是梯度阈值和归一化不要先动网络结构。5.3 早停策略与模型保存提前停止不是 MatLab 的内置功能但ValidationData配OutputNetwork,best-validation可以实现类似效果options trainingOptions(adam, ... OutputNetwork, best-validation, ... ... % 其余同上 );跑完以后net会自动保存验证集表现最好的那一次权重而不是最后一个 epoch 的权重。这对防止过拟合极其有效——我对比过用 best-validation 比用最后一轮的 RMSE 低 10% 左右。模型保存用save命令把整个net结构存到.mat文件里加载用load。部署时只需要net和数据归一化的data_mindata_max一起存就行save(lstm_model.mat, net, data_min, data_max, numSteps);6. 踩坑实录我把在 Matlab LSTM 项目里遇到的 9 个问题全列出来这部分是我最想写的。网上教程大多把代码跑通就结束但真实的坑都藏在文档的犄角旮旯。我这次用 2023b 版踩遍了大大小小的雷分类列出来希望你不用从头趟一遍。6.1 关于数据格式的坑第一个坑是predict和trainNetwork对数据维度的要求里numObservations必须放在第三维。很多人对图像HWC或者表格数据NC的维度规则很熟到了序列这里就懵了。记住口诀时间在第一维、特征在第二维、样本在第三维输入给trainNetwork的观测数组永远遵循这个顺序。第二个坑是X是numSteps × 1 × N的 D 维数组不能顺手 squeeze 掉那个为 1 的维度。有一次我把维度写成了numSteps × NMatlab 直接报错说“观测数据维度无效”折腾了很久才发现是维度问题。6.2 关于预处理顺序的坑第三个坑是归一化时机。我一开始先切分训练测试集再对每一份分别归一化结果测试集 RMSE 奇高。后来意识到测试集的归一化必须用训练集统计的min和max否则分布就对不齐了。正确做法先对整个序列计算data_min和data_max再全序列归一化最后切分。第四个坑是fillmissing和medfilt1的交互。medfilt1的omitnan选项不是万能的它会把 NaN 邻域的输出也变成 NaN 或扭曲。解决方法是先fillmissing再medfilt1或者顺序反过来但滤波窗口要加大。我实测先插值后滤波更稳。6.3 关于训练与验证的坑第五个坑是Shuffle默认值。我检查过Deep Learning Toolbox 在某些版本里默认是打乱观测的对时间序列这等于改变时间顺序模型效果波动巨大。设置Shuffle,never之后曲线一下就顺了。第六个坑是验证集不能随机抽取。开始我用cvpartition做了随机交叉验证结果训练的模型一测真实数据就崩——因为验证集里的样本在时间维度上不连续模型轻松记住了验证片段而真实预测是连续段。后来改成时间顺序切片做验证泛化就好多了。第七个坑是ValidationData和Plots,training-progress同时使用时如果验证数据有损坏训练会中途终止但报错信息不明确。我把验证数据归一化后就好了。检查一下你验证集的每个样本维度和训练集是否严格一致差一个维度就崩。6.4 关于预测阶段的坑第八个坑是滚动预测的速度。一开始我用predict在循环里滚动43200 个点要跑好几十分钟。后来发现用predictAndUpdateState才是正解——它可以在不重新编码整个观测的情况下增量更新 LSTM 状态速度提升 10 倍以上。代价是状态管理要更小心每次用真实值更新状态还是预测值更新状态要区分清楚。% 使用 predictAndUpdateState 的滚动预测 net resetState(net); % 重置隐状态 [net, y_hat] predictAndUpdateState(net, X_test(:, :, 1)); Y_pred_state zeros(num_test, 1); Y_pred_state(1) y_hat; for t 2:num_test % 第二个点开始输入可以是真实的上一时刻 X_test(:, :, t-1) 做 Teacher Forcing [net, y_hat] predictAndUpdateState(net, X_test(:, :, t-1)); Y_pred_state(t) y_hat; end第九个坑是状态重置。predictAndUpdateState会保持 LSTM 的隐状态如果你在多条序列上反复预测必须调用resetState(net)否则上一条序列的状态污染下一条。我一开始不知道这个测试集前面的预测结果全被训练集最后的状态污染得乱糟糟。7. 效果评估与调优方向我从 0.048 到 0.031 的迭代记录最后分享一下我整个调参过程中的迭代轨迹给卡在瓶颈的朋友一个方向感。我第一版模型单层 LSTM 50默认训练参数没归一化测试集 RMSE 0.048。以下是每次改动后的结果加上 min-max 归一化RMSE 降到 0.040主要收益来自归一化让梯度更稳定。数据预处理顺序修正先插值再滤波RMSE 降到 0.038。学习率从 0.001 调到 0.005 并加梯度裁剪RMSE 降到 0.035收敛速度明显加快。单层 50 改成双层 10050 并加 dropoutRMSE 降到 0.032。OutputNetwork设为best-validationRMSE 降到 0.031。再往下的提升空间越来越小我试过加第三层 LSTM、调numSteps到 100、改AdamW优化器收益都在个位数的千分位反而训练时间成倍上涨。至此我认为这个模型已经接近该数据量的性能上限了。如果你也想系统调优我的建议顺序是先保证归一化和数据顺序正确再盯梯度阈值和学习率最后才动网络结构。不要一上来就堆 LSTM 层数因为大部分问题出在数据管道而非网络容量。等待数据侧的新信号、特征扩展、异常检测能力提升往往比模型结构调整的成本更低、收益更大。每一次预测精度的提升都要回到你到底是在预测哪个变量、变化规律是什么、数据里有哪些干扰这些原始问题上模型只是表达这些规律的一种方式。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。