MATLAB实现LSTM多步时间序列预测:从数据预处理到100步递推
2026/9/11 22:36:39 网站建设 项目流程

简介:面向时间序列预测与深度学习入门者,这份资源提供基于MATLAB的LSTM未来多步预测完整实现,可应用于电力需求预测、股票市场分析、气象预报等场景,也适合作为课程设计或毕业设计的参考项目。压缩包内共7个文件,包含LSTMTIMEN.m源码、datatimen.xls示例数据、docx步骤说明文档以及4张结果对比图,整体仅544KB,轻量易用。源码覆盖数据加载与归一化、LSTM网络搭建、训练以及将预测值迭代输入生成未来多步输出的核心流程,并配有预测结果与实际值对比图像,便于直观评估模型效果。代码结构清晰、注释明确,读者可快速修改数据集与网络参数,迁移到自身业务数据上;目前已有5549人学习下载,适合希望快速上手MATLAB深度学习工具箱、完成时间序列多步预测实验或毕业设计的读者参考与二次开发。

1. 100 步预测才是 LSTM 时间序列的真正考验

同样是 LSTM,单步预测漂亮不代表 100 步预测能用。这个工程的价值在于:给了LSTMTIMEN.mdatatimen.xls,从原始数据直接跑到 100 步未来的结果图,中间没有省略数据预处理和结果反归一化。很多人在单步上把 MSE 做到很小,一拉长预测步数就发散,问题大多出在递推策略和训练样本构造上,而不是网络结构。本文按数据读取、样本构造、网络训练、递归多步预测、结果评价这条线拆开讲,代码可以直接抄下来对着自己的数据改。适合已经在 MATLAB 里跑过简单 RNN 或深度学习例程、想把预测步数从 1 步扩展到 100 步的工程师和学生。

2. datatimen.xls 到监督学习序列:数据预处理与样本构造

2.1 用 readtable 读入时间列和观测值

先要搞清楚datatimen.xls的格式。最常见是两列:第一列时间戳,第二列观测值。不要直接用xlsread一把梭,我一般用detectImportOptions先检视列类型,再决定是否把时间列转成datetime。示例:

opts = detectImportOptions('datatimen.xls', 'Sheet', 1); dataTable = readtable('datatimen.xls', opts); % 取数值观测列,时间列单独保存 t = dataTable{:, 1}; % 可能是 datetime 或数值编号 x = dataTable{:, 2}; % 观测值 if isa(t, 'datetime') tNum = datenum(t); else tNum = t; end

detectImportOptions会自动识别表头、列类型,比xlsread对混合类型更稳。如果时间列是文本日期,需要datetime(t, 'InputFormat', 'yyyy-MM-dd HH:mm')这类带格式的转换;如果本身是 Excel 序列号,直接datenum后当时间坐标用。这里重要的是把时间列和观测列都转成数值或 datetime,因为后续绘图和计算都要靠时间坐标对齐。

如果数据没有时间列,只有一列观测值,也可以只取x = dataTable{:, 1},时间索引就用1:length(x)。代码里t的作用是最后的横轴标签,不会参与 LSTM 输入,所以不需要对时间做特征编码。

2.2 归一化与滑动窗口生成训练样本

LSTM 对输入尺度敏感,尤其后续用 sigmoid 和 tanh,输入值落在 [-1,1] 附近训练更稳定。常用两种归一化:Z-Score 与 Min-Max。我的习惯是在这段时序数据上先用 Z-Score,因为时序数据偶尔有较大幅值波动,Min-Max 容易被离群点压低整体变化。

mu = mean(x); sigma = std(x); xNorm = (x - mu) / sigma;

预测完成后必须用x = yPred * sigma + mu反归一化,否则所有误差指标都会失真。窗口构造用循环写,便于理解:输入窗口 10 步,预测目标 1 步,然后递归到 100 步。

numSteps = 10; % 输入窗口长度,可根据自相关函数调整 numFuture = 1; % 训练时输出 1 步 XTrain = {}; YTrain = []; for i = 1:length(xNorm)-numSteps-numFuture+1 XTrain{end+1, 1} = xNorm(i:i+numSteps-1)'; % 1 x numSteps YTrain(end+1, :) = xNorm(i+numSteps:i+numSteps+numFuture-1)'; end

这里XTrain是 cell 数组,每个元素是[1, numSteps]的行向量,这是 MATLABtrainNetwork对 sequence-to-one 回归的标准格式;YTrain是普通矩阵,行数等于样本数,列数等于输出步数numFuture。如果numFuture = 1YTrain是列向量。窗口numSteps太小会丢掉周期信息,太大样本量骤降,可以先做自相关(autocorr)看显著滞后阶数再定。

2.3 训练集切分与数据泄漏边界

切分时间序列不能用随机打散。代码里通常把前 80% 作训练集、后 20% 作测试集,并且测试集要严格在时间上靠后。这样评价才有「用历史预测未来」的意义。

numTrain = round(0.8 * size(XTrain, 1)); XTrainData = XTrain(1:numTrain); YTrainData = YTrain(1:numTrain, :); XTestData = XTrain(numTrain+1:end); YTestData = YTrain(numTrain+1:end, :);

这里XTrain的 cell 数组索引方式要用对:XTrain(1:numTrain)返回子 cell 数组,XTrain{1}才返回内容。归一化参数musigma只能从训练集计算,再作用到测试集,不能在测试集上重新算,否则测试指标会被高估。很多初学者在这步把测试数据也纳入归一化统计量,导致验证阶段 MSE 非常好看,部署后直接劣化。

另一个容易忽略的泄漏点是滑动窗口的边界:构造样本时用到了整个时间序列,如果测试集样本的时间窗口跨越了训练集和测试集的分界点,相当于测试时看到了未来的归一化统计量。安全做法是先切分原始时间序列,再在训练段和测试段分别构造窗口,而不是先构造所有窗口再切分。

3. LSTM 网络搭建与 trainNetwork 训练参数

3.1 用深度学习工具箱搭建序列网络

在 MATLAB 里,sequenceInputLayer定义输入维度,lstmLayer定义隐藏单元数,dropoutLayer防止过拟合,fullyConnectedLayer把 LSTM 输出映射到预测维度,最后regressionOutputLayer计算回归损失。这个资源里的LSTMTIMEN.m大概率就是按这套结构组织的。

numFeatures = 1; % 单变量序列 numHiddenUnits = 50; % 隐藏单元,先给一个中等值 numResponses = 1; % 每次预测 1 步 layers = [ sequenceInputLayer(numFeatures) lstmLayer(numHiddenUnits, 'OutputMode', 'sequence') dropoutLayer(0.2) fullyConnectedLayer(numResponses) regressionOutputLayer ];

重点看lstmLayerOutputMode。这里把输出设为'sequence',让 LSTM 每个时间步都输出隐含状态,再接fullyConnectedLayer后,回归输出会是一个与输入时间步等长的序列。如果只想用最后一个时间步的输出预测未来,应该设OutputMode = 'last'。两种模式都能训练,但递推多步预测时,我的经验是'last'更直接:最后一步输出已经包含整个窗口的信息,拿它接全连接层做 1 步预测,训练更容易收敛。很多 Python 教程里的return_sequences=True对应这里的'sequence'return_sequences=False对应'last',迁移时可以换算。

如果numResponses大于 1,比如一次预测 5 步,fullyConnectedLayer(5)配合OutputMode='last'也能处理,输出是 5 维向量。但这种训练方式会把未来第 1 步到第 5 步的错误同等看待,长期预测时效果不一定比递归 1 步好。

3.2 训练选项:求解器、学习率、mini-batch 与序列长度

trainNetwork的训练选项用trainingOptions控制。与图像任务不同,时间序列样本长度差异不大时,不需要太在意MiniBatchSize大小,但学习率要保守一些。

options = trainingOptions('adam', ... 'MaxEpochs', 200, ... 'MiniBatchSize', 64, ... 'InitialLearnRate', 0.005, ... 'GradientThreshold', 1, ... 'Shuffle', 'every-epoch', ... 'Verbose', false, ... 'Plots', 'training-progress');

GradientThreshold是 LSTM 训练最容易崩溃的点。递推多步预测场景里,梯度容易爆炸,设置成 1 是把每个梯度的范数截断到 1,比单纯降学习率更能稳定训练。Shuffle设成'every-epoch',每个 epoch 内打乱样本顺序,但不会打乱样本内部时间步。学习率从 0.005 开始,如果损失振荡就降到 0.001,如果收敛太慢就提到 0.01。MaxEpochs不要只看自己设定的 200,早停更有效,可以在训练进度图上判断。下面的表格是我在类似时序任务上的常用初始值,具体还要看数据波动幅度和样本量:

参数初始值调整方向
numHiddenUnits50数据复杂度高可加到 100~200,不要轻易超过 300
InitialLearnRate0.005损失不降时降到 0.001,梯度消失时尝试 0.01
MiniBatchSize64样本少用 32,样本多且序列长用 128
GradientThreshold1训练中断或 NaN 时保持 1 或降到 0.5
Dropout0.2训练损失低测试损失高时加到 0.3~0.5

3.3 训练过程观察与过拟合判断

训练进度图里,最值得看的是训练集 RMSE 和验证集 RMSE 的差距。trainingOptions支持'ValidationData',可以传一个 cell{XTrainVal, YTrainVal}。如果训练损失一直降但验证损失从某个 epoch 开始上升,就是过拟合,直接取那个 epoch 的模型参数,而不是等MaxEpochs跑完。时间序列的验证集也要按时间顺序切在训练集之后,不能随机抽。

另外,trainNetwork默认用单精度训练,输出net也是单精度。如果数据量小,训练很快,不必刻意转 double。如果 GPU 显存不够,把MiniBatchSize调小,或者把序列长度固定到numSteps并用'SequenceLength','shortest'避免 padding 大量无意义时间步。这里容易踩的坑是:把lstmLayer'OutputMode'误解为预测步数。OutputMode控制的是每个时间步是否输出隐含状态,不是未来走多少步。未来多步预测靠的是第 4 章的递推逻辑。

4. 递归预测循环:从 predict 到未来 100 步

4.1 用 predict 做单步推理

训练完成后,模型输入一个[1, numSteps]的序列,输出一个 1 维预测值。假设已经用测试集最后一个numSteps窗口作为起点:

inputSeq = xNorm(end-numSteps+1:end)'; % 最后 10 个观测,1 x 10 yhat = predict(net, {inputSeq}); % yhat 是 1 x numResponses

predict对单个样本时,第二个参数需要 cell 数组,里面的每个元素是[features, timeSteps]。这里inputSeq[1, 10],所以包成{inputSeq}yhat的尺寸和fullyConnectedLayer的输出维度一致,在numResponses=1时是1×1。如果numResponses > 1yhat1×numResponses,取第 1 列作为下一步输入。注意predictpredictAndUpdateState的区别:前者每次调用都独立推理,状态不保留;后者维护网络状态,适合在线滚动场景。这里用独立推理已经足够。

4.2 滚动递推循环生成 100 步预测

未来多步预测最常用的做法是把刚预测出的值当作新观测,拼到输入序列末尾,同时移除最老的一个值,保持窗口长度不变。这个操作在信号处理里叫滑动窗口递推,在 LSTM 预测里是最直观的多步外推方式。

numPredSteps = 100; yPred = zeros(numPredSteps, 1); for i = 1:numPredSteps yhat = predict(net, {inputSeq}); yPred(i) = yhat(1); inputSeq = [inputSeq(:, 2:end), yhat(1)]; end yPredDenorm = yPred * sigma + mu;

循环里做了三件事:预测当前步的结果并保存;把预测值接到窗口尾部;把窗口头部的旧值丢掉。如果训练时设了numFuture = 5,则yhat是 5 维向量,此时仍然只取yhat(1)作为输入,因为未来第 2 步到第 5 步的预测量是建立在「第 1 步预测正确」前提上的,在实际长程递推中误差更大,回灌反而会让后续结果偏离真实轨迹。这种滚动递推在金融时序预测里也很常见,预测结果的可解释性比一刀切输出 100 步要强。

多步预测的另一种策略是直接多步,即一次输出 100 步,不滚动。这在 MATLAB 里也简单:把fullyConnectedLayer输出改成 100,训练时YTrain每一行存未来 100 个观测。但样本量和数据周期性要求很高,100 步同时优化的参数很多,很容易在远期预测上输出均值回归。对比两种策略,资源里描述的是滚动递推,这也是实际中更容易定位误差来源的方案。

4.3 反归一化与评价指标

预测结果yPred还在归一化空间,直接和原始数据比会得到离谱的 RMSE。先反归一化,再算误差:

yReal = yTestOriginal; % 真实未来 100 步,注意这里要对应最后 100 步 rmse = sqrt(mean((yPredDenorm - yReal).^2)); mae = mean(abs(yPredDenorm - yReal)); ssRes = sum((yPredDenorm - yReal).^2); ssTot = sum((yReal - mean(yReal)).^2); r2 = 1 - ssRes / ssTot;

RMSE 对大误差敏感,MAE 更鲁棒,R² 能反映模型相对均值预测的提升。如果 R² 为负,说明预测还不如直接拿历史均值当结果,这时要检查是不是归一化泄漏、窗口太短或训练轮数不够。多步预测的误差要按步长分别统计,比如第 1 步 RMSE、第 50 步 RMSE、第 100 步 RMSE,而不是只给一个整体均值。实际经验是误差随步长非线性增长,第 100 步的误差往往是第 1 步的 3~5 倍,这个现象在单变量序列上基本无法避免,只能通过更好的特征和模型结构缓解。

5. 可视化与调参:把 100 步预测结果讲清楚

5.1 四张结果图怎么对照看

资源中LSTMTSN1.pngLSTMTSN4.png,对应训练过程、预测曲线或损失曲线。看预测图时,先看纵轴是否还原到真实尺度,再看训练段和预测端的交界处是否有跳变。如果交界处连续但后续发散,是递推误差累积的正常表现;如果交界处就断掉,多半是反归一化参数没对齐。可以自己再画一张更清晰的预测图:

figure; plot(tTest, yTestOriginal, 'LineWidth', 1.5); hold on; plot(tFuture, yPredDenorm, '--', 'LineWidth', 1.5); legend('真实值', '100步预测', 'Location', 'best'); xlabel('时间'); ylabel('观测值'); title(['RMSE=', num2str(rmse, '%.3f'), ' R^2=', num2str(r2, '%.3f')]);

tTest是测试段的时间坐标,tFuture是从预测起点往后 100 个时间点。如果两者不是等间距,先用linspace生成一致的时间轴,否则预测曲线会被拉伸或压缩。

5.2 快速落地的调参路径与验证方法

第一,固定随机种子rng(0)放在脚本开头,否则两次训练因随机初始化不同,无法判断是哪项改动起作用。第二,把测试集最后 100 步从训练窗口构造中隔离出来,绝对不能让构造训练样本时用到这 100 步内的数据。第三,观察远期预测形状,如果 100 步后输出收敛到常数,可把窗口numSteps从 10 加到覆盖一个完整周期,或换成双向bilstmLayer对比。第四,每个参数组合都记录 RMSE、MAE、R²,我一般会做三组实验:隐藏单元 50/100/200、学习率 0.005/0.001、窗口 10/20/30,用最少组合跑出趋势。别忘了analyzeNetwork(layers)可以可视化每层输出尺寸,排查维度不匹配比反复爆错更高效。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询