光伏发电功率预测实战:Python机器学习方案从数据清洗到模型部署

发布时间:2026/10/11 22:06:57
光伏发电功率预测实战:Python机器学习方案从数据清洗到模型部署
简介基于Python与机器学习的光伏发电功率预测系统是一套面向高校毕业设计、课程结业及专题研究的应用型项目适合具备基础Python知识、希望快速上手机器学习预测流程的读者学习参考。资源包共20个文件以CSV训练与测试数据集、Python程序与IPython Notebook为核心另有Markdown说明文档、DOCX任务书、环境配置文件及代码备份文件整体压缩包约6.32MB结构清晰便于在本地快速部署。已有80人学习下载适用于需要从数据预处理、特征工程、模型训练到预测评估全流程复现的实践场景可作为毕业设计或课程作业的基线系统。代码中配有详细注释覆盖数据加载、特征处理、模型构建与预测等关键环节并提供独立测试样本便于验证模型效果与调整参数。获取后可据此轻松搭建光伏发电量预测模型结合多组CSV数据进行训练与测试为深入理解机器学习完整应用链路提供可靠参考。1. 光伏发电功率预测为什么难以及Python机器学习方案到底值不值得做光伏发电功率预测核心难点在于辐照度受云团影响而剧烈波动一片积云飘过电站出力在十几分钟内可能掉一半。传统用数值天气预报外推的物理方法小时级预测偏差经常超过20%而电站调度、现货交易和储能充放电策略恰恰最需要15分钟到4小时这个区间的准确预测。基于Python与机器学习的光伏发电功率预测系统核心思路是用历史气象数据、历史出力数据和数值天气预报数据训练一个能拟合“气象到出力”非线性关系的模型。对已有数据积累的电站这是目前性价比最高的落地路径。这篇实战笔记适合电站运维、电力交易、储能策略方向的工程师也适合正在做相关课题并需要完整实现思路的研发人员。2. 数据从哪来、怎么预处理光伏电站数据集的最小可用方案2.1 选数据集公开数据集和自己采集的SCADA数据怎么取舍做光伏功率预测久了你会发现模型性能的上限基本由数据集质量决定模型结构反而是次要矛盾。光伏发电功率预测系统实现的第一步不是急着搭模型而是定数据集。有自有电站的情况下最优选择是电站SCADA系统导出的运行数据因为功率曲线能真实反映组件衰减、清灰周期、逆变器限发等物理因素。时间分辨率我一般取15分钟一条秒级数据计算开销大且统计噪声明显1小时粒度又会把关键辐照突变抹平。气象数据至少要有总辐照度GHI、环境温度、相对湿度、风速四列如果气象站能提供斜面辐照度POA对预测效果提升非常显著。科研验证或没有自有数据时可以用公开数据集。业内常用的是澳大利亚DKASC光伏电站实测数据包含多年逐分钟功率与气象记录适合做算法对比。国内高校和研究所相关论文也常用自有光伏示范电站数据不过这些数据通常不公开复现时往往需要自己搭一个小型数据采集装置来积累原始数据。选公开数据集时尽量选带逆变器或组串明细的这样能分析单串出力特性而不是被总量曲线掩盖局部问题。2.2 缺失值、坏数据、夜间掩膜清洗流程的4个关键动作拿到原始数据集不要急着训练。我的清洗流程固定是四步每一步都是踩过坑才加上的。第一步时间轴对齐。SCADA系统和气象站时钟不同步是常态时间戳错位几分钟就会在模型中引入假噪声。常见做法是以功率数据的时间轴为基准用pandas把气象数据按15分钟重采样聚合。第二步剔除限电日。光照很好但电网限发导致的平台期出力属于非自然样本不剔除会让模型学出“高辐照也不一定高功率”的错误映射。第三步缺失值填充。辐照度短时间缺失用上一日同时刻插值超过2小时连续缺失的直接置空气温和湿度变化平缓线性插值即可。第四步夜间掩膜。辐照度低于10W/m²时组件已基本不出力但逆变器待机和仪表噪声仍会产生微小功率值这些样本统一将功率置零或不参与训练。下面是一个可复现的清洗脚本骨架import pandas as pd import numpy as np df pd.read_csv(pv_data.csv, parse_dates[timestamp], index_coltimestamp) df.sort_index(inplaceTrue) # 1) 时间对齐气象数据重采样到功率数据的时间轴15分钟聚合 weather df[ghi].resample(15min).mean().interpolate(limit8) # 2) 夜间掩膜辐照度低于阈值时强制功率置零 night_mask df[ghi] 10 df.loc[night_mask, power] 0.0 # 3) 剔除限电日按天计算功率/辐照度比值找出被限制出力的日子 daily_ratio df[power] / (df[ghi] 1.0) day_mean daily_ratio.resample(1D).mean() bad_days day_mean[day_mean day_mean.quantile(0.02)].index df df[~df.index.normalize().isin(bad_days)] # 4) 缺失值策略功率缺失直接删除气象特征按列插值 df df.dropna(subset[power]) df[t_amb] df[t_amb].interpolate(limit8) df[rh] df[rh].interpolate(limit8)逐段解释一下。resample(15min).mean()把分钟级气象数据聚合成15分钟均值聚合比插值更稳因为平均本身压掉了传感器抖动。interpolate(limit8)最多填充两小时窗口连续缺超过两小时宁可让模型少看一段也不引入错误气象值。夜间掩膜阈值10W/m²不是随便定的日出日落前后辐照度低于这个值时组件输出基本小于额定功率的1%此时把功率归零有助于模型学习“出力与辐照度”的真实边界。剔除限电日那段resample(1D).mean()计算每日平均功率除以平均辐照度的比值晴好且不受限的日子比值稳定落在最低2%分位的天大概率是限电或清洗故障直接按天删掉。如果限电天数占比超过5%建议额外做一版不限电模型并配上限电识别规则效果比硬删好。3. 特征工程哪些气象因子真的影响光伏功率3.1 核心特征总辐照度、温度、湿度之外还有一组天文特征光伏功率预测的特征取舍不是把气象站所有列都塞给模型就完事。XGBoost确实能自动筛选特征但有限样本下少而准的特征比多而杂的特征更利于模型稳定。我一般用这组基准特征总辐照度GHI、环境温度、组件温度如果有、相对湿度、风速、历史功率滞后值以及当前时间戳对应的太阳高度角与方位角。辐照度是最强特征但模型很容易只学这一列而忽略其他物理过程。加入环境温度很关键光伏组件输出功率与温度呈负相关温度系数一般在-0.3%/℃到-0.5%/℃同一辐照度下夏天出力可能低几个百分点。相对湿度影响气溶胶对辐照度的衰减风速通过冷却组件表面间接抬高出力。天文特征里最划算的是太阳高度角与方位角它给模型一个周期性的时间锚点。可以用pysolar或pvlib直接计算不想引入额外依赖时下面这段简化公式也够用import numpy as np import pandas as pd def solar_position(timestamp, lat, lon): # 简化版太阳赤纬与高度角计算精度满足功率预测建模需求 doy timestamp.dayofyear decl -23.44 * np.cos(np.radians(360.0 / 365.0 * (doy 10))) hour_angle 15.0 * ((timestamp.hour timestamp.minute / 60.0) - 12.0) lat_rad np.radians(lat) sin_elev ( np.sin(np.radians(decl)) * np.sin(lat_rad) np.cos(np.radians(decl)) * np.cos(lat_rad) * np.cos(np.radians(hour_angle)) ) elevation np.degrees(np.arcsin(np.clip(sin_elev, -1.0, 1.0))) azimuth np.degrees( np.arctan2( np.sin(np.radians(hour_angle)), np.cos(np.radians(hour_angle)) * np.sin(lat_rad) - np.tan(np.radians(decl)) * np.cos(lat_rad) ) ) return elevation, azimuth df[solar_elev] [solar_position(ts, lat32.1, lon118.8)[0] for ts in df.index] df[solar_az] [solar_position(ts, lat32.1, lon118.8)[1] for ts in df.index]这段简化天文公式按站址经纬度计算太阳位置。hour_angle正午为0下午为正上午为负高度角低于0的时段对应夜间正好配合前面清洗时的夜间掩膜。公式精度和pvlib相差1度以内对功率预测足够。需要注意时区对齐否则正午辐照度峰值和高度角峰值错位一小时模型学到的关系就乱了。滞后功率特征同样重要。当前时刻出力是连续物理过程的延续云的移动有一定惯性。用前三个时刻的功率做自回归特征是大多数预测模型涨点最快的操作。特征设计为power(t-1)、power(t-2)、power(t-3)三阶即可再往前意义不大。3.2 天气状态编码与突变样本让模型不只记住晴天只靠连续气象值建模模型会默认天气在短时间内平滑变化但真实云团会让辐照度在15分钟内剧烈跳变。为了让模型具备突变应对能力我通常叠加两类特征。第一类是差分特征。delta_ghi ghi(t) - ghi(t-1)刻画辐照度变化速率。云团刚遮住太阳的头15分钟delta_ghi会是一个很大的负值模型看到这个信号后下一步功率预测会被拉低。这是代价最低的突变感知手段。第二类是天气状态编码。如果数据集带总云量或晴雨标签直接整数编码没有标签就得自己造。用pvlib的clearsky模型算出理论晴空辐照度曲线把实际辐照度低于晴空值一定比例的时间段标为多云或阴。from sklearn.ensemble import RandomForestRegressor feats [ghi, t_amb, rh, wind, solar_elev, delta_ghi, lag_power_1, lag_power_2, lag_power_3] X df[feats].shift(1).dropna() # 用t-1时刻特征预测t时刻功率 y df[power].shift(-1).dropna() X X.iloc[: min(len(X), len(y))] y y.iloc[: min(len(X), len(y))] model RandomForestRegressor(n_estimators300, max_depth12, random_state42) model.fit(X, y) importance pd.Series(model.feature_importances_, indexfeats).sort_values() print(importance)这里用随机森林做特征重要性粗筛。shift(1)是时序建模和数据泄漏的分界线保证特征矩阵里只有历史信息。输出的重要性排序里如果辐照度滞后值和lag_power排在最前说明数据质量正常如果rh或wind排到前面大概率是辐照度传感器有标定偏差回查传感器日志比调参更有效。针对突变样本不要因为预测误差大就删恰好要保留并考虑加权。模型只在平稳天气上训练遇到雷暴性天气会彻底失灵适当提升突变样本权重让模型更保守用一点晴天精度换全天平均误差下降值得。4. 模型怎么选XGBoost基线、LSTM序列模型以及一个最小可复现实现4.1 先用XGBoost打底从数据集到单点预测的最短代码路径很多第一版光伏发电功率预测系统都是从XGBoost起步的。它和随机森林一样能吸收表格特征但正则化更强对异常值承受力更好训练速度也快。做深度学习方案前先把XGBoost跑通基线就有了后续LSTM如果连这个基线的MAE都超不过问题大概率在数据而不在模型。import xgboost as xgb from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import mean_absolute_error, mean_squared_error # 沿用上一章构造的特征矩阵X与目标y tss TimeSeriesSplit(n_splits5) for fold, (tr_idx, va_idx) in enumerate(tss.split(X)): X_tr, X_va X.iloc[tr_idx], X.iloc[va_idx] y_tr, y_va y.iloc[tr_idx], y.iloc[va_idx] model xgb.XGBRegressor( n_estimators600, learning_rate0.05, max_depth6, subsample0.8, colsample_bytree0.8, early_stopping_rounds50, ) model.fit( X_tr, y_tr, eval_set[(X_va, y_va)], verboseFalse ) pred model.predict(X_va) mae mean_absolute_error(y_va, pred) rmse mean_squared_error(y_va, pred, squaredFalse) print(ffold {fold}: MAE {mae:.2f} kW, RMSE {rmse:.2f} kW) print(fbaseline feature importance: {model.feature_importances_[:3]})这里用TimeSeriesSplit做时序交叉验证它和随机K折完全不同它强制训练集永远在验证集之前避免模型偷看未来。early_stopping_rounds50配learning_rate0.05是XGBoost实践里很稳的一对参数没有额外调参的情况下max_depth6、subsample0.8、colsample_bytree0.8通常能给出不差的基线。数据量很小的话n_estimators降到300防止过拟合。如果预测目标是未来15分钟的单点功率到这里就够了。如果目标是未来1-4小时功率曲线推荐做法不是让XGBoost一次输出多步而是按“预测目标时刻的历史特征加数值天气预报特征”分别训练每个时间步的模型比如15分钟模型、30分钟模型、1小时模型各一个。4.2 LSTM多步序列建模滑窗、递归预测与直接多输出当预测步数变多或希望模型自己从历史功率曲线抽象变化规律LSTM是常用选择。它的先验假设是近期功率序列包含未来演化模式信息这与云团移动的物理过程本质上一致。构造序列样本时常见做法是滑窗。输入取过去48个15分钟点即12小时的气象与功率序列输出未来96个15分钟点即24小时。多步预测策略容易走偏递归预测是让模型先预测下一步再把预测值当输入滚动预测后续直接多输出是一次性输出整个未来序列。对光伏这种强自相关但多气象突变的过程直接多输出更好因为误差不随步数累积也不会出现凌晨预测中午时还停留在凌晨值的毛病。import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout def make_sequences(data, input_steps, output_steps, feat_cols): Xs, ys [], [] for i in range(len(data) - input_steps - output_steps): Xs.append(data[i: i input_steps, feat_cols]) ys.append(data[i input_steps: i input_steps output_steps, feat_cols[0]]) return np.array(Xs), np.array(ys) seq_data df[[power, ghi, t_amb, solar_elev, delta_ghi]].values X_seq, y_seq make_sequences(seq_data, input_steps48, output_steps96, feat_cols[0, 1, 2, 3, 4]) day_mask X_seq[:, :, 3].mean(axis1) 5 X_seq, y_seq X_seq[day_mask], y_seq[day_mask] model Sequential([ LSTM(64, return_sequencesTrue, input_shape(48, 5)), Dropout(0.2), LSTM(64, return_sequencesFalse), Dense(128, activationrelu), Dense(96), ]) model.compile(optimizeradam, lossmae) model.fit(X_seq, y_seq, epochs30, batch_size64, validation_split0.2)这个结构比较常规两层LSTM加一个输出层。return_sequencesTrue让第一层把整条序列信息传给第二层第二层只返回最终状态压缩成一个编码向量最后Dense展开成96个输出点。day_mask按平均太阳高度角过滤样本这一步很关键如果夜间序列也进训练集LSTM会花大量容量学习“夜晚输出为0”这个显然规律挤压白天复杂天气形态的表征。本结构下两个常用调参方向input_steps取96即24小时往往比48更好模型能学到完整日循环边界Dropout(0.2)放在两层LSTM之间比放在输入处更稳妥。训练时盯validation loss如果验证集MAE在几十个epoch后不再下降说明模型开始背训练集的天气形态而不是提取普适规律。我个人的建议是第一版系统先只上XGBoost部署容易、调试透明当业务方明确要求“未来4小时功率曲线且误差要低”再引入LSTM作为升级方案同时保留XGBoost做对照最终线上两个模型融合输出。不要一上来就调LSTM结构数据量不够时会让人怀疑人生。5. 避坑光伏预测落地中的6个常见坑现象、原因、解决方案5.1 夜间样本没过滤模型把白天功率整体压低现象白天预测均值明显低于真实值尤其上午9点到10点偏差最大。 原因夜间大量零功率样本占数据集近一半树模型为了让整体损失最小学会了一个偏保守的映射。 解决手写清洗脚本时加“白天掩膜”用太阳高度角大于5度作为样本过滤条件或直接删除夜间样本不参与训练。5.2 随机切分训练集和验证集模型开卷考试还自我感觉良好现象时序交叉验证的RMSE非常低一旦上线预测性能大幅缩水。 原因时间序列数据自相关性强相邻样本高度相似随机切分会让模型“看到”待预测时刻的前后信息本质是数据泄漏。 解决一律用TimeSeriesSplit或按天划分验证集必须落在训练集之后且中间留出至少1天间隔避免云团状态延续带来的间接预测风险。5.3 限电日混在训练集里模型把晴天高功率学成不可达现象连续晴好天气下预测值偏低5%-10%业务方认为模型太保守。 原因限电日实际功率峰值被砍到额定出力以下但特征辐照度和温度都是强晴天形态模型为了拟合这种矛盾整体压低高辐照映射。 解决剔除限电日。用“当日实际峰值功率除以理论晴空峰值功率”做判断比值异常偏低且辐照度正常的打标后剔除。5.4 数值天气预报更新延迟模型赢在离线、输在在线现象离线回测效果好在线预测一到下午就偏差增大。 原因预测未来几小时功率需要未来辐照度在线系统用的是数值天气预报产品一般每3小时才更新一次。拿旧预报值当实际辐照度输入误差被放大。 解决和气象数据接口对好更新时间戳预报龄超过1.5小时就用上一版本做衰减修正或切换纯自回归模式。核心是不要让模型假设自己总能拿到新鲜预报。5.5 极端突变天气下所有模型都倾向于低报现象雷暴来临前功率从满发掉到10%预测曲线还停在50%左右。 原因训练集突变样本太少模型学到的是小步慢走不敢输出大跳跃。这属于样本分布问题不是调参能解决的。 解决对突变前2小时样本做过采样并提权同时提高差分特征权重。如果业务允许用分位数回归输出预测区间让调度看到的不是单点而是范围。5.6 滚动评估MAE合格但逐15分钟曲线对不上现象整体平均误差在合格范围画出曲线却发现预测峰值相位偏了15到30分钟。 原因辐照度传感器峰值时间和逆变器MPPT追踪、组件热惯性叠加后功率峰值实际滞后于辐照度峰值且多云天气滞后时间不固定。 解决在特征里加“辐照度峰值滞后差”这样的派生特征评估时同时看RMSE和峰值时点偏差。只盯RMSE是业务上线前最容易忽视的盲区。6. 上线前怎么验证回测、误差分解和一个可复用的突变时段检测技巧业务方最看重的预测维度有两个第一是平均偏差BIAS不能太大这直接决定交易结算亏不亏第二是突变天气下的误差上限这决定调度敢不敢按预测值安排储能。所以上线前我会做一套分场景误差剖析而不是只给一个整体RMSE。具体做法是把验证集按天气类型分成晴天、多云、阴天、突变四类。晴天的MAE应显著低于整体如果晴天也高多半是辐照度传感器标定问题。多云和突变天气的MAE允许是晴天的1.5到3倍但有个底线预测值不能系统性全部偏低或全部偏高否则极端天气下调度会做出错误决策。自动识别突变时段可以用辐照度差分序列构造一个突变指数突变指数 |delta_ghi| 在2小时窗口的累计值超过训练集高分位数的样本标为突变时段。这个指数也能直接作为特征加入模型# 突变指数特征示例 df[abs_delta_ghi] df[ghi].diff().abs() mut_index df[abs_delta_ghi].rolling(2h).sum() df[mut_flag] (mut_index mut_index.quantile(0.95)).astype(int)rolling(2h).sum()按两小时滚动窗口累计辐照度变化绝对值能捕捉到云团连续进出导致的剧烈波动。quantile(0.95)阈值按训练集分位数取避免固定区间在不同季节失真。最后分享一个我自己的习惯所有实验都保留一组“冷启动分割”结果训练集只用某年以前的数据验证集用全年数据。这个实验通过后再上真实业务因为真实业务就是这样背靠背运行的。连续两个冬季在冷启动验证里表现稳定的模型我才放心接进调度系统表现不稳定的绝不为了上线而强行上线。做预测系统做到后面你会发现最难的不是模型而是把数据边界、天气边界和管理边界想清楚。希望这篇笔记里记录的踩坑经验能帮你少走几条弯路顺利把光伏发电功率预测系统落到自己的电站里去希望帮到你。本文还有配套的精品资源点击获取