MATLAB LSTM时间序列预测实战:从数据切片到滚动预测
2026/9/15 15:16:24 网站建设 项目流程

简介:这是一套基于MATLAB的LSTM时间序列预测训练项目,面向需要掌握深度学习时序建模与回归预测的开发者、研究生或竞赛选手。压缩包内含14个文件,整体约5.64MB,覆盖m源码脚本、mat结果文件、xlsx原始数据、txt训练记录与jpg/tif可视化图表,能够支撑从数据清洗、网络搭建、超参数调整到结果评估的完整流程。已有1808人浏览学习,包内训练过程截图和预测结果对比图可供快速核查模型收敛情况与误差水平。通过运行lstm1_youzhushi.m、lstm2.m等Matlab脚本,可复现长时间序列预测实验,并结合jieguo1.mat、jieguo2.mat以及结果tif图验证输出,节省自行搭建和调参时间。其中多个m脚本对应不同的LSTM网络设计,便于对照比较训练效果。资源结构清晰,适合作为LSTM时序预测的入门模板、课程设计或毕设基础,也可延伸到股市走势、负荷预测等场景。

1. 在 MATLAB 里跑通 LSTM 时间序列预测,卡点根本不在“模型”

把一段传感器读数或水文观测序列交给 MATLAB 的lstmLayer之前,多数人的预估都放在“LSTM 好不好用”上,实际做起来才会发现,卡点几乎全在数据组织:时间序列要被切成滑动窗口,训练样本要装进 cell 数组,特征维度和时间步顺序反了会直接报维度错误,预测未来值还要自己在循环里滚动更新状态。项目名里那组编号并不重要,串起来的东西我一般称为“MATLAB + LSTM 的时序回归最小闭环”:输入历史窗口、训练一个逐点预测的回归网络、对训练区间之外做多步外推,并用 RMSE、NSE 这类指标评估。相比 Python 框架,训练管网和自动求导会省很多事,代价是先接受 Deep Learning Toolbox 规定的层结构与数据协定。下面按“层结构 → 数据切片 → 训练选项 → 滚动预测”展开。

2. 由 sequenceInputLayer 到 regressionLayer:按 MATLAB 的接口搭 LSTM 回归网络

2.1 四个层的职责与参数:sequenceInputLayer、lstmLayer、fullyConnectedLayer、regressionLayer

MATLAB 的 Deep Learning Toolbox 从 R2018a 起就提供了lstmLayer,搭建方式固定为四个层串联:sequenceInputLayer声明每个时间步的特征数,lstmLayer承接时序状态,fullyConnectedLayer把隐状态压缩到目标维度,末尾接regressionLayer计算损失。sequenceInputLayer(numFeatures)的参数numFeatures是每个时间步的变量数量,单变量序列填 1;如果同时用温度和流量两个变量做预测,这里就是 2。lstmLayer(numHiddenUnits)numHiddenUnits指隐状态维度,也就是记忆容量,它和时间步数没有关系。这个值在 32 到 256 之间通常够用,超过 256 对中小规模序列收益很小,训练时间反而明显上涨。

fullyConnectedLayer(numResponses)numResponses由预测目标决定:单步预测是 1,多步直接输出时就等于预测步数。regressionLayer没有参数,只提供均方误差损失并驱动反向传播。常见的配置错误是把回归任务接了classificationLayer,那会让网络把连续值当成类别概率来学,训练出来的输出完全不可用。

关键参数典型值在预测任务中的角色
sequenceInputLayernumFeatures1(单变量)声明每个时间步的输入变量数
lstmLayernumHiddenUnits, OutputMode128, 'sequence'在时间维上迭代并维护内部状态
fullyConnectedLayernumResponses1把隐状态映射到预测值
regressionLayer用 MSE 计算回归损失

执行顺序上,数据从输入层进入 LSTM 层,按时间步逐步展开;每个时间步结合当前输入和前一时刻的隐状态计算输出;全连接层把每个时间步的高维向量压成 1 维;回归层把所有时间步的误差汇总为损失。理解这四层各自管什么,排错时才能快速定位:维度报错多半在输入特征数或全连接输出维度上,loss 不收敛则要回头查 LSTM 层和训练选项。搭完结构后可以调用analyzeNetwork(net),它会打印每一层的激活尺寸和参数量,比肉眼数维度可靠。

2.2 遗忘门、状态更新和 OutputMode:理解 LSTM 内部再决定用 'sequence' 还是 'last'

很多资料在讲 LSTM 单元时都会追问遗忘门的输入是什么数据:答案是遗忘门接收上一时刻的隐状态 (h_{t-1}) 和当前时刻输入 (x_t),两者拼接后经过 sigmoid 变换,决定从记忆单元中丢弃多少旧信息。输入门和输出门也遵循类似的拼接逻辑,只是职责不同。在 Python 里手写这些门控公式并不难,但容易在反向传播时出错;MATLAB 的lstmLayer把整套公式封装好,实践中你只需要关心隐单元数和输出模式这两个旋钮。

OutputMode默认是'sequence',网络在每一个时间步都产生输出,适合逐点监督:输入 20 个点,目标也对应 20 个点,网络学到的是“每输入一个点,预测下一个点”。如果做整个序列打一个标签的任务,比如用一周数据预测未来一小时,则应把OutputMode设为'last',只取最后一个时间步的隐状态。这个选择直接影响第 3 章中 Y 的构造方式,先定模式再切数据,能省掉不少返工。

2.3 用 trainNetwork 跑动最小训练:一段可以直接粘贴的骨架

下面是最小可跑骨架,数据占位符XTrainYTrain先按 cell 数组理解,具体生成在下一章给出。

numFeatures = 1; % 单变量时间序列 numResponses = 1; % 单步预测,输出 1 个数 numHiddenUnits = 128; % 隐状态维度,先按 128 试 layers = [ sequenceInputLayer(numFeatures) % 输入层,特征数=1 lstmLayer(numHiddenUnits, 'OutputMode', 'sequence') % LSTM层,逐时间步输出 fullyConnectedLayer(numResponses) % 全连接层,输出1个值 regressionLayer]; % 回归损失层 options = trainingOptions('adam', ... 'MaxEpochs', 150, ... 'MiniBatchSize', 32, ... 'InitialLearnRate', 0.005, ... 'GradientThreshold', 1, ... 'Plots', 'training-progress'); net = trainNetwork(XTrain, YTrain, layers, options);

这里选adam优化器,对中小规模时间序列比sgdm收敛更快,前期不太需要手动调动量项。GradientThreshold设为 1 是长序列训练的关键预防措施:默认值是 Inf,一旦梯度爆炸,loss 直接跳到 NaN,先把它打开能省一半排查时间。Plots打开训练进度窗口,方便实时看 loss 是否下降。训练完成后net就是完整的网络对象,后续预测直接复用它。

3. 滑动窗口切片与 cell 数组约定:把原始序列喂给 lstmLayer 前必须做的事

3.1 滑动窗口切分:把一维向量变成“输入-预测”样本对

trainNetwork要求训练数据必须是 cell 数组:XTrain的每个元素是一个“特征数 × 时间步”的矩阵,YTrain的每个元素对应一个“响应数 × 时间步”的矩阵。最容易出错的地方不是 cell 本身,而是矩阵方向:行是特征,列是时间步,单变量序列就是一个 1×T 的行向量。如果给成 T×1 的列向量,工具箱会解释成每个时间步有 T 个特征,维度直接错乱。

切分窗口的常用做法是设定一个windowSize,每滑动一步取连续windowSize个观测作为输入,目标取它右移一位的同样长度。for 循环写起来直观,也方便断点调试。

% raw 是一个 N×1 的 double 列向量,按实际数据替换下面的占位 raw = randn(1000, 1); % 临时占位 windowSize = 20; numSamples = length(raw) - windowSize; XTrain = cell(numSamples, 1); YTrain = cell(numSamples, 1); for i = 1:numSamples XTrain{i} = raw(i:i+windowSize-1).'; % 1×windowSize 输入行向量 YTrain{i} = raw(i+1:i+windowSize).'; % 1×windowSize,右移一个时间步 end

注意raw是列向量时,切片raw(i:i+windowSize-1)是 column 向量,加.'转置成行向量才符合“特征数 × 时间步”。目标不是“未来 20 步”,而是“从第 2 个数到第 windowSize+1 个数”,这样网络学到的是每个时间步上的一步转移关系,最后做递归外推时,每次更新一步状态就能向前推进。

提示:cell 数组里每个样本的特征数必须一致,时间步可以不同;trainNetwork不会自动广播维度,对不上会直接报维度错误。

做多变量序列时,把每个变量的窗口行堆起来即可:XTrain{i} = [var1(i:i+windowSize-1)'; var2(i:i+windowSize-1)'],第一维就是特征数,LSTM 层会把这个特征向量作为每个时间步的输入。

数据形态维度约定说明
单个序列样本features × timesteps单变量为 1×T
一批样本cell 数组,单元内为矩阵各样本的时间步可以不等长
分类标签categorical 序列配 classificationLayer 用
回归输出double 序列配 regressionLayer 用

3.2 训练/验证/测试分段与 zscore 标准化,别让未来信息泄漏进训练

LSTM 对输入量级很敏感。序列均值如果是几百上千,固定学习率下梯度变化很不均匀,训练前期容易震荡。通常用 zscore 标准化:mu = mean(raw(trainInd)); sd = std(raw(trainInd)); raw = (raw - mu) / sd;。关键在于musd只从训练段计算,再应用到验证段和测试段。如果先在整个序列上算均值方差再切分,验证段的均值已经绕道进入了训练过程,这属于未来信息泄漏,会使评估误差偏乐观,且泄漏量越大,线上效果和验证效果差距越大。

切分比例常见 70% 训练、15% 验证、15% 测试,但必须按时间顺序切,不能随机打乱。时间序列的验证和交叉验证不同:随机 K 折会把时间上相邻的样本拆到不同集合,破坏序列依赖结构。trainNetworkValidationData参数可以单独传入验证集,它只在每隔若干个迭代计算一次验证损失,不参与权重更新,用于观察过拟合足够可靠。

3.3 单步监督的完整训练示例:合成数据、训练、预测与反标准化

为了让代码直接可运行,用带噪声的正弦叠加信号模拟一个传感器序列。读者可以原样替换raw为自己的数据源。

% 1. 生成模拟序列 fs = 100; % 采样频率 t = (0:2000)' / fs; % 时间轴 raw = sin(2*pi*0.1*t) + 0.2*sin(2*pi*0.03*t) + 0.05*randn(size(t)); raw = raw(1:1500); % 取前 1500 个点 % 2. 标准化(先切分再算均值,避免信息泄漏) trainLen = 1050; % 70% 作为训练区间 mu = mean(raw(1:trainLen)); sd = std(raw(1:trainLen)); raw = (raw - mu) / sd; % 3. 滑动窗口构造 cell 数组 windowSize = 20; numSamples = length(raw) - windowSize; X = cell(numSamples, 1); Y = cell(numSamples, 1); for i = 1:numSamples X{i} = raw(i:i+windowSize-1).'; Y{i} = raw(i+1:i+windowSize).'; end % 4. 按时间顺序拆分训练和验证 idxTrain = 1:floor(numSamples*0.8); idxVal = floor(numSamples*0.8)+1 : numSamples; XTrain = X(idxTrain); YTrain = Y(idxTrain); XVal = X(idxVal); YVal = Y(idxVal); % 5. 定义层与训练选项 layers = [sequenceInputLayer(1) lstmLayer(128, 'OutputMode', 'sequence') fullyConnectedLayer(1) regressionLayer]; options = trainingOptions('adam', ... 'MaxEpochs', 120, ... 'MiniBatchSize', 32, ... 'InitialLearnRate', 0.005, ... 'GradientThreshold', 1, ... 'ValidationData', {XVal, YVal}, ... 'ValidationFrequency', 20, ... 'Plots', 'training-progress'); % 6. 训练并保留 info 用于分析 [net, info] = trainNetwork(XTrain, YTrain, layers, options); % 7. 测试段单步预测 testStart = 1200; XTest = raw(testStart:testStart+windowSize-1).'; YTest = raw(testStart+windowSize); % 真实目标值 pred = predict(net, XTest); predScalar = pred(end) * sd + mu; % 反标准化

代码里的pred是网络对整个输入窗口的逐点预测,长度与XTest相同,pred(end)才是最后一个输入时间步的下一步预测。YTest是标准化后的值,比较误差时要减mu除以sd,或者把predScalar反标准化到原量纲。训练完成后查看info.TrainingLossinfo.ValidationLoss,两者都在下降且差距不大,再进行下一步的滚动预测。

4. trainingOptions 里的关键超参数:怎么调才收敛而不是碰运气

4.1 trainingOptions 里四个最影响收敛的参数:学习率、批大小、梯度裁剪和验证频率

参数默认值建议范围对收敛的影响
InitialLearnRate0.010.001 ~ 0.01偏大容易震荡或 NaN,偏小收敛慢
MiniBatchSize12816 ~ 64太小梯度噪声大,太大容易停在平坦区
GradientThresholdInf1 ~ 5设置太小抑制有效梯度,太大防不住爆炸
ValidationFrequency5020 ~ 50验证损失采样密度,偏大浪费时间,偏小抖动明显

InitialLearnRate是最先要动的参数。对时间序列回归,我一般从 0.005 起步,而不是工具箱默认的 0.01。如果训练曲线下降顺利,但在后期停滞,可以配合设置'LearnRateSchedule', 'piecewise', 'LearnRateDropFactor', 0.5, 'LearnRateDropPeriod', 50,让学习率每 50 轮减半,让权重在收敛后期更精细地落位。

MiniBatchSize决定一次迭代用多少条样本计算梯度。时间序列样本之间存在重叠,批量太大容易让重复的窗口主导梯度方向;批量太小则每个 batch 之间差异大,loss 曲线会高频抖动。32 是一个中庸起点,如果数据量只有几千条,16 更稳。GradientThreshold的默认值是 Inf,这在长序列或深网络中几乎总会出问题,一旦出现单个 batch 内梯度范数异常,loss 直接变 NaN。把这个值设到 1 可以避免大部分梯度爆炸,对精度损失通常很小。

ValidationFrequency只影响评估节奏。它在迭代次数上生效,与 epoch 无关;设成 20 表示每 20 次迭代计算一次验证损失。太小的值会让验证评估本身拖慢训练,太大的值则可能在过拟合拐点之后才发现问题。

4.2 训练曲线读法:Loss 不降、NaN 和过拟合的定位顺序

trainNetwork训练完成后返回的info结构里带着每次迭代的训练损失与验证损失,放在同一个图里能看到比训练进度窗口更完整的信息。直接画出来是判断问题的最快路径:

% 只有设置了 ValidationData,ValidationLoss 才会被记录 plot(info.TrainingLoss); hold on; plot(info.ValidationLoss); legend('TrainingLoss', 'ValidationLoss'); xlabel('Iteration'); ylabel('Loss');

先看训练集上有没有 NaN:有则检查输入数据里是否有NaNInf,执行any(isnan(raw))能快速定位;数据没问题再看是不是梯度爆炸,把GradientThreshold打开即可。再看两个 loss 的走势:如果训练集和验证集都降不下去,多半是隐单元数不够或窗口长度太短,先加numHiddenUnits到 256,同时把windowSize加大到上一组数据的周期长度;如果训练损失下降而验证损失上升,是过拟合,优先增加训练数据量、减小批大小,或者在 LSTM 层后加一个dropoutLayer(0.2),这个层在工具箱里直接拼在lstmLayer后面即可。

loss 曲线在低值附近抖动并不等于模型差,要看抖动幅度的量级。如果验证损失在 0.05 量级上抖出 0.01 的波动,这很正常;如果验证损失上下跳动超过一个数量级,则要把学习率调低一个档位再看。

4.3 记录 ValidationLoss 并自动保存验证最优权重

训练结束时的net默认是最后一个 epoch 的权重,但如果验证损失已经出现过拟合拐点,最后一轮权重反而不如拐点处。trainingOptions里有一个容易被忽略的参数OutputNetwork,设成'best-validation'后,训练结束返回的net自动是验证损失最小的迭代点上的权重:

options = trainingOptions('adam', ... 'ValidationData', {XVal, YVal}, ... 'OutputNetwork', 'best-validation', ... 'Plots', 'none'); [net, info] = trainNetwork(XTrain, YTrain, layers, options);

这个参数在验证集较大、曲线像锯齿的时候尤其有用,相当于白捡一次最优 checkpoint 选取。注意OutputNetwork只在显式提供ValidationData时生效,否则会报错。加上这行配置后,模型从训练到验证的整个流程就不再依赖眼睛盯训练窗口手动打断,可以直接跑批试验一组超参数。

5. 滚动多步预测:用 predictAndUpdateState 外推并用 NSE 验证

5.1 用 resetState 与 predictAndUpdateState 做递归多步外推

要预测未来 50 步,不能把最后一段窗口一次性喂给网络并期望输出未来序列,常见做法是递归外推:把预测值当成下一步输入,同时让网络保持内部状态继续迭代。predictAndUpdateState在计算预测的同时更新 LSTM 的隐状态,调用形式固定为[net, predicted] = predictAndUpdateState(net, X)

H = 50; % 外推步数 net = resetState(net); % 清空训练时残留的状态 startIdx = 1430; window = raw(startIdx:startIdx+windowSize-1).'; % 1×windowSize 最新窗口 future = zeros(H, 1); for k = 1:H [net, pred] = predictAndUpdateState(net, window); future(k) = pred(end); % 取最后时间步的预测值 window = [window(2:end), future(k)]; % 丢掉最早点,推入新预测 end future = future * sd + mu; % 反标准化到原量纲

调用时返回值顺序是net在前、预测值在后,漏掉net会导致后续迭代状态不连续。pred(end)取的是当前窗口内最后一个时间步的预测,对应窗口之外的下一个真实值。窗口更新时直接把新预测追加到末尾并丢弃最早的观测,长度始终保持windowSize。对于水文径流这类长期外推任务,递归方式会有误差累积:第 k 步的预测误差会进入第 k+1 步的输入,H 越大偏差越明显。H 较大时可以把训练目标改成直接输出未来多步,让Y{i}成为未来 h 个值的向量,同时把OutputMode设为'last',一条输入直接得到多条预测。

5.2 预报误差的三个指标:RMSE、MAE、NSE 怎么看

有了future和真实观测后,用三个指标交叉验证模型,而不是只看某一张图。

obs = raw(startIdx+windowSize : startIdx+windowSize+H-1); obs = obs * sd + mu; % 反标准化到原量纲 rmse = sqrt(mean((future - obs).^2)); mae = mean(abs(future - obs)); nse = 1 - sum((obs - future).^2) / sum((obs - mean(obs)).^2);
指标计算公式解读
RMSEsqrt(mean((pred-obs)^2))与数据同量纲,对大幅误差更敏感
MAEmean(abs(pred-obs))与数据同量纲,不过分放大异常点
NSE1 - SSE / SST接近 1 说明远优于均值基准,0 表示与用均值预测持平

NSE 低于 0 时,说明模型连“拿历史均值当预测”都没有超过,此时不要再堆复杂度,先回去检查窗口长度是否覆盖序列的主要周期,以及标准化是不是只在训练段上计算。把 RMSE、MAE、NSE 固定成一组评估接口,之后每次换超参数都能直接横向对比,比翻训练曲线更可靠。

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

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

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

立即咨询