ARTICLE DETAIL

资讯详情

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

基于PSO-LSTM的电力负荷预测系统:Python+Django实现与超参数自动优化

基于PSO-LSTM的电力负荷预测系统:Python+Django实现与超参数自动优化 简介本资源为基于PythonDjangoPSO-LSTM的电力负荷预测系统完整源码包面向计算机、人工智能、自动化等专业的在校学生与教师可用于课程设计、毕业设计、项目立项演示或自学进阶。项目将粒子群优化算法与LSTM神经网络结合配合Django搭建可视化Web界面解决电力负荷时序预测的建模与展示问题。压缩包共293个文件约4.06MB包含21个py源码文件、32个pyc编译文件、1个hdf5模型权重文件以及85个js、32个css、16个html等前端资源另有md说明文档、xlsx数据表与json配置文件目录结构清晰便于按模块阅读与二次开发。目前已有185人学习下载。代码均经测试运行成功答辩评审平均分达96分读者可据此掌握PSO优化LSTM超参数的完整流程、Django前后端交互方式与预测结果可视化方法并在此基础上修改功能以适配其他时序预测场景。1. 电力负荷预测系统从课设到可复现的 PSO-LSTM 方案做电力负荷预测的课程设计最怕两件事一是数据拿到手不知道怎么变成模型能吃的张量二是 LSTM 超参数调半天还不如老师随手给的基线。这套基于 PythonDjangoPSO-LSTM 的电力负荷预测系统核心思路是用粒子群算法PSO自动搜索 LSTM 的隐藏层单元数、学习率和训练轮数省掉手工调参的玄学环节再用 Django 把训练好的模型包成可交互的 Web 页面。它适合正在做电力、能源方向课程设计的学生也适合想快速搭一个「算法Web」完整链路的 Python 开发者。整条链路拆开看就是三块数据预处理、PSO 优化 LSTM、Django 前后端串联。下面按实际落地顺序把每一块的选型理由、代码实现和参数设置讲清楚。2. 数据准备与 Django 项目骨架先把地基打对2.1 电力负荷数据的来源与预处理逻辑电力负荷预测的输入通常是一段连续时间序列常见字段包括时间戳、有功负荷、温度、湿度、节假日标记。课程设计里拿到的数据大概率是 CSV 或 Excel采样间隔 15 分钟、30 分钟或 1 小时。不管来源是什么预处理要解决三个问题缺失值、异常值、归一化。缺失值用线性插值补异常值用 3σ 原则剔除后同样插值。归一化选 MinMaxScaler把负荷值压到 [0,1]因为 LSTM 对输入尺度敏感不归一化会导致梯度爆炸或收敛极慢。下面这段代码是预处理的标准写法import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler def load_and_clean(path): df pd.read_csv(path, parse_dates[timestamp]) df df.set_index(timestamp).sort_index() # 缺失值线性插值 df[load] df[load].interpolate(methodlinear) # 3σ 异常值处理 mean, std df[load].mean(), df[load].std() upper, lower mean 3 * std, mean - 3 * std df.loc[(df[load] upper) | (df[load] lower), load] np.nan df[load] df[load].interpolate(methodlinear) # 归一化 scaler MinMaxScaler() df[load_scaled] scaler.fit_transform(df[[load]]) return df, scaler逻辑说明先按时间排序保证序列连续性插值用线性是因为负荷变化本身平滑3σ 剔除的是极端毛刺。参数上interpolate的method可选time更适合不等间隔数据但课程设计数据一般等间隔linear够用。MinMaxScaler的fit_transform只能在训练集上做验证集和测试集要用同一个 scaler 的transform否则信息泄露这是很多人翻车的地方。2.2 滑动窗口构造与 Django 项目初始化LSTM 需要监督学习格式的样本用滑动窗口把序列切成 (输入序列, 预测值) 对。窗口长度一般取 24对应一天 24 小时或 9615 分钟粒度一天。预测步长可以是 1 步或多步课设通常做单步预测。def make_dataset(series, window24): X, y [], [] for i in range(len(series) - window): X.append(series[i:iwindow]) y.append(series[iwindow]) return np.array(X), np.array(y) # 假设 df[load_scaled] 已就绪 data df[load_scaled].values X, y make_dataset(data, window24) X X.reshape((X.shape[0], X.shape[1], 1)) # LSTM 输入维度参数说明window24表示用过去 24 个点预测下一个点如果数据是 15 分钟粒度24 个点只有 6 小时建议改成 96。reshape的第三维是特征数单变量负荷预测就是 1如果加入温度、湿度等多特征这里改成对应维度同时make_dataset里要同步扩展。Django 项目初始化用标准命令不依赖额外脚手架django-admin startproject loadforecast cd loadforecast python manage.py startapp predictor然后在settings.py的INSTALLED_APPS里注册predictor数据库默认 SQLite 就够课设用。模型文件、scaler 文件放在predictor/ml_models/目录下通过os.path.join(BASE_DIR, ...)引用避免路径写死。这一步常见坑是 Django 的BASE_DIR在部署环境变化后失效建议用pathlib.Path统一处理。3. PSO 优化 LSTM把超参数搜索自动化3.1 为什么用 PSO 而不是网格搜索LSTM 的关键超参数有三个隐藏层单元数hidden_size、学习率lr、训练轮数epochs。网格搜索在三维空间里组合爆炸比如 hidden_size 取 [32,64,128]、lr 取 [0.001,0.01,0.1]、epochs 取 [50,100,200]一共 27 组每组训练一次时间成本高。PSO 用粒子模拟一群鸟找食物每个粒子代表一组超参数通过个体最优和全局最优更新位置通常 10-20 个粒子迭代 20-30 次就能收敛到不错的解。选 PSO 的理由实现简单、参数少只有惯性权重 w、学习因子 c1/c2、不依赖梯度。相比遗传算法PSO 没有交叉变异操作代码量更小适合课设场景。下面给出 PSO 的核心实现。import numpy as np class PSO: def __init__(self, n_particles, dim, bounds, max_iter, w0.7, c11.5, c21.5): self.n n_particles self.dim dim self.bounds bounds # [(min1,max1), (min2,max2), ...] self.max_iter max_iter self.w, self.c1, self.c2 w, c1, c2 self.X np.random.uniform( [b[0] for b in bounds], [b[1] for b in bounds], (n_particles, dim) ) self.V np.zeros((n_particles, dim)) self.pbest self.X.copy() self.pbest_fit np.full(n_particles, np.inf) self.gbest None self.gbest_fit np.inf def optimize(self, fitness_fn): for _ in range(self.max_iter): for i in range(self.n): fit fitness_fn(self.X[i]) if fit self.pbest_fit[i]: self.pbest_fit[i] fit self.pbest[i] self.X[i].copy() if fit self.gbest_fit: self.gbest_fit fit self.gbest self.X[i].copy() r1, r2 np.random.rand(), np.random.rand() self.V (self.w * self.V self.c1 * r1 * (self.pbest - self.X) self.c2 * r2 * (self.gbest - self.X)) self.X self.X self.V # 边界裁剪 for d, (lo, hi) in enumerate(self.bounds): self.X[:, d] np.clip(self.X[:, d], lo, hi) return self.gbest, self.gbest_fit逻辑说明fitness_fn接收一组超参数返回验证集上的 RMSE 或 MAPE。粒子位置更新公式里w控制惯性c1调整个体认知c2调整社会认知。参数设置上w常用 0.4-0.9 线性递减课设固定 0.7 也能跑c1c21.5是经典取值。边界裁剪防止粒子飞出搜索空间这一步不能省否则 hidden_size 可能变成负数。3.2 把 LSTM 训练封装成适应度函数适应度函数是 PSO 和 LSTM 的接口。每次粒子更新后用当前超参数训练一个 LSTM返回验证集误差。为了控制时间epochs 不宜过大课设建议 30-80 轮配合早停。import torch import torch.nn as nn from sklearn.metrics import mean_squared_error class LSTMModel(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers1): super().__init__() self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue) self.fc nn.Linear(hidden_size, 1) def forward(self, x): out, _ self.lstm(x) return self.fc(out[:, -1, :]) def fitness_fn(params, X_train, y_train, X_val, y_val): hidden_size int(params[0]) lr params[1] epochs int(params[2]) model LSTMModel(hidden_sizehidden_size) optimizer torch.optim.Adam(model.parameters(), lrlr) loss_fn nn.MSELoss() X_t torch.tensor(X_train, dtypetorch.float32) y_t torch.tensor(y_train, dtypetorch.float32).unsqueeze(1) X_v torch.tensor(X_val, dtypetorch.float32) y_v torch.tensor(y_val, dtypetorch.float32).unsqueeze(1) for _ in range(epochs): model.train() optimizer.zero_grad() loss loss_fn(model(X_t), y_t) loss.backward() optimizer.step() model.eval() with torch.no_grad(): pred model(X_v).numpy() return np.sqrt(mean_squared_error(y_v.numpy(), pred))参数说明hidden_size搜索范围建议 [16, 128]lr搜索范围 [1e-4, 1e-2]epochs搜索范围 [30, 100]。注意lr在 PSO 里是连续值直接传给 Adam 没问题。fitness_fn每次调用都重新初始化模型保证评估独立。如果数据量大可以把训练集再切一小部分做验证减少单次评估时间。调用 PSO 的主流程bounds [(16, 128), (1e-4, 1e-2), (30, 100)] pso PSO(n_particles10, dim3, boundsbounds, max_iter20) best_params, best_score pso.optimize( lambda p: fitness_fn(p, X_train, y_train, X_val, y_val) ) print(最优超参数:, best_params, 验证集 RMSE:, best_score)跑完 PSO 后用最优参数在训练集验证集上重新训练最终模型保存state_dict和 scaler。这里有个血泪经验PSO 的随机种子要固定否则每次跑出来的最优参数不一样课设答辩时无法复现。在np.random.seed(42)和torch.manual_seed(42)上别偷懒。4. Django 前后端串联把模型变成能点的页面4.1 模型加载与预测接口设计Django 里不要每次请求都重新加载模型用模块级变量或缓存。在predictor/views.py里这样写import os import joblib import torch import numpy as np from django.conf import settings from django.http import JsonResponse from .ml_models.lstm import LSTMModel MODEL_PATH os.path.join(settings.BASE_DIR, predictor/ml_models/best_lstm.pth) SCALER_PATH os.path.join(settings.BASE_DIR, predictor/ml_models/scaler.pkl) _model None _scaler None def get_model(): global _model, _scaler if _model is None: _model LSTMModel(hidden_size64) _model.load_state_dict(torch.load(MODEL_PATH, map_locationcpu)) _model.eval() _scaler joblib.load(SCALER_PATH) return _model, _scaler def predict(request): if request.method ! POST: return JsonResponse({error: method not allowed}, status405) raw request.POST.get(series) # 逗号分隔的 24 个负荷值 values np.array([float(x) for x in raw.split(,)]).reshape(-1, 1) scaled _scaler.transform(values).flatten() x torch.tensor(scaled.reshape(1, 24, 1), dtypetorch.float32) model, scaler get_model() with torch.no_grad(): pred_scaled model(x).item() pred scaler.inverse_transform([[pred_scaled]])[0][0] return JsonResponse({prediction: round(pred, 2)})逻辑说明get_model用全局变量做懒加载第一次请求时加载后续复用。predict接收前端传来的 24 个历史负荷值先归一化再转 tensor模型输出反归一化后返回 JSON。参数上map_locationcpu保证没有 GPU 也能跑课设环境通常没有独显。round(pred, 2)保留两位小数避免前端显示一长串。4.2 前端页面与 URL 配置前端用一个简单的 HTML 表单输入逗号分隔的负荷序列点击按钮后 fetch 请求后端。模板放在predictor/templates/predictor/index.html!DOCTYPE html html headmeta charsetutf-8title电力负荷预测/title/head body h2输入过去 24 个负荷值逗号分隔/h2 textarea idseries rows3 cols80/textareabr button onclickdoPredict()预测/button p idresult/p script async function doPredict() { const series document.getElementById(series).value; const formData new FormData(); formData.append(series, series); const resp await fetch(/predict/, {method: POST, body: formData}); const data await resp.json(); document.getElementById(result).innerText 预测负荷 data.prediction; } /script /body /htmlURL 配置在loadforecast/urls.py里加一行path(predict/, views.predict)并在predictor/urls.py里做应用级路由。注意 CSRF如果前端用fetch发 POSTDjango 默认会拦截课设里可以在视图上加csrf_exempt但生产环境要用csrf_token。这一步的坑是模板路径没配TEMPLATES的APP_DIRS页面直接 404。启动命令python manage.py migrate python manage.py runserver 0.0.0.0:8000浏览器打开http://127.0.0.1:8000/predict_page/就能看到页面。如果要做 WebSocket 实时推送Django Channels 是常见方案但课设用轮询或手动刷新足够不必上 Channels 增加复杂度。5. 避坑与排查课设里最容易翻车的 5 个点5.1 现象模型训练 loss 不下降一直卡在 0.25 左右原因归一化没做或者做了但训练集和测试集用了不同的 scaler。LSTM 输入值在 0-1 之外时sigmoid/tanh 饱和梯度消失。 解决检查scaler.fit_transform只在训练集调用验证和测试用transform。打印训练数据的 min/max确认在 [0,1]。5.2 现象PSO 跑完最优 hidden_size 是 16但模型欠拟合原因PSO 的适应度函数只训练了很少的 epochs欠拟合的模型误差大PSO 误以为小网络更好。 解决适应度函数里 epochs 下限设 50或者用早停策略验证 loss 连续 5 轮不降就停。另外把 hidden_size 下限从 16 提到 32。5.3 现象Django 页面点预测报 500日志显示 tensor 维度不匹配原因前端输入的序列长度不是 24或者 reshape 时写成了(1, -1, 1)导致时间步不对。 解决在视图里加长度校验if len(values) ! 24: return JsonResponse({error: need 24 values}, status400)。reshape 固定写(1, 24, 1)。5.4 现象PSO 每次跑结果都不一样答辩时无法复现原因没固定随机种子PSO 初始化、LSTM 权重初始化、数据 shuffle 都有随机性。 解决在入口文件加np.random.seed(42)、torch.manual_seed(42)、random.seed(42)并在 DataLoader 里设shuffleFalse或固定 generator。5.5 现象预测值全部偏向均值曲线是一条直线原因模型没学到时序依赖可能是 window 太小或者 LSTM 的batch_first没设对导致维度错乱。 解决把 window 从 24 调到 96检查nn.LSTM的batch_firstTrue输入 shape 应为(batch, seq_len, features)。另外确认out[:, -1, :]取的是最后一个时间步不是第一个。6. 进阶技巧用 MAPE 和滚动预测验证模型真实水平课设里很多人只看 RMSE但电力负荷预测更关心相对误差MAPE平均绝对百分比误差更直观。计算方式def mape(y_true, y_pred): y_true, y_pred np.array(y_true), np.array(y_pred) mask y_true ! 0 return np.mean(np.abs((y_true[mask] - y_pred[mask]) / y_true[mask])) * 100RMSE 对大负荷值敏感MAPE 对全量程公平。答辩时两个都报老师会觉得你考虑周全。另一个技巧是滚动预测用模型预测下一个点后把预测值拼回输入序列再预测下下个点。这样能模拟真实调度场景但误差会累积一般滚动 4-6 步就明显发散。课设里做单步预测即可滚动预测作为加分项展示。验证模型是否过拟合看训练 loss 和验证 loss 的曲线。如果训练 loss 持续降但验证 loss 先降后升说明过拟合减小 hidden_size 或加 Dropout。LSTM 里加dropout0.2只在num_layers 1时生效单层 LSTM 的 dropout 参数会被忽略这是 PyTorch 的一个细节坑。最后说一个我自己的习惯每次改完超参数或数据预处理先把 PSO 的max_iter设成 3、粒子数设成 5快速跑一遍看流程通不通确认没问题再放大参数跑完整版。这样能省下大量等待时间也避免跑了两小时才发现某个路径写错。希望帮到你。本文还有配套的精品资源点击获取
返回列表