拓冰建站拓冰建站
首页 / 资讯中心 / 正文

多特征LSTM电力负荷预测实战:从数据预处理到工程落地

简介基于Python深度学习框架的多特征电力负荷预测项目面向需要完成课程设计、期末大作业的本科或高职学生也适合希望上手时序预测实战的Python开发者。项目从原始数据加载与清洗入手通过多特征输入构建LSTM神经网络完整覆盖数据预处理、特征工程、模型训练、预测评估与结果可视化等关键环节可帮助读者快速理解电力负荷预测的深度学习实现流程。压缩包内共8个文件包括3个Python脚本分别负责数据处理、LSTM模型预测、图表展示、2个CSV数据文件、1个Excel数据文件、1个Markdown说明文档以及1个使用说明文本资源总大小约831KB结构清晰、开箱即用。目前已有479人学习下载。代码已调试完善无需修改即可复现结果配套的README与使用说明可辅助逐模块对照学习整个项目既是高分课程设计的完整参考方案也可作为期末大作业的实用模板同时便于二次扩展与算法替换。1. 多特征电力负荷预测从单序列拟合到工程化落地电力负荷预测在配网调度里的位置很特殊它既是调度员排班、机组启停的决策输入也是很多高校深度学习课程设计的常见选题。不少同学第一次做这个题目习惯只拿历史负荷序列训练一个单特征 LSTM模型也能收敛但一到答辩环节老师问“为什么节假日那几天误差突然变大”就答不上来——因为温度、湿度、日期类型这些外部特征没有进入模型。这个项目把数据、预处理脚本、LSTM 训练脚本和可视化脚本完整打包核心思路是让负荷预测不再只依赖“过去 24 小时的负荷值”而是把气象数据和日类型也作为输入特征一起送进循环神经网络。适合正在做课程设计或期末大作业的同学也适合想快速搭一套可复现的时序预测流程、却又不想从零开始处理日期对齐和归一化细节的人。2. data_processing.py多特征构造与时间窗口滑窗预处理2.1 原始数据形态与特征选择解压后先把 raw_data.csv 打开看一眼。这是典型的小时级电力负荷记录时间轴按小时推进除了负荷值外还包含了温度和湿度列。很多人在这个阶段就急着写模型忽略了数据形态分析实际上后面所有问题——预测偏移、训练集测试集分布不一致、指标虚高——都能追溯到数据预处理这一步。以常见做法来说原始表至少应该包含这几列字段名含义数据样例datetime时间戳小时级2021-05-01 00:00:00load有功负荷单位 MW456.32temperature室外温度单位 ℃22.5humidity相对湿度单位 %63.0先检查三件事时间列是否按升序排列、是否存在空值、是否存在重复时间戳。时间序列的空值处理不建议用均值直接填充更稳的做法是前向填充加线性插值组合因为负荷曲线在相邻小时内通常具有连续性。这里的逻辑是前向填充解决设备短暂停采导致的 NaN线性插值解决中间一小时缺失但前后都有值的情况。import pandas as pd import numpy as np df pd.read_csv(raw_data.csv, parse_dates[datetime]) df.sort_values(datetime, inplaceTrue) # 时间严格升序 df[load] df[load].ffill().interpolate() # 缺失负荷填充 df[temperature] df[temperature].interpolate(limit_directionboth)这里对 load 先用 ffill 再用 interpolate是因为如果缺失段较长纯线性插值会抹掉负荷峰谷的真实形态而 ffill 能先保留最近的有效值再在局部做平滑。temperature 直接插值即可它对短期突变不敏感。2.2 特征对齐与时间窗口滑窗多特征预测的核心不是简单地把几列数据拼在一起而是要构造出“过去 N 小时的特征序列 → 未来 1 小时的负荷”这样的监督学习样本。这里的 N 就是 look_back 窗口项目里常见取 24对应过去一天 24 小时的完整周期。可以这样理解每一个训练样本是一个形状为 (24, 特征数) 的矩阵目标值是该 24 小时之后那一个小时的负荷数值。特征选择上除了 temperature 和 humidity 这类气象特征还强烈建议加入 hour 和 weekend 两个派生特征。hour 是 0-23 的小时编号weekend 是 0/1 标记周六日设为 1这两个特征帮助模型识别“早晚高峰”和“工作日/休息日”的负荷模式差异。hour 直接作为数值输入有个问题23 和 0 在数值上距离很远但它们在时间语义上只差一小时所以更规范的做法是对 hour 做周期编码即 sin(hour/24 * 2π) 和 cos(hour/24 * 2π)让模型感知到时间的循环属性。def create_cyclical_features(df): hour df[datetime].dt.hour df[hour_sin] np.sin(2 * np.pi * hour / 24) df[hour_cos] np.cos(2 * np.pi * hour / 24) df[weekend] (df[datetime].dt.dayofweek 5).astype(int) return df周期编码的原理是把一维的 hour 标量映射到二维单位圆上的点sin 和 cos 两个分量保留了小时之间的邻近关系21 点和 23 点在圆上的距离比 21 点和 9 点更近这符合负荷曲线的真实变化规律。weekend 特征之所以单独保留为 0/1是因为工作日与周末的负荷形态差异不是“渐变”而是“跳变”数值编码比周期编码更合适。滑窗构造是预处理的核心环节。做法是用一个固定长度的窗口在时间轴上逐小时滑动每滑动一步产生一个样本。注意窗口内部必须完全落在历史数据内不能混入未来信息。这里不直接使用 pandas 的 shift 操作而是用数组索引切片来构造三维数组执行效率更高。def build_sequences(data, look_back24): X, y [], [] for i in range(len(data) - look_back): X.append(data[i:i look_back]) # 窗口内所有特征 y.append(data[i look_back, 0]) # 窗口后一小时的负荷 return np.array(X), np.array(y)build_sequences 返回的 X 形状是 (样本数, 24, 特征数)y 形状是 (样本数,)。这里的二维索引 data[i look_back, 0] 取的是负荷列前提是数据在进入本函数前已经把标签列排在第 0 列。这样做的好处是 LSTM 输入层不需要再做 reshape直接喂入即可。2.3 时间切分与归一化边界时间序列的样本切分和普通分类任务有一个根本区别不能随机打乱。很多人在这个阶段套用 sklearn 的 train_test_split(shuffleTrue)这是时序预测里最常见、也最隐蔽的错误。随机打乱后训练集和测试集中的样本会相互穿插模型等于提前看到了“未来”的分布规律验证集指标会虚高到几乎没有参考价值。正确做法是按时间顺序前 80% 为训练集后 20% 为测试集并且保证窗口构造在切分之前完成。归一化同样是时间序列任务的重灾区。用 MinMaxScaler 或 StandardScaler 时必须先只在训练集上 fit再用同一个 scaler 去 transform 训练集和测试集。如果对全量数据先 fit 再做切分测试集的最大最小值已经参与训练集的缩放映射这在指标评估上属于数据泄漏答辩时被追问很容易翻车。from sklearn.preprocessing import MinMaxScaler feature_cols [load, temperature, humidity, hour_sin, hour_cos, weekend] scaler_x MinMaxScaler().fit(X_train.reshape(-1, X_train.shape[-1])) X_train_scaled scaler_x.transform(X_train.reshape(-1, X_train.shape[-1])).reshape(X_train.shape) X_test_scaled scaler_x.transform(X_test.reshape(-1, X_test.shape[-1])).reshape(X_test.shape) scaler_y MinMaxScaler().fit(y_train.reshape(-1, 1)) y_train_scaled scaler_y.transform(y_train.reshape(-1, 1)).ravel() y_test_scaled scaler_y.transform(y_test.reshape(-1, 1)).ravel()X_train 和 X_test 用同一个 scaler_x 做缩放但 y 用独立的 scaler_y这是为了后面反归一化还原预测值时操作对象更清晰避免混淆输入输出两套尺度。scale 的范围默认是 [0,1]LSTM 配合默认 tanh 激活函数时这个区间比较稳定。如果负荷数据存在明显的极端尖峰可以改为 StandardScaler 减少离群值对均值的拉扯但课程设计场景下 MinMaxScaler 足够。提示检查归一化泄漏有一个快速方法——分别打印训练集和测试集的缩放前 min/max。如果测试集 min/max 整整齐齐落在训练集范围内很可能之前对全量数据做过 fit需要回头检查代码执行顺序。3. lstm_predict.py模型结构设计与三维输入张量3.1 为什么选 LSTM 而不是普通神经网络如果只是把多列特征摊平输入一个全连接网络也能得到一个可用的预测模型但效果通常不如 LSTM。原因在于负荷数据是强序列相关的今晚 22 点的负荷不仅和过去 1 小时的负荷相关还和过去 12 小时乃至 24 小时的变化趋势相关。普通 DNN 对每个输入特征的位置是固定的无法在时间维度上共享参数而 LSTM 的循环结构在不同时间步上共享同一套权重天然具备捕捉时间依赖性的能力。LSTM 内部的遗忘门和输入门各自承担了不同职责。遗忘门决定上一时刻的记忆中有多少信息需要保留输入门决定当前时刻的新信息有多少写入记忆。对电力负荷来说遗忘门可以学到“一周前同一天的负荷对今天影响有限但昨天的同时刻负荷很重要”这样的权重分配这是普通前馈网络做不到的。从工程角度看这里选择库的实现只需要关心网络层数和每一层的单元数不必自己编写门控逻辑。项目里 lstm_predict.py 的主体逻辑属于 Keras 风格这也是 Python 深度学习入门和课程设计中最主流的选择模型定义直观单卡训练在 CPU 上也能在几分钟内收敛。3.2 网络结构搭建与参数对照模型结构不需要特别复杂。数据规模是小时级、一年不到一万条样本两层 LSTM 加 Dropout 再加全连接层已经足够表达负荷曲线的非线性特征。更深的网络结构在这个数据量级上容易过拟合注意收敛速度也会明显下降。一个可复现性较好的结构如下from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dropout, Dense from tensorflow.keras.optimizers import Adam model Sequential([ LSTM(64, return_sequencesTrue, input_shape(look_back, n_features)), Dropout(0.2), LSTM(32, return_sequencesFalse), Dropout(0.2), Dense(16, activationrelu), Dense(1) ]) model.compile(optimizerAdam(learning_rate0.001), lossmse, metrics[mae]) model.summary()第一层 LSTM 设置 return_sequencesTrue 是为了向第二层 LSTM 输出完整的序列信息而不是只输出最后一个时间步的结果如果这里设置成 False第二层 LSTM 接收到的只是 24 个时间步压缩后的一个向量序列信息已经损失大半。第二层 LSTM 使用 return_sequencesFalse最终只输出最后一个时间步的隐藏状态。Dropout 放在每个 LSTM 层之后作用是随机断开一部分神经元的连接减轻循环网络在训练集上的记忆过载这也是“动手深度学习”实践中常用的结构模式。模型输入的三维张量形状为 (batch_size, timesteps, features)。batch_size 自动由数据量决定timesteps 对应 look_back24features 对应预处理后特征矩阵的列数也就是 6。如果你的特征列有增删n_features 需要同步调整。model.summary() 输出中第一层显示的 Param 数量可以直接用来检查输入维度是否写对常见问题是这里多一列少一列导致全部参数错位。3.3 超参数建议与收敛检查超参数调优不需要做网格搜索课程设计的数据量级下按下面这张表的起始值跑一遍再看损失曲线决定放大还是缩小即可。参数建议值调整依据look_back24数据是小时级24 覆盖完整日周期粒度是分钟级时改为 96 或 48第一层 LSTM 单元数64数据量小就减半数据量上万可提高到 128第二层 LSTM 单元数32保持是第一层的一半左右防止过拟合Dropout0.2损失曲线震荡严重时提高到 0.3-0.4learning_rate0.001Adam 默认值loss 不降就降到 0.0005batch_size64训练样本数不是 64 的整数倍时需要手动调整避免最后一个 batch 报错epochs50配合早停实际生效轮次一般在 30 轮以内loss 不降时不要急着调网络结构。先检查训练数据的输入是否包含了 NaN 或 inf再检查归一化后的特征分布是否集中在 [0,1] 区间最后才考虑是不是学习率设置问题。LSTM 对输入尺度非常敏感一个量级异常的输入特征可能让整个训练曲线在初期就发散这一步排查顺序能省下大量时间。4. 训练、预测与评估指标损失曲线和 MAPE 怎么算才说服老师4.1 训练主流程与回调函数训练过程除了调用 model.fit 之外还需要加入 EarlyStopping 和 ReduceLROnPlateau 两个回调。EarlyStopping 监控验证集损失连续多个 epoch 不下降就停止训练目的是防止后期过拟合ReduceLROnPlateau 在验证损失进入平台期后自动把学习率减半让权重更新步长变小帮助损失继续下降。from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau early_stop EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) reduce_lr ReduceLROnPlateau(monitorval_loss, factor0.5, patience5, min_lr1e-5) history model.fit( X_train_scaled, y_train_scaled, validation_data(X_test_scaled, y_test_scaled), batch_size64, epochs50, callbacks[early_stop, reduce_lr], verbose1 )这里 validation_data 传入的是切分好的测试集而不是从训练集里再切一部分出来因为时序数据切三次会让每个段都太短。patience10 的意思是验证损失连续 10 轮不下降才算训练结束这个值太小时训练过早收敛太大则失去了早停的意义。restore_best_weightsTrue 保证最终保存的是验证损失最小的那组权重而不是最后一轮可能已经过拟合的权重。4.2 预测结果的反归一化模型输出的是归一化后的预测值直接拿去算误差没有任何意义。反归一化的目标是把预测值和真实值一起还原到原始量纲MW再计算误差指标。这里最容易犯的错误是维度不一致LSTM 的预测输出形状是 (样本数, 1) 或 (样本数,)而 scaler_y 期望输入形状至少是 (样本数, 1)直接用 ravel() 过的数组传给 inverse_transform 很可能报错或得到错误结果。y_pred model.predict(X_test_scaled) y_pred_inverse scaler_y.inverse_transform(y_pred.reshape(-1, 1)).ravel() y_test_inverse scaler_y.inverse_transform(y_test_scaled.reshape(-1, 1)).ravel()reshape(-1, 1) 将一维数组强行变为列向量inverse_transform 内部按特征维度逐列还原之后再 ravel 拉平为和 y_test_inverse 对齐的一维数组。如果最终两组数据的长度不一致可以打印两者的 shape 逐项检查大部分情况都出在这一个 reshape 上。4.3 MAE、RMSE、MAPE 的工程含义评估指标决定了答辩时老师对项目的接受程度。只用 loss 展示结果是不够的因为 loss 是归一化空间里的均方误差和真实负荷的物理单位无关。需要补充的指标至少包括 MAE、RMSE、MAPE 三个其中 MAPE 是电力负荷预测文献里最常用、也最直观的指标它直接反映平均误差占真实负荷的百分比。from sklearn.metrics import mean_absolute_error, mean_squared_error mae mean_absolute_error(y_test_inverse, y_pred_inverse) rmse np.sqrt(mean_squared_error(y_test_inverse, y_pred_inverse)) mape np.mean(np.abs((y_test_inverse - y_pred_inverse) / y_test_inverse)) * 100 print(fMAE: {mae:.2f} MW) print(fRMSE: {rmse:.2f} MW) print(fMAPE: {mape:.2f}%)表格里给出一个课程设计阶段的参考区间注意不同地区电网负荷特性不同区间只作参考。指标参考范围说明MAPE5% 优秀5-8% 良好10% 需要调整节假日样本会被拉高可单独统计工作日/非工作日MAE视负荷量级而定一般为峰值的 3-6%反映平均绝对误差RMSEMAE 的 1.2-1.5 倍左右如果远超这个倍数说明存在个别预测极差的尖峰时刻MAPE 计算公式里除以 y_test_inverse当真实负荷接近 0 时会得到极大的异常值。电力负荷一般不会为 0但这个坑仍然值得提一句如果所用的数据集里存在接近 0 的样本点MAPE 计算前需要先过滤这些点或改用 sMAPE。4.4 与朴素基线对比评估模型优劣不能只看自身误差还需要和简单基线做对比。最常用的基线是“上一时刻负荷”——也就是用 t-1 时刻的真实负荷当作 t 时刻的预测值以及 DNN 回归模型。对比的意义在于如果 LSTM 的预测误差比“昨天同时刻负荷”这种朴素思路还高说明模型没有学到有效的时序模式问题大概率出在数据处理而不是网络结构。这里可以写一个极简的 DNN 对比模型输入形状是 (look_back, n_features) 展平后的向量其余训练参数保持一致。比较两者的 MAPE 和测试集损失LSTM 通常会在 RMSE 上明显占优因为 DNN 无法利用时间步之间的依赖关系。这个对比在答辩时很有说服力因为老师看到的不只是一个“能跑通”的模型而是有实验对比依据的选型论证。5. data_show.py 可视化脚本的排查价值四张图定位模型问题5.1 四张核心图与它们的作用data_show.py 的价值不只在于出一张能放进论文的图更在于通过不同的图把训练过程和预测结果拆开检查顺藤摸瓜定位问题。四张图各有分工损失收敛曲线反映训练过程是否正常训练集拟合图反映模型对已见数据的拟合能力测试集预测对比图反映模型的泛化能力特征相关性热力图则用于数据层面的特征筛查。损失曲线是判断训练稳定性的第一入口。正常情况是训练损失和验证损失同步下降然后双双进入平台期。如果训练损失持续下降但验证损失在下降一段后开始回升说明已经过拟合如果两者从一开始就不下降说明学习率或数据归一化有问题。测试集预测对比图是最直观的展示图把真实负荷和预测负荷画在同一张折线图上。理想状态下两条线几乎重合在峰谷处有轻微偏移。最常见的异常是预测曲线整体滞后于真实曲线也就是预测的波峰比真实波峰晚了一到两个小时通常意味着 look_back 窗口太短模型没有足够长的历史来提前感知负荷上升趋势。import matplotlib.pyplot as plt plt.figure(figsize(12, 5)) plt.plot(y_test_inverse[:200], labelActual, linewidth1.2) plt.plot(y_pred_inverse[:200], labelPredicted, linewidth1.2, linestyle--) plt.legend() plt.xlabel(Hour) plt.ylabel(Load (MW)) plt.title(LSTM Test Set Prediction) plt.grid(alpha0.3) plt.tight_layout() plt.savefig(prediction_result.png, dpi200)这里只画前 200 个点即被测试集的 8 天多时间时间跨度太大会让峰谷细节挤压成一片黑色区域反而看不出预测质量。title 中标注 LSTM 和 Test Set 是为了防止多轮实验时混淆输出图。5.2 特征相关性热力的使用方式特征相关性热力图中使用皮尔逊相关系数把预处理后的特征列两两之间、特征与负荷之间的相关程度以颜色深度展示出来。核心查看方式是扫最后一列——每个特征和 load 的相关系数绝对值越大说明该特征对负荷的线性贡献越强。但要注意相关系数低不代表特征无用LSTM 能学习到非线性关系相关性热力图只用作特征筛查参考不作为增删特征的唯一依据。import seaborn as sns corr df[[load, temperature, humidity, hour_sin, hour_cos, weekend]].corr() plt.figure(figsize(8, 6)) sns.heatmap(corr, annotTrue, fmt.2f, cmapcoolwarm, cbarTrue) plt.tight_layout() plt.savefig(feature_correlation.png, dpi200)如果 temperature 与 load 的相关系数接近 0说明这份数据对应的地区或时间段气温对负荷影响不明显可以考虑在后续实验中把 temperature 从特征中剔除或改用更细粒度的温度数据。weekend 与 load 的相关性通常为负值因为周末负荷低于工作日符号本身不重要绝对值才有参考意义。5.3 常见踩坑排查清单下面这张表整理了课程设计过程中最常见的几类异常现象和排查路径按出现频率排序每一条都对应一个真实的检查点。现象可能原因检查与处理方式训练 loss 为 NaN学习率过大或输入数据含 NaN/inf降低学习率检查预处理后是否有 NaN预测曲线是水平直线LSTM 输出被压到均值附近检查 Dropout 是否过高确认训练集是否切分正确预测整体滞后实际 1-2 小时look_back 太短或特征中缺小时信息增大 look_back 到 48确认 hour_sin/hour_cos 已加入验证损失远高于训练损失训练集测试集分布差异大检查切分是否按时间顺序确认没有随机 shuffleMAPE 超过 15%未考虑节假日或温度突变加入 weekend/节假日特征按月份分段建模测试集归一化结果超出 [0,1]测试集存在训练集范围外的值改用 StandardScaler或扩大训练集覆盖时段最后一行的“归一化结果超出范围”是时序预测的常见边界问题。训练集覆盖的是春夏两季测试集落在秋冬气温和负荷的分布区间不同即使测试集包含训练集没见过的极端值MinMaxScaler 也会把这种变化用负数或大于 1 的数值表达出来。这种情况下建议把缩放器换成 RobustScaler它基于中位数和分位数对离群值不敏感。from sklearn.preprocessing import RobustScaler scaler_x RobustScaler().fit(X_train.reshape(-1, X_train.shape[-1]))RobustScaler 在负荷数据含极端尖峰时表现更稳定但注意它不会把特征压缩到 [0,1]LSTM 配合 tanh 激活时通常也没有问题。换 scaler 后需要重新训练模型不能只改预处理部分保留原有权重。6. 从课程设计到可用预测模块特征扩展与时序验证课程设计提交之后如果要继续往实用方向打磨最值得做的两件事是扩展外部特征和引入滚动时序验证。电力负荷预测的实际业务场景会加入更多影响因素节假日、学校寒暑假、电价浮动、天气预报中的降雨概率等。这些特征的通用做法是先用相关性分析筛选再按时间对齐后送入模型。def add_advanced_features(df): df[month] df[datetime].dt.month df[is_holiday] df[datetime].dt.date.isin(holiday_dates).astype(int) df[load_lag_24] df[load].shift(24) df[load_lag_168] df[load].shift(168) # 一周前同时刻 return dflag 特征的含义是直接用过去第 24 小时和过去第 168 小时的负荷值作为当前预测的额外输入。shift(168) 的前提是数据严格按小时连续如果中间有缺失时间戳周滞后特征会整体偏移需要在构造前用 reindex 补齐缺失小时。is_holiday 的判定需要提前准备节假日日期列表注意不同年份节假日日期不同不能只用 weekday 推断。滚动验证与单次切分不同它不是把数据集一次性切成训练和测试两部分而是以周为单位不断扩展训练集第一轮用前 8 周训练预测第 9 周第二轮用前 9 周训练预测第 10 周以此类推。这种验证方式更接近业务上“模型每周重新训练一次”的真实节奏也能看出模型在不同季节时段的稳定性。for test_week in range(8, total_weeks): train_data df[df[week] test_week] test_data df[df[week] test_week] # 复用 2.2 节构建序列 重新训练模型 # 记录该周的 MAPE 到列表每轮重新训练会带来明显的训练耗时但如果数据规模控制在一年内每轮训练不到一分钟整体可以接受。最终将所有周预测结果合并再计算整体 MAPE这个指标比单次切分得到的 MAPE 更稳健因为它覆盖了不同季节、不同节假日分布下的预测表现。再把预测结果按 hourly 粒度导出到 Excel与原始负荷列放在一起加上绝对误差和百分比误差两列用条件格式标出误差超过 10% 的时间点可以直接作为验收材料。这个导出文件既是交付物也是后续判断模型在哪类时段失效的依据——比如连续阴雨天、节假日首日这些场景往往是多特征模型最容易暴露短板的地方。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门