2026/10/12 20:42:21

多特征LSTM电力负荷预测:Python实战与避坑指南

多特征LSTM电力负荷预测:Python实战与避坑指南 简介这是一份面向高校学生与深度学习入门者的电力负荷预测实战项目源码围绕Python与LSTM构建多特征预测模型适用于毕业设计、期末大作业及课程设计等场景帮助读者快速理解时序预测的完整实现流程。压缩包共8个文件约830KB包含3个py脚本分别负责数据处理、模型训练与结果可视化2个md文档提供项目说明与使用指引另有2个csv与1个xlsx存放原始及预处理后的电力负荷数据结构清晰、便于按模块查阅。代码附有详细注释新手也能看懂下载后简单部署即可运行。目前已有239人学习关注项目在数据清洗、特征构造、LSTM建模与预测评估等环节均有体现读者可据此掌握多特征输入下的负荷预测思路并在此基础上替换数据集或调整网络结构完成自己的课题任务。1. 多特征 LSTM 电力负荷预测为什么单变量模型总是差一口气做过电力负荷预测的人大多有过这样的经历拿历史负荷序列直接喂给 LSTM训练损失降得很漂亮一到周末、节假日或者气温骤变那几天预测曲线就明显跑偏。问题往往不在模型本身而在于输入只有负荷这一个维度。电力负荷本质上是气温、湿度、日期类型、历史惯性共同作用的结果单变量模型等于让网络去猜那些它根本看不到的信息。这个标题讲的方案核心就是把「多特征」这件事做扎实用 Python 和深度学习框架搭一个 LSTM 网络输入同时包含负荷、气象、时间标签等多路特征输出未来一段时间的负荷值。它适合已经会写基础 Python、想从单变量时序预测进阶到工程可用方案的从业者也适合电力、能源、园区运维方向需要做短期负荷预估的开发者。下面从数据、特征、模型到排错把这条链路完整走一遍。2. 多特征电力负荷预测的数据准备与特征工程2.1 数据集长什么样字段怎么对齐电力负荷预测的原始数据通常来自两个源头一是负荷采集系统导出的时序记录粒度多为 15 分钟或 1 小时二是气象部门或公开气象接口提供的温度、湿度、降雨等数据。两份数据的时间戳粒度往往不一致气象数据常见是逐小时甚至逐日负荷数据可能是 15 分钟一条。第一步不是建模而是把时间轴对齐。常见做法是统一到负荷数据的最高频率气象数据用前向填充加线性插值补齐。比如温度逐小时一条15 分钟粒度下就用插值生成中间三个点。对齐后每条记录应该包含时间戳、负荷值、温度、湿度、是否节假日、星期几、小时。字段命名建议统一成英文小写下划线避免后续脚本里出现中文列名导致的编码问题。字段名类型含义缺失处理timestampdatetime采集时间去重、排序loadfloat有功负荷线性插值temperaturefloat气温前向填充插值humidityfloat相对湿度前向填充is_holidayint是否节假日默认 0weekdayint星期几 0-6由时间戳推导hourint小时 0-23由时间戳推导对齐之后要做异常值处理。负荷数据里常见的异常是采集设备掉线导致的连续零值或者量程跳变产生的尖峰。零值不能简单当真实负荷否则模型会学到「半夜负荷为零」这种错误模式。判断方法是看连续零值长度超过两个采集周期就标记为缺失用同日同时段的历史均值填补。2.2 特征工程把时间标签变成网络能吃的数字LSTM 吃的是数值is_holiday这种 0/1 标签可以直接用但weekday和hour是周期性变量直接当连续数值喂进去会出问题——网络会认为 23 点和 0 点相差很远实际上它们相邻。正确做法是做周期编码用正弦余弦把周期变量映射到单位圆上。import numpy as np import pandas as pd def cyclical_encode(df, col, period): 把周期变量编码成正弦余弦两列 df[col _sin] np.sin(2 * np.pi * df[col] / period) df[col _cos] np.cos(2 * np.pi * df[col] / period) return df # hour 周期 24weekday 周期 7 df cyclical_encode(df, hour, 24) df cyclical_encode(df, weekday, 7)这段代码的逻辑是把hour和weekday各拆成两列。参数period是该变量的周期长度小时是 24星期是 7。编码后原始的hour、weekday列可以保留也可以删掉保留不影响但要注意别让网络对同一信息重复学习。周期编码的好处是 23 点和 0 点的编码值在圆上相邻网络能正确理解这种邻近关系。负荷本身还需要做归一化。不同季节、不同园区的负荷量级差异很大用全局最大最小值归一化到 0-1 区间是最省事的做法。但要注意归一化参数必须用训练集统计出来再应用到验证集和测试集否则会引入未来信息泄露。这是时序预测里最容易被忽略的坑之一。from sklearn.preprocessing import MinMaxScaler # 只用训练集拟合 scaler train_len int(len(df) * 0.7) scaler MinMaxScaler() scaler.fit(df[[load]].iloc[:train_len]) df[load_scaled] scaler.transform(df[[load]])这里train_len取 70% 作为训练集边界scaler.fit只喂训练段数据。后面验证和测试段的load用同一个 scaler 变换保证量纲一致。如果反过来用全量数据拟合测试集的极值会提前影响归一化范围评估结果会虚高。2.3 滑窗构造多特征怎么组织成 LSTM 的输入张量LSTM 的输入是三维张量形状为(样本数, 时间步长, 特征数)。多特征方案里特征数就是负荷加气象加时间标签的总列数。滑窗的作用是把连续时序切成一个个定长片段每个片段对应一个预测目标。def make_windows(data, target_col, feature_cols, window_size, horizon): 滑窗切分返回 X 和 y X, y [], [] for i in range(len(data) - window_size - horizon 1): x_window data[feature_cols].iloc[i:i window_size].values y_target data[target_col].iloc[i window_size:i window_size horizon].values X.append(x_window) y.append(y_target) return np.array(X), np.array(y) feature_cols [load_scaled, temperature, humidity, is_holiday, hour_sin, hour_cos, weekday_sin, weekday_cos] X, y make_windows(df, load_scaled, feature_cols, window_size24, horizon1)window_size24表示用过去 24 个时间步预测未来horizon1表示预测下一个点。如果数据是 15 分钟粒度24 步就是 6 小时历史如果是小时粒度就是一天历史。这个参数没有标准答案一般先用一个采集周期的整数倍试比如小时数据用 24 或 48再看验证集误差调整。feature_cols里必须包含load_scaled本身否则模型看不到负荷的历史惯性预测会退化成纯气象回归。3. LSTM 模型搭建与训练从网络结构到损失曲线3.1 网络结构怎么定层数、隐藏单元和 Dropout多特征 LSTM 的网络结构不需要太深。两到三层 LSTM 堆叠每层隐藏单元 64 到 128后面接一个全连接输出层对大多数负荷预测场景已经够用。层数再多容易过拟合尤其是数据量只有一两年的时候。隐藏单元数可以按特征数的 4 到 8 倍起步比如 8 个特征就用 64 个隐藏单元。import torch import torch.nn as nn class LoadLSTM(nn.Module): def __init__(self, input_size, hidden_size64, num_layers2, dropout0.2): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout ) self.fc nn.Linear(hidden_size, 1) def forward(self, x): # x: (batch, seq_len, input_size) out, _ self.lstm(x) # 取最后一个时间步的输出 out out[:, -1, :] return self.fc(out)input_size等于feature_cols的长度这里是 8。num_layers2表示两层 LSTM 堆叠dropout0.2在层间做正则。batch_firstTrue让输入张量第一维是 batch符合 PyTorch 常见习惯。前向传播里取out[:, -1, :]是拿最后一个时间步的隐藏状态做预测这是单步预测的标准做法。如果要做多步预测这里需要改成序列到序列的结构或者把输出维度改成horizon。3.2 训练循环与早停损失不降时该看什么训练部分用 Adam 优化器学习率 1e-3 起步损失函数用 MSE 或 Huber。Huber 对异常值更稳负荷数据里偶发尖峰多的时候优先选它。早停是必须的验证集损失连续若干轮不降就停避免过拟合。from torch.utils.data import TensorDataset, DataLoader # 划分训练验证测试 n len(X) train_end int(n * 0.7) val_end int(n * 0.85) X_train, y_train X[:train_end], y[:train_end] X_val, y_val X[train_end:val_end], y[train_end:val_end] X_test, y_test X[val_end:], y[val_end:] train_loader DataLoader( TensorDataset(torch.tensor(X_train, dtypetorch.float32), torch.tensor(y_train, dtypetorch.float32)), batch_size64, shuffleTrue ) model LoadLSTM(input_sizelen(feature_cols)) optimizer torch.optim.Adam(model.parameters(), lr1e-3) criterion nn.HuberLoss() best_val float(inf) patience, wait 10, 0 for epoch in range(200): model.train() for xb, yb in train_loader: pred model(xb) loss criterion(pred, yb) optimizer.zero_grad() loss.backward() optimizer.step() # 验证 model.eval() with torch.no_grad(): val_pred model(torch.tensor(X_val, dtypetorch.float32)) val_loss criterion(val_pred, torch.tensor(y_val, dtypetorch.float32)).item() if val_loss best_val: best_val val_loss wait 0 torch.save(model.state_dict(), best_lstm.pt) else: wait 1 if wait patience: print(f早停于 epoch {epoch}) breakbatch_size64是常见起点数据量小可以降到 32。shuffleTrue只在训练集用验证和测试不能打乱否则时序顺序被破坏。早停的patience10表示验证损失连续 10 轮不创新低就停。保存best_lstm.pt是为了留下验证集最优的权重而不是最后一轮的权重。训练时如果训练损失一直降但验证损失往上走说明过拟合优先加 Dropout 或减层数而不是继续加数据。3.3 评估指标别只看 MSE负荷预测的评估不能只看 MSE。MSE 对量级敏感不同数据集之间没法比。工程上更常用 MAPE 和 RMSEMAPE 能直观看出百分比误差RMSE 保留量纲。计算 MAPE 时要注意负荷接近零的时段会导致除零通常加一个极小值或者只统计负荷大于某个阈值的点。def mape(y_true, y_pred, eps1e-6): return np.mean(np.abs((y_true - y_pred) / (y_true eps))) * 100 def rmse(y_true, y_pred): return np.sqrt(np.mean((y_true - y_pred) ** 2)) model.eval() with torch.no_grad(): test_pred model(torch.tensor(X_test, dtypetorch.float32)).numpy() # 反归一化 test_pred_inv scaler.inverse_transform(test_pred) y_test_inv scaler.inverse_transform(y_test.reshape(-1, 1)) print(MAPE:, mape(y_test_inv, test_pred_inv)) print(RMSE:, rmse(y_test_inv, test_pred_inv))反归一化这一步经常被漏掉导致算出来的误差是归一化尺度下的没有物理意义。scaler.inverse_transform要用训练时拟合的同一个 scaler。MAPE 在 3% 到 5% 以内对短期负荷预测算不错超过 10% 就要回头查特征或数据对齐问题。4. 多特征 LSTM 落地避坑五条血泪经验4.1 现象验证损失正常但测试集 MAPE 爆高原因通常是归一化参数泄露。如果 scaler 用全量数据拟合测试集的极值参与了归一化范围计算模型在训练时间接看到了测试段的分布信息验证集因为和训练集同源看不出问题一到测试就露馅。解决方法是严格只用训练段拟合 scaler验证和测试段只做 transform。检查方式很简单打印 scaler 的 data_min_ 和 data_max_看是否落在训练段范围内。4.2 现象模型预测曲线整体平移形状对但数值偏这是典型的特征尺度不一致。负荷归一化到 0-1温度可能还是 20 到 35 的原始值LSTM 的输入门会被大数值特征主导负荷特征被淹没。解决办法是所有输入特征都做归一化或标准化温度用 MinMax 或 Z-score 都行关键是量纲统一。检查方式是打印 X_train 每个特征维度的均值和方差差异超过一个数量级就要处理。4.3 现象周末和节假日预测误差明显大于工作日原因是日期类型特征表达不够。只用is_holiday一个 0/1 标签网络分不清周六、周日和法定节假日的差异。改进做法是加一列day_type把工作日、周末、节假日分成三类做独热编码或者把is_holiday拆成is_weekend和is_holiday两列。另外节假日前后一天的负荷模式也特殊可以加一列is_adjacent_holiday标记。4.4 现象训练损失震荡不收敛常见原因是学习率太大或者 batch 太小。Adam 默认 1e-3 对多数场景合适如果损失曲线剧烈震荡先降到 1e-4 试。另一个原因是数据没打乱训练集按时间顺序喂进去每个 batch 内的样本高度相关梯度方向单一。shuffleTrue能缓解但时序预测里打乱会破坏窗口内的时序结构正确做法是打乱窗口顺序而不是窗口内的时间步DataLoader 的 shuffle 正好是这个粒度。4.5 现象多步预测越往后越离谱如果horizon大于 1直接让 LSTM 输出多个值后面的步长误差会累积。常见做法是改成自回归预测每次只预测一步把预测值拼回输入再预测下一步。但这样误差同样会累积而且推理慢。更稳的方案是序列到序列结构编码器读历史解码器逐步输出训练时用 teacher forcing。如果只是做日前预测horizon24配合 seq2seq 比直接多输出头更可靠。5. 让多特征 LSTM 再准一点的三个进阶技巧第一个技巧是加入负荷的差分特征。原始负荷序列是非平稳的直接预测绝对值难度大。加一列load_diff即当前负荷减上一时刻负荷让网络同时看到绝对水平和变化趋势。差分特征在归一化后同样要处理通常用 Z-score 而不是 MinMax因为差分有正有负。实测里加差分特征能让 MAPE 降 0.5 到 1 个百分点代价是特征维度增加训练稍慢。第二个技巧是注意力机制。在 LSTM 输出后加一个注意力层让网络自己决定历史 24 步里哪些时刻对当前预测更重要。实现上可以用一个线性层算注意力权重再对 LSTM 各时间步输出做加权求和。代码不复杂但要注意注意力权重可视化时别只看平均值逐样本看能发现模型是否真的学到了「早高峰前几小时负荷开始爬升」这类模式。class AttentionLSTM(nn.Module): def __init__(self, input_size, hidden_size64, num_layers2): super().__init__() self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue) self.attn nn.Linear(hidden_size, 1) self.fc nn.Linear(hidden_size, 1) def forward(self, x): out, _ self.lstm(x) # (batch, seq, hidden) weights torch.softmax(self.attn(out), dim1) context (out * weights).sum(dim1) return self.fc(context)self.attn把每个时间步的隐藏状态映射成一个分数softmax 归一化成权重再对隐藏状态加权求和。相比直接取最后一步注意力能利用整个历史窗口的信息。参数上hidden_size和 LSTM 保持一致不需要额外调。第三个技巧是滚动预测加误差修正。先用模型跑一遍测试集把预测误差按小时统计出来得到一个误差修正表。正式预测时模型输出加上对应小时的修正值。这个方法在负荷预测里很实用因为误差往往有日内规律比如傍晚时段系统性偏低。修正表要用验证集统计不能用测试集否则又是信息泄露。我自己的习惯是每次换数据集先跑一版无修正的基线再决定要不要加修正避免过度工程。这三个技巧不用全上按数据质量和误差来源选。特征工程没做干净之前加注意力或修正表都是治标。希望帮到你。本文还有配套的精品资源点击获取