简介:这份资源提供了一套基于LSTM的时间序列预测MATLAB实现代码,面向具备一定机器学习基础、希望快速上手深度学习预测实验的高校学生与算法工程师。代码围绕长短期记忆网络处理时间关联数据展开,可用于经济走势、股票价格、气象变化等序列预测场景。压缩包共8个文件,约1.2MB,以m脚本为主,辅以xlsx与mat数据文件、csv结果文件及asv备份文件,覆盖数据加载、模型构建、训练验证、指标计算与结果输出等环节。其中包含MSE、RMSE、MAE、MBE等误差评估函数以及R²决定系数计算脚本,便于从多角度衡量预测精度。目前已有232人学习下载,适合作为课程设计、毕业设计或科研入门阶段的参考模板,帮助读者理解LSTM预测流程、评估指标实现方式与结果组织思路,并在此基础上进行超参数调整与模型改进。
1. 基于LSTM的时间序列预测,MATLAB里到底能跑出什么
很多人第一次接触时间序列预测,都是从“用LSTM跑一个预测曲线”开始的。标题里这个方向,说白了就是在MATLAB环境里,用长短期记忆网络对一串按时间排列的数据做建模,然后预测未来一段时间的值。它适合谁?适合手头有传感器数据、负荷数据、股价数据、气象数据,想快速验证LSTM能不能比传统ARIMA、指数平滑做得更好的工程师和学生。MATLAB的优势在于工具箱成熟、矩阵操作顺手、绘图方便,不用折腾环境配置,一个脚本就能从数据加载跑到预测可视化。但坑也在这里:很多人直接拿默认参数硬套,结果训练集拟合得很好,测试集一塌糊涂,还以为是代码写错了。其实问题往往出在数据归一化、窗口构造和网络结构上。这一章先把这件事讲清楚:LSTM做时间序列预测,在MATLAB里不是调个函数就完事,它是一套从数据预处理到网络训练再到滚动预测的完整流程。
2. 先把数据整明白:时间序列窗口构造与归一化
2.1 为什么不能把原始序列直接喂给LSTM
LSTM的输入不是单个时间点,而是一个个滑动窗口。假设你有一列长度为N的时间序列,要预测未来第t+1个点,常见做法是用前k个点作为输入特征,第k+1个点作为标签。这个k就是窗口长度,也叫时间步长。很多人忽略这一步,直接把整条序列转置一下丢进去,结果网络根本学不到时序依赖。窗口构造的本质是把一维序列重构成监督学习样本对。窗口太短,模型看不到足够的历史信息;窗口太长,训练样本数骤减,还容易过拟合。我一般会先画自相关图,看序列在滞后多少阶之后相关性明显衰减,再结合数据采样频率定窗口。比如分钟级数据,窗口取60到120比较常见;日级数据,窗口取7到30。归一化同样关键,LSTM对输入尺度敏感,不做归一化,梯度要么爆炸要么消失。常见做法是最大最小归一化到[0,1]或[-1,1],也有人用z-score标准化。注意:归一化参数必须用训练集计算,然后应用到验证集和测试集,否则就是数据泄露。
2.2 用MATLAB构造滑动窗口的完整代码
下面这段代码演示如何把一列时间序列转成LSTM需要的输入输出格式。假设原始数据是单变量,存在变量data里,窗口长度设为windowSize。
% 假设 data 是 N x 1 的时间序列 windowSize = 60; % 窗口长度,根据自相关分析确定 forecastHorizon = 1; % 预测未来1个点 % 最大最小归一化,参数只用训练集计算 trainRatio = 0.7; valRatio = 0.15; testRatio = 0.15; n = length(data); nTrain = floor(n * trainRatio); nVal = floor(n * valRatio); trainData = data(1:nTrain); valData = data(nTrain+1:nTrain+nVal); testData = data(nTrain+nVal+1:end); % 用训练集计算归一化参数 minVal = min(trainData); maxVal = max(trainData); rangeVal = maxVal - minVal; % 归一化函数 normalizeFunc = @(x) (x - minVal) / rangeVal; denormalizeFunc = @(x) x * rangeVal + minVal; trainNorm = normalizeFunc(trainData); valNorm = normalizeFunc(valData); testNorm = normalizeFunc(testData); % 构造滑动窗口样本 function [X, Y] = createWindows(series, windowSize, horizon) n = length(series); numSamples = n - windowSize - horizon + 1; X = zeros(windowSize, numSamples); Y = zeros(1, numSamples); for i = 1:numSamples X(:, i) = series(i : i + windowSize - 1); Y(i) = series(i + windowSize + horizon - 1); end end [XTrain, YTrain] = createWindows(trainNorm, windowSize, forecastHorizon); [XVal, YVal] = createWindows(valNorm, windowSize, forecastHorizon); [XTest, YTest] = createWindows(testNorm, windowSize, forecastHorizon);这段代码的逻辑说明:createWindows函数把一维序列切成一个个窗口,每个窗口对应一个标签。X的每一列是一个窗口,Y是对应的预测目标。归一化参数只从训练集计算,然后应用到验证集和测试集,避免未来信息泄露。参数方面,windowSize决定看多长的历史,forecastHorizon决定预测未来几步。如果要做多步预测,可以把Y改成矩阵,每列对应未来多个点。注意:MATLAB的LSTM层要求输入是numFeatures x numTimeSteps x numSamples的三维数组,所以后面还需要用reshape或permute调整维度。
2.3 数据集划分的坑:别让验证集和测试集“偷看”未来
时间序列不能随机打乱划分,必须按时间顺序切分。上面代码用的是前70%训练、中间15%验证、最后15%测试。有些人为了省事,用randperm随机划分,结果模型在训练时已经见过未来的数据,测试指标虚高,上线就翻车。另外,验证集的作用是早停和调参,测试集只在最后用一次。如果你反复用测试集调参,测试集就变成了验证集,最终指标不可信。我一般会保留一个完全没碰过的测试集,只在最终评估时用。如果数据量足够,还可以做滚动窗口验证,比如扩展窗口或滑动窗口交叉验证,但MATLAB里手动实现比较繁琐,新手先用固定划分就够了。
3. 搭LSTM网络:层结构、训练参数与MATLAB实现
3.1 LSTM层数、隐藏单元数和dropout怎么选
MATLAB的Deep Learning Toolbox里,LSTM网络用sequenceInputLayer、lstmLayer、fullyConnectedLayer、regressionLayer搭起来。层数不是越多越好,单变量时间序列预测,通常1到2层LSTM就够了。隐藏单元数一般取32到128,数据越复杂、窗口越长,单元数可以适当增加。但要注意,单元数翻倍,训练时间可能翻三倍。dropout层用来抑制过拟合,常见取值0.1到0.3。如果训练损失下降但验证损失上升,说明过拟合了,加dropout或者减小隐藏单元数。如果训练损失和验证损失都下不去,说明欠拟合,增加层数或单元数。我一般会先从一个较小的网络开始,比如64个隐藏单元、单层LSTM、dropout 0.2,跑一遍看损失曲线,再决定往哪个方向调。另外,sequenceInputLayer的输入维度要和特征数一致,单变量就是1,多变量就是特征个数。
3.2 训练选项:求解器、学习率、批大小和早停
MATLAB用trainingOptions设置训练参数。求解器常用adam,学习率初始值0.005到0.01,批大小32到128。MaxEpochs设大一点,比如200,配合验证集早停。ValidationFrequency设成每轮验证几次,ValidationPatience设成10到20,意思是验证损失连续多少轮不下降就停止。Shuffle选项要注意:时间序列不能设为every-epoch,否则窗口之间的时序关系被打乱,应该设为never。GradientThreshold设1或2,防止梯度爆炸。下面是一个完整的网络定义和训练代码。
% 调整数据维度为 LSTM 需要的格式 % XTrain: windowSize x numSamples -> 需要变成 1 x windowSize x numSamples XTrainLSTM = permute(XTrain, [3, 1, 2]); % 变成 numSamples x windowSize x 1 XValLSTM = permute(XVal, [3, 1, 2]); XTestLSTM = permute(XTest, [3, 1, 2]); % 标签需要是 numSamples x 1 YTrainLSTM = YTrain'; YValLSTM = YVal'; % 定义网络结构 numFeatures = 1; numHiddenUnits = 64; dropoutRate = 0.2; layers = [ sequenceInputLayer(numFeatures) lstmLayer(numHiddenUnits, 'OutputMode', 'last') dropoutLayer(dropoutRate) fullyConnectedLayer(1) regressionLayer ]; % 训练选项 options = trainingOptions('adam', ... 'MaxEpochs', 200, ... 'MiniBatchSize', 64, ... 'InitialLearnRate', 0.005, ... 'LearnRateSchedule', 'piecewise', ... 'LearnRateDropPeriod', 50, ... 'LearnRateDropFactor', 0.5, ... 'GradientThreshold', 1, ... 'ValidationData', {XValLSTM, YValLSTM}, ... 'ValidationFrequency', 10, ... 'ValidationPatience', 15, ... 'Shuffle', 'never', ... 'Verbose', 1, ... 'Plots', 'training-progress'); % 训练网络 net = trainNetwork(XTrainLSTM, YTrainLSTM, layers, options);逻辑说明:permute把窗口样本从windowSize x numSamples转成numSamples x windowSize x 1,符合LSTM输入格式。lstmLayer的OutputMode设为last,表示只取最后一个时间步的输出用于回归。fullyConnectedLayer(1)输出一个预测值。训练选项里,Shuffle设为never是关键,时间序列不能打乱。ValidationPatience设为15,验证损失15轮不降就停。LearnRateSchedule用分段下降,每50轮降一半。参数方面,MiniBatchSize根据内存调整,显存不够就减小。GradientThreshold防止梯度爆炸,尤其当序列有异常值时。
3.3 训练完怎么评估:RMSE、MAE和预测曲线
训练完成后,用测试集评估。先预测,再反归一化,然后算指标。
% 测试集预测 YPredNorm = predict(net, XTestLSTM); YPred = denormalizeFunc(YPredNorm); YTrue = denormalizeFunc(YTest'); % 计算指标 rmse = sqrt(mean((YPred - YTrue).^2)); mae = mean(abs(YPred - YTrue)); fprintf('RMSE: %.4f, MAE: %.4f\n', rmse, mae); % 画图 figure; plot(YTrue, 'b-', 'LineWidth', 1.5); hold on; plot(YPred, 'r--', 'LineWidth', 1.5); legend('真实值', '预测值'); xlabel('样本序号'); ylabel('数值'); title('LSTM时间序列预测结果'); grid on;逻辑说明:predict返回归一化后的预测值,用训练集的归一化参数反归一化。RMSE和MAE是常用指标,RMSE对大误差更敏感。画图能直观看出预测曲线是否滞后、是否平滑过度。如果预测曲线明显滞后于真实值,说明窗口长度可能太短,或者网络没学到趋势。如果预测曲线过于平滑,可能是dropout太大或隐藏单元太少。参数方面,denormalizeFunc必须和训练时一致,否则数值对不上。
4. 避坑与排查:LSTM时间序列预测的5个血泪教训
4.1 现象:训练损失正常下降,验证损失剧烈震荡
原因:批大小太小,或者学习率太高。时间序列样本之间相关性高,小批量采样时梯度方向波动大。解决:把MiniBatchSize从16调到64或128,学习率从0.01降到0.005或0.001。如果还震荡,加GradientThreshold,设成1。
4.2 现象:预测曲线整体偏移,所有预测值都偏高或偏低
原因:归一化参数用错了。比如用全体数据算最大最小值,而不是只用训练集。或者反归一化时用了错误的参数。解决:检查归一化和反归一化是否用同一组参数,且这组参数只来自训练集。另外,如果数据有趋势,最大最小归一化会把趋势压缩,可以考虑差分后再归一化。
4.3 现象:多步预测时误差累积,越往后越离谱
原因:直接多步预测(直接输出未来多个点)比滚动预测更难,误差会累积。解决:改用滚动预测,每次预测一个点,然后把预测值追加到输入窗口末尾,再预测下一个点。但要注意,滚动预测会放大误差,所以单步预测精度必须足够高。另一种做法是训练多个模型,每个模型预测一个步长。
4.4 现象:训练集RMSE很低,测试集RMSE高出一大截
原因:过拟合。数据量太少、网络太大、dropout太小。解决:增加数据量(如果可能),减小隐藏单元数,增大dropout,加L2正则化。MATLAB的lstmLayer没有直接的正则化参数,可以在fullyConnectedLayer后加dropoutLayer,或者用trainingOptions里的L2Regularization。
4.5 现象:训练进度图里验证损失一直不下降,早停触发太早
原因:验证集划分不合理,或者验证集太小。解决:调整划分比例,验证集至少占总数据的10%到15%。如果数据量本来就少,可以用交叉验证,但MATLAB里手动实现较麻烦。另一个原因是学习率太低,网络还没学动就停了,可以适当提高初始学习率。
5. 进阶技巧:用滚动预测和超参数搜索把精度再提一档
滚动预测是时间序列预测里最接近真实部署的方式。训练时用单步预测,预测时每次只输出一个点,然后把预测值拼接到输入序列末尾,再预测下一个点。这样做的好处是模型始终看到最新的历史信息,缺点是误差会累积。为了缓解累积误差,可以在每次滚动时用真实值校正,但真实值在预测时不可得,所以只能靠模型自身。下面是一个滚动预测的代码片段。
% 滚动预测,预测未来 numSteps 个点 numSteps = 50; inputWindow = testNorm(1:windowSize); % 初始窗口 predicted = zeros(1, numSteps); for i = 1:numSteps % 调整输入维度 inputLSTM = reshape(inputWindow, [1, windowSize, 1]); predNorm = predict(net, inputLSTM); predicted(i) = predNorm; % 更新窗口:去掉第一个点,追加预测值 inputWindow = [inputWindow(2:end); predNorm]; end % 反归一化 predictedActual = denormalizeFunc(predicted);逻辑说明:每次预测一个点,然后把预测值追加到窗口末尾,窗口向前滑动。reshape把窗口变成LSTM需要的三维格式。注意:滚动预测的初始窗口必须来自测试集,不能用训练集,否则评估不真实。参数方面,numSteps根据需求设定,但不要超过测试集长度。如果预测步长很长,误差会明显累积,可以考虑每隔几步用真实值重置窗口,但这在实际预测中不可行。
超参数搜索方面,MATLAB没有内置的自动调参工具,但可以手动写循环。常见做法是网格搜索窗口长度、隐藏单元数、dropout率和学习率。比如窗口长度取[30, 60, 90],隐藏单元数取[32, 64, 128],dropout取[0.1, 0.2, 0.3],学习率取[0.001, 0.005, 0.01]。每组参数训练一次,记录验证集RMSE,选最好的。但这样计算量很大,建议先用粗网格,再在最优附近细调。我一般会先固定其他参数,单独调窗口长度,因为窗口长度对结果影响最大。窗口太短,模型看不到趋势;窗口太长,训练样本变少,容易过拟合。另一个技巧是使用Experiment Manager应用,MATLAB里可以并行跑多组参数,但需要Parallel Computing Toolbox。如果没有,就手动写循环,耐心跑。
最后说一个我自己的习惯:每次训练完,先把预测曲线和真实曲线画在一起,看滞后和偏差。如果滞后明显,优先调窗口长度;如果偏差明显,优先查归一化。不要一上来就调网络结构,数据层面的问题往往比模型层面更致命。希望帮到你。
本文还有配套的精品资源,点击获取