资讯动态

TCN+LSTM+Attention多变量时间序列预测的Matlab实现与调参实践

发布时间:2026/9/11 19:42:31 来源:尧图企业网站定制
简介面向大学生课程设计与毕业设计的TCN-LSTM-Attention多变量时间序列预测Matlab完整实现融合时间卷积网络、长短期记忆网络与注意力机制适用于输入多个特征、考虑历史影响并输出单个变量的回归预测任务。压缩包共8个文件涵盖4个m格式源码文件、风电场预测Excel数据集、指标说明文本和效果示意图整体仅4.34MB轻量易用。已有118人学习/下载。代码采用参数化编程结构清晰、注释详细所有文件放于同一文件夹运行主程序即可完成数据读取、训练、预测和绘图并在命令窗口输出R2、MSE、MAE、MAPE等多项评价指标便于客观评估模型性能。读者可据此快速复现实验也可替换数据集、调整超参数开展二次开发是课程设计、期末大作业与毕业设计中算法对比和案例分析的高质量参考特别适合需要快速搭建深度学习预测模型的在校学生。1. 为什么把TCN、LSTM和Attention叠在一起做多变量时间序列预测做电力负荷、气象要素、水文径流这类多变量预测时最常见的困境是单用LSTM序列拉到一两百步后早期信息衰减训练也慢单用TCN靠膨胀因果卷积能覆盖很长历史却没有显式的记忆单元去建模变量之间的递进关系单加Attention又需要对时序状态有意义的中间表示。把三者串起来等于把问题拆成三段TCN先做局部特征提取与长程感受野展开LSTM承接TCN输出的特征序列继续压缩时间依赖Attention在最后对每个时间步加权让模型决定“过去哪几步对预测未来最有用”。这个结构在Matlab里完全可用深度学习工具箱实现但坑也不少数据格式、因果padding、注意力层的自定义写法每一步都可能让训练结果从可复现变成玄学。这篇文章按模型拆解、数据构造、训练调参、进阶验证的顺序把这条链路讲透。2. 先拆模型TCN、LSTM、Attention在多变量预测里各管哪一段2.1 TCN的膨胀因果卷积怎么做到“看得远又不看未来”TCN的核心是膨胀因果卷积dilated causal convolution。因果的含义是输出t时刻的值时卷积核只能访问t、t-d、t-2d这些历史位置不能访问未来膨胀的含义是卷积核在时间轴上每隔d个点取一个值d按层数指数增长。感受野的计算公式是R 1 sum_{i1..L} (K_i - 1) * d_i假设卷积核大小K3三层膨胀率分别为1、2、4则感受野等于1 2*(124) 15。这意味着第3层每个输出点能“看到”前15个时间步的信息而LSTM要处理同样长度需要15个时间步的状态依次传递计算路径更长。TCN和LSTM的第二个关键差异是并行性。LSTM的每个时间步依赖上一步的隐状态必须串行计算TCN每一层都是卷积同一层内所有时间步可以同时计算。在多变量预测里输入维度通常是“特征数×时间步数×样本数”TCN能在特征维和时间维上同时做卷积这对后续LSTM来说是更好的前置特征提取器。Matlab实现TCN时最容易被忽略的是因果padding。普通卷积默认是居中padding会让卷积核看到未来数据因果卷积只在序列左侧补零右侧不补。对膨胀率为d、卷积核大小为K的卷积层左侧需要补的零长度为pad (K - 1) * d在Matlab的layerGraph里我一般写一个极简的自定义层专门做左侧补零classdef causalPadLayer nnet.layer.Layer properties PadSize end methods function layer causalPadLayer(padSize) layer.Name causalPad; layer.PadSize padSize; end function Z predict(layer, X) % X: [C, T, S]C为特征数T为时间步S为样本数 [C, T, S] size(X, 1, 2, 3); pad zeros(C, layer.PadSize, S, like, extractdata(X)); pad dlarray(pad, CBT); Z cat(2, pad, X); % 只在时间维第2维左侧拼接 end end endextractdata(X)在这里只用来创建一个与输入同设备、同数据类型的零张量补零区域不参与梯度计算所以不会破坏反向传播。有了这个层后面再接内置的convolution1dLayer就不需要再去手动算边缘索引了。需要强调补零方向一定不能错补在右侧就变成了“泄漏未来”训练损失会异常低但测试集上立刻崩掉。2.2 LSTM承接TCN的输出负责把时序状态传下去TCN输出的其实还是一段特征序列它没有显式的状态记忆。LSTM在序列建模中承担的是“状态接力”角色通过遗忘门、输入门、输出门决定哪些历史信息保留、哪些丢弃。在Matlab中lstmLayer有三个参数最常被调lstmLayer(64, OutputMode, sequence, StateActivationFunction, tanh, GateActivationFunction, sigmoid)OutputMode必须设为sequence因为后面要接Attention层需要对每个时间步的输出加权如果设成lastLSTM只返回最后一步隐状态Attention就没有可加权的对象。StateActivationFunction默认tanhGateActivationFunction默认sigmoid一般不需要改。一个常被问到的点是这里能不能用双向LSTM如果做的是事后回测或离线分析双向LSTM能利用未来信息准确率更好看但如果要做真正的预测预测时未来数据不存在双向结构在推理阶段必须截断容易引入不一致。多变量时间序列预测默认按单向处理除非你明确知道自己是在做插值或平滑。LSTM的隐藏单元数不需要等于TCN的滤波器数。通常TCN输出通道数多LSTM隐单元少一些起到降维和去噪的作用。比如TCN每层输出64维LSTM隐层设32Attention层再对这32维的每个时间步打分。2.3 Attention在时间步上做加权输出Attention在这里解决的问题是LSTM的最后一个隐状态只是“整段序列的压缩”它默认把最后时刻当作最重要的信息载体这对长序列不公平。加性Attention的做法是对LSTM每个时间步的输出计算一个重要性分数然后加权求和e_t v^T * tanh(W * h_t) alpha_t softmax(e_t) c sum(alpha_t * h_t)其中W是将隐状态映射到注意力空间的权重矩阵v是打分向量。alpha_t就是对第t个时间步的注意力权重。在Matlab中自定义Attention层常见写法是按样本循环计算代码直观代价是批量大时稍慢classdef attentionLayer nnet.layer.Layer properties (Learnable) Wq % 注意力映射矩阵 [A, C] v % 打分向量 [A, 1] end methods function layer attentionLayer(A, C) layer.Wq dlarray(randn(A, C) * 0.01, CB); layer.v dlarray(randn(A, 1) * 0.01, CB); end function Z predict(layer, X) % X: [C, T, S]C为LSTM隐单元数T为时间步S为样本数 C size(X, 1); T size(X, 2); S size(X, 3); Z zeros(C, 1, S, like, X); for s 1:S hs squeeze(X(:, :, s)); % [C, T] e layer.v * tanh(layer.Wq * hs); % [1, T] alpha softmax(e, 2); % 沿时间步归一化 Z(:, 1, s) hs * alpha; % 加权求和[C, 1] end end end end注意softmax(e, 2)的第二个参数是维度方向这里必须对时间维做归一化使权重和为1如果沿特征维归一化含义就变成了特征之间的竞争失去时间显著性。Wq的维度A一般取16或32A越大注意力能表达的非线性关系越复杂但参数量也会上涨。在样本数少于几千的小数据集上A16就足够过大反而容易过拟合。2.4 推荐连接顺序TCN、LSTM、Attention、FC整个网络的结构可以总结为原始多变量输入 [F, T] - TCN块causalPad Conv1d ReLU Dropout重复2~4次 - LSTMOutputModesequence - Attention时间步加权求和 - FullyConnected预测未来horizon个值 - Regression层MSE损失为什么不把TCN和LSTM并联并联结构适合“TCN提取局部特征、LSTM提取全局状态最后拼接”的思路但它多了一路输入分支调参时要同时平衡两条路径的尺度。而串联结构有一个更直观的解释TCN先把原始序列压缩成更干净的特征序列LSTM再在特征序列上建模状态转移Attention最后决定哪些状态对预测目标最相关。三者的分工没有重叠训练时梯度更新也更稳定。最后一层全连接输出的维度由预测步长决定。如果预测未来3个点fullyConnectedLayer(3)输出的形状是[3, 1, S]因此在构造训练目标时Y也要保持[horizon, 1, S]的形状这是Matlab中regressionLayer最容易报错的地方。3. Matlab工程化数据怎么搭、网络怎么建、训练怎么跑3.1 多变量滑动窗口数据集构造与归一化多变量时间序列预测的第一步是把原始数据切成“窗口样本”。假设data是一个[T_total, F]的矩阵每一行是一个时间步每一列是一个变量win是回看窗口长度horizon是预测未来步数构造代码如下function [XTrain, YTrain] makeSlidingWindows(data, win, horizon, targetCol) [T_total, F] size(data); N T_total - win - horizon 1; X zeros(F, win, N); Y zeros(horizon, 1, N); for i 1:N X(:, :, i) data(i : i win - 1, :); % [F, win] Y(:, 1, i) data(i win : i win horizon - 1, targetCol); % [horizon, 1] end XTrain X; YTrain Y; end这段代码有三点值得说明。第一X的形状是[特征数, 时间步, 样本数]不是[时间步, 特征数, 样本数]。sequenceInputLayer默认要求数据格式为CBTC是通道或特征维B是批次维T是时间维。如果搞反trainNetwork会报维度不匹配或者干脆把特征和时间混在一起训练结果完全不可解释。第二Y的形状是[horizon, 1, N]。因为Attention层输出[C, 1, S]全连接输出[horizon, 1, S]回归目标必须和它形状一致。很多人在这一步只构造[N, horizon]结果训练时报“目标尺寸与预测输出不匹配”。第三归一化不能全局混着做。常见做法是按每个变量分别做z-scoremu mean(data(1:trainLen, :), 1); sigma std(data(1:trainLen, :), 0, 1); dataNorm (data - mu) ./ sigma;mu和sigma只能用训练集统计验证集和测试集都用同一组均值和方差变换。如果在全部数据上先归一化再划分验证集的信息泄漏进了训练集测试集上的RMSE会虚低落地后立刻现原形。3.2 用 layerGraph 拼装 TCN-LSTM-Attention 网络TCN块在Matlab里可以用causalPadLayer配合内置convolution1dLayer实现。下面是一个三层TCN加LSTM加Attention的完整网络定义filterSize 3; numFilters 32; numHidden 64; F size(XTrain, 1); horizon size(YTrain, 1); causal1 causalPadLayer((filterSize - 1) * 1); causal2 causalPadLayer((filterSize - 1) * 2); causal3 causalPadLayer((filterSize - 1) * 4); layers [ sequenceInputLayer(F, Name, input) causal1 convolution1dLayer(filterSize, numFilters, DilationFactor, 1, Padding, valid, Name, conv1) reluLayer(Name, relu1) dropoutLayer(0.2, Name, drop1) causal2 convolution1dLayer(filterSize, numFilters, DilationFactor, 2, Padding, valid, Name, conv2) reluLayer(Name, relu2) dropoutLayer(0.2, Name, drop2) causal3 convolution1dLayer(filterSize, numFilters, DilationFactor, 4, Padding, valid, Name, conv3) reluLayer(Name, relu3) lstmLayer(numHidden, OutputMode, sequence, Name, lstm) attentionLayer(16, numHidden, Name, attention) fullyConnectedLayer(horizon, Name, fc) regressionLayer(Name, regression) ]; lgraph layerGraph(layers);需要特别说明convolution1dLayer的DilationFactor参数。在新版Matlab深度学习工具箱中它可以直接设置但早期版本不支持这时需要把卷积层替换成自定义层或改用dlconv手写。上面的causalPadLayer已经事先在左侧补了(K-1)*d个零所以卷积层用Padding, valid即可不需要再做其它padding。attentionLayer在layers数组里的构造参数是attentionLayer(16, numHidden)对应前面自定义层构造函数里的Wq维度A16和输入维度C64。如果LSTM隐藏单元数改了这里必须同步改否则layerGraph会报维度不一致。dropoutLayer放在TCN的每层ReLU之后作用是抑制卷积层对训练集噪声的过拟合。预测时Matlab会自动关闭dropout不需要额外处理。3.3 训练选项adam、学习率、验证集网络定义好之后用trainingOptions设置训练参数再用trainNetwork启动rng(2024); % 固定随机种子保证可复现 options trainingOptions(adam, ... MaxEpochs, 200, ... MiniBatchSize, 64, ... InitialLearnRate, 0.001, ... LearnRateSchedule, piecewise, ... LearnRateDropPeriod, 50, ... LearnRateDropFactor, 0.5, ... ValidationData, {XVal, YVal}, ... ValidationFrequency, 10, ... Shuffle, never, ... Plots, training-progress, ... Verbose, false); net trainNetwork(XTrain, YTrain, lgraph, options);Shuffle, never是时间序列预测里必须显式关闭的选项。图像分类任务默认每个epoch打乱样本没有影响但时间序列样本如果被打乱验证集里的时间点可能出现在训练集之前等于让模型间接看到了未来。这在trainingOptions的默认设置里不会自动处理必须手动指定。ValidationFrequency设为10表示每10个迭代计算一次验证集损失。多变量预测建议同时监控RMSE而不是只看损失值因为MSE对离群点敏感RMSE更贴近业务上的量级。训练结束后保存模型save(tcn_lstm_attention_model.mat, net, mu, sigma);保存时连同训练集的mu、sigma一起存预测时反归一化要再用它们。4. 超参数怎么调从TCN层数到Attention权重设置4.1 一组可从起点复现的参数表TCN-LSTM-Attention的超参数比单模型多调参时需要分清主次。我一般按下面这张表作为默认起点参数推荐起点参数变大时的影响滑动窗口长度 win24或48窗口越长TCN感受野越容易覆盖长依赖但训练样本数N减少TCN卷积核大小 K3K越大单层感受野越大但参数量和过拟合风险同步上升膨胀率序列[1, 2, 4]膨胀率跨度越大感受野扩展越快跨度过大会忽略中间细节TCN滤波器数32越多特征表达越丰富但训练时间几乎线性增加TCN层数2~3超过4层时梯度衰减明显建议加残差连接LSTM隐藏单元数32~64越大记忆容量越大超过128后小数据集容易过拟合Attention映射维度 A16越大注意力非线性越强但训练数据少时权重难收敛初始学习率0.001学习率过大会导致loss震荡过小则收敛极慢调参顺序建议是先固定窗口长度和膨胀率调LSTM隐藏单元数再调TCN滤波器数和层数最后才动Attention的映射维度。Attention是最后一道特征压缩如果前面的特征本身质量不高光调Attention维度提高有限。4.2 训练时盯三条曲线训练过程中Matlab训练窗口会画出迭代次数与损失的关系曲线但只看Training Loss远远不够。我一般同时盯三条曲线训练损失如果下降平稳模型在正常学习如果出现锯齿状震荡初始学习率可能过高。验证损失训练损失持续下降、验证损失在第30轮左右开始回升就是过拟合信号此时应加大dropout或减少TCN层数。验证集RMSERMSE的单位与预测目标相同。如果RMSE曲线在某个值附近长期横盘说明模型容量已经饱和加隐藏单元不如换输入特征。如果验证损失出现“先降后升”的V形曲线最早上升的那个epoch就是早停点。可以用OutputFcn配合ValidationLoss字段在训练中途停止但更简单的方法是直接把MaxEpochs设大然后根据验证曲线手动截断模型。训练结束后用minibatchpredict或predict在测试集上复算RMSE以这个值为准。4.3 常见坑归一化泄漏、shuffle、边界补零、随机种子很多复现失败不是模型结构问题而是以下四个细节。第一个坑是归一化泄漏。前面提过mu和sigma只能用训练集计算但还有一个隐蔽版本如果特征列包含滞后变量滞后变量的统计量本质上来自同一段历史仍应放在训练集内统计不能在全部数据上一次性求均值。判断标准是验证集在预测那一刻任何统计量都不能来自未来数据。第二个坑是Shuffle。trainingOptions有个Shuffle参数默认对图像任务是once每个epoch前打乱数据。时间序列必须设为never否则相当于把时间顺序破坏掉模型学到的依赖关系在真实预测中不存在。第三个坑是因果padding方向。本文第2章的causalPadLayer只在左侧补零。如果补零方向写反模型训练时看起来收敛很快但测试集效果差到不如线性回归。排查办法是拿一个已知的简单序列做单步预测检查预测值和真实值之间是否存在“提前一拍”的对应关系。第四个坑是随机种子。深度学习工具箱的训练初始化、dropout、数据打乱都涉及随机数不设置rng时每次运行结果不一样。想要复现论文里的某个数字必须在数据构造之前就rng(2024)并且确保数据划分在随机种子设置之后进行。5. 模型验证与进一步榨取精度的三个技巧模型训练完并不是终点多变量预测模型最容易在“看起来准确”和“真正可用”之间出现巨大差距。下面三个技巧是按验证价值从低到高排列的。第一个技巧是可视化Attention权重确认模型学到了有意义的时间依赖。由于自定义层没有把alpha保存下来需要修改attentionLayer增加一个公共属性存放最近一次前向传播的权重。预测时取某个测试样本用activations(net, XSingle, attention)或直接调用该层的predict拿到权重画成柱状图。如果Attention权重几乎均匀分布说明模型没有真正聚焦到关键时间点可能是LSTM已经把有效信息均匀地展开了如果权重集中在某一个时间步可以回去检查那个时间步是否对应业务上的突变时刻比如电力负荷中的尖峰时段。第二个技巧是残差自相关检验。预测残差如果仍然存在明显的时序自相关说明模型没有把序列信息提取干净。Matlab的Econometrics Toolbox里可以这样看residual yTest(:) - yPred(:); [acf, lags] autocorr(residual, NumLags24); bar(lags, acf);如果滞后1~3阶的ACF超出置信区间通常意味着TCN的膨胀率跨度太大细节信息被跳过或者窗口长度不足。此时把膨胀率从[1,2,4]改成[1,2,3]或把窗口长度加长能治本如果ACF还显示明显的周期性则需要在输入特征中加入同时刻的历史值比如“前24小时同一时刻的负荷”。第三个技巧是用MC Dropout给预测结果一个区间。常规predict在推理时自动关闭dropout得到的只是点预测但若用dlnetwork转成forward模式dropout会在预测时继续生效多次前向传播的结果就能近似采样后验分布dlnet dag2dlnetwork(net); dlXTest dlarray(XTest, CBT); preds zeros(horizon, numMC, numTestSamples); for i 1:numMC preds(:, i, :) extractdata(forward(dlnet, dlXTest)); end meanPred mean(preds, 2); stdPred std(preds, 0, 2);stdPred就是每个预测点的经验不确定性。负荷预测中如果某一天的预测方差明显放大通常对应节假日或天气突变这类样本仅靠RMSE看不出来但有了区间之后业务侧可以提前按更宽的波动范围做调度。MC Dropout的迭代次数一般取20~50次超过50次对标准差估计的提升已经不明显。最后提醒一句所有验证指标都要在反归一化之后计算。很多人直接在归一化后的数据上算RMSE得到一个零点几的漂亮数字但部署时那个数字对应的单位可能是几千千瓦或几十立方米每秒。用训练集保存下来的mu和sigma还原预测值再和原始量纲的真实值对比才是最终能写进报告里的结果。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价