ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于TCN-BiGRU-Attention的多变量时间序列预测与Matlab实现

基于TCN-BiGRU-Attention的多变量时间序列预测与Matlab实现 简介面向多变量时间序列预测任务这份Matlab源码包融合时间卷积网络TCN、双向门控循环单元BiGRU与注意力机制可同时考虑多个输入特征的历史影响并预测单一输出变量适合高校学生完成课程设计、期末大作业或毕业设计。资源共包含8个文件其中6个.m脚本/函数覆盖数据整理、模型构建、误差计算与雷达图绘制1个.mat数据文件与1个.xlsx风电场数据集提供实验验证数据整个压缩包仅4.26MB结构紧凑便于直接替换数据使用。代码采用参数化编程模块化区分数据预处理、网络搭建、训练预测与结果绘图注释详细、思路清晰在Matlab2023b环境下运行主程序即可并自动输出R2、MSE、MAE、MAPE评价指标方便读者分析精度和调整参数。目前已有88人学习该资源对希望快速上手深度学习时序预测算法的初学者和开发者而言是一份结构完整、可直接落地的参考项目也可作为进一步算法改进和论文实验的基线代码。1. 多变量时间序列预测为什么值得用 TCN-BiGRU-Attention在工业传感器、智能电网、金融行情这类场景里预测对象往往不只是单条曲线而是多组变量相互牵制的系统某一刻的风速、温度、功率、负荷同时变化上一时刻的状态会以不同延迟影响后续输出。传统的 LSTM、GRU 虽然擅长处理序列但对长时程依赖的捕捉能力有限纯 Transformer 又存在计算开销大、小样本训练不稳定的问题。TCN-BiGRU-Attention 把这三种结构按“粗粒度感受野提取 → 双向时序建模 → 关键步加权”的顺序串联能在中等数据规模下获得比单一模型更稳的精度而且用 Matlab 深度学习工具箱可以直接实现。本文会从模型结构、数据预处理、训练调参到评估验证给出在 Matlab 中落地这套预测方案的完整思路适合正在做多变量回归预测、手头已有采样数据的算法工程师和研究生参考。2. TCN-BiGRU-Attention 模型结构与选型理由2.1 TCN 模型结构膨胀因果卷积与残差块的组合TCNTemporal Convolutional Network不是把普通卷积直接搬到时间轴上它的核心是“因果”与“膨胀”两步。因果卷积保证 t 时刻的输出只依赖 t 及以前的信息不会像普通卷积那样看到未来这在时间序列预测里是硬约束。膨胀卷积则在每个卷积核之间插入空洞使感受野随层数指数增大第 l 层的感受野是 1 (kernel - 1) × dilation^l。一个 kernel 为 3、dilation 按 1、2、4 递增的三层 TCN能覆盖约 15 个时间步的信息而参数量远小于同感受野的普通卷积堆叠。构建 TCN 时往往采用残差块主路径做两层膨胀因果卷积、权重归一化和 ReLU旁路做 1×1 卷积调整通道数最后相加。这样的好处是梯度可以跨层直接流动网络加深后训练更稳定。在 Matlab 中TCN 没有现成层需要用dlnetwork 自定义层或convolution1dLayer 手动 dilation 实现。下面是一段用dlarray在前向传播中执行膨胀卷积的示例只展示单个残差块的核心逻辑function Y tcnResidualBlock(X, convWeight, convBias, dilation) % X: [C, T, N]C为特征通道数T为时间步N为样本数 % convWeight: 卷积核权重维度 [k, Cin, Cout] % dilation: 膨胀系数 k size(convWeight, 1); % 在通道维度上按帧填充保证因果性 XPad padarray(X, [0, (k-1)*dilation, 0], left, replicate); Z dlconv(XPad, convWeight, convBias, ... Stride, 1, Dilation, [dilation, 1], DataFormat, CTN); % 残差旁路调整通道数 if size(Z, 1) ~ size(X, 1) X dlconv(X, reshape(eye(size(Z,1))/size(X,1), [1,size(X,1),size(Z,1)]), ... zeros(size(Z,1),1), Stride, 1, DataFormat, CTN); end Y relu(Z X); end这段代码展示了关键点padarray在时间维度的左侧补零/复制边缘保证因果性dlconv中的Dilation参数直接对应膨胀率DataFormat使用CTN表示通道、时间、样本三维。在真正使用时你会把多个这样的残差块堆叠起来每个块用不同的膨胀系数。需要根据输入长度计算每一个膨胀值下的初始化边界避免卷积核超出序列范围。2.2 BiGRU在前向与反向时间方向的上下文增强GRU门控循环单元通过更新门与重置门控制信息保留程度参数比 LSTM 少在中等规模数据上不容易过拟合。BiGRU 则是在两个方向上各跑一个 GRU前向 GRU 编码从前到后的时序依赖反向 GRU 编码从后到前的依赖最后把两个方向的隐状态在特征维拼接起来。TCN 提取到的特征序列本身是不分方向的局部时序模式BiGRU 能进一步捕捉“这个波峰出现后下一个波谷可能落在哪里”这类双向关系尤其适合存在周期性、但相位不固定的多变量数据。Matlab 中bilstmLayer直接支持双向 GRU 功能但双向 GRU 本身没有内置层需要借助特征维度拼接。一个常见替代方案是使用bilstmLayer双向 LSTM如果你想严格使用 GRU 单位可以分别构建两个gruLayer并通过concatenationLayer合并隐藏状态。下面的网络分支结构显示如何在 Matlab 层图中实现双向 GRU 的感觉% 前向 GRU forwardGRU gruLayer(numHiddenUnits, OutputMode, sequence, Name, gru_f); % 反向输入需先将序列翻转训练后回翻 flipLayer sequenceFlippingLayer(Name, flip); backwardGRU gruLayer(numHiddenUnits, OutputMode, sequence, Name, gru_b); unflipLayer sequenceFlippingLayer(Name, unflip); % 时间步对齐后拼接 concatLayer concatenationLayer(1, 2, Name, concat_bi);sequenceFlippingLayer是 Matlab 深度学习工具箱内置的序列变换层可对时间维度翻转。这里把同一输入分别送入正序与倒序 GRU再将两个方向的特征在特征通道维拼接得到的concat输出就等价于 BiGRU。参数numHiddenUnits通常取 32~128过大会让注意力模块学到冗余权重。注意两个方向的 GRU 层不能共享权重否则无法学到不同的时序语义。2.3 Attention用双注意力模块聚焦关键时间步与关键变量加入 Attention 的目的是让模型在解码信息时不再平均看待 BiGRU 输出的每个时间步。对于多变量时间序列关键变量可能在某个时段起主要作用例如风速骤增时功率预测应优先参考风速传感器的近期模式而不是历史负荷。双注意力模块的思路是先对时间步施加注意力再对变量维度施加注意力或者两者同时计算。在 Matlab 自实现时通常采用加性注意力或点积注意力通过一个可学习打分网络得到每个时间步的权重再对 BiGRU 隐状态加权求和。核心前向传播代码如下function [context, attnWeights] attentionForward(encoded, query, scoreWeight) % encoded: [numFeatures, numTimeSteps, batchSize] BiGRU输出 % query: [numFeatures, batchSize] 一般为解码器上一时刻状态 % scoreWeight: [numHidden, numHidden] 打分矩阵 [C, T, N] size(encoded); scores zeros(T, N, like, dlarray(1)); for t 1:T % 每个时间步 h_t 与 query 做双线性打分 ht encoded(:, t, :); % [C, 1, N] qt reshape(query, [1, C, N]); scores(t, :) sum(ht .* (scoreWeight * qt), 1); % 双线性简化 end attnWeights softmax(scores, 1); % 在时间维归一化 context sum(encoded .* attnWeights, 2); % 加权和得到 [C, 1, N] end这里使用双线性打分h_t^T W q同样可以替换成拼接后经全连接层的加性打分。scores在时间维做了归一化因此注意力权重的含义是“当前预测目标应该关注哪个历史时刻”attnWeights可以被可视化用于解释模型的预测依据。如果你希望同时关注变量维度可以把同样的加权机制作用在特征维这就是双注意力模块的典型做法——一组权重作用于时间步另一组作用于特征。2.4 完整组合网络的前向数据流与参数量参考把前述三个模块串起来完整模型的数据流动是输入 [T×F] → TCN残差块堆叠 → BiGRU双向拼接 → Attention加权输出 → 全连接层 → 预测值。对于多变量输入TCN 的通道数一开始与变量数相同经过残差块逐渐升到隐层通道数再送入 BiGRU。下面是一张参考参数表模块关键参数取值参考输出维度输入层变量数 F、序列长度 TF8, T24[F, T, N]TCN 残差块层数 L、内核 k、膨胀倍数 dL3, k3, d[1,2,4][64, T, N]BiGRU隐藏单元数 HH64前向/反向各64[128, T, N]Attention打分函数类型双线性/加性[128, 1, N]输出层预测步数 QQ1 或 Q5[Q, N]从表中可以看到BiGRU 输出的特征维度是前向和反向隐藏单元之和Attention 后压缩成一个上下文向量最终接全连接层输出目标步数。这样的参数规模在普通 CPU 上也能训练但用 GPU 会快很多。需要特别注意 TCN 的层数不要堆太多否则序列被过度抽稀BiGRU 反而失去可学习的时间细节。3. Matlab 环境下多变量时序数据的预处理与训练样本构造3.1 数据格式与归一化从 CSV 到可输入的张量多变量时间序列通常以一张二维表存储每一行代表一个时间戳每一列代表一个变量如温度、压强、能耗。Matlab 深度学习模型的输入要求是四维或三维 dlarray其中序列数据用CTN格式通道数变量数在前时间步居中样本在最后。因此你要先把 CSV 读入一个矩阵再做转置。归一化建议使用mapminmax或zscore并且必须只在训练集上拟合归一化参数再用同一参数作用于验证集和测试集避免信息泄露。rawData readmatrix(sensors.csv); % [N, F]N 为时间步数F 为变量数 % 前 80% 作为训练后 20% 作为测试 trainRatio 0.8; splitIdx round(size(rawData,1) * trainRatio); trainRaw rawData(1:splitIdx, :); testRaw rawData(splitIdx1:end, :); % 使用 mapminmax 在训练集上计算归一化参数 [~, ps] mapminmax(trainRaw, -1, 1); % 转置成 [F, N] trainNorm mapminmax(apply, trainRaw, ps); testNorm mapminmax(apply, testRaw, ps);ps保存训练集每个特征的最小、最大值。对测试集只做apply而不重新计算这是一条铁律。若你的预测目标只有其中一列可以将其单独取出作为标签其余列作为特征。常见做法是原始矩阵多包含一个目标变量比如下一时刻的功率预测时用历史多变量的值去回归这个未来值。3.2 滑动窗口构造输入输出对对于多变量预测每个样本应该是由过去T个时间步若干变量组成的特征块标签是未来Q步的目标值。通常用循环滑动窗口生成。T_backstep 24; % 回看步数用过去24个时刻 Q_future 1; % 预测未来1步 X []; Y []; for i T_backstep1 : size(trainNorm, 2) - Q_future 1 X(:, :, end1) trainNorm(:, i-T_backstep : i-1); % 包含目标列在内的所有变量 Y(end1, :) trainNorm(targetCol, i : iQ_future-1); end % 转成 dlarray格式 CTN 或 CBT X dlarray(X, CBT); % 这里 C特征数B时间T样本注意检查 Y dlarray(Y, CB);上面这段代码有一个容易踩坑的地方Matlab 中dlarray的CBT表示 通道、批量、时间或者CTB表示 通道、时间、批量需要与后续sequenceInputLayer的输入格式保持一致。为了避免混乱建议用普通数组构建然后用single(X)直接喂给trainNetwork由网络层定义来解析维度。另外注意X(:, :, end1)这种动态扩容效率低数据量大时先预分配zeros(F, T, numSamples)会更好。3.3 数据泄漏风险与样本顺序洗牌时间序列样本之间不是独立的相邻两个窗口的重叠部分很大。如果直接打乱所有样本的 batch 顺序会让模型“看到”未来信息因为训练集和验证集可能来自同一时间段。正确做法是先按时间顺序切出互不重叠的三段训练、验证、测试再在训练段内部把样本顺序打乱。验证集应保持时间顺序或至少不要与训练集重叠。测试集必须完全置于训练集时间范围之后。Matlab 中trainNetwork默认将训练数据打乱成 mini-batch因此你需要自己控制数据分割确保训练集样本的标签时间都在验证集之前。4. 在 Matlab 中亲手搭建 TCN-BiGRU-Attention 网络4.1 用自定义层封装 TCN 残差块Matlab 深度学习工具箱没有内置膨胀卷积层但你可以通过nnet.layer.Layer自定义一个层对象。以下代码定义了一个最简单的 TCN 残差层每次调用执行一次膨胀卷积和残差加和classdef tcnResidualLayer nnet.layer.Layer properties Dilate % 膨胀系数 NumFilters end properties (Learnable) ConvW ConvB end methods function layer tcnResidualLayer(numFilters, dilate, name) layer.Name name; layer.Dilate dilate; layer.NumFilters numFilters; % 默认使用 He 初始化 layer.ConvW dlarray(randn(3, 1, numFilters) * sqrt(2/3)); layer.ConvB zeros(numFilters, 1, single); end function Z predict(layer, X) % 调整维度为 CTN Xdl dlarray(X, CTN); Z dlconv(Xdl, layer.ConvW, layer.ConvB, ... Stride, 1, Dilation, [layer.Dilate, 1], DataFormat, CTN); Z relu(Z Xdl(:, :, :)); % 残差简化版 end end end这个简化层假设输入通道数等于numFilters因此省略了残差旁路的 1×1 卷积。实际使用时第一层 TCN 通常要改变通道数你需要在类中多写一个残差转换权重。要注意dlconv在自定义层里接收和返回都是dlarray而外部调用trainNetwork时会自动匹配合法性。自定义层必须实现predict或forward方法否则无法装入网络图。对于多膨胀系数的 TCN可以将多个这样的层串联每层设置不同的Dilate。4.2 BiGRU 与 Attention 层的组装方式在layerGraph中BiGRU 部分可以通过两个gruLayer与一个concatenationLayer实现。注意序列翻转层需要与输入时间步一一对应所以要用sequenceFlippingLayer再回翻一次。Attention 层需要访问 BiGRU 输出的全部时间步因此 BiGRU 的OutputMode必须设成sequence否则只会输出最后一个时间步Attention 就没有意义。一段可运行的网络构建代码如下% 构建 layerGraph layers [ sequenceInputLayer(numFeatures, Name, input) % numFeatures 变量数 tcnResidualLayer(64, 1, tcn1) tcnResidualLayer(64, 2, tcn2) tcnResidualLayer(64, 4, tcn3) % BiGRU 前向 gruLayer(64, OutputMode, sequence, Name, gruF) % 反向分支 sequenceFlippingLayer(Name, flipIn) gruLayer(64, OutputMode, sequence, Name, gruB) sequenceFlippingLayer(Name, flipOut) concatenationLayer(1, 2, Name, concat) % Attention 在这里用自定义层见 attentionLayer 定义 attentionLayer(att) fullyConnectedLayer(1, Name, fc) regressionLayer(Name, output) ]; graph layerGraph(layers); graph connectLayers(graph, input, flipIn); % 反向分支输入 graph connectLayers(graph, tcn3, gruF); graph connectLayers(graph, tcn3, flipIn); % 注意顺序翻转层要接相同的TCN输出 graph connectLayers(graph, gruF, concat/in1); % 拼接第一个输入 graph connectLayers(graph, flipOut, concat/in2);上述代码中concatenationLayer需要先标好两个输入名。attentionLayer是自定义层内部需要实现类似前面 2.3 节的双线性加权求和。如果你不想写自定义层也可以把 Attention 简化成基于 BiGRU 最后隐状态的加权使用attentionLayer需要单独编写predict方法。在layerGraph里做完所有连接后用analyzeNetwork(graph)可以检查维度是否匹配。4.3 训练选项设置与 mini-batch 划分策略多变量时序样本数量通常不大batch 大小设置在 16~128 之间比较常见。选择训练选项时重点看ValidationData与OutputNetwork两个参数options trainingOptions(adam, ... MaxEpochs, 60, ... MiniBatchSize, 32, ... InitialLearnRate, 0.001, ... LearnRateSchedule, piecewise, ... LearnRateDropFactor, 0.5, ... LearnRateDropPeriod, 20, ... GradientThreshold, 1, ... ValidationData, {XVal, YVal}, ... ValidationFrequency, 10, ... OutputNetwork, best-validation); trainedNet trainNetwork(XTrain, YTrain, graph, options);GradientThreshold对 TCN-BiGRU 组合格外重要因为 BiGRU 的反向传播路径仍会随长度放大梯度1 是一个保守的阈值。OutputNetwork设为best-validation可避免过拟合。训练完成后用predict得到测试集预测值然后必须把之前保存的ps参数映射回原始尺度否则评估误差单位是归一化后的无法与业务指标直接对应。5. 预测效果评估与超参数调优5.1 回归预测的四个核心评价指标对多变量时间序列预测最常用的指标是均方根误差RMSE、平均绝对误差MAE、决定系数R2以及平均绝对百分比误差MAPE。RMSE 对大误差更敏感适合放大离群惩罚MAE 反映平均偏差幅度R2 表达预测值与真实值的相关程度越接近 1 越好MAPE 适合业务上关心相对误差的场合但当真实值接近 0 时会爆表需要谨慎使用。下面是 Matlab 计算示例predictions predict(trainedNet, XTest); predNorm extractdata(predictions); trueNorm extractdata(YTest); % 反归一化到原始尺度 pred mapminmax(reverse, predNorm, ps); true mapminmax(reverse, trueNorm, ps); % 计算指标 rmse sqrt(mean((true - pred).^2)); mae mean(abs(true - pred)); r2 1 - sum((true - pred).^2) / sum((true - mean(true)).^2); mape mean(abs((true - pred) ./ true)) * 100;注意mapminmax(reverse, predNorm, ps)中的转置要与你保存ps时的行方向一致。通常trainNetwork的输出维度是[numResponses, numObservations]需要先转置再反归一化。实际业务中RMSE 的绝对值受数据量级影响最好与一个简单基线例如恒用上一时刻值对比才能判断 TCN-BiGRU-Attention 是否真正带来了增量提升。5.2 核心超参数与调参优先序多变量 TCN-BiGRU-Attention 网络可调参数较多按经验调参优先级依次是序列回看长度、TCN 膨胀系数组合、BiGRU 隐藏单元数、注意力打分类型、学习率。回看长度决定了模型能看到多远的过去通常先尝试 12、24、48 这种整数倍周期值膨胀系数组合常用 [1,2,4]、[1,2,4,8] 两种如果回看长度很长可以选择后者。下表是一个参数初选建议参数推荐范围调参方向典型影响回看长度 T逐变量周期估算如 12/24/48随采样频率升高而增大太小遗漏周期太大引入噪声膨胀系数组合[1,2,4] 或 [1,2,4,8]增大覆盖长依赖但可能稀疏训练感受野 12×dilation 的和BiGRU 隐藏单元数32~128增大提升拟合能力但易过拟合双向后输出维度翻倍注意力打分加性 / 双线性 / 缩放点积小数据用加性更稳影响权重分布初始学习率0.0005~0.005使用余弦退火可提升效果过高梯度爆炸过低收敛慢调参时不要逐参数网格搜索那会消耗大量时间。推荐先固定回看长度和膨胀组合只调隐藏单元数确定一个较优区间后再用bayesopt或手动逐步修改其他参数。Matlab 的bayesopt可以对trainingOptions中的学习率、mini-batch 等待超参数做贝叶斯优化但每次实验都需完整训练一张网络成本不低建议先用少量样本做预筛。5.3 训练时的常见坑与排查方法第一个常见坑是训练集和测试集数据重叠带来的过度乐观。如果按默认函数随机切分样本序列数据的相邻窗口会高度重合导致验证损失虚低、实际部署效果远差于测试表现。第二个坑是 BiGRU 双向分支连接错误很多人把序列翻转层接在 BiGRU 之后而不是之前导致时间步错位。检查方法是用analyzeNetwork查看每一步输出的 time 维度确认翻转前后时间长度一致。第三个坑是 Attention 层在推理时由于维度排列不同而报错如果在自定义层中使用extractdata和dlconv混用请确保DataFormat始终与网络图一致。最后当 loss 出现 NaN优先检查归一化参数是否有 0 方差特征以及GradientThreshold是否调得过小。6. 提升泛化能力用滚动预测验证 TCN-BiGRU-Attention 的真实步数表现单步预测虽然常用但实际业务往往需要连续预测 5 步、10 步甚至更久。把训练好的模型直接用于多步预测有两种模式递归预测和直接多输出。递归预测会把上一步的预测值作为下一步输入的一部分缺点是误差会逐步累积直接多输出则在全连接层后输出多个目标步降低累积效应但会增加最后全连接层的自由度。可以在测试集上分别计算这两种模式的误差曲线判断模型的稳定步数边界。Matlab 中的滚动验证代码段如下% 假设回看 T_backstep 步预测 horizon 步 h 10; % 预测水平 predRecur zeros(h, 1); inputBuffer XTestNorm(:, 1:T_backstep, 1); % 用测试集第一个样本的历史窗口 for step 1:h stepOut predict(trainedNet, inputBuffer); predRecur(step) stepOut(1); % 更新输入窗口丢掉最老一个时间步把最新预测值拼接到最后 inputBuffer [inputBuffer(:, 2:end, :), stepOut]; end这段代码中inputBuffer始终保持[F, T, 1]每预测一步就滑动窗口。需要留意预测值在送入下一次前应保持同一归一化尺度否则窗口内数据分布偏移会快速增大误差。通过对比递归预测和直接多输出的 RMSE你可以决定线上服务到底应该每次重新预测还是用缓存历史实现单步滚动更新。另一个实用技巧是同时保存每个时间步的注意力权重当某个变量取值异常导致预测偏移时权重图能快速定位是哪些历史样本在主导决策这比单纯看误差曲线更有诊断价值。本文还有配套的精品资源点击获取
返回列表