简介:这是一份基于长短期记忆网络(LSTM)开展多变量时间序列预测的MATLAB实现资源,适合正在学习深度学习、时间序列分析,或需要使用MATLAB进行预测建模的研究人员与工程师。资源聚焦多维特征输入下LSTM网络的构建、训练与效果评估,帮助用户快速掌握处理多变量时序数据的关键环节。压缩包共8个文件,整体大小约1022KB,主要包含LSTM.m主程序、数据集.xlsx、4张结果可视化PNG图片、运行说明docx文档及文本文档。主程序可直接运行,数据集提供练习用多维时序样本,图片展示预测值与真实值的对比结果,文档则对关键步骤和评价指标作出说明。目前已有359人学习下载。配套内容覆盖数据预处理、LSTM网络结构设计、训练选项设置以及R2、MAE、MSE、RMSE、MAPE等指标计算,基本涵盖多变量时序预测的完整流程。通过研读代码与图表,可以理解LSTM门控机制如何捕捉多变量间的长期依赖,并可将这套框架迁移到气象、交通或金融等领域的多维预测任务中。
1. 为什么是 LSTM 处理多变量时间序列
实际工程项目里,很少有单一变量的时间序列。一台风力发电机的功率预测,至少要同时看风速、桨距角、发电机转速、机舱温度;一套污水处理系统要预测出水水质,入水COD、pH、流量、溶解氧全部是输入。传统做多变量预测常用ARIMA或VAR,但这两类模型都假设序列之间存在线性关系,变量一旦耦合、非线性、有噪声,预测误差就明显放大。LSTM靠门控机制在时间维度上筛选信息,能把过去几十个时间步的多通道输入压缩成一个隐状态,这个隐状态再去拟合目标变量,线性假设被彻底打破。
我拆过不少MATLAB版的时序预测项目,发现一个共性现象:纯Python方案在数据读取和自定义算子上的确有优势,但MATLAB在模型迭代、训练过程可视化和指标对比上更直观。这篇就是从一个可运行的LSTM多变量预测工程出发,讲清楚网络怎么搭、数据怎么喂、指标怎么算。压缩包里LSTM.m、数据集.xlsx、运行.docx、3.png和4.png这几个文件,正好构成一个完整的“数据到结果”闭环。适合两类人看:一类是把MATLAB当主力工具做数据分析的工程师,另一类是刚接触深度学习时序预测、想在MATLAB里快速跑通一个能出图出指标的流程的学生。
2. LSTM 门控机制与 MATLAB 网络层配置
2.1 从 RNN 到 LSTM,解决的是什么问题
经典RNN在每个时间步做同样的矩阵运算,把短期记忆通过隐状态往后传。问题在于误差反向传播时,梯度沿着时间步连乘,若权重矩阵的特征值小于1,梯度会指数级衰减,到第几十个时间步基本归零。LSTM引入了三个门,本质上是给信息流增加“闸门”和“记忆单元”,让梯度在时间维上有一条“高速公路”。
输入门决定当前候选值有多少写入记忆单元C_t,遗忘门决定C_{t-1}保留多少,输出门决定C_t经tanh压缩后有多少进入隐状态h_t。三个门都是sigmoid输出,值域0到1,乘到候选或旧状态上,相当于做软性保留或丢弃。这个结构在MATLAB里被封装成lstmLayer,用户不需要手动写门的公式,但要理解每个参数对应的语义。
| 门控 | 作用 | 对应MATLAB层属性 |
|---|---|---|
| 输入门 | 控制候选值写入记忆单元的比例 | lstmLayer内部自动学习 |
| 遗忘门 | 控制上一时刻记忆保留比例 | lstmLayer内部自动学习 |
| 输出门 | 控制记忆单元对隐状态的输出权重 | lstmLayer内部自动学习 |
| 隐含单元数 | 决定记忆容量和输出维度 | lstmLayer第一个参数直接设置 |
2.2 MATLAB 中搭建 LSTM 网络的标准结构
MATLAB的深度学习工具箱从R2017b开始完整支持LSTM训练。底层用的还是trainNetwork、predict这套函数,但层定义比TensorFlow直观得多。针对多变量时间序列预测,网络的输入是三维数组或cell数组,其中每个时间步的特征维度就是变量个数。
% 数据集.xlsx 中列1~n-1为输入特征,最后一列为预测目标 data = readtable('数据集.xlsx'); X = data{:, 1:end-1}; % 多变量输入特征矩阵 Y = data{:, end}; % 目标变量 % 将矩阵按时间步切分为带重叠的样本 numTimeSteps = 50; % 每个样本包含50个历史时间步 numFeatures = size(X, 2); numSamples = size(X, 1) - numTimeSteps; XTrain = cell(numSamples, 1); YTrain = cell(numSamples, 1); for i = 1:numSamples idx = i : i + numTimeSteps - 1; XTrain{i} = X(idx, :)'; % 转置为 [特征数, 时间步] YTrain{i} = Y(i + numTimeSteps); end % 定义LSTM网络结构 layers = [ sequenceInputLayer(numFeatures) % 输入层,接收numFeatures维向量 lstmLayer(128, 'OutputMode', 'last') % 128个隐含单元,最后一帧输出 dropoutLayer(0.2) % 防止过拟合 fullyConnectedLayer(32) % 回归头前的特征压缩 reluLayer fullyConnectedLayer(1) % 输出单个预测值 regressionLayer];代码逻辑说明 1. 数据切分采用滑窗方式:每50个连续时间步组成一个样本,目标值取第51个时间步的Y。 2. XTrain中的每个元素是[numFeatures, numTimeSteps]的矩阵,这正是sequenceInputLayer要求的格式。 3. lstmLayer的OutputMode设为'last',表示丢弃中间时间步的隐状态输出,只保留最后一个时间步的结果。这段代码里最关键的是numTimeSteps的选择。设太短,模型看不到足够的趋势信息;设太长,梯度传导路径变长,训练难度显著上升。常见做法是先做自相关分析,看目标变量在哪个滞后阶数上相关性最高,再把numTimeSteps定在滞后阶数的1.5到2倍附近。项目附带的运行.docx里记录了在数据集.xlsx上的实验,50步滑窗搭配128个隐含单元,收敛速度和拟合精度平衡得比较好。
2.3 为什么选 LSTM 而不是 GRU 或 Transformer
GRU参数更少,训练在中小规模数据上更快,但LSTM的多门结构在变量间耦合关系复杂的场景下往往更稳定,尤其是数据中存在多个不同变化频率的序列时,遗忘门可以独立学会每个变量的“记忆时长”。Transformer虽然最近讨论度很高,但位置编码和自注意力矩阵在短序列、多维特征的场景里优势不明显,而且训练需要更大样本量,MATLAB中transformer支持也不如LSTM成熟。在数据集.xlsx这种几千到几万行的规模下,LSTM是性价比最高的起点。
3. 数据规整与训练全流程:从 xlsx 到预测曲线
3.1 数据预处理:归一化和训练集划分
多变量时间序列最大的坑在于各列量纲不一致。风速可能是0到30 m/s,温度是20到80,压力则可能是几百上千帕。LSTM内部的激活函数sigmoid和tanh对输入范围敏感,不归一化会导致梯度更新非常不稳定。项目里推荐的做法是用zscore标准化,每个特征列独立计算均值和标准差,归一化公式为x_std = (x - mean) / std。
% 对X和Y分别做zscore标准化 muX = mean(X); sigX = std(X); muY = mean(Y); sigY = std(Y); XN = (X - muX) ./ sigX; YN = (Y - muY) ./ sigY; % 按时间顺序划分训练集和测试集,不能用随机打乱 trainRatio = 0.8; trainSamples = floor(trainRatio * numSamples); XTrainN = XN(1:trainSamples); YTrainN = YN(1:trainSamples); XTestN = XN(trainSamples+1:end); YTestN = YN(trainSamples+1:end);参数说明 1. trainRatio=0.8表示用前80%的数据训练,后20%用于测试。 2. 时间序列切分必须保持时间顺序,防止未来信息泄漏到训练集。 3. 测试集上的预测结果需要乘回sigY并加回muY才能得到真实量纲的预测值。3.2 训练选项参数表与训练过程可视化
MATLAB的trainingOptions提供了一批控制训练行为的参数,下面是这个项目里实际用到的配置:
| 参数 | 本次取值 | 作用与调参建议 |
|---|---|---|
| solver | 'adam' | 自适应矩估计,适合LSTM这种非凸优化问题 |
| MaxEpochs | 300 | 轮数太少欠拟合,太多容易过拟合 |
| MiniBatchSize | 32 | 受显存限制,数据量大就减小 |
| InitialLearnRate | 0.005 | 学习率过高导致震荡,过低收敛慢 |
| GradientThreshold | 1 | 梯度裁剪,防止梯度爆炸 |
| Shuffle | 'never' | 时序数据必须保持顺序,不能随机打乱 |
| ValidationFrequency | 20 | 每20轮计算一次验证集损失 |
options = trainingOptions('adam', ... 'MaxEpochs', 300, ... 'MiniBatchSize', 32, ... 'InitialLearnRate', 0.005, ... 'GradientThreshold', 1, ... 'Shuffle', 'never', ... 'ValidationData', {XValidation, YValidation}, ... 'ValidationFrequency', 20, ... 'Plots', 'training-progress', ... 'Verbose', 1); net = trainNetwork(XTrainN, YTrainN, layers, options);训练过程中会在MATLAB里弹出损失曲线窗口,观察训练集和验证集的RMSE曲线:两条曲线同时下降则正常;验证集先降后升,则进入过拟合,需要增大dropout比例或减小隐含单元数。训练完成后,net就是结构完整的网络对象,直接用于测试集预测。
3.3 测试集预测与结果反归一化
预测时需要注意一个容易绕晕的点:训练时属于teacher forcing,网络每一步的输入都是真实历史值;测试时也要用真实历史值做时间步输入,只是输出和真实目标对比。预测代码先把测试集cell数组传入predict函数,得到标准值域的预测,再反归一化还原。
YPredNorm = predict(net, XTestN); YPred = YPredNorm * sigY + muY; YReal = cellfun(@(x) x * sigY + muY, YTestN);说明 predict输出每个测试样本的一个数值,顺序和XTestN中的样本顺序一致。 cellfun把每个cell里的目标值逐一还原,得到原始量纲的连续向量。 将YPred和YReal画在同一张图里,就是项目图片3.png显示的效果。4. 评价指标计算与调参避坑:R2、RMSE 怎么算才有效
4.1 指标公式与 MATLAB 实现
模型训练完不能只看损失曲线,指标是判断能不能投产的直接依据。每个指标看问题的角度不同:R2衡量拟合优度,RMSE对大误差敏感,MAE描述平均偏差,MAPE适合对比不同量纲的序列。
| 指标 | 公式 | 语义 |
|---|---|---|
| R2 | 1 - SS_res / SS_tot | 接近1表示模型解释了大部分方差 |
| RMSE | sqrt(mean((y - y_pred).^2)) | 对大误差放大,适合高精场景 |
| MAE | mean(abs(y - y_pred)) | 直接反映平均误差幅度 |
| MAPE | mean(abs((y - y_pred)./y)) * 100 | 百分比误差 |
% 将测试集真实值全部拼接为向量 YRealV = cell2mat(YReal(:)); YPredV = YPred(:); % R2 SSRes = sum((YRealV - YPredV).^2); SSTot = sum((YRealV - mean(YRealV)).^2); R2 = 1 - SSRes / SSTot; % RMSE, MAE, MAPE RMSE = sqrt(mean((YRealV - YPredV).^2)); MAE = mean(abs(YRealV - YPredV)); MAPE = mean(abs((YRealV - YPredV) ./ YRealV)) * 100; fprintf('R2=%.4f, RMSE=%.4f, MAE=%.4f, MAPE=%.2f%%\n', ... R2, RMSE, MAE, MAPE);逻辑说明 cell2mat将测试集的每个目标cell展开成普通数值向量,YPredV是网络输出的预测向量,二者长度必须相同。 R2计算时使用了残差平方和与总平方和的比值,本质上是比较“预测误差”和“直接拿均值预测”的改进程度。R2为负说明模型比无脑预测均值还差,需要回头检查数据泄露或网络结构。4.2 常见错误一:反归一化顺序不对
如果先对预测值做指标计算,再反归一化,RMSE和R2会出现巨大偏差,因为标准化后的数据方差是1,RMSE天然偏小。正确流程是预测完成后立刻反归一化,所有指标都在原始量纲下计算。很多人在MATLAB社区提问说指标奇高但曲线不对,基本都是这个原因。
4.3 常见错误二:数据划分导致信息泄漏
时间序列的滑窗切分,天然存在训练集最后一个窗口和测试集第一个窗口重叠的问题。如果测试数据参与了归一化统计量的计算,相当于把未来的均值信息带进了训练过程。解决方法是先归一化再切分,或者只使用训练集部分计算mu和sigma,再把同样的mu和sigma应用到测试集。
% 正确做法:只用训练集统计量 trainRaw = X(1:trainWindow, :); muX = mean(trainRaw); sigX = std(trainRaw);4.4 调参顺序与常见踩坑
调参不是随机试,我一般按下面顺序推进,每步只动一个变量:
- 第一步:固定滑窗长度50,隐含单元128,先用Adam跑200轮,确认损失能下降,再谈优化。
- 第二步:如果训练损失震荡剧烈,减小InitialLearnRate到0.001,或增大MiniBatchSize增加梯度稳定性。
- 第三步:如果验证损失持续高于训练损失,增大dropoutLayer从0.2到0.4,或在LSTM层之间再插入一个lstmLayer构成双层结构。
- 第四步:如果模型预测有滞后,往往是滑窗长度不够,尝试60、80,看R2是否提升。
一个常见争论是“单层LSTM还是双层LSTM”。我在项目里试过把128个单元的lstmLayer后面再接一个64个单元的LSTM层,R2提升了0.02,但训练时间增加了接近一倍。数据量不够大的情况下,深度带来的收益很小,反而是浅层大宽度的网络更稳定。
5. 滚动多步预测:从单步走向实际应用
评测LSTM时序预测模型,单步预测通常表现好,因为每一步都用了真实历史值;但实际生产里要做的是未来多步预测,比如未来24小时的风功率,每一步拿不到未来真实值。多步预测有两种常用策略。直接多步预测需要把输出层改成多个神经元,代价是输出之间的时序关系被割裂;递归预测则是把上一步的预测值当作下一步输入,循环执行,简单而且贴合LSTM本身的滚动推理逻辑。我在这个项目里实现的是递归预测,代码结构如下。
% 用测试集最后一个窗口初始化隐状态 lastWindow = XN(trainsamples, :); % 最后一个训练样本输入 currentInput = lastWindow(end-numTimeSteps+1:end, :)'; numFutureSteps = 10; futurePred = zeros(numFutureSteps, 1); net = resetState(net); for s = 1:numFutureSteps [net, yhat] = predictAndUpdateState(net, currentInput); futurePred(s) = yhat; % 将新预测值拼接到历史窗口,并滑动窗口 currentInput = [currentInput(:, 2:end), yhat]; end futurePred = futurePred * sigY + muY;参数说明 1. resetState清空LSTM的内部状态,保证从干净状态开始预测。 2. predictAndUpdateState每调用一次,LSTM的隐状态向前更新一个时间步,不需要重新训练。 3. currentInput每轮滑动一个时间步:丢弃最旧的一列,补入刚生成的新预测值。 4. numFutureSteps决定向前看多远,实际步数越长,误差累积越明显。这套滚动预测方式对短期多步预测有效,一般控制在10到20步以内。超过这个范围,预测值会逐渐收敛到训练集的均值附近,这是递归预测的固有特性。项目里的4.png如果显示了预测曲线,可以对比单步和多步两条曲线,观察误差的累积效应。
把整个工程串联起来看,数据和代码的配合方式已经构成了一个可复用的LSTM多变量预测模板:数据读取与标准化、滑窗与cell格式转换、网络定义与训练、指标计算与可视化、递归滚动推理。下一步可以在这个模板上继续扩展,比如把lstmLayer换成bilstmLayer做双向建模,或者把输出层改成softmax做分类。每种改动对应的收敛曲线和指标变化,都用同一套训练选项去对比,是最快理解LSTM特性的路径。
本文还有配套的精品资源,点击获取