ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于CNN-GRU-Attention的电力负荷预测完整代码实现

基于CNN-GRU-Attention的电力负荷预测完整代码实现 简介基于注意力机制与CNN-GRU混合结构的负荷预测方法面向电力系统负荷预测及时间序列建模需求的开发者与研究者。方案通过卷积网络提取局部特征、门控循环单元捕获长期依赖并引入注意力机制动态加权关键历史时刻有效提升预测精度与鲁棒性可推广至风电功率等同类预测任务。压缩包体积仅1.32MB包含3个文件核心Python脚本实现数据预处理、模型构建与训练评估CSV数据集提供电力负荷历史样本依赖版本清单便于快速复现环境。已有443人学习适合具备Python与深度学习基础、希望掌握混合神经网络在时序预测中完整实践流程的读者。包内不仅给出了可直接运行的代码框架还梳理了从数据清洗、归一化到模型调优的完整思路以及CNN、GRU、Attention协同工作的原理细节方便二次开发与扩展。1. 负荷预测的难点与混合模型的解题思路在某次调度系统的改造中我发现传统RNN构建的负荷预测模型在换季时段的预测误差会突然放大一倍以上。换季意味着负荷模式从供暖型快速切换到制冷型单靠循环网络很难在几十个时间步后仍然保持对早期模式特征的记忆。把CNN、GRU和Attention机制按顺序级联起来恰好能分别处理局部特征、长程依赖和关键时间步聚焦这三个问题。这份基于CNN-GRU-Attention的负荷预测项目提供了一套可直接运行的完整代码包包含load1.csv数据、CNN-GRU-Attention.py主程序和依赖包版本_2.txt依赖清单。读者既能把它当作混合网络在时间序列上的落地方案来复现也能从中借鉴Attention权重计算的工程实现细节。2. 数据预处理从load1.csv到可训练的张量负荷预测模型的精度天花板有一半由输入张量的构造方式决定。项目中的load1.csv是典型的一维电力负荷序列时间间隔通常为15分钟或1小时字段一般包含时间戳和负荷值MW。这类数据在进入CNN-GRU-Attention之前必须完成清洗、滑窗、归一化三步任一环节的失误都会直接反映在预测曲线上。2.1 滑窗机制与时间步参数CNN的卷积核在时间轴上滑动时需要固定长度的输入序列。常见的做法是把连续24个时间步对应一天的小时级数据作为一个样本预测未来1个或多个时间步。滑窗的构造代码如下import numpy as np import pandas as pd def create_sequences(data, window24, horizon1): X, y [], [] for i in range(len(data) - window - horizon 1): X.append(data[i:iwindow]) y.append(data[iwindow:iwindowhorizon]) return np.array(X), np.array(y) df pd.read_csv(load1.csv, parse_dates[date], index_coldate) load df[load].values.reshape(-1, 1) X, y create_sequences(load, window24, horizon1) print(f样本数: {X.shape[0]}, 输入形状: {X.shape}, 标签形状: {y.shape})这里的window24表示用过去24个时间步预测未来1步。如果数据是小时级24恰为一天能完整覆盖一个日周期如果是15分钟级则一个窗口只覆盖6小时需要结合业务周期调整。horizon控制预测步长做多步预测时建议采用自回归方式逐步预测而不是一次性输出多步因为一次性多步会放大误差累积生成输出层时也要把Dense单元的个数改为horizon的对应值。2.2 load1.csv的缺失值与异常值清洗真实采集的负荷数据往往存在传感器故障、通信中断造成的缺失值以及瞬时尖峰等异常值。直接喂给GRU会导致梯度异常波动。我一般分两步处理数据类型判定方法处理策略缺失值df.isnull().sum()统计出现位置df.interpolate(methodlinear)线性插值异常尖峰超过均值±3σ 或超过滑动中位数±5倍MAD用该点前后24个负荷值的中位数替换重复时间戳df.index.duplicated()检查保留最后一条记录或按时间戳取均值合并需要特别注意的是负荷数据中的节假日效应比如春节、国庆会呈现持续数天的异常模式。我曾遇到过一个案例——直接把节假日数据当作异常剔除反而导致模型在节假日预测上严重偏小。正确做法是把节假日标记为独立特征列作为模型的辅助输入而不是粗暴删除。2.3 归一化方法与数据泄漏防范GRU使用tanh激活函数Attention层的权重计算也会受输入尺度影响所以归一化是必须的。项目常见的做法是使用MinMaxScaler将负荷值映射到[0,1]区间from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) scaled_load scaler.fit_transform(load) # 划分训练集和测试集注意只能对训练集fit train_size int(len(scaled_load) * 0.8) train_data scaled_load[:train_size] test_data scaled_load[train_size:] X_train, y_train create_sequences(train_data, window24, horizon1) X_test, y_test create_sequences(test_data, window24, horizon1)这里最关键的原则是scaler只能在训练集上调用fit_transform测试集使用同一个scaler.transform。如果对整个数据集做归一化再切分测试集的信息会通过均值、最大值等统计量泄漏到训练过程中导致验证指标虚高部署后真实误差显著增大。另外GRU层对输入尺度敏感归一化后的数据能明显加快收敛速度这一点在使用Adam优化器时尤其明显。2.4 时间特征扩展与依赖环境配置在使用依赖包版本_2.txt之前需要先把load1.csv解析成模型可用的特征矩阵。单纯使用负荷值序列时模型很难学到跨天的周期规律我一般会叠加小时序号、星期几、是否工作日三个特征df[hour] df.index.hour df[dayofweek] df.index.dayofweek df[is_workday] (df[dayofweek] 5).astype(int) # 时间特征归一化后与负荷值拼接 hour_encoded df[hour] / 24.0 day_encoded df[dayofweek] / 7.0 feature_matrix np.column_stack([scaled_load, hour_encoded, day_encoded, df[is_workday]])时间特征参与训练后模型能明确区分凌晨和晚高峰的负荷形态差异而不是仅靠卷积核从数值变化中隐式推断。feature_matrix的列数决定了模型输入层的n_features参数在下一章的模型搭建中需要同步修改。3. 模型搭建CNN-GRU-Attention各层设计混合模型的核心思想是分工——CNN负责在短时间窗口内提取变化模式GRU负责把多窗口信息串成连续记忆Attention则在GRU的输出序列上做重要程度筛选。这三层如果单独使用分别只能覆盖部分特征组合后才能在真实负荷数据上取得稳定优势。3.1 一维CNN层局部模式提取的细节这里使用Conv1D而不是Conv2D因为负荷序列本质是一维的形状为(batch_size, time_steps, features)。卷积核在时间轴上滑动每个滤波器相当于一个模式探测器——有些滤波器学到快速上升沿、有些学到缓慢下降沿组合起来就能覆盖常见的负荷变化形态。import tensorflow as tf from tensorflow.keras import layers, Model, Input def cnn_gru_attention_model(window24, n_features1): inputs Input(shape(window, n_features)) # CNN层两个卷积块逐步提取高层特征 x layers.Conv1D(filters64, kernel_size3, paddingsame, activationrelu)(inputs) x layers.BatchNormalization()(x) x layers.MaxPooling1D(pool_size2)(x) x layers.Conv1D(filters128, kernel_size3, paddingsame, activationrelu)(x) x layers.BatchNormalization()(x) x layers.MaxPooling1D(pool_size2)(x) # GRU层捕获时间维度的长期依赖 x layers.GRU(units128, return_sequencesTrue)(x) x layers.Dropout(0.2)(x) x layers.GRU(units64, return_sequencesTrue)(x) # Attention层自适应分配时间步权重 attention_w layers.Dense(1, activationtanh)(x) attention_w layers.Flatten()(attention_w) attention_w layers.Activation(softmax)(attention_w) attention_w layers.RepeatVector(64)(attention_w) attention_w layers.Permute([2, 1])(attention_w) sent_representation layers.Multiply()([x, attention_w]) x layers.Lambda(lambda x: tf.reduce_sum(x, axis1))(sent_representation) # 输出层 x layers.Dense(32, activationrelu)(x) outputs layers.Dense(1)(x) model Model(inputsinputs, outputsoutputs) return model model cnn_gru_attention_model(window24, n_features1) model.summary()代码里的前两个卷积块构成一个特征金字塔第一层64个滤波器捕捉宽度为3的小尺度变化池化后序列长度减半第二层128个滤波器在更高抽象级别上组合特征。paddingsame保证卷积后序列长度不缩减池化层是唯一的降采样来源。BatchNormalization在每级卷积后加入目的是稳定中间层特征分布防止深层网络出现协变量偏移尤其在负荷数据分布随季节漂移时能有效抑制梯度震荡。3.2 GRU层为什么比LSTM更合适GRU由Cho等人在2014年提出与LSTM相比只有重置门和更新门参数少了四分之一。负荷序列的长期依赖不是那种需要精确记忆几千步信息的问题它更多是周期性模式的加权组合GRU的简化结构在效果接近LSTM的前提下训练速度通常快20%左右。这在调参时直接体现为实验迭代次数的差距当需要反复尝试不同窗口长度时GRU能明显缩短等待时间。上面代码中两层GRU都设置了return_sequencesTrue目的是让第二层GRU输出完整的时间步序列供Attention计算权重而不是只输出最后一个状态。如果第二层GRU改成return_sequencesFalseAttention层就失去了作用对象。这是一个容易踩的坑——不少实现把GRU的最终输出直接接Dense层模型退化为没有注意力的普通RNN预测精度会明显下降。3.3 Attention权重计算与维度对齐Attention层的实现采用了加性注意力additive attention的思路先用tanh激活的Dense层为每个时间步计算原始分数再通过softmax归一化成概率分布。下面的代码展示了权重计算的核心流程# 假设gru_output形状为 (batch_size, time_steps6, gru_units64) score layers.Dense(1, activationtanh)(gru_output) # score形状: (batch_size, time_steps, 1) weights tf.nn.softmax(score, axis1) # 权重形状: (batch_size, time_steps, 1)每个时间步的注意力分布 context tf.reduce_sum(weights * gru_output, axis1) # 上下文向量形状: (batch_size, gru_units)softmax作用在axis1即时间步维度上保证所有时间步权重之和为1。权重与GRU输出逐元素相乘后按时间步求和得到的上下文向量包含了模型认为最重要的历史信息。从实验观察来看Attention权重在负荷平稳期接近均匀分布而在负荷突变的时段会集中到突变发生前的几个时间步上这符合直觉——突变时刻的前序状态对预测结果影响最大。3.4 完整模型的参数配置与选型依据模型整体参数梳理如下层输出形状参数量说明Conv1D (64, kernel3)(24, 64)256小卷积核提取局部变化MaxPooling1D (2)(12, 64)0时间轴降采样一半Conv1D (128, kernel3)(12, 128)24,704第二级特征组合MaxPooling1D (2)(6, 128)0时间步降为6GRU (128)(6, 128)98,688学习中间层时序关系GRU (64)(6, 64)36,480压缩特征并输出完整序列Attention(64)65加权求和上下文向量Dense (32)(32)2,080非线性映射Dense (1)(1)33线性输出预测值整套模型参数量约16万在电力负荷这种万级样本规模的数据集上属于轻量级。如果换用LSTM单元参数量会增加到20万以上但预测精度提升通常不超过1%性价比不高。批处理维度在表中没有体现实际训练时batch_size64一次前向传播计算64个样本的梯度。4. 训练过程与结果评估模型结构决定了能力上限但训练策略直接影响最终是否收敛到有效解。负荷预测中常见的失败模式有两种一种是用MSE作为损失时模型倾向于输出历史均值另一种是训练集损失很低但测试集严重滞后。这两种问题分别对应损失函数选择不当和评估方式有偏差。4.1 损失函数与优化器配置电力负荷预测的目标是让预测曲线尽可能贴近真实值最常用的损失是均方误差MSE。但只用MSE会导致模型在负荷低谷期贡献的梯度极小模型把所有注意力放在拟合高峰低谷段的预测曲线会变成近似直线。我通常会在MSE的基础上叠加一定比例的MAE组成组合损失def combined_loss(y_true, y_pred): mse tf.keras.losses.mean_squared_error(y_true, y_pred) mae tf.keras.losses.mean_absolute_error(y_true, y_pred) return mse 0.2 * mae model.compile( optimizertf.keras.optimizers.Adam(learning_rate0.001), losscombined_loss, metrics[mae] )MAE对绝对误差不敏感加入后会让模型在负荷平缓段也保持一定的拟合力度。系数0.2是根据实验经验选取的如果调大模型会偏向MAE优化峰值的拟合精度降低如果调小则退化为纯MSE。学习率设置为0.001是Adam优化器的常用起点。如果训练日志中出现loss振荡或NaN优先检查两点一是学习率是否过大需要降到0.0001二是输入数据中是否存在未处理的极大值。4.2 学习率衰减与早停策略循环神经网络在训练集上前期收敛迅速但通常20轮以后开始出现过拟合表现为验证集loss回升。常见做法是引入ReduceLROnPlateau回调当验证loss连续多个周期不下降时把学习率减半同时用EarlyStopping在验证loss持续不降时终止训练from tensorflow.keras.callbacks import ReduceLROnPlateau, EarlyStopping callbacks [ ReduceLROnPlateau(monitorval_loss, factor0.5, patience5, min_lr1e-6, verbose1), EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue, verbose1) ] history model.fit( X_train, y_train, validation_split0.15, epochs100, batch_size64, callbackscallbacks, verbose1 )早停的restore_best_weightsTrue容易被忽略但很关键。如果不设置模型在训练终止时保存的是最后一个epoch的权重而不是验证集表现最好的那一个。启用该参数后模型会自动恢复到验证loss最小的权重相当于多了一层保障。4.3 模型预测与反归一化训练结束后测试集上的预测值处于归一化空间需要映射回原始负荷单位才能与真实值对比pred_scaled model.predict(X_test) pred_values scaler.inverse_transform(pred_scaled.reshape(-1, 1)) y_test_original scaler.inverse_transform(y_test.reshape(-1, 1)) from sklearn.metrics import mean_absolute_error, mean_squared_error import numpy as np mae mean_absolute_error(y_test_original, pred_values) rmse np.sqrt(mean_squared_error(y_test_original, pred_values)) mape np.mean(np.abs((y_test_original - pred_values) / y_test_original)) * 100 print(fMAE: {mae:.2f} MW, RMSE: {rmse:.2f} MW, MAPE: {mape:.2f}%)反归一化时最常见的问题是预测结果范围与真实数据对不上。原因往往是测试集的负荷最大值超出了训练集的覆盖范围这种情况在负荷持续增长的场景中很容易发生。解决方法是每训练一个周期重新计算归一化参数或者对数据做差分后再缩放差分操作能把非平稳序列转化为平稳序列从而缩小训练集和测试集的数值分布差异。4.4 评估指标的对比视角同一份数据、同一个模型指标口径不同会得出完全不同的结论指标重点关注典型陷阱MAE平均绝对偏差鲁棒性好对少数大误差不敏感RMSE惩罚大误差被单个传感器异常值拉高MAPE相对误差直观负荷接近0时计算失真在实际电力业务中调度部门更关心高峰时段的预测精度因为这对发电计划制定影响最大。建议在整体指标之外额外统计每天8:00-22:00的时段MAE通常这个指标会比全天MAE高10%-20%这是正常现象。如果高峰时段误差高于整体30%以上需要检查训练数据中高峰时段的样本权重是否不足必要时在损失函数中给高峰时段样本增加权重系数。5. 模型迁移到风电功率预测的实用技巧这套CNN-GRU-Attention框架不局限于电力负荷迁移到风电功率预测时需要调整的核心是输入特征的构造方式和Attention的使用粒度。风电功率比负荷波动更剧烈受风速、风向、气温等多气象因素影响如果仍然只用功率单变量序列预测精度会明显下降。把气象数据作为扩展维度将one-hot编码的风向方位与风速、功率拼接成多特征输入CNN的输入通道数从1增加为19卷积核在多通道上同时提取气象与功率的联合空间特征。下面代码展示了具体的构造方式# 假设 wind_speed, wind_direction_encoded, power 已完成归一化 multi_feature np.hstack([wind_speed, wind_direction_encoded, power]) def create_multi_seq(features, window12, horizon6): X, y [], [] for i in range(len(features) - window - horizon 1): X.append(features[i:iwindow]) # 预测未来6个时间步的平均功率减少单点波动干扰 y.append(np.mean(features[iwindow:iwindowhorizon, -1])) return np.array(X), np.array(y)迁移时需要注意两点。第一卷积核的kernel_size从3适当减小到2因为风速变化的时间尺度比负荷更细大卷积核容易把突变信息平滑掉。第二GRU层的单元数需要减少风电功率预测中气象特征对功率的影响往往在6小时以内过大的隐层单元会导致过拟合实测中把GRU单元从128/64缩减到64/32通常能提升泛化能力。验证迁移是否有效时不要只看MAPE整体下降了多少还应该对比模型在中等风速时段和爬坡时段的误差分布分位数。CNN-GRU-Attention在爬坡时段的优势通常在10%-15%而平稳时段优势很小甚至没有如果只报整体指标很容易淹没这个分布上的差异。调试入口除了模型本身还可以从预测残差入手——绘制残差随时间变化的曲线观察残差是否在特定时段聚集这能帮助判断是特征缺失还是模型容量不足。在Attention权重可视化的对比中风电场景的权重会明显集中在爬坡前几个时间步而负荷场景的权重相对分散这个差异也可以作为判断模型是否学对了模式的快速依据。本文还有配套的精品资源点击获取
返回列表