ARTICLE DETAIL

资讯详情

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

LightGBM风电功率预测实战:数据清洗、特征工程与调参全攻略

LightGBM风电功率预测实战:数据清洗、特征工程与调参全攻略 简介基于机器学习 LightGBM 模型的风电预测项目源码包面向计算机相关专业学生、高校教师及公司开发者可满足毕业设计、课程设计或期末大作业需求也适合希望用真实竞赛数据练习回归建模的初学者。资源共30个文件包括22个功率预测赛题数据集、4个训练好的模型、2个Python脚本模型训练与预测、1个项目文档说明和1张结构图压缩包约25.42MB按数据、模型、脚本、文档分模块存放便于按流程学习。目前已有161人学习下载属于易上手的优质项目。借助项目文档可快速运行完整流程覆盖数据读取、预处理、LightGBM模型构建、调参与风电功率预测输出训练好的模型可直接加载使用也可在此基础上二次开发如调整超参数、增加特征或迁移至其他时序预测场景。整体工程结构清晰是一份兼具学术参考与实战意义的可运行项目尤其适合需要提交完整课题代码的同学参考。1. 风电预测为什么选 lightGBM一个毕业设计就能看清的模型选型逻辑拿到一份风电功率预测的项目包多数人的第一反应是先把模型加载起来对着数据集跑一次预测。但如果你手里只有SCADA导出的风速、风向、温度和历史功率我建议先别碰模型先回答一个问题预测的是未来1小时还是24小时是单台机组还是整个场站。目标没对齐后面换什么模型都是白做。我一般会先用lightGBM搭一条基线原因很直接风电预测本质是带噪声的表格回归也是机器学习算法里最适合先跑基线的一类问题lightGBM对这类场景几乎是零门槛训练快、调参路径清晰特征重要性还能帮你定位是天气变量还是滞后功率在起作用。这个方案正好覆盖标题里的python源码、项目文档说明、数据集和模型四块适合课程设计、毕业设计也适合风电场做功率预测基线。2. 先搞定输入风电数据集要做的清洗、滑窗与切分2.1 风电 SCADA 数据长什么样字段、缺失和异常功率风电预测的数据集通常来自风电场SCADA系统导出的CSV时间粒度常见10分钟或15分钟字段至少有时间戳、风速m/s、风向°、气温、气压、湿度、有功功率kW或MW。有些数据集还会附带理论功率或可用功率用来判断限电。拿到数据的第一个动作不是画图而是先看结构import pandas as pd import numpy as np df pd.read_csv(wind_farm.csv, parse_dates[timestamp], index_coltimestamp) print(df.info()) print(df.describe()) print(时间范围, df.index.min(), -, df.index.max()) print(缺失值统计) print(df.isna().sum())这段代码的作用是确认三件事时间索引有没有被正确解析、功率列的量纲是什么、哪些列存在缺失。df.info()看列类型和总行数df.describe()看风速和功率的均值、极值如果功率大量为0先确认是风机停机还是限电这两种情况对建模的处置完全不同。时间索引要排序去重SCADA导出偶尔会出现重复时间戳直接用groupby取均值即可。限电是风电数据集里最典型的脏数据某段时间实际功率持续低于按功率曲线算出的理论功率不是数据录错是调度要求限制出力。处理方式取决于你的预测目标。如果预测的是实际出力不要把这些样本删掉删掉会让模型系统性学不到低出力工况更好的做法是加一个is_curtailment标记列让模型自己学。如果数据集里没有理论功率只能靠规则近似风速高于切入风速但功率长期为0时大概率是停机或限电先人工核对再决定。缺失值方面风速传感器故障常见整段缺失。短于2小时的缺口用线性插值长缺口直接删除该时间段不要用均值去填长段否则风速方差被压平后续构造滞后特征时模型会误以为风速一直平稳。2.2 滑窗构造把时间序列改造成监督学习表lightGBM本身不关心时间先后它只认特征表。所以要把序列转成“用过去预测未来”的监督学习表。对预测下一个时间点比如未来10分钟或1小时常见做法是构造滞后特征t-1、t-2、t-3、t-6、t-12、t-24按实际采样间隔换算的风速和功率再加滚动统计量和时间编码。df df.sort_index() # 滞后特征不同历史深度覆盖短期惯性和日周期性 for lag in [1, 2, 3, 6, 12, 24]: df[fwind_speed_lag_{lag}] df[wind_speed].shift(lag) df[fpower_lag_{lag}] df[power].shift(lag) # 滚动统计过去 6 个采样点的均值与标准差刻画风速变化剧烈程度 df[wind_speed_roll_mean] df[wind_speed].rolling(6).mean() df[wind_speed_roll_std] df[wind_speed].rolling(6).std() # 时间特征风电出力有明显昼夜和季节节律 df[hour] df.index.hour df[month] df.index.month # 风向是环形变量0° 和 360° 是同一个方向拆成 sin/cos 给模型留出表达空间 df[wind_dir_sin] np.sin(np.deg2rad(df[wind_direction])) df[wind_dir_cos] np.cos(np.deg2rad(df[wind_direction])) df df.dropna()这段代码里最容易错的是shift的方向。shift(lag)是拿“过去第lag个采样点的值”填到当前行方向反了就成了用未来预测过去属于直接的数据泄漏。滚动窗口按采样点计10分钟粒度的数据6个点等于1小时12个点等于2小时具体窗口长度要按你的数据集粒度换算。dropna会丢掉最老的24个样本这是滞后特征必须付出的代价可接受。时间特征hour和month对lightGBM很有用因为风电有明显昼夜和季节节律白天风速大、夜晚背风夏季和冬季的出力曲线也不同。不需要做onehot树模型可以直接切分数值特征month当数值也能用只是切分方式不如环形编码优雅但对效果影响不大。2.3 训练/验证切分时间序列不许随机 shuffle这是数据集环节最常翻车的地方。很多入门代码用sklearn的train_test_split默认随机切分对时间序列是错的验证集里会混入训练集时间段附近的样本滞后特征高度重叠相当于开卷考试验证分数虚高。正确做法是按时间排序前80%训练后20%验证。split_idx int(len(df) * 0.8) # 前 80% 训练后 20% 验证这里刻意不打乱 X_train df.iloc[:split_idx] X_valid df.iloc[split_idx:] y_train X_train[power] X_train X_train.drop(columns[power]) y_valid X_valid[power] X_valid X_valid.drop(columns[power]) print(ftrain: {X_train.shape}, valid: {X_valid.shape})如果数据覆盖跨年不要只按行数切最好按月份切比如前10个月训练、后2个月验证避免季节分布不一致。验证集要至少包含完整一周保证昼夜循环都出现过否则模型在夜间时段的误差你根本看不到。这些切分规则看起来不起眼但项目文档说明里必须写清因为别人复现时最容易改错、且出错后最不容易发现的就是这一步。3. 用 lightGBM 做风电功率预测最小可运行的 python 源码3.1 基线模型LGBMRegressor 的初始化与早停特征表准备好了模型部分其实很短。lightGBM有两套接口原生lgb.train和sklearn风格的LGBMRegressor。对于入门和复用我一般用LGBMRegressorfit接口和sklearn一致调参时不容易写错。做未来多步预测时可以每个预测步长单独训一个模型工程上比多输出模型更稳也更容易排查是哪一步误差大。import lightgbm as lgb from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score feature_cols [c for c in X_train.columns if c ! power] model lgb.LGBMRegressor( objectiveregression, n_estimators500, learning_rate0.05, num_leaves31, min_child_samples20, subsample0.8, colsample_bytree0.8, subsample_freq1, random_state42, verbose-1, ) model.fit( X_train[feature_cols], y_train, eval_set[(X_valid[feature_cols], y_valid)], eval_metricrmse, callbacks[lgb.early_stopping(50), lgb.log_evaluation(0)], )objective默认就是regression写出来是提醒自己用的是回归损失。eval_metricrmse表示用验证集RMSE判断是否早停early_stopping(50)是连续50轮没有改善就停止训练log_evaluation(0)则把每轮日志关掉避免刷屏。这里有个细节early_stopping的判断指标是rmse它是训练期的止损线和你后面选的业务指标无关训练完成后best_iteration_会被记录后续预测必须用这个轮数而不是n_estimators设定值。预测和指标计算y_pred model.predict(X_valid[feature_cols], num_iterationmodel.best_iteration_) rmse mean_squared_error(y_valid, y_pred, squaredFalse) mae mean_absolute_error(y_valid, y_pred) r2 r2_score(y_valid, y_pred) print(fRMSE{rmse:.2f}, MAE{mae:.2f}, R2{r2:.3f})predict时带上best_iteration_这一行是关键。测试时如果忘了带模型会用满n_estimators棵树早停省下来的泛化收益等于没吃到。RMSE和MAE一起看RMSE对高峰值误差敏感MAE反映平均误差水平两个数差距大说明预测里存在少数严重偏离的样本。3.2 模型保存与加载同一个模型在源码和文档间的闭环项目包里的“模型”一般有两种形态一种是joblib/pickle包着的LGBMRegressor一种是lightGBM原生Booster的文本模型。推荐后者因为Booster.save_model生成的txt里能看到树的数量、目标函数、特征顺序和特征重要性原始记录这些对项目文档说明里的“模型说明”章节是直接的素材打开就能核对不用反序列化黑盒子。# 方式一sklearn 接口整体保存适合继续微调或拿 best_params_ import joblib joblib.dump(model, model/lgbm_wind.pkl) # 方式二原生 Booster 的文本模型跨版本兼容更好 model.booster_.save_model(model/lgbm_wind.txt) # 加载文本模型 loaded lgb.Booster(model_filemodel/lgbm_wind.txt) y_pred_loaded loaded.predict(X_valid[feature_cols], num_iterationloaded.best_iteration)两种方式的取舍很清楚pkl保存的是完整Python对象包括训练时见过的特征顺序换环境容易因Python或lightGBM版本不一致报错txt是lightGBM自己的格式只要lightGBM能装上就能加载。但注意Booster.predict不负责重排列顺序训练时feature_cols的顺序必须和预测时完全一致这个约定要在项目文档里写明。3.3 第一版结果怎么看先看残差再看分桶误差拿到基线结果后不要只盯RMSE一个数字。把预测值和真实值按风速分箱逐段看误差能定位模型在哪个工况下不行。高风速段误差大说明极端天气样本少、模型学不够低风速段绝对误差小但相对误差大业务上通常可以接受。import pandas as pd df_valid_res pd.DataFrame({actual: y_valid.values, pred: y_pred}) df_valid_res[wind_speed] df.loc[X_valid.index, wind_speed].values bins [0, 3, 6, 9, 12, 100] labels [0-3, 3-6, 6-9, 9-12, 12] df_valid_res[wind_bin] pd.cut(df_valid_res[wind_speed], binsbins, labelslabels) grouped df_valid_res.groupby(wind_bin, observedTrue).apply( lambda g: pd.Series({ rmse: mean_squared_error(g[actual], g[pred], squaredFalse), mae: mean_absolute_error(g[actual], g[pred]), }) ) print(grouped)pd.cut分箱后逐段计算RMSE/MAE能快速回答“误差从哪个风速段开始变大”。如果12m/s以上样本只有几十条说明是样本量问题而不是模型问题先收集更多极端天气数据比换模型更实际。这一步的经验值也应该写进项目文档作为后续迭代的基线记录。4. lightGBM 风电模型调参清单从学习率、叶子数到正则的先后顺序4.1 学习率和 n_estimators 是第一个组合这两个参数必须绑定调。learning_rate越小每棵树贡献越小需要的树越多训练时间越长但验证精度通常更好。风电数据常有几万到几十万行我的习惯是learning_rate0.05起n_estimators500到1000配合early_stopping(50)。如果早停在接近上限触发说明树还不够调大n_estimators如果几十轮就早停说明学习率太大或树结构过简单。先给一张参数范围表后面每条展开说参数常用范围调参方向learning_rate0.01 ~ 0.1验证集还在降就调小n_estimators300 ~ 2000配合早停不用手工精调num_leaves15 ~ 127增大提升拟合过拟合则缩小max_depth-1 / 6 ~ 12数据量小时限制深度更稳min_child_samples20 ~ 100噪声大时调高subsample0.7 ~ 1.0调小增加随机性、缓解过拟合colsample_bytree0.7 ~ 1.0特征多时调小reg_alpha / reg_lambda0 ~ 1.0过拟合明显时才加大这张表可以直接放进项目文档说明的参数配置页比贴一大段代码更直观。注意表里的方向是“调参方向”不是“越大越好”风电数据噪声大很多参数调大反而更差。4.2 num_leaves 和 max_depth树的复杂度控制num_leaves是lightGBM最核心的复杂度参数它控制每棵树最多有多少叶子比max_depth更直接地决定了模型容量。默认31对风电这种带噪声的时序回归我一般从31起步验证集分数不再降就不动。数据量超过20万行可以试63但不要一上来就开大否则模型会记住训练集里的阵风毛刺。max_depth默认-1表示不限制如果发现num_leaves涨了验证集反而变差可以同时限制max_depth在8到12防止某些单条样本被反复切分。这里有个隐含关系设了max_depth时num_leaves理论上不能超过2的max_depth次方lightGBM不会报错但会自动限幅所以两个参数要一起调不要单独盲试。4.3 min_child_samples 和 subsample/colsample专治风电噪声风电功率受湍流、阵风、限电影响标签噪声比一般表格回归大得多。min_child_samples默认20在噪声大的数据集上经常要调到50到100让叶子有足够样本再分裂否则模型会学着复读某些单点极端值。subsample做行采样0.8常用subsample_freq1表示每轮都重新采样colsample_bytree是特征采样特征超过30个时调到0.7到0.8能明显提升泛化。这些参数的共同思路是树越多越深越需要随机性来对冲噪声否则训练集分数一路涨验证集却在某个拐点后开始掉。4.4 正则化参数过拟合明显时才动reg_alpha和reg_lambda默认都是0在特征少、数据量大的风电数据集上往往不用调。什么时候调训练集RMSE明显低于验证集而且特征重要性里某个滞后特征独占大头时给reg_lambda0.1到1.0或reg_alpha0.1到0.5。L1会把不重要特征的权重压到接近0顺带做了特征选择L2是均匀收缩。对lightGBM来说先调前面的结构参数正则最后加顺序反了会调半天找不到原因。4.5 一个实用的迭代顺序先早停跑基线再看特征重要性不要一上来就GridSearchCV风电数据几十万行全网格搜一次要几小时而且网格搜索在时间序列上有泄漏风险。我一般流程是先固定learning_rate0.05、num_leaves31跑一版记录验证RMSE然后看特征重要性用gain删掉重要性总和不到1%的特征再调num_leaves和min_child_samples最后才考虑正则。imp pd.DataFrame({ feature: feature_cols, gain: model.booster_.feature_importance(importance_typegain), }) imp[gain_norm] imp[gain] / imp[gain].sum() imp imp.sort_values(gain_norm, ascendingFalse) print(imp.head(15))feature_importance有两种类型split表示分裂次数gain表示这个特征在所有分裂中带来的总增益后者更能反映真实贡献。如果功率的滞后特征霸榜说明数据集记忆性很强可以试试减小特征窗口如果风速滞后排不上号先检查风速列在做滑窗时是不是被覆盖了——这种错误很常见列名重复赋值后前一列被静默替换重要性全跑到新列上。5. 风电预测建模避坑4 条一踩一个准的实战记录先说排查思路。这类问题的共性特点是源码能跑、分数好看、部署时翻车。所以拿到一个风电预测项目第一件事不是夸模型好而是先怀疑三件事特征里有没有未来信息、训练和验证集有没有时间交叉、评估指标是不是和业务目标错位。下面4条是我在这类项目里反复见到的高发问题。5.1 数据泄漏之一全量归一化现象训练R2 0.97验证R2 0.96一换现场数据直接崩到0.7。原因StandardScaler在包含验证集的全体数据上做了fit_transform均值和方差已经见过验证集。lightGBM作为树模型本来不需要归一化但很多源码为了“特征均衡”还是习惯归一化然后在错误的位置执行fit。解决归一化必须在切分之后只对训练集fit验证集和测试集只能transform。对lightGBM可以完全跳过归一化直接使用原始量纲。from sklearn.preprocessing import StandardScaler # 错误在切分前对整个 X 做 fit scaler StandardScaler().fit(X) X_all scaler.transform(X) # 正确先切分再只对训练集 fit scaler StandardScaler() X_train scaler.fit_transform(X_train) X_valid scaler.transform(X_valid)这段代码背后的原则是任何学习参数的fit都只能发生在训练集上包括归一化、特征选择、缺失值填充统计量。缺失值用均值填充也一样均值只能从训练集算。5.2 数据泄漏之二验证集时间顺序被打乱现象用sklearn默认的train_test_split验证集RMSE非常漂亮部署时预测下一天误差直接翻倍。原因随机切分让验证集里的样本在时间上和训练集重叠滑窗特征相当于提前看过答案这就是典型的“开卷考试”。解决一律按时间排序切分。如果业务是预测未来24小时就把最后一段完整24小时整段留出当验证集训练集只用它之前的数据中间不要交叉。还要注意dropna之后索引仍然保持时间顺序不要因为reset_index而丢失排序关系。5.3 低风速时 MAPE 爆炸评估指标选错现象总体RMSE只有额定功率的5%汇报时却被问“为什么低风速段误差200%”。原因MAPE的分母是真实功率低风速时接近0误差一点点就被放大到百分之几百。风电功率预测业务上常用RMSE、MAE或容量归一化后的NMAEMAPE只在功率显著大于0的区间有意义。解决把主评估指标换成RMSE/MAE如果业务必须报MAPE把功率低于阈值比如额定功率的5%的样本排除改用sMAPE。同时按风速段分组统计误差低风速段看绝对误差高风速段看相对误差。valid_res pd.DataFrame({actual: y_valid, pred: y_pred}) low_wind valid_res[df.loc[X_valid.index, wind_speed] 3] print(低风速段 MAE, mean_absolute_error(low_wind[actual], low_wind[pred]))这一条在项目文档说明里也要写清楚结论里的“误差5%”到底是什么口径不写清楚换个人复现就会得出完全不同的结论。5.4 模型跨环境加载失败版本不一致是最典型的黑匣子现象在A机器训练好并save_model换B机器加载直接报错或者加载成功predict结果整体偏移。原因两个环境的lightGBM版本不一致原生booster文件格式有细微变化更常见的是特征列顺序不一致训练时特征顺序是wind_speed_lag_1在前预测时DataFrame列顺序被改动Booster按位置读特征顺序一错全错。解决模型文件统一用booster_.save_model()生成txt格式预测前用feature_cols对DataFrame做reindex在requirements.txt里把lightgbm固定到训练环境的版本示例写法是lightgbm3.x.y具体版本以训练环境为准。如果不方便锁版本至少把训练和预测的特征列顺序存成feature_order.json预测时按这个顺序取列。注意模型文件不是越大越好txt格式反而最方便排查因为可以直接打开看里面记录的特征顺序、目标函数和树数量对账时很有用。6. 验证模型到底行不行误差分箱与一键复现6.1 误差不能只看一个数按风速段和时间段拆开看前面第3章已经做了风速分箱最后一章把验证流程补完整整体指标、分箱误差、残差时间模式三件套。残差如果出现明显的昼夜模式说明模型没学到时间特征残差如果在限电时段整体为负说明训练集混入了限电样本而模型把限电视作普遍规律。valid_res[residual] valid_res[actual] - valid_res[pred] # 按小时看平均残差检查是否存在系统偏差 hourly_bias valid_res.groupby(df.loc[X_valid.index, hour]).agg({residual: mean}) print(hourly_bias) # 如果深夜残差整体为负说明模型倾向低估夜间出力分箱和分组验证的目的不是为了让RMSE更好看而是确认误差结构符合业务认知。风电预测的验收标准一般不是“R2多高”而是“最大误差出现在什么工况、这个工况有多重要”。比如夜间低风速段的负偏差如果业务上夜间本来就是低谷影响就小但如果是夏季午后的高风速段有系统性低估影响就大。6.2 写一个可复现的入口把源码、数据集、模型串起来项目包里带项目文档说明最重要的作用就是让别人能复现。我通常建议把训练过程收敛成一个run_pipeline.py脚本参数从命令行传入数据集换掉也能重新出模型。文档里只写三件事数据字典、环境依赖、参数表。# 最小调用方式 python run_pipeline.py --data data/wind_farm.csv \ --train_start 2023-01-01 --train_end 2023-10-31 \ --valid_start 2023-11-01 --valid_end 2023-12-31 \ --output model/lgbm_wind.txt把时间范围做成命令行参数而不是写死在源码里是容易被忽略但极有用的习惯。风电数据集经常要追加新月份数据写死之后每次都要改源码项目文档就失去了复现的价值。脚本输出时把RMSE、MAE、R2和分箱误差表存成csv下次训练直接对比。我现在拿到这类风电预测项目包第一步不是跑模型而是看文档里有没有写清楚三件事预测目标单机还是场站、预测未来多长时间、数据时间范围、评估指标口径。这三件事没对齐模型再漂亮都是白做。希望帮到你。本文还有配套的精品资源点击获取
返回列表