☰
VMD-CNN-BiLSTM-Attention负荷预测实战:从分解到调参避坑
2026/10/9 15:56:47 网站建设 项目流程

简介:这份资源面向电力系统负荷预测方向的学习者与毕业设计开发者,提供基于VMD-CNN-BiLSTM-Attention组合模型的完整Python代码实现。内容围绕变分模态分解(VMD)与深度学习网络融合思路展开,适合具备一定Python与深度学习基础、需要完成相关课题或复现实验的读者参考。压缩包共15个文件,约2.86MB,以py脚本为主体,辅以ipynb交互式笔记、zbak备份文件、csv与xlsx数据表及说明文档,覆盖VMD分解数据保存、CNN-BiLSTM、CNN-BiGRU、CNN-LSTM、LSTM等多组对比模型,便于横向比较不同网络结构的预测效果。目前已有119人学习下载。读者可从中获取完整的负荷预测建模流程、数据预处理与分解脚本、多模型对照实验代码以及四折交叉验证的notebook示例,为论文写作与算法改进提供可运行的参考基线。

1. 负荷预测里,VMD-CNN-BiLSTM-Attention 到底解决了什么问题

电力负荷曲线看着平滑,实际是强非线性、多周期叠加的产物:日周期、周周期、季节趋势、节假日突变、温度敏感负荷全缠在一起。直接把原始序列丢给 LSTM,模型往往学不到高频细节,预测出来一条"温吞水"曲线,峰谷差被抹平,调度侧根本不敢用。VMD-CNN-BiLSTM-Attention 这套组合的思路是:先用变分模态分解(VMD)把负荷序列拆成若干条相对平稳的子模态,再用 CNN 抓局部波形特征,BiLSTM 抓前后双向时序依赖,最后用 Attention 给关键时间步加权。它适合做短期负荷预测(日前、日内),也适合做光伏、风电这类波动性强的功率预测迁移。这篇笔记按"分解→建模→训练→排错→进阶"的顺序,把每个环节的参数和坑讲清楚,代码用 Python + PyTorch 实现,能直接照着跑。

2. VMD 分解:把负荷序列拆成可学习的子模态

2.1 为什么先分解,而不是直接喂给网络

负荷序列的频谱里,低频是趋势,中频是日周期,高频是随机扰动和突变。单一模型同时拟合这三种成分,梯度会被高频噪声主导,收敛慢且容易过拟合。VMD 的核心是把信号分解成 K 个有限带宽的模态分量(IMF),每个模态围绕一个中心频率,通过交替方向乘子法迭代求解变分问题。相比 EMD,VMD 没有模态混叠,端点效应也弱,这对负荷预测很关键——负荷序列两端往往是凌晨低谷,EMD 在这里容易发散。

VMD 有两个必须调的参数:模态数 K 和惩罚因子 alpha。K 太小,模态欠分解,趋势和周期混在一起;K 太大,过分解,出现无意义的噪声模态。工程上常用中心频率法确定 K:逐步增大 K,观察各模态中心频率,当出现两个模态中心频率接近时停止。alpha 控制带宽,一般取 2000 左右,噪声大时调大。

2.2 VMD 分解的 Python 实现与参数说明

import numpy as np from vmdpy import VMD def vmd_decompose(signal, K=5, alpha=2000, tau=0, DC=0, init=1, tol=1e-7): """ signal: 一维负荷序列, shape (N,) K: 模态数, 常用 4~7 alpha: 带宽约束, 常用 1000~3000 tau: 噪声容限, 无噪声取 0 DC: 是否保留直流分量, 负荷序列取 0 init: 中心频率初始化, 1 表示均匀初始化 tol: 收敛容差 返回: u (K, N) 各模态, u_hat, omega 中心频率 """ u, u_hat, omega = VMD(signal, alpha, tau, K, DC, init, tol) return u, omega # 示例:对单条负荷序列分解 load = np.loadtxt('load_15min.csv', delimiter=',') # 15分钟采样, 一天96点 u, omega = vmd_decompose(load, K=5, alpha=2000) print('各模态中心频率:', omega[-1])

这段代码调用vmdpy库,输入是一维归一化后的负荷序列。K=5是短期负荷的常用起点,alpha=2000对大多数日负荷曲线够用。omega[-1]是最后一次迭代的中心频率,如果两个值相差小于 0.01,说明 K 偏大,需要减小。分解后每个模态单独归一化,再分别送入后续网络,最后把各模态预测结果相加得到最终负荷。

注意:VMD 分解必须用训练集和测试集拼接后的完整序列做一次分解,再按时间切分。如果先切分再分别分解,测试集的模态和训练集不对齐,预测结果无法相加,这是最常见的翻车点。

2.3 模态数 K 怎么定:中心频率法实操

中心频率法的操作是:K 从 3 开始,每次加 1,记录最后一组中心频率。当 K=K0 时出现两个中心频率接近(差值小于 0.01),说明 K0 过大,取 K0-1。下面是一个批量测试的脚本:

def find_best_K(signal, K_range=range(3, 9), alpha=2000): results = {} for K in K_range: u, omega = vmd_decompose(signal, K=K, alpha=alpha) freqs = np.sort(omega[-1]) min_diff = np.min(np.diff(freqs)) results[K] = min_diff print(f'K={K}, 中心频率={freqs}, 最小间隔={min_diff:.4f}') return results # 一般选最小间隔开始小于 0.01 的前一个 K

实际负荷数据里,K=5 或 6 最常见。如果数据含明显周周期,K 可以到 7。不要盲目追求大 K,过分解会让每个模态样本量不足,BiLSTM 训练时 batch 内方差过大,反而掉点。

3. CNN-BiLSTM-Attention 网络搭建:结构、维度与代码

3.1 各模块分工与数据流

分解后的每个模态是一个一维序列,先做滑动窗口构造样本。假设用过去 96 个点(一天)预测未来 16 个点(4 小时),窗口长度 96,预测步长 16。数据进入 CNN 层,用一维卷积提取局部波形特征,卷积核沿时间轴滑动,输出通道数 32 或 64。CNN 输出送入 BiLSTM,双向各 64 隐藏单元,拼接后得到 128 维时序特征。Attention 层对 BiLSTM 每个时间步的输出计算权重,加权求和后送入全连接层输出预测值。

这个结构里,CNN 负责"看局部形状",BiLSTM 负责"看前后文",Attention 负责"挑重点时间步"。负荷预测中,Attention 权重往往集中在早晚高峰前的时间步,这是符合物理直觉的。

3.2 PyTorch 网络定义与关键参数

import torch import torch.nn as nn class CNN_BiLSTM_Attention(nn.Module): def __init__(self, input_dim=1, cnn_channels=32, kernel_size=3, lstm_hidden=64, lstm_layers=1, pred_len=16, dropout=0.2): super().__init__() # CNN: 一维卷积 + 池化, 提取局部特征 self.conv = nn.Sequential( nn.Conv1d(input_dim, cnn_channels, kernel_size, padding=kernel_size//2), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(cnn_channels, cnn_channels, kernel_size, padding=kernel_size//2), nn.ReLU() ) # BiLSTM: 双向时序建模 self.bilstm = nn.LSTM(cnn_channels, lstm_hidden, lstm_layers, batch_first=True, bidirectional=True, dropout=dropout) # Attention: 对时间步加权 self.attn = nn.Linear(lstm_hidden * 2, 1) self.fc = nn.Linear(lstm_hidden * 2, pred_len) def forward(self, x): # x: (batch, seq_len, input_dim) -> (batch, input_dim, seq_len) x = x.permute(0, 2, 1) x = self.conv(x) # (batch, cnn_channels, seq_len/2) x = x.permute(0, 2, 1) # (batch, seq_len/2, cnn_channels) out, _ = self.bilstm(x) # (batch, seq_len/2, lstm_hidden*2) # Attention 权重 attn_score = self.attn(out) # (batch, seq_len/2, 1) attn_weight = torch.softmax(attn_score, dim=1) context = torch.sum(out * attn_weight, dim=1) # (batch, lstm_hidden*2) pred = self.fc(context) # (batch, pred_len) return pred

cnn_channels=32对单模态输入够用,如果多模态并行输入可以加到 64。kernel_size=3是负荷序列的常用值,太大容易平滑掉尖峰。lstm_hidden=64配合双向得到 128 维,再大容易过拟合。dropout=0.2在 BiLSTM 层间使用。Attention 用单层线性映射到标量再 softmax,这是最简洁有效的实现,不需要多头。

注意:CNN 的 MaxPool1d(2) 会把序列长度减半,如果原始窗口是 96,经过一次池化变 48,BiLSTM 的序列长度是 48。预测步长 16 不受影响,因为最后是全连接映射。如果窗口很短(比如 24),建议去掉池化层,否则时序信息损失太多。

3.3 多模态输入的处理方式

VMD 分解出 K 个模态,有两种喂法。第一种是每个模态单独训练一个 CNN-BiLSTM-Attention,最后相加,这样模型简单但训练 K 次。第二种是把 K 个模态作为 K 个通道,输入维度改成 K,一次训练。第二种更高效,但要求各模态对齐。我一般用第二种,把input_dim设为 K,数据构造时把 K 个模态堆叠成 (N, K) 再滑窗。

def make_dataset(modes, window=96, pred_len=16): """ modes: (K, N) 各模态 返回: X (samples, window, K), y (samples, pred_len) """ K, N = modes.shape data = modes.T # (N, K) X, y = [], [] for i in range(N - window - pred_len + 1): X.append(data[i:i+window]) y.append(data[i+window:i+window+pred_len, 0]) # 用第一个模态或原始负荷做目标 return np.array(X), np.array(y)

目标 y 通常用原始负荷序列的对应片段,而不是某个模态,因为最终要预测的是真实负荷。训练时对 X 和 y 分别做归一化,预测后再反归一化。

4. 训练、调参与评估:让模型真正收敛

4.1 损失函数与优化器选择

负荷预测常用 MSE 或 MAE。MSE 对尖峰敏感,MAE 更鲁棒。如果数据有极端天气导致的尖峰,建议用 HuberLoss,兼顾两者。优化器用 Adam,学习率 1e-3 起步,配合 ReduceLROnPlateau 在验证损失不降时减半。

model = CNN_BiLSTM_Attention(input_dim=K, pred_len=16) criterion = nn.HuberLoss(delta=1.0) optimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-5) scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=5) for epoch in range(100): model.train() for xb, yb in train_loader: optimizer.zero_grad() pred = model(xb) loss = criterion(pred, yb) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) optimizer.step() # 验证 model.eval() val_loss = evaluate(model, val_loader, criterion) scheduler.step(val_loss)

clip_grad_norm_是必须的,BiLSTM 在负荷序列上容易梯度爆炸,尤其是 K 较大时。weight_decay=1e-5轻微正则化,不要太大,否则欠拟合。

4.2 评价指标与结果解读

短期负荷预测看三个指标:MAPE(平均绝对百分比误差)、RMSE(均方根误差)、MAE。MAPE 在负荷接近零时不稳定,所以凌晨时段要单独看。一般日前预测 MAPE 在 2%~4% 算可用,日内 4 小时预测可以到 1.5%~2.5%。

def metrics(y_true, y_pred): mae = np.mean(np.abs(y_true - y_pred)) rmse = np.sqrt(np.mean((y_true - y_pred) ** 2)) mape = np.mean(np.abs((y_true - y_pred) / (y_true + 1e-6))) * 100 return mae, rmse, mape

如果 MAPE 正常但 RMSE 很大,说明个别尖峰预测失败,检查 VMD 是否把尖峰分到了高频模态而该模态被过平滑。如果 MAPE 在凌晨特别高,检查归一化方式,min-max 归一化在低谷段会放大误差。

4.3 消融实验怎么做才有说服力

要证明 VMD、CNN、BiLSTM、Attention 各自有用,做四组对比:去掉 VMD 直接预测、CNN 换全连接、BiLSTM 换单向 LSTM、去掉 Attention。每组跑 3 个随机种子取平均,否则单次结果波动可能误导。表格如下:

模型MAPE(%)RMSE(MW)MAE(MW)
VMD-CNN-BiLSTM-Attention2.3145.232.1
无 VMD3.8778.555.3
CNN 换 FC2.9558.741.2
BiLSTM 换 LSTM2.6851.336.8
无 Attention2.7453.638.5

这张表是典型趋势,VMD 贡献最大,Attention 贡献最小但稳定。如果无 Attention 反而更好,检查 Attention 是否过拟合,减少参数量或加 dropout。

5. 避坑与排查:那些让我重跑实验的细节

5.1 现象:预测曲线整体平移,形状对但幅值偏

原因:VMD 分解后各模态分别归一化,预测后反归一化时用了错误的均值/方差。或者目标 y 用了归一化后的模态而非原始负荷。解决:统一用原始负荷的均值和方差做反归一化,各模态预测相加后再反归一化一次。

5.2 现象:训练损失下降但验证损失震荡

原因:batch size 太小,负荷序列样本间相关性高,梯度方差大。或者学习率偏高。解决:batch size 调到 64 或 128,学习率降到 5e-4,加梯度裁剪。

5.3 现象:VMD 分解耗时过长,K=7 时几分钟

原因:VMD 迭代次数多,tol 设得太小。解决:tol 设 1e-6 够用,不要 1e-8。数据量大时先降采样或分段分解。

5.4 现象:Attention 权重几乎均匀,没有区分度

原因:BiLSTM 输出本身区分度低,或者 Attention 线性层初始化不好。解决:检查 BiLSTM 隐藏单元是否太小,加到 128;Attention 前加一层 tanh 激活。

5.5 现象:换数据集后 MAPE 翻倍

原因:新数据采样频率不同,窗口长度没改。15 分钟采样一天 96 点,1 小时采样一天 24 点,窗口必须跟着变。解决:窗口长度按"覆盖一个完整日周期"设定,96 或 24,预测步长按需求定。

6. 进阶技巧:让这套模型在真实场景里更稳

一个具体技巧是"残差修正"。VMD 分解后,低频模态预测很准,高频模态误差大。可以把高频模态的预测残差再用一个轻量 GRU 修正,或者直接把高频模态的预测值乘以一个衰减系数。我一般会先看各模态的预测 MAPE,对误差最大的模态单独调参,而不是整体调。

另一个技巧是滚动预测。不要一次性预测 16 个点,而是每次预测 1 个点,把预测值拼回输入窗口,滚动 16 次。这样 MAPE 通常能降 0.3~0.5 个百分点,代价是推理时间线性增加。如果做日前调度,滚动预测值得。

验证方法上,除了常规指标,建议画三张图:预测 vs 真实曲线、误差随时间分布、Attention 权重热力图。Attention 权重如果集中在负荷爬坡段,说明模型学到了物理规律;如果随机分布,说明 Attention 没起作用,需要回退检查。

最后说个血泪经验:这套模型对数据质量极其敏感,缺失值插补方式不同,MAPE 能差 1 个百分点。我现在的习惯是,拿到数据先画一遍曲线,把异常点标出来,用前后均值插补,再进 VMD。别指望模型能自动处理脏数据,那是后悔药都买不回来的坑。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询