简介:本资源面向具备一定信号处理与深度学习基础、熟悉MATLAB编程的研发人员、数据科学家与研究生,聚焦多变量时序中非平稳性、多尺度波动与变量耦合等难点。资源为1个docx文档,压缩包约117KB,以项目报告形式系统梳理了基于变分模态分解(VMD)、样本熵(SE)与Transformer-GRU组合模型的完整预测方案,涵盖理论设计、算法实现与工程部署全流程,并配有可复现的MATLAB代码示例。内容从多变量VMD分解的参数选择与复杂度问题切入,说明样本熵如何量化各模态复杂度并构造特征,再讲解Transformer全局注意力与GRU局部动态建模的协同结构、特征融合与归一化重构策略,同时给出数据预处理、模型训练、性能评估与GUI可视化界面设计等模块。已有321人学习,可用于智能电网、机械故障诊断、金融风控等场景的多变量预测建模,帮助读者掌握调参、特征融合与结构对比的实践思路。
1. 多变量非平稳序列的预测困局:VMD-SE-Transformer-GRU 在 MATLAB 里的位置
风电功率、区域负荷、光伏出力这类多变量时间序列,往往同时包含风速、温度、辐照度、历史功率等多个通道。直接把原始序列送进 GRU 或 LSTM,模型容易被高频噪声和趋势突变带偏,验证集误差忽高忽低,调参也变成碰运气。变分模态分解(VMD)把每个通道拆成若干本征模态函数,样本熵(SE)再给每个模态的复杂度打分,把熵值接近的模态合并成少数几个分量,输入分支数量降下来。Transformer 负责跨时间步的长程依赖,GRU 负责局部连续变化,两者串行或并行组合成 VMD-SE-Transformer-GRU。在 MATLAB 里做这件事,信号处理工具箱的vmd和深度学习工具箱的序列层就能把流程串起来,不必从零写优化器。适合已经用 MATLAB 做过数据分析、想把手里的多变量预测脚本升级到分解加深度组合模型的人。
2. VMD 分解与样本熵重构:MATLAB 端的数据准备与参数选择
拿到一份多变量时序数据,第一件事不是急着搭网络,而是把数据整理成“每列一个变量、每行一个时间点”的矩阵。假设原始数据为rawData,大小为T×N,T是时间步数,N是变量个数。缺失值用线性插值补齐,然后逐列做归一化。常见做法是使用 z-score 或 min-max,但要注意:训练集和测试集必须用同一组归一化参数,否则反归一化后的误差没有意义。多变量预测里,目标列通常是其中一列,比如负荷功率;其余列作为外生输入。VMD 对每一列单独分解,得到该通道的若干 IMF 和一个残差项,再把所有通道的 IMF 按时间对齐,形成新的多变量序列。
2.1 用 vmd 函数完成逐通道分解:K、Alpha 与容差怎么设
MATLAB 的vmd函数在信号处理工具箱中提供,基本调用形式如下。不同版本参数名可能略有差异,可以用help vmd确认。
% rawData: T×N 多变量矩阵,每列一个通道 % 先归一化,再逐列 VMD dataNorm = normalize(rawData, 'zscore'); K = 5; % 模态数,常用 3~8 alpha = 2000; % 带宽惩罚因子,常用 500~3000 tol = 1e-7; % 收敛容差 allIMF = cell(1, size(dataNorm, 2)); allRes = cell(1, size(dataNorm, 2)); for col = 1:size(dataNorm, 2) [imf, res, info] = vmd(dataNorm(:, col), ... 'NumIMFs', K, ... 'PenaltyFactor', alpha, ... 'RelativeTolerance', tol); % imf 维度为 T×K,每列是一个模态 allIMF{col} = imf; allRes{col} = res; fprintf('通道 %d 分解完成,迭代次数:%d\n', col, info.NumIterations); end逻辑说明:vmd对单通道信号做变分模态分解,返回imf矩阵和残差res。NumIMFs决定分解出多少个模态,PenaltyFactor控制每个模态的带宽,值越大带宽越窄,模态越集中;值太小会导致模态混叠。RelativeTolerance影响迭代停止条件,一般设在1e-6到1e-8。多通道数据必须逐列分解,不能把多列直接传给vmd,否则函数会把每列当成独立信号处理,但返回结构不利于后续对齐。
K 的选择没有万能公式。K 太小,趋势项和高频项混在同一模态里,样本熵分组失去意义;K 太大,会出现过分解,相邻模态中心频率接近,后续重构反而引入虚假分量。一个可操作的判断方法是观察每个 IMF 的中心频率和重构误差。如果增加 K 后,相邻模态的主频差异小于 5%,或者残差能量占比已经低于 1%,就可以停止增加。
| K 取值 | 典型现象 | 处理建议 |
|---|---|---|
| 2~3 | 高频细节被合并,趋势清晰但丢失突变 | 适合变化平缓的负荷序列 |
| 4~6 | 多数场景的平衡点 | 先固定 K=5,再微调 |
| 7~10 | 出现中心频率接近的模态 | 检查重构误差,考虑降 K 或增大 alpha |
| >10 | 过分解,噪声被拆成多个模态 | 不推荐,样本熵分组会失去稳定性 |
Alpha 的调节和 K 有耦合。K 增大时,通常也要适当增大 alpha,否则带宽变宽,模态之间容易重叠。如果分解结果里某个 IMF 明显包含两种不同频率成分,优先增大 alpha;如果所有 IMF 都过于平滑,丢失了高频波动,则减小 alpha。残差项一般单独保留,不参与样本熵分组,因为它代表分解后剩余的低频趋势或直流分量。
2.2 样本熵的计算与 IMF 重组:把 K 个模态压成 2~3 个输入分量
样本熵衡量时间序列的复杂度,值越大说明序列越不规则、越接近噪声;值越小说明序列越有规律、越接近趋势。对每个 IMF 计算样本熵,再根据熵值把 IMF 分成高频组和低频组,分别相加,得到两个重构分量。这样输入到 Transformer-GRU 的通道数从N×K降到N×2左右,既能保留多尺度信息,又能控制模型输入维度。
样本熵的常用参数是嵌入维数m=2,相似容限r=0.1~0.25×std。下面是一个可直接调用的函数实现。
function se = sample_entropy(x, m, rFactor) % x: 输入序列,行向量或列向量 % m: 嵌入维数,通常取 2 % rFactor: 容限系数,通常取 0.1~0.25 x = x(:)'; N = length(x); r = rFactor * std(x); if r == 0 se = 0; return; end % 构造 m 维和 m+1 维模板向量 Xm = zeros(N - m, m); Xm1 = zeros(N - m, m + 1); for i = 1:N - m Xm(i, :) = x(i:i + m - 1); Xm1(i, :) = x(i:i + m); end % 计算匹配数 B = 0; A = 0; for i = 1:N - m - 1 distM = max(abs(Xm(i + 1:end, :) - Xm(i, :)), [], 2); B = B + sum(distM <= r); distM1 = max(abs(Xm1(i + 1:end, :) - Xm1(i, :)), [], 2); A = A + sum(distM1 <= r); end if B == 0 || A == 0 se = Inf; else se = -log(A / B); end end逻辑说明:sample_entropy先计算序列标准差,再乘以rFactor得到容限r。然后构造m维和m+1维模板向量,统计距离小于等于r的匹配对数量。最后用-log(A/B)得到样本熵。如果B或A为零,说明序列太短或r太小,返回Inf,此时需要增大rFactor或检查序列长度。对每个 IMF 调用该函数,得到熵值列表。
% 假设 imf 为 T×K 矩阵,逐列计算样本熵 K = size(imf, 2); seVals = zeros(1, K); for k = 1:K seVals(k) = sample_entropy(imf(:, k), 2, 0.2); end % 方法一:按阈值分组,高频组和低频组 threshold = median(seVals); highIdx = seVals >= threshold; lowIdx = seVals < threshold; imfHigh = sum(imf(:, highIdx), 2); imfLow = sum(imf(:, lowIdx), 2); % 方法二:kmeans 聚成两类,适合熵值分布差异明显的情况 [idx, C] = kmeans(seVals', 2); imfGroup1 = sum(imf(:, idx == 1), 2); imfGroup2 = sum(imf(:, idx == 2), 2);阈值分组简单直接,适合模态数不多的情况。kmeans分组更客观,但要注意初始点敏感,可以设置'Replicates', 5来稳定结果。分组后,每个通道得到两个重构分量,所有通道的分量按时间拼接,形成新的多变量输入矩阵。下表记录了一个通道的典型结果,实际数值会随数据变化。
| IMF 序号 | 样本熵(m=2, r=0.2σ) | 分组 | 含义 |
|---|---|---|---|
| IMF1 | 1.42 | 高频组 | 噪声和快速波动 |
| IMF2 | 1.18 | 高频组 | 局部突变 |
| IMF3 | 0.76 | 低频组 | 中周期波动 |
| IMF4 | 0.51 | 低频组 | 缓慢趋势 |
| IMF5 | 0.33 | 低频组 | 主趋势 |
| 残差 | 0.08 | 不参与 | 直流或极低频 |
重组之后,建议再检查一次能量占比。高频组能量一般不超过总能量的 30%,如果超过,说明 VMD 的 K 可能偏小,或者阈值分组把过多模态划到了高频。此时可以增大 K 或调整rFactor,重新计算样本熵。
3. Transformer-GRU 组合模型的 MATLAB 搭建与训练参数
VMD-SE 处理完的数据仍然是时间序列,只是通道数减少、不同尺度的分量被分开。接下来要把这些分量送进 Transformer-GRU。Transformer 的自注意力机制擅长捕捉长距离依赖,但参数量大,对局部连续变化的建模不如 GRU 直接;GRU 门控结构适合短时依赖,但感受野有限。两者组合可以在不显著增加计算量的前提下,兼顾全局和局部。MATLAB 的深度学习工具箱提供了selfAttentionLayer、layerNormalizationLayer、gruLayer等层,可以直接拼接成dlnetwork或layerGraph。
3.1 串行、并行还是残差:三种组合方式的取舍
组合方式决定了信息流动路径。串行结构让序列先经过 Transformer 编码器,再进入 GRU;并行结构让两个分支同时处理输入,最后拼接或相加;残差结构在串行基础上加入跳跃连接,缓解梯度消失。三种方式没有绝对优劣,取决于数据长度和算力。
| 组合方式 | 结构 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 串行 | 输入→Transformer→GRU→输出 | 全局特征先被提取,GRU 再做局部平滑 | 梯度路径长,训练慢 | 序列长度大于 200 |
| 并行 | 输入同时进 Transformer 和 GRU,输出拼接 | 两条路径互补,收敛快 | 参数量增加约 30% | 序列长度 50~200 |
| 残差 | 输入→Transformer→GRU,并加跳跃连接 | 缓解梯度消失,保留原始尺度 | 层数多时可能过拟合 | 深层网络,序列长 |
我一般会先从串行结构开始,因为它的物理含义清晰:Transformer 输出的是全局加权后的表示,GRU 在这个表示上继续提取局部动态。如果验证集损失下降很慢,再改成并行结构,把原始输入同时接入 GRU 分支。残差连接建议在 Transformer 块内部使用,而不是在整个模型外面加,否则原始噪声会直接传到输出。
3.2 用 dlnetwork 搭建含自注意力与 GRU 的回归网络
下面是一个串行结构的网络定义骨架。位置编码用自定义函数生成,加到输入序列上。自注意力层输出经过层归一化和残差连接,再送入 GRU。最后用全连接层输出预测值。
% 参数设置 numFeatures = size(XTrain, 1); % 输入通道数,即重组后的分量数 dModel = 64; % 注意力隐藏维度 numHeads = 4; % 注意力头数 numGRUUnits = 80; % GRU 隐藏单元数 dropout = 0.2; % 定义网络层 layers = [ sequenceInputLayer(numFeatures, 'Name', 'input') % 位置编码通过自定义层或函数在训练前加入,这里用全连接做维度映射 fullyConnectedLayer(dModel, 'Name', 'fc_in') selfAttentionLayer(numHeads, dModel, 'Name', 'self_attn') layerNormalizationLayer('Name', 'ln1') dropoutLayer(dropout, 'Name', 'drop1') % 残差连接需要相同维度,此处简化为直接串接 gruLayer(numGRUUnits, 'OutputMode', 'sequence', 'Name', 'gru') dropoutLayer(dropout, 'Name', 'drop2') fullyConnectedLayer(1, 'Name', 'fc_out') regressionLayer('Name', 'regression') ]; % 转换为 dlnetwork net = dlnetwork(layers);逻辑说明:sequenceInputLayer接收numFeatures个通道的序列,输入格式为C×B×T,其中C是通道数,B是批大小,T是时间步。fullyConnectedLayer(dModel)把输入映射到注意力维度。selfAttentionLayer需要指定头数和模型维度,MATLAB 的该层要求dModel能被numHeads整除。layerNormalizationLayer对每个样本做归一化,稳定训练。gruLayer设置'OutputMode','sequence'时返回完整序列,如果只做最后一步预测,可以设为'last'并接全连接。regressionLayer计算均方误差损失。
位置编码可以用正弦函数生成,加到输入上。如果不想写自定义层,也可以在数据预处理阶段把位置编码拼接到每个时间步的特征后面。常见做法是:
function pos = positional_encoding(T, dModel) % 生成 T×dModel 的位置编码矩阵 pos = zeros(T, dModel); for t = 1:T for i = 1:dModel/2 omega = 1 / (10000 ^ ((2*i - 2) / dModel)); pos(t, 2*i - 1) = sin(t * omega); pos(t, 2*i) = cos(t * omega); end end end把位置编码加到输入序列的每个时间步后,再送入网络。如果输入通道数不等于dModel,先用全连接映射到dModel。注意dlnetwork对输入格式敏感,XTrain需要是C×B×T的dlarray,并且要标记维度顺序,例如'CBT'。
3.3 trainingOptions 的关键参数:学习率、批大小与验证策略
训练选项直接影响收敛速度和过拟合程度。多变量时序预测的样本量通常不大,批大小不宜过大,否则梯度估计噪声小但更新次数少;学习率过高会导致损失震荡,过低则收敛慢。验证集用来观察过拟合,早停可以避免无效训练。
options = trainingOptions('adam', ... 'MaxEpochs', 100, ... 'MiniBatchSize', 64, ... 'InitialLearnRate', 1e-3, ... 'LearnRateSchedule', 'piecewise', ... 'LearnRateDropPeriod', 20, ... 'LearnRateDropFactor', 0.5, ... 'GradientThreshold', 1, ... 'ValidationData', {XVal, YVal}, ... 'ValidationFrequency', 30, ... 'ValidationPatience', 10, ... 'Shuffle', 'every-epoch', ... 'Plots', 'training-progress', ... 'Verbose', false);MiniBatchSize取 32 到 128 之间,样本量少时用 32。InitialLearnRate从1e-3开始,如果损失曲线剧烈震荡,降到1e-4。LearnRateDropPeriod和LearnRateDropFactor实现分段衰减,每 20 轮学习率乘 0.5。GradientThreshold设为 1 可以裁剪梯度,防止 GRU 梯度爆炸。ValidationPatience设为 10,验证损失连续 10 次不下降就停止。Shuffle设为'every-epoch'打乱样本顺序,避免批内相关性过强。
如果使用dlnetwork而不是Layer数组,需要用trainnet或自定义训练循环。trainnet支持dlnetwork,但损失函数和输出格式要匹配。对于回归任务,可以在trainnet中指定'LossFcn','mse'。无论哪种方式,都要保证训练集、验证集、测试集按时间顺序切分,不能随机打乱后再切分,否则会造成未来信息泄漏。
4. 多变量时序预测全流程:滑动窗口、训练脚本与指标计算
数据经过 VMD-SE 重组后,得到新的多变量矩阵。接下来要把时间序列切成监督学习样本。多变量预测的输入是过去lookback个时间步的所有通道,输出是未来horizon个时间步的目标通道。滑动窗口构造样本对时,要保证输入窗口和输出窗口之间没有重叠,否则模型会“看到”未来。训练集、验证集、测试集按时间先后切分,通常 70%/15%/15%。
4.1 构造 lookback-horizon 样本对与多变量输入格式
下面函数把T×N矩阵转成C×B×T格式的元胞数组,方便送入trainNetwork或dlnetwork。
function [X, Y] = create_dataset(data, lookback, horizon, targetCol) % data: T×N 多变量矩阵 % lookback: 输入历史长度 % horizon: 预测步长 % targetCol: 目标列索引 T = size(data, 1); N = size(data, 2); numSamples = T - lookback - horizon + 1; X = cell(1, numSamples); Y = zeros(horizon, numSamples); for i = 1:numSamples % 输入:lookback×N,转置为 N×lookback X{i} = data(i:i + lookback - 1, :)'; % 输出:horizon×1,目标列 Y(:, i) = data(i + lookback:i + lookback + horizon - 1, targetCol); end end逻辑说明:X{i}的维度是N×lookback,对应sequenceInputLayer的通道维和时间维。Y的每一列是一个样本的未来horizon步目标值。如果horizon大于 1,输出层需要输出多个值,可以把fullyConnectedLayer(1)改成fullyConnectedLayer(horizon),或者保持单步输出并滚动预测。滚动预测会累积误差,适合短期预测;直接多步输出适合固定步长预测。
调用方式:
lookback = 48; % 例如过去 48 个时间点 horizon = 1; % 预测下一步 targetCol = 1; % 目标列 [XAll, YAll] = create_dataset(dataReconstructed, lookback, horizon, targetCol); % 按时间切分 numTrain = floor(0.7 * length(XAll)); numVal = floor(0.15 * length(XAll)); XTrain = XAll(1:numTrain); YTrain = YAll(:, 1:numTrain); XVal = XAll(numTrain+1:numTrain+numVal); YVal = YAll(:, numTrain+1:numTrain+numVal); XTest = XAll(numTrain+numVal+1:end); YTest = YAll(:, numTrain+numVal+1:end);注意YAll是horizon×numSamples矩阵,切分时要按列切。XTrain是元胞数组,trainNetwork可以直接接受。如果使用dlnetwork,需要把元胞数组转成dlarray并标记维度。一个简单做法是写循环,把每个样本转成dlarray(X{i}, 'CT'),批处理时再拼接。
4.2 训练、验证与测试集切分
用trainNetwork训练 Layer 数组:
net = trainNetwork(XTrain, YTrain, layers, options);如果layers最后是regressionLayer,YTrain应该是horizon×numTrain的矩阵。trainNetwork会自动按批处理元胞数组。训练过程中观察training-progress窗口,重点看验证损失是否在下降。如果训练损失下降但验证损失上升,说明过拟合,可以增大 dropout、减小网络宽度或增加训练数据。如果两者都不下降,检查学习率是否太小、输入是否归一化、VMD 重构是否引入异常值。
训练完成后,在测试集上预测:
YPred = predict(net, XTest); YPred = YPred'; % 转成 numTest×horizon YTest = YTest'; % 转成 numTest×horizon如果输出是多步,YPred的维度要和YTest对齐。预测结果还需要反归一化。假设归一化时保存了目标列的均值和标准差:
YPredReal = YPred * stdTarget + meanTarget; YTestReal = YTest * stdTarget + meanTarget;4.3 反归一化与 RMSE/MAE/MAPE/R2 计算
评价指标不能只看一个。RMSE 对大误差敏感,MAE 更稳健,MAPE 反映相对误差,R2 衡量拟合优度。多变量预测中,目标列的量纲不同,MAPE 要避免分母接近零的情况。
rmse = sqrt(mean((YTestReal - YPredReal).^2, 'all')); mae = mean(abs(YTestReal - YPredReal), 'all'); mape = mean(abs((YTestReal - YPredReal) ./ YTestReal), 'all') * 100; r2 = 1 - sum((YTestReal - YPredReal).^2, 'all') / ... sum((YTestReal - mean(YTestReal, 'all')).^2, 'all'); fprintf('RMSE=%.4f, MAE=%.4f, MAPE=%.2f%%, R2=%.4f\n', rmse, mae, mape, r2);| 指标 | 公式 | 特点 | 使用注意 |
|---|---|---|---|
| RMSE | sqrt(mean((y-yhat)^2)) | 对大误差敏感 | 与目标量纲一致 |
| MAE | mean(abs(y-yhat)) | 稳健,解释直观 | 不放大异常值 |
| MAPE | mean(abs((y-yhat)./y))*100 | 相对误差,跨量纲可比 | 分母接近零时失效 |
| R2 | 1 - SSres/SStot | 拟合优度,越接近 1 越好 | 对非线性模型可能为负 |
把 VMD-SE-Transformer-GRU 的结果和单一 GRU、单一 Transformer、未分解的 GRU 做对比,才能看出分解和组合是否真的有效。对比时保持训练集、验证集、测试集切分一致,随机种子固定,否则差异可能来自数据划分而不是模型结构。
5. 调参与排错:从模态混叠到过拟合的排查清单
VMD-SE-Transformer-GRU 的调参不是孤立地调某一层,而是分解参数、样本熵分组、网络容量三者联动。VMD 的 K 和 alpha 决定了输入分量的尺度和数量,样本熵阈值决定了分组是否合理,Transformer 的头数和 GRU 的隐藏单元决定了模型能拟合多复杂的映射。一个常见误区是先把 VMD 调到完美,再单独调网络;实际上 VMD 产生的分量如果过于平滑,网络会欠拟合;如果分量噪声过多,网络会过拟合。
5.1 VMD 参数与网络容量联动调整
| 现象 | 可能原因 | 调整方向 |
|---|---|---|
| 验证损失远大于训练损失 | 输入分量噪声多,网络容量过大 | 增大样本熵阈值,把更多 IMF 归入低频组;减小 dModel 或 GRU 单元数 |
| 训练损失下降很慢 | 输入分量过于平滑,缺少可学习变化 | 减小样本熵阈值,保留更多高频组;增大学习率或增加注意力头数 |
| 预测曲线滞后 | lookback 太短,GRU 感受野不足 | 增大 lookback,或增加 GRU 层数 |
| 预测曲线抖动 | 高频组能量占比过高 | 增大 VMD 的 K 或 alpha,重新做样本熵分组 |
| 某些测试点误差极大 | 归一化参数不一致或存在异常值 | 检查反归一化代码,用训练集统计量处理测试集 |
我一般会先固定网络结构,用网格搜索调 VMD 的 K 和 alpha,观察验证集 RMSE。K 从 3 到 8,alpha 从 500 到 3000,步长可以取 500。每次分解后重新计算样本熵并重组,再训练同一个网络。记录每组参数下的验证误差,选误差最小且分量数量适中的组合。然后固定 VMD 参数,调 Transformer 头数和 GRU 单元数。头数通常取 2、4、8,GRU 单元数取 32、64、80、128。如果算力有限,优先调 GRU 单元数,因为自注意力层参数量随 dModel 平方增长。
5.2 常见报错与验证方法
VMD 分解后,第一件事是验证重构误差。所有 IMF 加残差应该接近原信号。如果误差过大,说明分解参数不合理。
% 验证 VMD 重构误差 recon = sum(imf, 2) + res; err = norm(dataNorm(:, col) - recon) / norm(dataNorm(:, col)); fprintf('通道 %d 重构相对误差:%.2e\n', col, err);相对误差在1e-6以下说明分解正常;如果大于1e-3,检查NumIMFs是否太小,或者PenaltyFactor是否过大导致部分分量未被提取。样本熵返回Inf时,通常是序列长度小于m+1或r太小。可以增大rFactor到 0.25,或者对 IMF 做重采样。如果kmeans分组出现空簇,说明所有 IMF 的熵值太接近,此时用阈值分组更稳定。
trainNetwork报维度不匹配时,重点检查输入元胞数组的维度。sequenceInputLayer(numFeatures)要求每个样本是numFeatures×T,如果构造时用了T×N,就会报错。dlnetwork的dlarray必须标记'CBT',批处理维不能丢。如果使用predict时忘记把XTest转成元胞数组,也会报错。训练不收敛时,先在一个极小子集上过拟合,比如取 50 个样本,看训练损失能否降到接近零。如果连小子集都拟合不了,说明网络结构或学习率有问题;如果小子集能拟合但全量验证差,说明数据划分或正则化需要调整。
最后一个实用技巧:把 VMD-SE 重组后的分量单独画出来,和原始目标列对比。高频组应该只在突变处有响应,低频组应该跟随主趋势。如果高频组包含了明显的趋势,说明样本熵分组把低频模态误判为高频,此时应提高阈值或改用kmeans并检查聚类中心。
本文还有配套的精品资源,点击获取