news 2026/10/12 15:13:36

MATLAB+Elman神经网络:Excel时间序列预测完整流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MATLAB+Elman神经网络:Excel时间序列预测完整流程

直接上干货。手里有一份Excel表格,第一列是时间序列,后面几列是特征数据,想用MATLAB训练一个Elman神经网络做预测。这个需求我在实际项目里碰到过很多次,尤其是一些做设备状态预测、销量预估、能耗分析的朋友,数据都存在Excel里,没有复杂数据库,也不想起Python那套环境,就指着MATLAB一把梭。这篇文章我就把完整流程拆开讲清楚:脚本怎么读Excel、怎么把原始数据变成Elman网络能吃的样本、怎么训练和调参、怎么评估预测效果,最后再把我踩过的坑一个一个列出来。

这篇文章特别适合三类人:一是准备做时间序列预测但更习惯MATLAB的工程师;二是课程设计或毕业设计需要“Excel数据读取+神经网络预测”完整案例的同学;三是手里有大量历史报表、想快速验证预测方案可行性的业务分析人员。不用懂太多理论基础,跟着脚本走就能把预测跑起来。

1. 整体思路与方案选型

1.1 为什么选Elman神经网络而不是普通BP网络

先说结论:预测时间序列,Elman比普通BP网络更合适。原因在于Elman是一种递归神经网络,它在隐藏层旁边多了一组“上下文单元”,会把上一时刻隐藏层的输出缓存下来,再作为当前时刻的输入一起送进网络。说白了,网络内部自带了一个“记忆”,能感知到数据在时间上的先后关系。

普通BP网络是前馈结构,它假设每个样本之间是独立的,输入和输出之间是一个静态映射。但时间序列不一样,今天的气温、销量、负载值,往往和昨天、前天甚至上周的数据有关。你用BP网络硬套,相当于让一个没有记忆的人做连续推理,他每次只能看到当前这一步,前面发生过什么全忘了。而Elman网络会把前面几步的“印象”保留下来,再结合当前特征做判断。用一个生活化的类比:BP像临时工,每次接到任务都从零开始;Elman像老师傅,手里有个工作笔记,能参考以往经验。

所以在MATLAB里做时间序列预测,Elman是一个性价比很高的选择。它比LSTM结构简单,训练速度快,数据量不大时效果也不差;比BP网络又更贴合时间序列的本质。如果你要预测的对象是负荷、温度、销量、流量这类有明显时间依赖的数据,Elman值得优先试。

1.2 为什么用Excel加脚本而不是写死数据

很多人一开始图省事,直接在MATLAB代码里写一个数组,比如data = [1,2,3,4,5]。数据量小的时候无所谓,但一旦数据变成几百行、几千行,或者业务方隔几天就要更新一次数据,这种写法就是灾难。

Excel在这里最大的价值是“可维护性”。业务人员可以像平时填报表一样维护数据,不需要懂MATLAB;你只需要保证列的顺序和约定,脚本一次写好,之后每次跑预测只改文件路径或者文件名。这样就把“数据更新”和“模型训练”彻底解耦了。脚本的作用则是把这条流水线自动化:读取、清洗、建模、评估、出图,一键完成。

整个流程概括起来就是:Excel作为数据源,脚本作为流水线,Elman网络作为预测引擎。三个角色各司其职,缺一不可。这样的结构还有一个好处:以后想换模型,比如换成LSTM或者随机森林,只需要替换中间的训练部分,前面的数据读取和后面的评估代码都可以复用。

1.3 整体流程拆解

我习惯把整个流程分成七步,每一步都有明确的输入和输出:

  1. 准备Excel文件,约定好列结构,第一列是时间序列,后面各列是特征数据。
  2. 用MATLAB脚本读取Excel,检查数据完整性,处理缺失值。
  3. 用滑动窗口把原始时间序列构造成“输入样本+目标值”的监督学习格式。
  4. 对数据进行归一化,并按照时间顺序划分训练集和测试集。
  5. 构建Elman神经网络(新版MATLAB用layrecnet实现),设置隐层节点数和训练参数。
  6. 训练网络,保存训练好的模型。
  7. 对测试集做预测,反归一化,计算误差指标,绘图对比。

下面的章节就按这个顺序一步步来。

2. 数据准备与脚本读取Excel

2.1 Excel表结构怎么设计最省心

先说表格结构。既然标题里已经说明了:第一列放时间序列,其他列放特征数据。那我建议你按下表这种格式设计:

时间戳特征1(温度)特征2(湿度)目标值(销量)
2024-01-0112.543320
2024-01-0213.247356
2024-01-0311.851289

第一列可以是日期,也可以是纯序号1、2、3。如果第一列是日期,脚本里可能不会直接把它当作数值特征使用,更多是作为画图时的横轴;如果你想让日期参与计算,可以先用datenum转成序列号,但一般情况下不建议这么做,因为日期本身并不包含太多预测信息。后面列可以是特征,也可以是目标变量。如果你要预测的是某个具体指标,建议把目标值单独放在最后一列,这样读取之后操作起来最清楚。

我在实际中经常遇到一种情况:有人把Excel文件第一行写了表头,有人没写;有人的第一列是日期,有人第一列就是序号;还有人的Excel里混着空的单元格、甚至中文备注。这些不统一的地方,才是脚本读取时最容易翻车的地方。所以我的建议是,拿到的Excel先手动看一眼前几行,做到心里有数,再决定用readtable还是readmatrix,以及读取后要不要跳过表头。

2.2 用脚本读取Excel的两种写法

MATLAB读取Excel的常用函数有readtable、readmatrix和xlsread。xlsread比较老,新版MATLAB不推荐用,虽然还能跑,但读取速度和处理灵活性都一般。我主要说前两个。

如果你的Excel第一行是表头,我优先推荐readtable。它会自动把第一行识别成变量名,读进来的是一个table对象,用列名访问数据非常直观:

% 读取Excel dataTable = readtable('data.xlsx'); % 查看前几行,确认列名和数据格式 head(dataTable) % 提取数值部分:忽略第一列时间,把后面的特征列转为矩阵 % 假设第一列叫"时间戳",最后一列叫"销量",中间是特征 features = dataTable{:, 2:end-1}; % 特征列 target = dataTable{:, end}; % 目标列 timeStr = dataTable{:, 1}; % 时间列,画图用

如果你的Excel里全是数值,没有表头,或者第一行就是数据,那直接用readmatrix更省事:

% 读取纯数值Excel,第一列是时间序号,其他列是特征 data = readmatrix('data.xlsx'); % 查看数据的尺寸和大致内容 size(data) disp(data(1:5, :)) % 拆分数据 timeSeq = data(:, 1); % 第一列:时间 featMat = data(:, 2:end); % 后面的列:特征或目标

这里有个细节要特别注意:readmatrix会把所有内容按数值矩阵读入,如果Excel里有中文表头或混合文本,读取时会直接报错或者把那一列变成NaN。所以用readmatrix的前提是数据必须干净、纯数值。相比之下,readtable容错性更好,遇到非数值内容会填入NaN,不会轻易中断运行。

还有一个使用频率极高的坑:文件路径。很多人把Excel放在桌面上,MATLAB当前工作目录还在默认的matlab文件夹里,一运行就报“无法读取文件”。解决办法很简单,要么把Excel复制到当前目录,要么用绝对路径:

% 绝对路径方式 data = readmatrix('C:\Users\yourname\Desktop\data.xlsx');

2.3 数据清洗与滑动窗口样本构造

Excel数据不能指望它天生完美。最常见的几种问题:缺失值、异常值、重复值。在喂给神经网络之前,一定要处理。

处理缺失值我一般用fillmissing。时间序列的缺失值不要直接删掉,因为删掉会破坏时间连续性,更好的办法是插值:

% 对特征矩阵按列填充缺失值,线性插值 featMat = fillmissing(featMat, 'linear', 1);

如果是明显的异常跳变,比如温度突然从20度跳到80度,可以用移动中位数或者自己设定阈值去清洗。这一步看起来很基础,但直接影响预测效果。神经网络不挑数据,你喂什么它学什么,脏数据学出来的只能是脏模型。

清洗完之后,就到了最关键的环节:把原始时间序列构造成神经网络能训练的样本对。Elman虽然能记住历史信息,但并不意味着你直接把一条序列丢给它,它就能神奇地预测未来。标准的做法是“滑动窗口”,用过去的若干个时刻预测下一个时刻。

举个例子:假设你只有目标值这一列数据,序列是y1, y2, y3, y4, y5, ...。如果窗口大小设为3,那么训练样本就是:

  • 输入:[y1, y2, y3],输出:y4
  • 输入:[y2, y3, y4],输出:y5
  • 输入:[y3, y4, y5],输出:y6

这就把时间序列预测问题转化成了标准的监督学习问题。如果你Excel里还有额外特征,比如温度、湿度,也可以把对应的历史值拼到输入向量里,让模型有更多信息可用。

窗口大小(lag)的选择是有讲究的。太小,模型看不到足够的趋势;太大,输入维度暴涨,训练慢,还容易过拟合。我一般先试3、5、7,再根据效果调整。如果你有足够多的数据,还可以用自相关函数(autocorr)看几步滞后相关性最强,再决定窗口大小。

构造样本的脚本如下:

function [X, Y] = createSlidingWindow(data, lag) % data: 列向量,目标序列 % lag: 窗口大小 n = length(data); X = zeros(n - lag, lag); Y = zeros(n - lag, 1); for i = 1 : n - lag X(i, :) = data(i : i + lag - 1)'; Y(i, :) = data(i + lag); end end

如果你有多个特征,可以把每个特征的滞后值横向拼接。拼接后的每一行就是模型的一个输入样本,每一行对应一个目标值。

3. Elman神经网络搭建与训练

3.1 Elman网络结构在MATLAB里的实现方式

很多教程会让你直接用newelm,但这里必须提醒你:新版MATLAB的神经网络工具箱已经把这个函数移除了。你在新版本里运行newelm,大概率会直接报错“未定义函数或变量”。这是因为MathWorks在R2010a之后重构了神经网络工具箱,旧版API大量废弃。如果你打开的是新版本MATLAB,推荐用layrecnet,它是官方提供的层递归网络,核心思想和Elman完全一致:隐藏层的输出经过一个延迟器反馈到隐藏层输入,形成短期记忆。

layrecnet的基本用法如下:

% 构建一个带反馈的层递归网络 hiddenSizes = 12; % 隐层节点数 net = layrecnet(1, hiddenSizes, 'tansig');

第一个参数1表示反馈延迟为1步,也就是说网络会记住上一个时刻的隐层状态。这正好对应Elman网络经典的“上下文单元”。隐层激活函数用tansig,输出层默认是线性函数。如果你真想手动搭建一个Elman结构,也可以用network函数自己一个个节点接,但新手不建议这么做,直接用layrecnet就够了,底层原理一样,调试起来还省心。

训练之前还要注意一个非常关键的问题:layrecnet默认的数据划分方式是随机划分(dividerand)。时间序列最忌讳随机划分,因为那等于让模型偷看未来的数据。训练时看到的“测试集”如果混在训练集之间,评估结果会虚假地好看,但真正做预测时立刻现原形。所以必须把划分方式改成按顺序划分:

net.divideFcn = 'divideblock';

这句代码的意思是把样本按顺序切块,前一段训练、后一段测试,保持时间上的因果关系。

3.2 数据归一化与训练/测试集划分

数据不进模型之前,必须先归一化。神经网络对输入数据的尺度很敏感,如果特征数值范围差异太大,比如温度是十几、销量是几百、有的特征可能是零点几,模型训练时大数值的特征会主导梯度更新,小数值特征等于被淹没了。

归一化我用mapminmax,把数据缩放到[-1, 1]区间:

% X为输入矩阵,每行是一个样本,每列是一个特征 [Xnorm, Xps] = mapminmax(X_train', -1, 1); [Ynorm, Yps] = mapminmax(Y_train', -1, 1); % 注意:mapminmax默认按行处理,所以这里要转置 % 训练完成后,反归一化要用训练时保存的ps结构体

这里有一个很多人容易踩的坑:归一化参数只能从训练集上计算,然后用同一套参数去变换测试集。千万不要对训练集和测试集分别做mapminmax,因为那样测试集的分布信息就泄露到模型评估里了,评估结果失真。正确做法是把训练集的Xps和Yps保存下来,后面测试集预测完再用它反归一化。

训练/测试集的划分,时间序列必须按时间顺序来。比如数据一共1000个样本,可以取前800个训练,后200个测试。不能用randperm打乱。这一点我在实际评审别人代码时见过太多次,很多同学习惯性地把分类问题的随机划分带到时间序列里,结果模型表现虚高,落地一测就崩。

% 按时间顺序划分 trainLen = floor(size(X, 1) * 0.8); X_train = X(1:trainLen, :); Y_train = Y(1:trainLen, :); X_test = X(trainLen+1:end, :); Y_test = Y(trainLen+1:end, :);

3.3 模型参数设置与训练实操

数据准备好,网络选好,接下来就是训练。下面给出一段完整可复现的脚本,涵盖读取Excel、构造样本、归一化、训练和保存模型:

%% 1.读取Excel数据 data = readmatrix('data.xlsx'); values = data(:, 2); % 假设第二列是目标值,第一列是时间 feat = data(:, 3:end); % 如果有其他特征,从第三列开始 %% 2.数据清洗 values = fillmissing(values, 'linear'); %% 3.滑动窗口构造样本 lag = 5; [X, Y] = createSlidingWindow(values, lag); % 如果有其他特征,也可以拼进来 % X = [X, feat(lag+1:end, :)]; %% 4.划分训练集和测试集(按时间顺序) trainLen = floor(length(Y) * 0.8); X_train = X(1:trainLen, :); Y_train = Y(1:trainLen, :); X_test = X(trainLen+1:end, :); Y_test = Y(trainLen+1:end, :); %% 5.归一化 [Xnorm, Xps] = mapminmax(X_train', -1, 1); [Ynorm, Yps] = mapminmax(Y_train', -1, 1); Xnorm = Xnorm'; Ynorm = Ynorm'; % 注意:测试集也用训练集的ps做变换 Xtest_norm = mapminmax('apply', X_test', Xps); Xtest_norm = Xtest_norm'; %% 6.构建Elman网络(layrecnet) hiddenSizes = 15; net = layrecnet(1, hiddenSizes, 'tansig'); net.trainFcn = 'trainlm'; % Levenberg-Marquardt训练算法 net.trainParam.epochs = 500; % 最大迭代次数 net.trainParam.goal = 1e-5; % 目标误差 net.trainParam.min_grad = 1e-6; net.divideFcn = 'divideblock'; % 保持时间顺序划分 %% 7.训练网络 [net, tr] = train(net, Xnorm', Ynorm'); %% 8.保存模型 save('elman_model.mat', 'net', 'Xps', 'Yps', 'lag');

训练函数我首选trainlm(Levenberg-Marquardt),它收敛快、精度高,对小规模数据非常友好。但要注意它非常吃内存,如果样本量很大(比如几万条),trainlm可能让内存直接爆掉,这时候可以换成trainscg(量化共轭梯度法),内存占用小,速度也不慢。

隐层节点数也是一个需要反复试的参数。节点太少,网络表达能力不够,欠拟合;节点太多,模型把训练集的噪声都背下来了,测试集上表现得一塌糊涂。我给的经验是:先试12到20个节点,看训练误差和测试误差的差距。如果差距明显,说明过拟合了,减小节点数,或者把net.trainParam.mu调大一点增加正则化效果。没有万能参数,只有反复对比。

4. 预测效果评估与可视化

4.1 预测结果的反归一化

训练完成之后,拿测试集输入去预测,得到的是归一化之后的输出,必须把它还原成原始量纲,才能和真实值做对比。反归一化的时候,一定要使用训练时保存下来的Yps参数:

%% 测试集预测 Y_pred_norm = sim(net, Xtest_norm'); %% 反归一化 Y_pred = mapminmax('reverse', Y_pred_norm, Yps); Y_pred = Y_pred'; % 真实的测试目标值(之前没有归一化过) Y_true = Y_test;

反归一化这一步看着简单,实则是评估环节里最容易出错的点。有人图省事,直接对预测值再调一次mapminmax,那等于把预测结果重新映射到一个完全不同的尺度,数值全乱了。记住:归一化是一次性的,反归一化用的必须是同一个ps结构体。

4.2 用RMSE、MAE、MAPE三种指标评估

光看曲线不好量化模型好坏,还需要数值指标。我常用三个:RMSE(均方根误差)、MAE(平均绝对误差)、MAPE(平均绝对百分比误差)。它们的计算方式如下:

err = Y_true - Y_pred; RMSE = sqrt(mean(err.^2)); MAE = mean(abs(err)); MAPE = mean(abs(err ./ Y_true)) * 100; fprintf('RMSE: %.4f\n', RMSE); fprintf('MAE: %.4f\n', MAE); fprintf('MAPE: %.2f%%\n', MAPE);

三个指标各有侧重点。RMSE对大误差更敏感,因为它先平方再开方,一个特别离谱的预测会把RMSE拉得很高,适合用来发现“严重偏差”;MAE是平均绝对误差,更直观,量纲和原始数据一致;MAPE是百分比误差,方便跨数据集对比,但它的致命弱点是当真实值接近0时,MAPE会直接变成无穷大。如果你的目标值本身就是小数值或者可能为0,建议改用SMAPE或者只报告RMSE和MAE。

我自己做评估时,一般先看RMSE,再看MAPE,两个都合理才觉得模型靠谱。举个例子,假设销量预测的真实值和预测值对比下来,RMSE在30左右、MAPE在8%以内,那这个模型就有实用价值了。

如果需要更全面的拟合优度,还可以算一下R²(决定系数),也就是模型解释了多大比例的真实波动:

SS_res = sum((Y_true - Y_pred).^2); SS_tot = sum((Y_true - mean(Y_true)).^2); R2 = 1 - SS_res / SS_tot;

R²越接近1说明拟合效果越好,一般超过0.9就可以接受。

4.3 绘制预测对比图

数字指标只能给出一个总体印象,真正判断模型好坏还得看图。把训练集和测试集的真实值画成一条线,再把预测值叠加上去,一眼就能看出模型在趋势拐点处是不是跟得上。

figure; plot(1:length(Y_true), Y_true, 'b-', 'LineWidth', 1.5); hold on; plot(1:length(Y_true), Y_pred, 'r--', 'LineWidth', 1.5); legend('真实值', '预测值'); xlabel('样本序号'); ylabel('目标值'); title('Elman神经网络测试集预测结果对比'); grid on;

如果你的Excel第一列是日期,还可以把日期作为横轴画出来,实用性更强。这里有一个中文乱码的小技巧:如果图例或坐标轴中文显示成了方框,可以加一行设置中文字体:

set(gca, 'FontName', 'SimHei');

画完图之后,我会特别留意预测曲线和真实曲线在波峰、波谷处的对应关系。如果预测曲线整体上比真实曲线“慢半拍”,也就是所谓的滞后现象,那说明模型学到的主要是惯性,对突变不敏感。这种情况下,要么增加窗口长度,要么把其他相关特征(比如节假日标记、天气特征)加入输入。

5. 常见问题排查与避坑

5.1 常见问题速查表

下面这张表是我长期使用MATLAB做时间序列预测时整理出来的高频问题,按报错信息或现象快速定位问题来源。

问题现象可能原因解决方案
错误使用 network/sim输入输出维度不匹配检查Xnorm和Ynorm的行列方向,确保train和sim时输入维度一致
无法读取文件 'data.xlsx'Excel不在当前工作目录使用绝对路径,或cd切换到文件所在目录
newelm未定义新版MATLAB已移除旧API改用layrecnet,核心思想一致
MAPE为Inf或异常大真实值中存在接近0的数值改用SMAPE或只报告RMSE/MAE
训练误差很小但测试误差巨大过拟合或数据泄露减少隐层节点数,检查是否用了随机划分
预测曲线整体滞后模型对突变不敏感增大滑动窗口,或增加外生特征
mapminmax输出维度混乱没有注意mapminmax按行处理输入输出都使用行向量,必要时转置
每次训练结果都不一样网络初始化随机训练前加rng('default')固定随机种子

5.2 被忽略但很重要的三个细节

第一,Excel里的日期列。如果第一列是日期文本,用readmatrix会直接读成NaN,因为日期不是纯数值;但用readtable能读成datetime类型,画图时可以直接用。所以我的建议是:有日期就优先readtable,并单独把日期列提取出来作为画图横轴,不要强行参与模型计算。

第二,时间序列千万不要随机划分数据集。我用divideblock就是为了防止训练集和测试集在时间上交错。如果模型在训练过程中见过未来数据,评估指标会显得很漂亮,但真正上线预测时效果会大打折扣。这一点几乎是时间序列新手最容易犯的错。

第三,训练前加一行rng('default')。神经网络每次初始化权重都是随机的,如果不固定随机种子,你调参时很难判断效果变好是参数动了还是纯粹运气好。固定种子之后,每次跑出来的结果一致,调参才有可比性。

5.3 两个我反复踩过的实战教训

一个是数据质量问题。有一次我拿一份生产车间的传感器数据做预测,Excel里有大量缺失值和异常跳变,当时图省事没清洗,直接送进网络训练。结果RMSE大得离谱,检查才发现有一段传感器的值连续一周都是同一个常数,模型以为这里是平台期,后面真实值突变时预测完全跟不上。从那以后我所有的数据都会先画一遍折线图,肉眼扫一遍异常,再进入建模流程。

另一个教训是归一化参数的保存。早期我训练完之后没有保存Xps和Yps,预测时重新计算了一次,导致反归一化之后的结果和训练时的尺度不一致,看起来误差特别大,查了很久才发现是归一化参数前后不统一。所以现在我的脚本一律把net、Xps、Yps、lag一起存成.mat文件,下次预测时直接加载,保证流程是一致的。

我个人实际用下来最大的体会是:这套流程里最花时间的地方往往不是模型调参,而是数据预处理和样本构造。Elman网络本身并不复杂,数据喂得干净、窗口构造合理、训练测试不串时间,预测效果基本不会差到哪里去。如果你已经跑通了单步预测,接下来可以试试多步递推预测,或者用真实值滚动更新输入,再进阶一点可以尝试小波分解加Elman的组合。这些都是在今天这套基础脚本上做的扩展,先把底子打好,后面的路会顺很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 15:08:05

编译原理实验C++实现:从词法分析到中间代码生成的完整路径

简介:面向杭电编译原理课程的实验源码包,完整覆盖词法分析、NFA到DFA的子集构造法、递归下降分析及LL(1)语法分析等核心模块,适合正在学习编译器构造、需要课程设计参考或备战实验考核的计算机专业学生。压缩包内共11个文件,包括4…

作者头像 李华
网站建设 2026/10/12 15:04:14

FastReport批量打印实战:从循环直打到队列调度的稳定方案

1. 批量打印这件事,为什么值得单独拎出来说做过企业信息化的人都有一个共识:报表模块往往是项目里最不起眼、但上线后挨骂最多的部分。单张打印谁都会做,拖一个控件、绑一个数据源、点一下预览,完事。可一旦业务方甩过来一句“这批…

作者头像 李华
网站建设 2026/10/12 15:01:29

LangChain社区实用组件盘点:文档问答系统落地实战

LangChain社区这块地方,越逛越觉得藏了不少好东西。大多数人刚开始接触LangChain时,基本只碰官方文档里出现频率最高的那几样:链、提示模板、输出解析器,再套一个模型就算完事。但如果真在社区里多泡上一段时间,你会发…

作者头像 李华
网站建设 2026/10/12 15:01:24

ARouter服务发现机制:组件化架构下获取Fragment的最佳实践

1. 为什么要通过路由拿Fragment而不是直接new1.1 组件化架构下的依赖困境先说一个很典型的场景。如果你所在的App是组件化架构,主工程需要加载一个来自“用户模块”的Fragment,而主工程在编译期不应该依赖用户模块的代码,因为那样一来所谓的组…

作者头像 李华
网站建设 2026/10/12 14:55:19

SSA优化CNN-BiLSTM时序回归:Matlab轻量级超参调优实战

简介:本资源是一套面向机器学习与智能预测方向初学者及进阶研究者的Matlab实战代码包,聚焦于多输入单输出回归预测任务,特别适用于时间序列预测、能源负荷预测、环境参数建模等实际场景。资源基于麻雀优化算法(SSA)对C…

作者头像 李华