简介:面向计算机相关专业学生、高校教师及公司程序员,这是一套基于lightGBM完成风电功率预测的完整项目源码包,覆盖数据处理、模型训练、预测与评估全流程,非常适合用于毕业设计、课程设计或期末大作业。包内含30个文件,由22个CSV赛题数据、4个joblib预训练模型、2个Python脚本、1个Markdown项目说明及1张示意图组成,压缩包整体约25.42MB,目录结构清晰,便于按模块学习调用。目前已有161人浏览/学习下载,适合希望以lightGBM快速搭建风功率预测基线模型的同学,也方便在此基础上做特征工程、参数调优等二次开发。资源提供可直接运行的训练与预测代码、预训练模型和详细说明文档,使用者可对照文档快速复现项目效果,也可进一步改进模型用于论文实验或工程实践。
1. 风电功率预测为什么选lightGBM:一个能落地的机器学习方案
风电功率预测是新能源并网调度里最现实的问题——电场要提前上报次日发电计划,报多了被考核,报少了浪费容量,误差直接折算成钱。传统物理方法要做气象建模和流体仿真,门槛高、维护重;纯统计方法又扛不住风速的剧烈波动。近几年的常见做法是把机器学习拿来直接做回归:用历史功率、风速、风向、温度这些场站SCADA数据,训练一个模型去拟合未来一段时间的功率曲线,而lightGBM几乎是这类任务里最能快速落地的选择。它训练快、内存占用小、对表格数据拟合能力强,不需要像深度学习那样堆GPU和调网络结构,一台普通工作站就能跑完训练和推理。这个方案适合手里有场站历史数据、想做短期功率预测但没有专职算法团队的工程师,也适合做毕设和课题研究的学生——数据、代码、文档、训练好的模型都在压缩包里,从跑通到改参数再到换数据集,路径是完整的。
2. 风电数据准备与特征工程:从原始SCADA数据到lightGBM输入
2.1 风电数据集里有哪些字段,哪些能直接喂给模型
先打开数据集里的CSV文件看字段。典型的场站SCADA数据按时间戳采样,常见的列有风速、风向、温度、湿度、气压、桨距角、有功功率,有些还带单机状态码。拿到数据不要急着训练,先做三件事:看时间跨度、看采样间隔、看缺失率。多数数据集是10分钟一条,一天144条,一个月4320条,一年约5万条。这个量级对lightGBM来说很小,训练几秒钟就完成了。
字段筛选上,风速和风向是物理上最相关的输入,必须保留。温度和气压对功率有间接影响——空气密度随温度变化,直接影响风能捕获效率——建议保留。湿度相关性弱,但来源稳定,保留也无妨。桨距角是控制系统的输出,和功率强相关,但要小心:某些工况下桨距角已经是结果而非原因,直接喂进去容易引入因果倒置。我的做法是先做一版相关性矩阵,把与功率的Pearson相关系数低于0.1的列剔除,再用剩余字段训练,对比特征筛选前后的验证集误差。
import pandas as pd df = pd.read_csv('wind_farm_data.csv', parse_dates=['time']) df = df.sort_values('time').reset_index(drop=True) # 看基本情况和缺失 print(df.shape) print(df.isnull().sum()) print(df.describe()) # 相关性筛选:保留与功率相关性较高的特征 corr = df.corr(numeric_only=True)['power'].abs().sort_values(ascending=False) keep_cols = corr[corr > 0.1].index.tolist() keep_cols = [c for c in keep_cols if c != 'power'] print('保留特征:', keep_cols)这段代码先把时间列解析并排序——时序数据最忌讳乱序,很多数据集导出后行顺序是乱的,不排序直接切训练集和测试集会严重翻车。缺失值统计用于决定填充策略:风速和功率的连续缺失超过5条记录时建议直接删除该段,零星缺失用前后线性插值,所有列的填充方式保持一致。
2.2 特征构造:滞后特征、滚动窗口与时间周期特征
原始字段直接喂模型能跑,但效果一般。风电功率预测本质是时间序列回归,而lightGBM是树模型,不具备天然的时序记忆能力,需要手动把历史信息构造成特征。三条经验:
第一,滞后特征。用过去1到6个时刻(10分钟到1小时)的风速和功率作为特征,让模型看到变化趋势。滞后阶数不是越多越好,超过12阶即2小时后,相关性衰减明显,还会增加过拟合风险。
第二,滚动窗口统计特征。比如过去3小时风速的均值、标准差、最大值、最小值,这些描述近期风况的稳定性。功率的滚动标准差很有用——它直接反映波动剧烈程度,波动大时预测难度天然增加,模型需要感知这个状态。
第三,时间周期特征。风电有典型的日周期性(早晚负荷变化)和季节周期性(冬春风大、夏秋风小),构造hour、month这样的整数特征,再转换成sin/cos编码,避免0点和23点在数值上割裂。
import numpy as np def make_features(df, target='power', lags=(1, 2, 3, 6), win=18): df = df.copy() # 滞后特征 for lag in lags: df[f'ws_lag_{lag}'] = df['wind_speed'].shift(lag) df[f'{target}_lag_{lag}'] = df[target].shift(lag) # 滚动窗口特征:3小时(18个点)的风速与功率统计 df['ws_roll_mean'] = df['wind_speed'].rolling(win).mean() df['ws_roll_std'] = df['wind_speed'].rolling(win).std() df['power_roll_mean'] = df[target].rolling(win).mean() df['power_roll_std'] = df[target].rolling(win).std() # 时间周期特征 df['hour'] = df['time'].dt.hour df['month'] = df['time'].dt.month df['hour_sin'] = np.sin(2 * np.pi * df['hour'] / 24) df['hour_cos'] = np.cos(2 * np.pi * df['hour'] / 24) df['month_sin'] = np.sin(2 * np.pi * df['month'] / 12) df['month_cos'] = np.cos(2 * np.pi * df['month'] / 12) # 删除构造初期产生的NaN return df.dropna().reset_index(drop=True) feature_df = make_features(df) print(feature_df.shape)这里滞后特征用了1、2、3、6,对应10分钟、20分钟、30分钟、1小时前,覆盖短时惯性;滚动窗口用18个点即3小时,兼顾了近期趋势和波动状态。sin/cos编码时间特征要成对使用,只保留其中一个会丢失周期信息。注意,shift和rolling产生的NaN行必须drop掉,否则模型会把NaN当作有效值训练,lightGBM虽然能处理缺失值,但这里本身就是无意义行,保留只会干扰。
3. 训练lightGBM风电预测模型:核心代码与必调参数
3.1 数据划分:时序预测严禁随机打乱
表格分类任务里常用的train_test_split随机划分,在时序预测里是绝对禁区。风电功率数据有强自相关性,随机打乱后训练集里混入未来的信息,验证集会严重虚高——这是这个项目里最常见的错误,没有之一。
正确做法是按时间顺序切分:假设数据覆盖一年,用前10个月训练、中间1个月验证、最后1个月测试。验证集用于早停,测试集只用于最终评估,全程不能碰。
train_size = int(len(feature_df) * 0.75) val_size = int(len(feature_df) * 0.15) feature_cols = [c for c in feature_df.columns if c not in ('time', 'power')] X = feature_df[feature_cols] y = feature_df['power'] X_train = X.iloc[:train_size] y_train = y.iloc[:train_size] X_val = X.iloc[train_size:train_size+val_size] y_val = y.iloc[train_size:train_size+val_size] X_test = X.iloc[train_size+val_size:] y_test = y.iloc[train_size+val_size:] print(f'train: {X_train.shape}, val: {X_val.shape}, test: {X_test.shape}')这样的75/15/15比例在时序场景下是个稳妥起点。注意不能用shuffle=True,也不能在划分前做任何基于全量数据的归一化——滞后特征本来就是原始值,树模型不需要归一化,这也是选lightGBM的隐性优势,少踩一个坑。
3.2 lightGBM训练代码与关键参数说明
先给出一份能直接跑通的基础代码,再逐个说参数。
import lightgbm as lgb model = lgb.LGBMRegressor( objective='regression', boosting_type='gbdt', n_estimators=3000, learning_rate=0.05, num_leaves=63, max_depth=-1, min_child_samples=30, subsample=0.8, subsample_freq=1, colsample_bytree=0.9, reg_alpha=0.1, reg_lambda=0.1, random_state=42, n_jobs=-1 ) model.fit( X_train, y_train, eval_set=[(X_train, y_train), (X_val, y_val)], eval_metric='mae', callbacks=[lgb.early_stopping(stopping_rounds=100, verbose=True)] ) print('最佳迭代次数:', model.best_iteration_) print('训练MAE:', model.best_score_['training']['mae']) print('验证MAE:', model.best_score_['valid_0']['mae'])几个参数是风电场景下需要重点关注的:
n_estimators设到3000配合early_stopping,让模型自己决定何时停。风电数据噪声大,训练轮数过多必然过拟合,早停是最实用的后悔药。
learning_rate和num_leaves要一起调。0.05的学习率偏保守但稳定,配合63的叶子数能拟合较复杂的非线性关系。如果数据集只有几千条,num_leaves降到31甚至15,否则模型容量超出数据承载能力。
min_child_samples设30,控制叶子节点最少样本数,这是对抗风电功率剧烈波动的关键——某些时段功率从0跳到满发,样本不足的叶子很容易记住噪声。
subsample和colsample_bytree都设0.8到0.9,做行列采样增强泛化性。训练数据是时序的,不能用bagging替代subsample——bagging按行重采样会打乱时间结构,虽然样本量不变,但顺序乱了对滞后特征会有轻微影响,不如subsample直接按比例抽行干净。
reg_alpha和reg_lambda是L1和L2正则,建议都从0.1起步。风电特征之间相关性不低,加正则能抑制特征共线性带来的不稳定。
早停的verbose=True会在每轮打印日志,训练完成后把best_iteration_记下来,后续预测和重新训练都用这个轮数,避免每次训练早停点不同导致结果不一致。
3.3 预测与结果保存
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score y_pred = model.predict(X_test, num_iteration=model.best_iteration_) # 裁剪物理不合理值:功率不能为负,也不能超过装机容量 y_pred = np.clip(y_pred, 0, installed_capacity) mae = mean_absolute_error(y_test, y_pred) rmse = np.sqrt(mean_squared_error(y_test, y_pred)) r2 = r2_score(y_test, y_pred) print(f'MAE: {mae:.2f} MW, RMSE: {rmse:.2f} MW, R2: {r2:.4f}') import joblib joblib.dump(model, 'lightgbm_wind_model.pkl')用num_iteration指定迭代次数预测,是配合早停的标准动作。功率裁剪这一步经常被忽略——模型预测值可能出现负数和超过装机容量的值,前者在物理上不存在,后者会误导调度。装机容量数值在数据集的文档里会标注,一般取场站铭牌功率。保存模型用joblib,比pickle更稳定,对lightGBM这类带C扩展的模型兼容性更好。
4. 模型评估与预测结果分析:MAE、RMSE、MAPE怎么看不被骗
4.1 回归评估指标:为什么只盯RMSE会吃亏
风电功率预测的评估指标,业界常用的有MAE、RMSE、MAPE。先说各自脾气。
MAE是平均绝对误差,单位是MW,直观反映平均预测偏差。缺点是它把所有时刻的误差等权看待,而风电功率在满发时段和停机时段的误差绝对值差异极大——满发时误差20MW可能只算小偏差,停机时误差5MW都显得刺眼。
RMSE对大误差更敏感,因为先平方再开方,一次50MW的误差对RMSE的贡献是25次10MW误差的量级。调度考核通常盯着RMSE看,因为它惩罚峰值误差,而峰值误差往往出现在风速骤变时。但RMSE的毛病是受极端天气样本影响大,某次强阵风过程就能把整体RMSE拉高0.5MW,掩盖模型在常规工况下的真实水平。
MAPE是百分比误差,单看数值会欺骗你——当实际功率接近0时,哪怕预测值只差了0.1MW,MAPE也会飙升到几百甚至上千。风电在低风速时段功率本来就接近0,MAPE会剧烈波动,不建议作为主要指标。
我的建议是MAE+RMSE+R2三个一起看,MAPE只看分段结果。具体操作是:按实际功率分桶(比如0-10%出力、10%-50%、50%-100%),分别计算各桶的MAE和RMSE,这样能定位模型在哪个出力区间表现差——通常高功率段绝对误差大但相对误差小,低功率段反过来。
bins = [0, 0.1, 0.5, 1.0] labels = ['0-10%', '10%-50%', '50%-100%'] segments = pd.cut(y_test / installed_capacity, bins=bins, labels=labels) seg_metrics = pd.DataFrame({ 'MAE': y_test.groupby(segments).apply(lambda x: mean_absolute_error(x, y_pred[x.index])), 'RMSE': y_test.groupby(segments).apply(lambda x: np.sqrt(mean_squared_error(x, y_pred[x.index]))) }) print(seg_metrics)按功率区间分桶评估是我在这个项目里的固定动作。分段评估的结果直接决定优化方向——如果高功率段RMSE大,优先调整滞后特征和num_leaves;如果低功率段MAE偏高,说明模型在低风速区间的判断力不足,需要增加低风速样本的权重或构造风速比特征。
4.2 预测曲线对比与误差分布分析
只算指标不够,必须画图看预测曲线和实际曲线的贴合程度。选连续3天的测试集数据,画出实际功率与预测功率的对比曲线,重点看三类区域:功率爬坡段(风速上升时功率陡增)、功率骤降段、平稳段。
import matplotlib.pyplot as plt plot_start = 500 # 从测试集第500个点开始 plot_end = plot_start + 288 # 连续3天共288个点(10分钟采样) plt.figure(figsize=(14, 5)) plt.plot(y_test.iloc[plot_start:plot_end].values, label='actual', linewidth=1.5) plt.plot(y_pred[plot_start:plot_end], label='predicted', linewidth=1.5, alpha=0.7) plt.legend() plt.ylabel('power (MW)') plt.xlabel('point (10min interval)') plt.title('Actual vs Predicted Power - 3 days') plt.show() residual = y_test.values - y_pred print('残差均值: %.3f MW' % residual.mean()) print('残差标准差: %.3f MW' % residual.std())残差均值接近0说明没有系统性偏差,残差标准差反映波动幅度。如果残差均值明显不为0,比如整体偏正,说明模型系统性低估,需要在特征里补充趋势项或者调整模型偏差——XGBoost里有base_score可以调,lightGBM里则靠训练前对目标做偏移。曲线对比图里另一个常见现象是预测值比实际值平滑——模型对陡变反应慢,这是滞后特征和树模型容量共同决定的,不算bug,但要在项目文档里如实说明。
5. 风电功率预测的避坑指南:数据泄露、过拟合与负功率
5.1 数据泄露:归一化放错位置,结果好得不敢信
现象:验证集MAE低到不可思议,比同类论文里的结果好一截,但测试集一测就垮。原因往往是数据预处理时用了全量数据的统计量。比如对风速做标准化,如果用全量数据的均值和标准差去变换,再切训练集和测试集,测试集的信息已经渗进训练过程。树模型本身不需要特征归一化,但很多人习惯性地套用深度学习流程,或者用MinMaxScaler处理功率目标值,这一手就把未来信息泄漏进去了。
解决:凡是需要统计量的预处理(标准化、归一化、缺失值均值填充),一律只fit训练集,再transform验证集和测试集。lightGBM场景下最简单——干脆不做归一化,原始值直接训练,省掉一类问题。如果用了时序交叉验证,同样要在每一折内重新fit预处理器,不能在折外做。
5.2 过拟合:验证集在掉点,训练集还在涨
现象:训练到几百轮时,训练MAE持续下降,验证MAE先降后升,曲线劈叉。原因很直白:模型开始背诵训练集的噪声。风电功率数据噪声大,同样的风速和温度下功率都有不小的随机波动,模型容量过大时就记住了这些随机性。
解决:早停是最好的第一道防线,stopping_rounds设在100到200之间,让验证集连续100轮没有改善就停。第二步是调结构,num_leaves从63降到31,min_child_samples从30提到50,让每个叶子承载更多样本,学更稳定的规律。第三步是加正则,reg_alpha和reg_lambda从0.1提到1.0甚至5.0,压制权重幅度。这三步按顺序做,不要一上来就调learning_rate。
5.3 负功率、超装机容量与预测值抖动
现象:预测曲线在功率接近0的时段出现小幅负值,在满发时段偶尔超过装机容量。原因:模型是纯数值回归,没有植入物理边界。风电功率的物理约束是0到装机容量之间,但回归模型不管这些,线性外推和树模型的叶子均值都可能越界。
解决:预测后在外部做clip,把负值归0,超过装机容量的值截断。但clip只是治标,如果大量预测值集中在0附近且频繁抖动,说明模型在低风速区间不稳定,需要在特征里加入低风速判别特征——比如构造风速是否低于切入风速的0/1标记,让模型显式学习停机状态。有些方案还会用两段式建模:先用分类器判断机组是否出力,再用回归模型预测具体功率值,工程上完全可行,但需要更细致的标签构造。
提示:功率预测值是场站层面的总功率,数据清洗时不要剔除停机时段。停机样本是模型学习“零出力”规律的必要数据,全删掉会让模型永远预测正功率。
5.4 时间对齐问题:滞后特征串位
现象:验证集指标不错,但滚动回测时一帧比一帧偏。原因:特征构造时用了shift,但如果数据里存在重复时间戳或时间间隔不均匀,shift会把上一行的值错配到错误时刻。举个例子,某两个时刻间隔了1小时而不是10分钟,lag=1对应的其实是1小时前而不是10分钟前,模型学到的滞后关系就乱了。
解决:在构造特征之前先检查时间戳的唯一性和等间隔性。用df['time'].diff()算相邻间隔,把间隔异常的记录标记出来。等间隔数据才允许用shift构造滞后特征,不等间隔数据要么重采样到统一频率,要么改用基于时间差的聚合特征——按过去30分钟、1小时做rolling聚合,而不是按行数shift。
6. 让lightGBM预测更准的三个进阶技巧
6.1 用Optuna搜索超参数
手动调参能到及格线,想要更稳需要系统搜索。我在这个项目里的做法是先用Optuna做200次TPE采样,搜索空间放在learning_rate、num_leaves、min_child_samples、subsample、reg_alpha、reg_lambda六项上,以验证集MAE为目标函数。每折训练用早停固定200轮,保证每次评估的公平性。搜索完成后取最优参数,再在完整训练集上按最优迭代次数重训一次。注意搜索过程中不能用测试集,否则调参本身就变成了一次信息泄漏。
6.2 构造风速-功率曲线偏离特征
风电场的风速-功率曲线是设备出厂时标定的,但实际运行中叶片磨损、结冰、限电都会让实际曲线偏离理论曲线。可以构造一个偏离特征:用理论功率曲线函数查表得到理论功率,把实际功率减去理论功率得到偏差值,让模型学习这个偏差的时序变化。这个特征对功率预测的提升往往比多调几十轮参数更明显。
6.3 多模型融合兜底
lightGBM在这个任务上单模型够用,但如果追求稳定,可以用lightGBM+ExtraTrees+XGBoost做加权平均,权重按验证集MAE的反比分配。融合的收益通常在MAE上再降3-5%,代价是训练和推理时间翻倍。对实时预测场景要评估这个成本是否值得,对批量离线预测则基本无压力。
我自己的习惯是:先用单模型跑通全流程,确认数据质量和评估口径没问题,再做特征迭代,最后才考虑融合和超参搜索。顺序反了的话,性能瓶颈在数据上还是模型上都说不清,白费功夫。风电预测这行,数据脏、波动大、考核严,把基线的数据清洗和特征工程做扎实,比堆模型复杂度管用得多。希望帮到你。
本文还有配套的精品资源,点击获取