☰
光伏发电功率预测实战:Python机器学习方案从数据清洗到模型部署
2026/10/11 22:06:39 网站建设 项目流程

简介:基于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_col="timestamp") df.sort_index(inplace=True) # 1) 时间对齐:气象数据重采样到功率数据的时间轴,15分钟聚合 weather = df["ghi"].resample("15min").mean().interpolate(limit=8) # 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(limit=8) df["rh"] = df["rh"].interpolate(limit=8)

逐段解释一下。resample("15min").mean()把分钟级气象数据聚合成15分钟均值,聚合比插值更稳,因为平均本身压掉了传感器抖动。interpolate(limit=8)最多填充两小时窗口,连续缺超过两小时宁可让模型少看一段,也不引入错误气象值。夜间掩膜阈值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, lat=32.1, lon=118.8)[0] for ts in df.index] df["solar_az"] = [solar_position(ts, lat=32.1, lon=118.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_estimators=300, max_depth=12, random_state=42) model.fit(X, y) importance = pd.Series(model.feature_importances_, index=feats).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_splits=5) 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_estimators=600, learning_rate=0.05, max_depth=6, subsample=0.8, colsample_bytree=0.8, early_stopping_rounds=50, ) model.fit( X_tr, y_tr, eval_set=[(X_va, y_va)], verbose=False ) pred = model.predict(X_va) mae = mean_absolute_error(y_va, pred) rmse = mean_squared_error(y_va, pred, squared=False) print(f"fold {fold}: MAE = {mae:.2f} kW, RMSE = {rmse:.2f} kW") print(f"baseline feature importance: {model.feature_importances_[:3]}")

这里用TimeSeriesSplit做时序交叉验证,它和随机K折完全不同,它强制训练集永远在验证集之前,避免模型偷看未来。early_stopping_rounds=50配learning_rate=0.05是XGBoost实践里很稳的一对参数;没有额外调参的情况下,max_depth=6、subsample=0.8、colsample_bytree=0.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_steps=48, output_steps=96, feat_cols=[0, 1, 2, 3, 4]) day_mask = X_seq[:, :, 3].mean(axis=1) > 5 X_seq, y_seq = X_seq[day_mask], y_seq[day_mask] model = Sequential([ LSTM(64, return_sequences=True, input_shape=(48, 5)), Dropout(0.2), LSTM(64, return_sequences=False), Dense(128, activation="relu"), Dense(96), ]) model.compile(optimizer="adam", loss="mae") model.fit(X_seq, y_seq, epochs=30, batch_size=64, validation_split=0.2)

这个结构比较常规,两层LSTM加一个输出层。return_sequences=True让第一层把整条序列信息传给第二层,第二层只返回最终状态,压缩成一个编码向量,最后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)阈值按训练集分位数取,避免固定区间在不同季节失真。

最后分享一个我自己的习惯:所有实验都保留一组“冷启动分割”结果,训练集只用某年以前的数据,验证集用全年数据。这个实验通过后再上真实业务,因为真实业务就是这样背靠背运行的。连续两个冬季在冷启动验证里表现稳定的模型,我才放心接进调度系统;表现不稳定的,绝不为了上线而强行上线。做预测系统做到后面你会发现,最难的不是模型,而是把数据边界、天气边界和管理边界想清楚。希望这篇笔记里记录的踩坑经验能帮你少走几条弯路,顺利把光伏发电功率预测系统落到自己的电站里去,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询