资讯详情

资讯详情

LSTM光伏预测与储能控制:从时间序列到工程实践

简介这套基于LSTM的短期光伏预测Python实现源码专为计算机、电气类高年级学生与新能源从业者设计可无缝用于毕业设计、课程设计或期末大作业。资源共11个文件以6个Jupyter Notebook为主分别覆盖光伏预测主流程、单变量预测、负荷预测以及基于规则集的模拟程序可对照不同建模思路另有xlsx格式的真实园区SOC数据集、1个py辅助脚本、效果图与简明说明文档。目前已有223人学习下载项目经严格调试评审分达到95分以上运行稳定可靠。通过该套源码可系统学习时间序列特征处理、LSTM网络构建与超参数调优、预测结果可视化以及外部数据获取等实用技巧整体压缩包仅3.89MB内容紧凑且结构清晰学习成本低兼具实用性与参考价值。1. 短期光伏预测为什么选LSTM而非物理模型光伏功率预测这事做过的人都知道最怕的不是阴天是那种云层边缘刚好压过电站的半小时——物理模型在云图更新不及时的情况下误差能到40%以上。LSTM这类循环神经网络之所以在短期15分钟到4小时光伏预测里比线性回归、ARIMA和纯物理外推更稳是因为它用遗忘门和输入门显式建模了时间依赖性前一时刻的辐照度、温度、湿度对下一时刻功率的影响不是等权重的LSTM能自动学出哪个历史时刻的信息该留、哪个该丢。这份大作业源码恰好把这一点落到了工程上园区真实数据、多个Notebook分场景建模、带SOC约束的储能规则集整体不是玩具demo而是一套能跑出评价指标的预测-控制闭环。适合正在做时间序列课设、毕业设计里光伏/负荷预测部分的人也适合想看看LSTM在真实业务数据上怎么处理缺失值和凌晨零功率段的入门者。这篇文章不泛泛讲LSTM原理而是顺着项目里的文件结构把数据管道、单变量/多变量建模、规则集储能和外部气象数据接入逐段拆开。2. 光伏数据管道从xlsx到滑动窗口样本集2.1 多源文件组织与数据清洗项目包里的数据形态比较典型SOC_1101-1107.xlsx是储能荷电状态记录光伏和负荷数据散落在园区CSV或Excel里。第一步不是急着建模型而是把所有文件读进来后统一索引。用园区的数据测试光伏预测-Copy1.ipynb走的就是这个路子核心逻辑和下面这段等价import pandas as pd import numpy as np def load_plant_data(pv_path, load_path, soc_path): df_pv pd.read_excel(pv_path, parse_dates[time], index_coltime) df_load pd.read_excel(load_path, parse_dates[time], index_coltime) df_soc pd.read_excel(soc_path, parse_dates[time], index_coltime) df pd.concat([df_pv[power], df_load[load], df_soc[soc]], axis1) df.columns [pv_kw, load_kw, soc] df df[~df.index.duplicated(keepfirst)].sort_index() df df.resample(15min).mean() # 统一到15分钟粒度 return df代码逻辑三个文件按时间字段解析并设为索引concat横向拼接成一张宽表重复时间戳保留第一条最后统一重采样到15分钟。需要留意的是重采样后凌晨无光伏时段会产生大量NaN如果直接丢会给LSTM造成深夜样本偏差。常见做法是保留这些行、但把pv_kw的缺失值在夜间时段填0白天时段用前后线性插值night_mask (df.index.hour 5) | (df.index.hour 19) df.loc[night_mask, pv_kw] df.loc[night_mask, pv_kw].fillna(0) df[pv_kw] df[pv_kw].interpolate(limit_directionboth)参数说明凌晨5点到晚上19点以外的时段强制归零因为光伏在无日照时输出就是0白天用interpolate做线性填充比均值填充更能保留辐照变化的趋势。项目里的模拟程序.ipynb也对比过删行和填充两种方式删掉夜间缺失行之后模型在清晨启动段的预测会明显滞后原因是训练样本里缺少了从零功率到首峰时刻的过渡模式。2.2 单变量与多元输入的序列构造项目里区分了单变量.ipynb和Copy1.ipynb多变量版本区别就在构造X的方式。单变量只拿pv_kw自身的历史窗口去预测未来而多变量会把load_kw、soc、甚至温度列拼进特征维度。滑动窗口函数是这段代码的核心。def make_sequences(data, input_steps12, pred_steps6): X, y [], [] for i in range(len(data) - input_steps - pred_steps 1): X.append(data[i:iinput_steps]) y.append(data[iinput_steps:iinput_stepspred_steps, 0]) # 只预测pv_kw return np.array(X), np.array(y)逻辑拆解input_steps12对应12个历史时间点数据是15分钟粒度也就是拿过去3小时预测未来90分钟。pred_steps6是预测未来6个点。y只取第一列光伏功率是因为预测目标单一而X保留了所有特征列。这里有几个容易犯的错一是没有做时间顺序切分直接随机划分训练集导致模型偷看未来数据二是窗口的步长stride默认1会让相邻样本高度重叠训练集膨胀且过拟合。我一般会把步长设为pred_steps让相邻样本之间不重叠减少序列相关性def make_sequences_stride(data, input_steps12, pred_steps6, stride6): X, y [], [] for start in range(0, len(data) - input_steps - pred_steps 1, stride): X.append(data[start:startinput_steps]) y.append(data[startinput_steps:startinput_stepspred_steps, 0]) return np.array(X), np.array(y)2.3 归一化与训练/验证切分LSTM对输入尺度非常敏感光伏功率的量纲是千瓦到兆瓦而SOC是0到1不归一化的话梯度会被大数值特征主导。项目里的归一化直接调用sklearn.preprocessing.MinMaxScalerfrom sklearn.preprocessing import MinMaxScaler scaler_X MinMaxScaler(feature_range(0, 1)) scaler_y MinMaxScaler(feature_range(0, 1)) X_2d X.reshape(-1, X.shape[-1]) X_scaled scaler_X.fit_transform(X_2d).reshape(X.shape) y_scaled scaler_y.fit_transform(y.reshape(-1, 1)).reshape(y.shape)切分时注意不要打乱顺序。按时间7:2:1切成训练、验证、测试三段验证集用来早停测试集只评估一次。clearoutside_url.py这个脚本在这个阶段还没用上它属于第5章要讲的外部气象数据增强先按下不表。3. LSTM模型实现与超参数调优3.1 网络结构与遗忘门的作用模型结构不复杂但每个组件都有明确的设计意图。数据进入网络后首先过LSTM层LSTM单元内部有三个门遗忘门决定上一时刻的细胞状态保留多少输入门决定当前候选值写入多少输出门决定当前隐状态输出多少。在光伏预测里遗忘门的实际行为很有意思夜间数据段的细胞状态基本被清空清晨辐照度陡升段的辐照信息会被输入门放大写入。项目里的LSTMNet定义如下import torch import torch.nn as nn class PVLSTM(nn.Module): def __init__(self, n_features, hidden_size64, num_layers2, pred_steps6): super().__init__() self.lstm nn.LSTM( input_sizen_features, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropout0.2 if num_layers 1 else 0 ) self.regressor nn.Sequential( nn.Linear(hidden_size, 32), nn.ReLU(), nn.Dropout(0.1), nn.Linear(32, pred_steps) ) def forward(self, x): out, _ self.lstm(x) last_hidden out[:, -1, :] return self.regressor(last_hidden)需要说明的是这里只取LSTM最后一个时间步的隐状态out[:, -1, :]送进全连接层意味着窗口内最后时刻的隐状态被当作整个序列的摘要。对光伏来说这个设计是合理的因为预测目标紧邻窗口末端越靠近当前时刻的信息越关键。hidden_size64是折中值——园区数据量通常在几万行级别64维隐状态足够拟合功率曲线而又不容易过拟合num_layers2让网络能学到更高层的时间抽象比如连续低功率后出现陡升这种非线性的天气模式。3.2 PyTorch训练循环与早停机制训练部分项目里直接用PyTorch原生循环没有套用高层封装。损失函数用MSE优化器用Adam这两个选择的理由分别是MSE对大幅误差的惩罚力度强能让模型优先拟合功率峰值段Adam在光伏这类非平稳序列上比SGD收敛快很多而且对学习率初始值不那么敏感。训练循环的精简版本def train_model(model, train_loader, val_loader, epochs60, lr1e-3): optimizer torch.optim.Adam(model.parameters(), lrlr, weight_decay1e-5) criterion nn.MSELoss() best_val_loss float(inf) best_state None patience 0 for epoch in range(epochs): model.train() train_loss 0 for X_batch, y_batch in train_loader: optimizer.zero_grad() pred model(X_batch) loss criterion(pred, y_batch) loss.backward() nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() train_loss loss.item() * X_batch.size(0) model.eval() val_loss 0 with torch.no_grad(): for X_batch, y_batch in val_loader: pred model(X_batch) val_loss criterion(pred, y_batch).item() * X_batch.size(0) val_loss / len(val_loader.dataset) if val_loss best_val_loss: best_val_loss val_loss best_state model.state_dict().copy() patience 0 else: patience 1 if patience 8: break print(fEpoch {epoch1}: train_loss{train_loss/len(train_loader.dataset):.6f}, val_loss{val_loss:.6f}) model.load_state_dict(best_state) return model三个关键点要说明。第一clip_grad_norm_梯度裁剪设max_norm1.0这是LSTM训练的常规操作防止长序列BPTT时间反向传播过程中梯度爆炸导致loss变成NaN光伏数据的尖峰段容易产生大梯度不裁剪的话训练曲线会突然飞掉。第二weight_decay1e-5是L2正则用于抑制对训练集中噪声样本的过拟合这个值不能设太大否则模型会偏向输出均值。第三早停的patience设为8轮意思是验证集连续8轮不降就终止训练项目里还额外保存了best_state保证返回的是验证集最优权重而非最后一轮权重——光伏数据天气模式日间差异大最后几轮往往已经过拟合到最近几天的辐照特征上了。3.3 超参数表与调参顺序超参数项目默认值调参范围调参优先级input_steps128-24高hidden_size6432-128高num_layers21-3中learning_rate1e-35e-4-3e-3高batch_size3216-64低dropout0.20.1-0.4中patience85-15低调参顺序我一般是这样先固定input_steps12、hidden_size64把学习率从1e-3往两边扫观察训练loss是否在10轮内有明显下降loss不降就降学习率loss震荡就调低batch_size或加大clip阈值。学习率定下来后再调input_steps用验证集RMSE评估——窗口太短比如4会丢失辐照变化的趋势信息窗口太长比如48对应12小时会引入前一天的数据模式在天气突变时反而是噪声。最后调层数和dropout层数优先试2层因为1层表达能力经常不够捕捉早晚负荷差异光伏峰形的组合特征3层在小数据集上则容易出现训练损失低、验证损失反弹的过拟合。用园区的数据测试负荷预测LSTM.ipynb里把同样的LSTM结构用在负荷预测上只改了pred_steps和目标列。这说明这套代码的核心抽象是时间序列窗口PyTorch LSTM换数据不换结构。但要注意别照搬超参数——负荷曲线的峰谷特征比光伏平滑得多input_steps可以从12缩到8hidden_size也可以缩到32效果基本不降。4. 规则集与储能框架预测之后的控制闭环4.1 规则集思路与SOC约束单做光伏预测只能看曲线项目的价值增量在把预测结果接进储能控制。规则集-第一版.png和基于规则集的.ipynb展示了一套不依赖优化求解器的启发式控制策略。规则集的输入是光伏预测值、负荷预测值、当前SOC、储能额定功率输出是储能的充放电指令。核心规则如下if soc soc_max * 0.95: 禁止充电强制放电或待机 elif soc soc_min * 1.05: 禁止放电优先充电 elif pv_predict load_predict: 差额功率优先充电充电功率不超过额定值 elif pv_predict load_predict: 差额功率由储能放电补充放电深度受SOC下限约束 else: 储能待机为什么不用模型预测SOC而是用规则因为储能调度是带约束的序贯决策问题完整建模需要混合整数规划在课程设计这个量级里实现复杂且难以调试。规则集的核心优势是每条规则都可解释评审时能说清楚为什么这个时刻放电缺点是没法全局优化充放电时机。SOC_1101-1107.xlsx里的数据正好用来校验SOC是否越界——如果连续多天SOC频繁触顶或触底说明规则里的阈值设置不合理。4.2 储能策略落地伪代码储能框架目录里的实现把上述规则翻译成了可执行的逻辑结构类似这样def dispatch_rule(pv_forecast, load_forecast, soc, p_rated, soc_min0.2, soc_max0.9): net_power pv_forecast - load_forecast p_batt 0.0 if soc soc_max and net_power 0: p_batt 0 # 满电不再充光伏多余功率弃掉 elif soc soc_min and net_power 0: p_batt 0 # 亏电不再放缺额从电网购电 elif net_power 0: p_batt min(net_power, p_rated) # 限功率充电 if soc p_batt * dt / capacity soc_max: # 防止SOC越上限 p_batt (soc_max - soc) * capacity / dt else: p_batt max(net_power, -p_rated) # 限功率放电 if soc p_batt * dt / capacity soc_min: # 防止SOC越下限 p_batt (soc_min - soc) * capacity / dt return p_batt这个实现里有三个工程细节值得说。第一p_batt min(net_power, p_rated)限的是充电功率不能超过储能额定功率因为光伏富余量可能远大于储能吸收能力不限制的话控制指令会超出实际硬件能力。第二SOC的更新公式soc p_batt * dt / capacity里的dt是控制周期如果调度是15分钟一次、储能容量是500 kWh、功率100 kW那么一个周期最多改变SOC的100*0.25/500 0.05容量读错会导致SOC越界。第三SOC接近边界时用比例调节逐步逼近而不是直接切到待机状态避免储能频繁启停。4.3 负荷预测LSTM与光伏预测的协同用园区的数据测试负荷预测LSTM.ipynb的存在说明这套框架不是孤立预测光伏而是把光伏和负荷两条预测曲线同时作为储能控制的输入。园区场景里光伏发电和负荷消耗在时间上天然错位——光伏峰值在中午负荷峰值在上午和傍晚没有储能的话只能高价购电或弃光。两个LSTM共享同一套数据管道但归一化要分开做因为负荷的峰谷形态和光伏完全不同混在一起归一化会导致负荷的夜间基数被压缩到很小模型学不到夜间负荷的波动特征。协同控制的流程是先用光伏模型和负荷模型分别预测未来90分钟的两条曲线然后计算净负荷负荷减光伏的期望值将该期望值输入规则集决定储能充放电。模拟程序-Copy1.ipynb还会在每15分钟滚动更新一次预测——用最新观测数据重新构造滑动窗口而不是用一次预测管完整个控制周期。这个滚动预测机制对规则集效果影响很大因为光伏预测误差会随时间累积滚动更新能把误差收敛到最近15分钟的观测值上。5. 用clearoutside_url.py引入气象数据与融合预测5.1 外部气象数据拉取与特征扩展项目里的clearoutside_url.py是一个容易被人忽略但很有价值的工具它负责从Clear Outside这类气象服务拉取辐照度预报数据。单独使用LSTM做光伏预测的瓶颈在于纯历史功率序列是一个结果导向的信号云层突然遮挡导致的功率骤降在历史序列里没有先兆模型只能等功率真的降下来才能反应。而辐照度预报直接给出了未来时刻的云层遮挡信息属于原因信号两者互补。这个脚本的典型用法是拉取目标地点未来24至48小时的逐小时GHI全球水平辐照度然后与光伏功率数据对齐# clearoutside_url.py 的返回值结构示意 { timestamp: 2025-03-15T10:00:00, ghi_wm2: 680, cloud_cover_pct: 40, temp_c: 22.5, wind_kph: 18 }注意气象预报的粒度通常是小时级的而项目里的光伏数据是15分钟级直接拼接会让部分时间点上气象特征是重复值前向填充出来的。在处理时我倾向于把气象列单独做一次滞后处理——用T-1小时的气象预报去预测T时刻的功率而不是用T时刻的气象特征预测T时刻功率。原因有一个当前时刻的预报本身包含与当前功率的同步相关性但在预测未来90分钟的任务中T1、T2时刻的气象预报才是有效特征如果模型训练时用当前气象预测当前功率到了推理阶段会用未来时刻的气象预报去预测未来功率这个跨时间映射在训练中没有对齐。5.2 预测误差分段分析与融合技巧引入气象特征后建议按天气类型分段评估模型表现而不是只看整体RMSE。项目里的评估集中在Notebook的末尾用sklearn.metrics.mean_absolute_error和root_mean_squared_error输出指标但没有做按时间分段的误差拆解。一种简便做法是以辐照度变化率 \( dGHI/dt \) 为阈值把测试样本分成稳定段和波动段分别计算误差。ghi_diff np.abs(np.diff(g hi_forecast, prependg hi_forecast[0])) stable_mask ghi_diff 50 # 辐照度变化小 volatile_mask ghi_diff 50 rmse_stable np.sqrt(np.mean((pred[stable_mask] - y_true[stable_mask])**2)) rmse_volatile np.sqrt(np.mean((pred[volatile_mask] - y_true[volatile_mask])**2)) print(f稳定段 RMSE: {rmse_stable:.2f} kW, 波动段 RMSE: {rmse_volatile:.2f} kW) fusion_pred 0.6 * pred_lstm 0.4 * pred_gbm # GBM为气象特征支持的其他模型常见的效果是波动段误差是稳定段的2至3倍。如果融合后波动段RMSE下降超过15%说明气象特征确实捕捉到了LSTM从历史序列中看不到的云层突变信息反之则说明气象预报本身滞后严重需要检查时间对齐逻辑。我通常还会把融合权重做成随辐照度变化率的函数——波动大的时刻给GBM或XGBoost更大权重平稳时刻给LSTM更大权重在scikit-learn的GradientBoostingRegressor上训练一个辐照度特征到残差的映射比固定权重在园区数据上的表现平均能再降5%左右的RMSE。这个折中处理解决了LSTM对突变反应慢和气象模型在稳定段噪声大的各自短板也是这套源码基础上最容易加分的改进方向。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →