简介:基于MATLAB实现的长短期神经网络(LSTM)测量误差预测源码包,面向需要处理时间序列数据建模与误差分析的科研、工程人员,也适合本科及以上学生的课程设计与毕业设计。资源内含完整的.m脚本,覆盖数据读取、LSTM网络构建、训练、预测与测试全流程,并配套MSE、RMSE、MBE、MAE、决定系数R²等误差评价指标的计算代码,便于用户从多个维度量化评估模型预测精度。压缩包共8个文件,以MATLAB源码为主,辅以xlsx/csv格式的训练与测试数据、mat格式的原始数据集,以及结果对比图片,整体仅1.24MB,文件类型清晰、目录结构简明,可快速定位所需模块。所有代码均已添加详细注释,理解成本低,扩展性好,可直接运行或改编用于测量误差、碳排放量等常见时间序列预测任务。目前已有137人学习下载,是一份实用的LSTM时间序列预测参考样例。
1. LSTM 测量误差预测:为什么是它而不是 BP 神经网络
做过误差补偿的人大概率遇到过这种场景:手里有一批连续采样的测量误差序列,想用神经网络预测下一点的误差值,好在测量结果上做实时修正。拿 BP 网络上去跑,训练集表现不错,测试集却经常出现整体滞后、预测值像“抄作业”一样贴着上一时刻真值的现象。原因倒不是调参不够,而是 BP 把每个采样点当成独立样本,误差在时间上的相关性完全没有被利用。LSTM 通过遗忘门、输入门、输出门维护一条内部时序记忆,让当前预测能“看到”前 20 步甚至 200 步的变化趋势,这正好是测量误差这类时间序列预测需要的结构。这份基于 MATLAB 的 LSTM 测量误差预测资源,带完整代码、训练测试数据与误差指标脚本,适合想快速跑通 LSTM 时序预测的工程师,也适合拿现成数据完整复现一遍建模流程的入门者。
2. 文件结构与数据格式:xlsx、mat、csv 三个入口怎么对齐
拿到这份资源,第一件事不是打开 main2.m 逐行读,而是先列一遍文件清单,搞清楚数据从哪里进来、结果从哪里出去。我按运行顺序把文件排了一遍,避免上来就对着代码发怵。
2.1 文件清单:每个文件到底负责什么
| 文件 | 职责 | 关键输出 |
|---|---|---|
| main2.m | 主程序入口:读数据、构造滑窗样本、建 LSTM、训练、预测、导出结果 | 模型变量、结果.csv |
| R_2.m | 计算决定系数 R² | R² 数值 |
| MSE_RMSE_MBE_MAE.m | 计算 MSE、RMSE、MBE、MAE 四类误差指标 | 指标汇总 |
| 训练和测试数据.xlsx | 训练/测试数据集,按工作表分放 | 原始时序数据 |
| maydata.mat | MATLAB 格式数据文件,变量已命名 | 训练/测试输入与标签 |
| 结果.csv | 预测值、真实值与指标导出 | 验收入口 |
main2.m 是唯一需要直接运行的脚本,另外两个指标脚本以函数形式被调用,也可以单独对一组预测值手动调用。第一次跑,我建议在编辑器里用节选运行一段一段执行,而不是一键跑完:训练前先确认数据读进来了、滑窗维度没问题,真出问题能定位到具体某一步。数据文件存在两份等价的输入,xlsx 和 mat,main2.m 默认用哪个要看读文件那几行注释;我的习惯是优先用 mat,变量名和维度都固定,省去对列的麻烦。结果.csv 是训练全部完成后才生成的,里面逐条列出预测值和真实值,方便拖进 Excel 直接看图。
提示:解压后路径里不要带中文目录名,部分版本 MATLAB 读写文件会失败,整个文件夹放到纯英文路径下再运行。这个动作能挡掉一批最基础的环境问题。
代码包里注释写得比较全,适合拿来二次开发。很多人把 LSTM 当黑匣子,觉得训练结束出一堆指标就够了,但这份资源的价值恰恰在指标脚本和主流程的边界是清晰的,你可以把指标脚本替换成自己的评价函数,也可以把主程序里的数据读取段换掉。先别急着改,先把默认的 xlsx 或 mat 入口跑通,确认结果.csv 能正常生成,再动结构。
2.2 xlsx 和 mat:两种数据入口的处理差异
两种入口各有适用场景,我用一个代码块把读法摆在一起看差异。
% 方式一:从 Excel 读入,Sheet 分别存训练和测试 train_raw = readmatrix('训练和测试数据.xlsx', 'Sheet', '训练'); test_raw = readmatrix('训练和测试数据.xlsx', 'Sheet', '测试'); % readmatrix 在 R2019a 之后可用,老版本用 xlsread % 如果第一行是表头,readmatrix 可能会把表头当数据读进来,务必核验尺寸 % 方式二:从 mat 文件直接读,变量名以实际工作区为准 load('maydata.mat'); % 加载后先用 whos 看看有哪些变量 % 常见命名:trainData / testData 是输入特征,trainLabel / testLabel 是对应的真实误差readmatrix 返回的是纯数值矩阵,比 xlsread 快,但遇到混合单元格会自动补 NaN,所以读完后第一件事是检查 size(train_raw) 和数据预览。mat 文件用 load 直接进工作区,变量名是生成时定死的,我一般先用 whos 扫一遍变量名和维度,确认 trainData 是 N×1 还是 N×M,N 是样本数,M 是特征数;做单变量测量误差预测时,通常是 N×1,每行一个采样时刻的误差值。如果数据里有时间戳列,读进来后记得把时间列剥离,只保留数值列喂给模型,时间戳本身不作为特征输入。
另一方面,Excel 入口的好处是能直接看到数据,方便人工检查;坏处是 Sheet 名、表头行、缺失值只要有一处不一致,脚本就可能跑崩或静默读错。mat 入口的好处是变量名和维度固定,缺点是看不见数据内部长什么样,一旦变量本身有问题不容易发现。建议以 main2.m 默认入口为准,跑通后再切换另一种入口验证结果一致性。
参数说明:两种入口读出的结果应当严格一致,如果发现预测曲线不同,先检查两种入口是否都做了同样的归一化和滑动窗口处理。常见坑是 xlsx 入口多读了一列表头,或者 mat 里的序列方向是列向量而 xlsx 读出来是行向量。LSTM 层要求输入排列是“特征×时间步”或“序列×特征”,方向反了会在训练时报维度错误,代码里一般有 permute 处理,没处理的话自己在数据构造阶段加一行转置。实际跑下来的经验是,因为行列方向不一致导致的翻车,比参数不收敛还要多。
2.3 结果.csv:一条快速验收路径
跑完 main2.m 后打开结果.csv,通常能看到三列以上:真实误差、预测误差、绝对误差。如果脚本把误差指标也写进去了,表格底部会有 MSE、RMSE、MBE、MAE 的汇总行。第一次拿到结果,先别急着看 R² 好不好,先看预测值和真实值的量级对不对——如果真实误差在 0.01 量级而预测值全是 0.5 量级,说明归一化或反归一化环节出了问题。
其次是看残差是否明显偏置,比如预测值整体比真实值小一个固定值,这是 MBE 会暴露的问题。csv 存在的意义是给你一个不用重新跑训练也能检查模型的出口:把 csv 拖进 Excel,拉一张预测对真实的折线图,如果两条线走势贴合、细节有差异,说明模型学到了时序规律;如果两条线几乎重合或者整体延迟一拍,就要回到第 4 章的坑里去查。看完 csv 再去看 main2.m 里的网络结构和训练参数,能省去很多盲调的时间。
3. 从数据到指标:滑窗构造、训练参数与五个误差指标的全流程
时序预测的代码链路比分类问题多两步:划分和反归一化。下面按 main2.m 的常见实现顺序拆,每段都说明为什么这么做。
3.1 数据划分与归一化:测试集不能“偷看”训练集的统计量
时间序列预测第一步是划分,这一步做错后面全是错的。普通机器学习里随机打乱数据没问题,但时序数据一旦 shuffle,等于把未来的点混进训练集,模型变成了“背题”。样本之间必须有严格先后关系,我一般按 8:2 切,训练集用前 80% 的点,测试集用后 20% 的点。
% 按时间顺序划分,先训练后测试,绝不 shuffle trainLen = 800; trainData = raw(1:trainLen, :); testData = raw(trainLen+1:end, :); % 归一化:只在训练集上计算统计量 mu = mean(trainData); sigma = std(trainData); trainNorm = (trainData - mu) ./ sigma; testNorm = (testData - mu) ./ sigma; % 用训练集的 mu/sigma,不是测试集的逻辑说明:trainLen 取多少要看总样本量,经验值是 70%~80%,保留足够多的测试点才能看出模型在未见数据上的表现。归一化的统计量 mu 和 sigma 必须来自训练集,测试集只是套用同一组参数做变换,这一点写进注释里是为了防止后面对齐出错。反归一化时同样要用训练集的 mu/sigma,把预测结果还原到原始量级时才能和真实误差直接比较。
为什么测试集不能用自己算的均值方差:因为“测试集的信息不能参与模型构建”是基本底线。一旦用了测试集的统计量做归一化,测试集的整体分布信息已经通过公式泄露给了模型,测试误差就不干净了,后面算出来的 R² 会虚高。
3.2 滑动窗口:用过去 numTimeSteps 个点预测当前点
LSTM 不是把整条序列一口吞进去,而是通过滑窗切成固定长度的样本对。每次输入过去 numTimeSteps 个时刻的误差值,输出下一个时刻的误差值,这就是监督信号。
numTimeSteps = 20; % 对归一化后的序列构造(输入窗口,目标值)样本对 XTrain = []; YTrain = []; for i = numTimeSteps+1 : length(trainNorm) XTrain = [XTrain; trainNorm(i-numTimeSteps : i-1)']; YTrain = [YTrain; trainNorm(i)]; end % XTrain 每一行是一个长度为 numTimeSteps 的窗口 % YTrain 是对应的下一个时刻目标值逻辑说明:循环里取 i-numTimeSteps 到 i-1 作为输入,取 i 作为目标,这样就形成了错位一拍的监督信号。如果偷懒把输入取到 i、目标也取到 i,模型很快会发现最优解是“把最后一个输入直接复制成输出”,训练指标很好看,实测预测曲线平滑得像延迟线。这个错位是滑窗构造里最容易错也最容易忽略的一步。
参数说明:numTimeSteps 的选择和采样频率、误差变化的快慢有关。波动快的序列取 20~50,变化平缓的可以取 100 以上;窗口太大会引入过多历史噪声,太小则记不住趋势。调试时可以从 10 开始,每次翻倍看测试集 R² 变化,找到一个平台期就停下来。循环里用 double 数组方便理解,数据量大时可以改用 cell 数组配合 minibatch 构造,速度会快不少。
3.3 LSTM 网络与训练参数:怎么配才不会翻车
网络结构在这类单变量回归任务里通常不需要太深,一层 LSTM 加一个全连接输出层就够了。层数堆多只会让训练变慢,并不能稳定提升精度。
numHiddenUnits = 100; layers = [ sequenceInputLayer(1) lstmLayer(numHiddenUnits, 'OutputMode', 'last') fullyConnectedLayer(1) regressionLayer]; options = trainingOptions('adam', ... 'MaxEpochs', 200, ... 'MiniBatchSize', 32, ... 'InitialLearnRate', 0.005, ... 'GradientThreshold', 1, ... 'Shuffle', 'never', ... 'Verbose', true);逻辑说明:sequenceInputLayer(1) 表示每个时间步输入一个特征,也就是当前时刻的误差值。lstmLayer 的 OutputMode 设成 'last',因为滑窗后每个样本输出的是最后一步的预测值,不是逐时间步输出。fullyConnectedLayer(1) 把 LSTM 的隐状态压成单个数值,regressionLayer 对应回归任务的均方误差损失。Shuffle 设成 'never' 是时序任务的硬性要求,默认的 every-epoch 打乱会破坏样本先后关系。
参数说明:InitialLearnRate 0.005 是个比较稳的起点,Adam 优化器配 GradientThreshold 1 能有效防止梯度爆炸,特别是误差序列里有离群点时。MaxEpochs 200 对小数据集足够,数据量大的时候配合验证集提前停止更省时间。MiniBatchSize 32 在 CPU 上也跑得动,如果你只有 CPU 且数据量上千,可以降到 16 减少内存压力。
3.4 误差指标计算:MSE、RMSE、MBE、MAE、R² 分别在看什么
包里把五个指标单独拆成脚本,说明作者也认为指标是验收的一部分,不是附属品。
% 反归一化:把预测结果还原到原始量级 predNorm = predict(net, XTest); pred = predNorm * sigma + mu; true = YTest * sigma + mu; % 五个指标 MSE = mean((pred - true).^2); RMSE = sqrt(MSE); MBE = mean(pred - true); % 正负表示预测偏高/偏低 MAE = mean(abs(pred - true)); SS_res = sum((true - pred).^2); SS_tot = sum((true - mean(true)).^2); R2 = 1 - SS_res / SS_tot;逻辑说明:MSE 对离群点敏感,一个极端预测值会把指标拉得很难看,适合做训练损失。RMSE 和原始数据同量纲,看误差大小最直观。MBE 的符号有价值:为正说明系统性地高估误差,为负说明系统性低估,接补偿逻辑时用来修正方向。MAE 相对稳健,不放大离群点。R² 的语义是模型相对“直接用均值预测”提升了多少,小于 0 说明模型比常数预测还差。
参数说明:如果预测曲线看起来不错但 R² 依然为负,优先检查反归一化用的 sigma 和 mu 是不是训练集那组,以及 YTest 有没有先做同样的归一化。R² 对序列的均值位置很敏感,误差序列本身波动很小、集中在某个常数附近时,R² 天然偏低,这时候 RMSE 和 MAE 比 R² 更有参考价值。
4. 避坑排查:训练不收敛、预测滞后、R² 为负的五个常见原因
LSTM 时序预测的报错大多是同几类问题反复出现,大概率不是模型结构不行,而是数据预处理或参数设置出了偏差。下面五条是我跑同类项目时最常碰到的,按出现频率排,每一条都写了现象、原因、解决。
4.1 loss 在训练中途变成 NaN
现象:前几十轮 loss 正常下降,某一步突然变成 NaN,后面再也回不来。
原因:最常见是学习率偏大导致梯度震荡发散;其次是输入数据里有 NaN 或 Inf 没处理;归一化后仍存在极大离群点也会触发数值不稳定。
解决:把 InitialLearnRate 调回 0.005 以下,GradientThreshold 设 1 截断梯度;读入数据后先跑一句 sum(isnan(raw(:))) 确认没有缺失值。我一般会在训练前用一小段数据先跑 20 轮,确认 loss 稳定下降再上全量,这个习惯帮我省了不少时间。
4.2 预测曲线整体滞后一拍,像“抄”上一时刻的值
现象:预测值和真实值走势几乎一样,但整体向右平移一个时间步,峰值晚到,看起来就是“昨天的值今天再用一次”。
原因:滑窗 target 对齐错误。常见的是把目标设成了当前时刻 i,而输入窗口也结束在 i,模型学到的是输入窗口最后一个值直接复制成输出;另一种可能是原始数据本身做了差分,预测完没做差分还原。
解决:检查 XTrain 第 i 行是 t-numTimeSteps 到 t-1,而 YTrain(i) 必须对应 t。核对办法是打印第一组样本:X 的最后一个元素和 Y 的差值,应当等于相邻时间步的实际变化,而不是 0。如果用了差分,预测结果要累加回去。
4.3 训练集 R² 很高,测试集 R² 为负
现象:训练集 R² 在 0.9 以上,测试集却是 -0.2,预测值甚至不如“用训练集均值糊一个常数”。
原因:两种可能,一是划分时 shuffle 了时序数据造成未来信息泄露,二是归一化时用了整个数据集的均值和方差去变换测试集,相当于测试集的整体位置已经告诉模型了。
解决:严格按时间顺序切分,先算训练集 mu、sigma,测试集只做变换;再检查划分边界处数据有没有突变。确认这两步都没问题后,R² 为负就说明模型泛化能力确实不足,需要回到网络容量和滑窗长度上调整。
4.4 明明有 GPU,训练却慢得离谱
现象:训练进度条显示跑得很慢,任务管理器里 GPU 利用率几乎为零,显存没被占用。
原因:trainingOptions 里 ExecutionEnvironment 没设成 'gpu',或者 GPU 环境初始化失败;老版本 MATLAB 的 LSTM 层默认走 CPU 也有可能。
解决:训练前执行 gpuDevice 确认环境可用,options 里显式加 'ExecutionEnvironment','gpu'。没有 GPU 的机器就减小 MiniBatchSize、降低 MaxEpochs,小数据量用 CPU 也能在几分钟内跑完,不必强上 GPU。
4.5 从 xlsx 读数据后维度对不上,训练直接报错
现象:size(train_raw) 显示 801×2,但数据本身是 801 个时刻的单变量误差,训练时 LSTM 层报维度不匹配。
原因:Excel 里多了表头行或自动加了序号列,readmatrix 把文本转成了 NaN 列,导致多出一列无效特征。
解决:读完后先删除全 NaN 列,一行代码就能处理:train_raw(:, all(isnan(train_raw))) = [];或者改用 readtable 指定数据列。读出来的序列方向也要确认是列向量,LSTM 层对行列排列很敏感,方向反了第一时间报错总比静默算错好。
如果按上面五条排查完,预测曲线还是飘,那大概率回到数据本身:误差序列是否平稳、有没有周期性分段。测量误差数据如果包含明显的趋势项或周期性漂移,先做一阶差分再预测,效果往往比直接硬训要好,这也是我后来调这类项目的一个默认动作。
5. 扩展应用:把误差预测接到补偿逻辑里,换数据只改三处
预测本身不是终点,把预测误差用起来才是利益点。我习惯把 LSTM 的输出接进补偿逻辑,形成“预测—修正—再采样”的闭环。
5.1 误差补偿公式:修正后的测量值才是真正要的
% 假设 y_measured 是带误差的测量值,epsPred 是 LSTM 预测出的误差 y_corrected = y_measured - epsPred; % 前提:误差定义是 测量值 = 真值 + 误差逻辑说明:补偿逻辑要非常注意误差的符号定义。如果包里的数据定义是“测量值 = 真值 + 误差”,那修正时就要减去预测误差;如果定义是“真值 = 测量值 + 误差”或者“误差 = 真值 - 测量值”,修正时就要加回去。这个符号反了,补偿不但没用,还会把误差放大一倍,属于代码逻辑里最容易静默翻车的地方。
参数说明:判断符号对不对有个简单办法,看第 3 章算出来的 MBE。如果 MBE 为正,说明模型整体高估了误差,修正时减去预测值可能会过度修正;MBE 为负则相反。实际接到测量链路上时,建议先在离线数据上做一遍补偿仿真,确认修正后的误差分布比原始误差更集中,再上在线逻辑。
5.2 换成自己的数据:最小改动清单
这套流程不绑死某一类数据。如果你想拿自己的误差序列替换,甚至想换到碳排放量这类长周期时序预测上,只需要改三处:数据读取、trainLen、numTimeSteps。其他结构可以先用默认值。
% 把自己的数据读进来,按时间顺序排列,单变量序列 raw = readmatrix('my_error_sequence.xlsx'); % 第一处:确认 raw 是 N×1 的数值列,去掉时间戳列 % 第二处:调整划分比例,样本多时可以取 90% 训练 trainLen = floor(size(raw, 1) * 0.85); % 第三处:根据数据波动快慢改窗口长度 numTimeSteps = 30; % 其余沿用 main2.m 的 LSTM 结构和训练参数逻辑说明:换数据后最容易被忽略的是缺失值处理。测量序列中间一旦有 NaN,滑窗构造出来的 XTrain 里会出现不连续窗口,模型会把缺失当成正常值学进去,所以读入后先做线性插值或删除整段。碳排放量这类慢变化数据,numTimeSteps 可以适当加大,因为它的自相关尺度比测量误差更长,窗口太小记不住季度性趋势。
参数说明:如果你的数据是 N×M 的多变量序列,sequenceInputLayer(1) 要改成 sequenceInputLayer(M),lstmLayer 输入维度会自动匹配。但多变量场景下要小心特征之间的量纲差异,归一化要按列分别做 mu、sigma,不能用一个标量套所有列。
5.3 参数调优:从“玄学”到有依据
LSTM 参数在很多人眼里是玄学,其实每个参数都有明确的调整方向和边界。
| 参数 | 主要影响 | 典型范围 | 注意事项 |
|---|---|---|---|
| numHiddenUnits | 模型容量 | 50~200 | 容量越大越吃内存,小数据别上 300 |
| InitialLearnRate | 收敛速度与稳定性 | 0.001~0.01 | 配 GradientThreshold 1 防发散 |
| numTimeSteps | 记忆窗口长度 | 10~100 | 与采样周期强相关,先粗扫再精调 |
| MiniBatchSize | 训练稳定性与速度 | 16~64 | 数据量小时取 16 更稳 |
| MaxEpochs | 训练充分度 | 100~300 | 结合验证集看 loss 是否还有下降 |
调参的可靠方法是每次只动一个变量,固定其他参数跑一遍测试集 R²,记录结果。先粗调 numTimeSteps,再调 numHiddenUnits,最后调学习率。我见过不少人同时改三个参数,训练出来不知道是哪个变量起作用,等于白跑。小数据集上训练很快,一次网格扫描 20 组参数也就十几分钟,比凭感觉试靠谱得多。
6. 验证模型:残差自相关与滚动预测的实战检查
R² 和 RMSE 只能告诉你“整体误差有多大”,不能告诉你“模型把时序规律学干净了没有”。两个模型的 R² 可能一样,一个残差里还带着明显的周期波动,另一个残差接近白噪声,后者才是真正学到了误差序列演化规律的模型。所以我跑完训练会额外做两件事:残差自相关检查和滚动预测稳定性检查。
residual = true - pred; % 粗略残差自相关:看 lag-1 相关系数 lag1Corr = corr(residual(1:end-1), residual(2:end)); if abs(lag1Corr) > 0.3 disp('残差还有明显时序结构,模型没吃干净'); end % 滚动预测:每次只推一步,下一步把上一步真值接回窗口 history = XTest(1, :); nextPred = zeros(size(XTest, 1), 1); for i = 2:size(XTest, 1) nextPred(i) = predict(net, history); history = [history(2:end), YTest(i-1)]; % 用真值而不是预测值 end逻辑说明:残差 lag-1 相关系数接近 0 是好现象,说明模型已经把可预测的时序信息榨干了,剩下的只是不可约噪声。如果残差还明显相关,说明滑窗长度不够或者 LSTM 容量不足,可以继续加 numTimeSteps 或 numHiddenUnits。滚动预测和一次性预测的差别在于:一次性预测是拿整段测试集直接喂进去,滚动预测只给第一个真实窗口,后面每一步都把上一步的真实值接回窗口,这样才能看出模型对误差累积的敏感程度。
参数说明:lag1Corr 的阈值 0.3 是我常用的经验线,大于这个值我基本断定模型还有提升空间;小于 0.1 就相当干净了。滚动预测的初始窗口长度必须和训练时的 numTimeSteps 一致,否则 predict 接口会报维度错误。
我自己的教训是:有一版模型训练集 R² 0.95、测试集 R² 0.82,看起来已经很好了,结果残差 lag-1 相关高达 0.6,一查发现滑窗目标对齐错了,模型学的是“抄上一时刻”。从那以后,我每次跑 LSTM 时序预测都会强制走一遍残差自相关检查,这个动作拦下过至少三次看着指标漂亮、实际没学到规律的假模型。希望这个检查习惯也能帮到你的项目。
本文还有配套的精品资源,点击获取