工业用电量预测的完整实战:从特征工程到LightGBM建模避坑指南 简介面向制造业能源管理和生产过程优化这套基于机器学习预测工业用电量的RAR压缩包提供了从数据处理、多模型对比到可视化交互的完整实现。包内共15个文件以11个Python脚本、3张预测结果图表和1个训练checkpoint为主压缩包整体约175KB。脚本覆盖数据读取、日期特征处理以及SVM、KNN、逻辑回归、随机森林、AdaBoost、XGBoost、LightGBM、Transformer等模型训练与对比并配有GUI程序便于查看能耗趋势checkpoint可恢复训练或继续调优PNG图直接展示AdaBoost与Transformer的预测效果。目前已有230人学习下载适合具备一定Python和机器学习基础、希望在工业节能领域落地数据科学方案的开发者可据此快速构建用电量预测基线辅助节能决策。1. 机器学习预测工业用电量为什么制造场景比想象的更依赖特征工程制造业里最让人头疼的往往不是缺数据而是数据拿到手却不知道怎么变成模型能学的东西。用电量预测就是一个典型例子很多团队直接拉半年历史电量丢给 XGBoost 跑回归结果 MAPE 高得离谱换模型也没有实质改善。我在实际做过这个项目后意识到工业用电量预测的核心矛盾不是模型选型而是有没有把“生产节拍”翻译成特征。一个车间里同一台设备在工作日和检修日、白班和夜班、夏季和冬季的用电曲线能差出一倍只告诉模型“当前几点”它根本学不到这种差异。这份资源的价值就在这里它把从数据清洗、特征构造到模型训练和评估的完整流程整理成可复现的代码适合制造业能源管理、设备维护和成本核算相关从业者也适合准备进入时序预测方向的学生。它解决的是“拿着真实电表数据也能做出能用的预测结果”而不是停留在教科书级别的算法演示。2. 任务定义与数据准备把预测目标变成一行可计算的代码2.1 区分任务类型预测未来 24 小时还是下一个采样点工业用电量预测在建模前先要回答一个问题预测的目标到底是什么。常见做法有两种一种是预测未来单个采样点的功率值另一种是预测未来一段时间比如 24 小时的累计电量。这两种任务在数据表结构和特征构造上的要求完全不同。单点预测适合做设备异常告警因为模型只看当下半小时或一小时的状态就够了累计电量预测适合做生产排程和成本控制因为它需要捕捉一整天的负荷曲线形态。任务类型预测粒度典型场景需要的特征范围单点功率预测15min / 30min / 1h设备异常、容量告警滞后负荷、温度、班次累计电量预测24h / 周生产调度、需求响应、成本核算生产计划、节假日、温度曲线这个资源里默认按小时粒度建模预测未来 24 小时逐小时负荷。这样做的原因是工业现场的电表采集频率通常是 15 分钟到 1 小时小时级输出既保留了足够的曲线细节又不会因为粒度过细而导致噪声干扰。代码里用一行resample把高频数据聚合到小时级避免电表在整点附近的瞬时波动污染标签。2.2 原始电表数据的清洗时间戳对齐与重复值处理拿到电表数据后第一件事不是建模而是把数据和时间对齐。实际采集过程中最常见的坑是电表在通讯中断时会产生缺数在恢复通讯后会补传上一段数据于是同一个时间点出现两行记录。如果直接对全表去重可能会出现“整点 10 分的数据被删、整点 20 分的数据被保留”的随机结果导致时间戳错位。import pandas as pd # 原始电表数据读入假设表结构为meter_id, record_time, active_power_kw df pd.read_csv(meter_raw.csv, parse_dates[record_time]) # 按电表和小时聚合取每个小时区间内的均值作为该小时的负荷 df[hour] df[record_time].dt.floor(h) agg df.groupby([meter_id, hour])[active_power_kw].mean().reset_index() # 对同一小时内重复出现的记录做去重保留后一条因为通常后一条更完整 agg agg.drop_duplicates(subset[meter_id, hour], keeplast) agg agg.sort_values([meter_id, hour]).reset_index(dropTrue)这段代码先按小时取均值再对重复记录保留后一条。之所以先聚合再去重是因为通讯中断后补传的数据经常会覆盖同一个小时内的多个采样点直接按小时取均值本身就完成了一次降噪。floor(h)的作用是把任意时间戳规整到当前小时的开头比如08:17:33会变成08:00:00。这样后续所有特征都可以以小时为单位对齐。2.3 缺失值处理工业场景不建议用全局均值填充工业电表数据缺失的原因通常比较集中计划停机、设备检修、通讯故障。如果直接把缺失时段填成全局均值会把检修日的负荷曲线拉成正常生产日的形状模型学习到的规律会被严重误导。我在实际项目中通常采用分段填充策略缺失时长在两小时以内用前后相邻时段的线性插值超过两小时的缺失段直接标记为检修时段单独用一个特征字段标识而不是试图猜测这段负荷是多少。# 小时级数据列hour, active_power_kw df_hourly agg[[hour, active_power_kw]].copy() # 先判断缺失段长度 df_hourly[power_nan] df_hourly[active_power_kw].isna().astype(int) df_hourly[gap_group] df_hourly[power_nan].ne(df_hourly[power_nan].shift()).cumsum() # 短缺失用线性插值长缺失标记为检修 gap_len df_hourly.groupby(gap_group)[power_nan].sum() fill_mask df_hourly[gap_group].map(gap_len).le(2) df_hourly[power_nan].eq(1) df_hourly[is_maintenance] (~fill_mask df_hourly[power_nan].eq(1)).astype(int) df_hourly[active_power_kw] df_hourly[active_power_kw].interpolate(limit2)gap_group的构造思路是每次power_nan的值从 0 变成 1 或从 1 变成 0就生成一个新的组号这样每一段连续的缺失就对应一个组。map(gap_len)把每组的缺失总数回填到每一行。le(2)判断缺失是否不超过 2 小时短缺失走插值长缺失则单独用is_maintenance字段记录后续特征构造时可以把这个字段作为输入。这个处理方式保留了业务含义而不是把数据机械地填平。3. 特征构造把电耗拆成可学习的五个维度3.1 时间特征从循环编码到班次周期工业负荷的周期性很强但直接用hour和weekday两个整数列喂给模型会带来一个隐蔽的问题23 点和 0 点之间的距离是 1但在真实物理世界里它们只隔了一个小时模型如果把这种数值差异当成“跨越 23 个小时”就会在夜间边界产生奇怪的预测误差。解决办法是构造循环编码把小时映射到二维平面上的 sin / cos 坐标。import numpy as np # 时间循环编码小时、星期分别映射到单位圆 hours df_hourly[hour].dt.hour weekdays df_hourly[hour].dt.weekday df_hourly[hour_sin] np.sin(2 * np.pi * hours / 24) df_hourly[hour_cos] np.cos(2 * np.pi * hours / 24) df_hourly[week_sin] np.sin(2 * np.pi * weekdays / 7) df_hourly[week_cos] np.cos(2 * np.pi * weekdays / 7) # 是否工作日配合生产线排班记录使用 df_hourly[is_weekday] (weekdays 5).astype(int)hours / 24把小时归一化到 [0, 1) 区间再乘2 * pi得到对应的弧度。这样 23 点和 0 点在 sin 坐标上会处于相邻位置模型学到的是“夜间相邻时段”的关系而不是“数字相差 23”。同理week_sin和week_cos处理七天周期。值得注意的是是否工作日这个特征在制造业里并不完全等于“周末不干活”很多工厂周末有加班排产所以is_weekday只是备用特征最终用不用取决于实际生产日历。3.2 温度特征制冷设备负荷与室外温度的非线性关系制造业用电里空调、冷库、恒温车间的负荷和室外温度高度相关。但这种相关性不是线性的——温度在 28 度以上时每升高 1 度制冷负荷可能增加 2% 到 3%温度在 10 度以下时可能切换为制热模式负荷曲线又会抬升。所以在特征里除了原始温度值还需要加入冷却度日数CDD和加热度日数HDD这类变体。# 假设已有气温序列 temp_c基准温度通常取 18 度 base_temp 18.0 df_hourly[temp_c] weather_temp df_hourly[cdd] np.maximum(df_hourly[temp_c] - base_temp, 0) df_hourly[hdd] np.maximum(base_temp - df_hourly[temp_c], 0) # 高温极值标记温度高于 32 度的时段冷库设备可能满负荷运行 df_hourly[temp_extreme] (df_hourly[temp_c] 32).astype(int)cdd和hdd把温度特征拆成两个单向变量高于基准温度的部分对制冷负荷有正贡献低于基准温度的部分对供暖负荷有正贡献。直接用原始温度的话模型需要自己在分裂节点上找阈值而通过 CDD / HDD 预先注入“制冷和供暖分开考虑”的业务先验可以明显减少模型在边界温度上的不稳定预测。temp_extreme则是针对夏季高温时段冷链设备满载运行的特征标记这个在普通住宅用电预测里不需要但在制造场景里经常是负荷尖峰的直接原因。3.3 滞后特征与滑动窗口让模型“看到”过去两小时的趋势工业负荷有很强的惯性如果过去两小时负荷都在爬升那下一小时大概率不会突然掉下去。这种惯性需要通过滞后特征和滑动窗口特征传递给模型。滞后特征直接取过去 1、2、3 小时的负荷值滑动窗口特征则计算过去 2 小时均值和过去 8 小时均值分别对应短期惯性和长期水平。# 滞后特征过去 1、2、3 小时的负荷 for lag in [1, 2, 3]: df_hourly[flag_{lag}] df_hourly[active_power_kw].shift(lag) # 滑动窗口特征过去 2 小时均值、过去 8 小时均值 df_hourly[rolling_2h] df_hourly[active_power_kw].rolling(2).mean() df_hourly[rolling_8h] df_hourly[active_power_kw].rolling(8).mean() # 过去 8 小时负荷标准差反映生产稳定性 df_hourly[rolling_8h_std] df_hourly[active_power_kw].rolling(8).std()shift(lag)把当前行的值向后移动指定行数这样第t行的lag_1实际上是第t-1行的真实负荷。注意生成这些特征时不能直接使用未来数据否则会造成数据泄漏模型在训练时表现极好但换到在线预测就立刻崩掉。rolling_8h_std是判断产线是否处于稳定运行状态的参考特征标准差值突然变大说明状态在切换比如换班、设备启停这些时段的负荷波动通常比稳定生产时更大。3.4 生产排程特征从手工台账到模型输入制造场景里最容易被忽略但影响最大的特征是生产排程信息。比如一条注塑产线今天排了 3 个班次和排了 1 个班次用电量可能差出 40%。但大多数企业不会把排程数据和电表数据放在同一个表里需要手工合并。资源里提供的方法是把排班表按时间展开生成每小时的“班次数量”和“主要设备开机数”两个聚合字段。# 假设排班表shift_start, shift_end, machine_count, line_id schedule pd.read_csv(production_schedule.csv, parse_dates[shift_start, shift_end]) # 把每个班次展开为小时级记录并累加设备开机数 rows [] for _, row in schedule.iterrows(): hours_range pd.date_range(row[shift_start], row[shift_end], freqh) for hr in hours_range: rows.append({hour: hr, machine_count: row[machine_count], line_id: row[line_id]}) schedule_hourly pd.DataFrame(rows) schedule_agg schedule_hourly.groupby(hour)[machine_count].sum().reset_index() df_hourly df_hourly.merge(schedule_agg, onhour, howleft) df_hourly[machine_count] df_hourly[machine_count].fillna(0)这里把每个班次的起止时间用date_range展开成小时级记录然后按小时汇总所有在运行的设备数。合并时用howleft保留电表的全部时间范围没有排班的时段设备数为 0。对于没有排班台账的企业可以用“滞后 24 小时同时段的负荷差”来间接反映今天的生产节奏是否和昨天一致比如diff_24h active_power_kw - active_power_kw.shift(24)如果这个差值持续偏正说明今天比昨天多开了产线。4. 模型选择与训练从线性基线到 LightGBM 的参数边界4.1 时序交叉验证不能用普通 KFold 随机打乱处理时间序列数据时如果直接使用 scikit-learn 的默认KFold模型会看到未来的数据评估结果会虚高到近乎失真。工业用电量预测的评估也必须遵守时间顺序训练集时间在前验证集时间在后。这里采用TimeSeriesSplit来构造训练集和验证集同时设置gap参数来避免相邻时段的泄漏。from sklearn.model_selection import TimeSeriesSplit # 特征列与目标列 feature_cols [hour_sin, hour_cos, week_sin, week_cos, is_weekday, cdd, hdd, temp_extreme, lag_1, lag_2, lag_3, rolling_2h, rolling_8h, rolling_8h_std, machine_count] X df_hourly[feature_cols].values y df_hourly[active_power_kw].values # 5 折时序交叉验证gap 设为 3给特征构造留出缓冲 tscv TimeSeriesSplit(n_splits5, gap3) for train_idx, val_idx in tscv.split(X): X_train, X_val X[train_idx], X[val_idx] y_train, y_val y[train_idx], y[val_idx] # 在这里训练模型并记录验证集误差gap3的含义是在每个训练集末尾和验证集开头之间留出 3 个小时的空隙。这个空隙是必要的因为特征构造时我们计算了lag_1到lag_3如果没有这个间隙验证集第一条样本的滞后特征会来自训练集的最后几小时导致信息泄漏。按小时粒度的数据我一般建议gap至少等于最大滞后期数。4.2 线性岭回归作为基线先确认特征是否有效不要一上来就上集成模型。先用一个线性模型确定特征的原始预测力这种做法成本低而且能快速暴露特征构造上的大问题。岭回归处理相关特征比较稳定适合作为基线。from sklearn.linear_model import Ridge from sklearn.metrics import mean_absolute_percentage_error ridge Ridge(alpha1.0) ridge.fit(X_train, y_train) y_pred_ridge ridge.predict(X_val) mape_ridge mean_absolute_percentage_error(y_val, y_pred_ridge) print(fRidge MAPE: {mape_ridge:.3f})Ridge 的alpha控制正则化强度工业数据特征之间往往有较强的相关性比如lag_1和rolling_2h高度相关cdd和temp_extreme也有重叠alpha1.0是一个比较中庸的起点。如果 Ridge 的 MAPE 已经低于 10%说明特征工程做得基本到位后续换复杂模型只是锦上添花如果 Ridge 的 MAPE 很高大概率不是模型问题而是特征里缺少了关键变量比如设备开机数或排班信息。4.3 LightGBM 训练关键参数与避开过拟合的策略LightGBM 是工业负荷预测场景里效果和效率比较均衡的默认选择。它的叶子生长策略对非线性关系拟合能力强对缺失值也有原生的处理机制。下面是一组适合小时级用电量的参数起点。import lightgbm as lgb model lgb.LGBMRegressor( n_estimators800, learning_rate0.05, num_leaves63, max_depth7, min_child_samples30, subsample0.8, colsample_bytree0.8, reg_alpha0.1, reg_lambda0.1, random_state42 ) model.fit( X_train, y_train, eval_set[(X_train, y_train), (X_val, y_val)], eval_metricmape, callbacks[lgb.early_stopping(stopping_rounds100, verboseFalse)] ) y_pred_lgb model.predict(X_val) mape_lgb mean_absolute_percentage_error(y_val, y_pred_lgb) print(fLightGBM MAPE: {mape_lgb:.3f})num_leaves63对应约 6 层树的叶子数比默认的 31 大一些因为小时级负荷曲线本身是非线性的需要更多分裂节点但min_child_samples30限制每个叶子至少要有 30 个样本防止末端的统计噪声被当规律。subsample0.8和colsample_bytree0.8增加随机性可以让模型在验证集上的表现更稳。reg_alpha和reg_lambda的正则项在样本量只有几千条时很重要能明显抑制特征噪声。early_stopping的stopping_rounds100意味着如果连续 100 轮验证集 MAPE 没有下降就停止训练避免在训练集上无限拟合。4.4 重要度分析确认模型吃的是排班还是温度训练完成后看一眼特征重要度能帮你确认模型是否学到了业务上预期的规律。如果machine_count排在最前面说明生产排程信息捕捉得很充分如果排在最前面的是temp_extreme说明你的工厂很大一部分电耗来自制冷设备。import pandas as pd importance pd.Series(model.feature_importances_, indexfeature_cols) print(importance.sort_values(ascendingFalse))feature_importances_输出的是每个特征被用于分裂的总次数。这里有一个容易被忽略的点这个值反映的是“分裂中使用的频率”而不完全是“预测误差贡献度”。对于相关性较高的特征组比如lag_1和rolling_2h重要度会在两者之间分摊这时不要单独砍掉其中一个而是把特征组整体看待。5. 避坑排查工业负荷预测的五个常见问题记录5.1 排班表合并后出现 NaN导致模锻变成“只看心情”现象合并排班表后用电数据里出现了大段 NaN模型预测结果在深夜时段直接变成常数。原因排班表只有白班和夜班的开始时间没有结束时间合并时date_range没有覆盖完整的 24 小时或者是排班表里周六日没有任何记录按howleft合并后全部留空。解决先检查合并后的时间覆盖率比如df_hourly[machine_count].isna().mean()覆盖率低于 95% 时优先排查排班表本身就是否包含了跨午夜班次的结束时间确认无误后再用fillna(0)填充因为无排班记录等同于无设备开机。5.2 滞后特征在在线预测时取不到值现象训练时 MAPE 只有 6%模型部署到在线环境却变成 18%而且误差集中在每天的前几个小时。原因在线预测未来 24 小时是用当前时刻的lag_1特征去预测下一个小时。但当你迭代预测第 3 个小时时需要用到前一个预测值作为lag_1这会导致误差逐步累积。训练时验证集直接用了真实值做滞后特征所以评估结果偏乐观。解决训练阶段对验证集也做滚动预测也就是用第t步的预测值作为第t1步的滞后特征而不是用真值。代码里可以用一个循环逐小时推理并动态更新 lag 特征。5.3 节假日对预测误差的影响被低估现象法定节假日当天 MAPE 会跳到 20% 以上而且预测值明显偏向“偏高”。原因制造企业的节假日是停线检修或只开少量产线负荷水平只有正常生产日的 30%。模型训练数据中有大量正常生产日样本学习到的“典型负荷水平”会拉高预测值。解决把节假日作为一个强特征注入而不是依赖星期特征。可以维护一个节假日列表生成is_holiday字段同时将节假日前后一天也标记出来因为很多工厂会在节前赶工、节后复工负荷会有补偿性抬升。# 维护本年度节假日列表并生成三个特征字段 holiday_dates [2024-02-10, 2024-02-11, 2024-02-12, 2024-10-01] df_hourly[date] df_hourly[hour].dt.date df_hourly[is_holiday] df_hourly[date].isin(holiday_dates).astype(int) df_hourly[is_pre_holiday] df_hourly[date].shift(-1).isin(holiday_dates).astype(int) df_hourly[is_post_holiday] df_hourly[date].shift(1).isin(holiday_dates).astype(int)5.4 夏季尖峰时段模型预测偏低现象每天下午 2 点到 5 点实际负荷明显高于预测值误差集中在高温时段。原因温度特征使用了当天当时的实测温度但在真正预测未来 24 小时时你手里只有气象预报温度没有实测值。如果用实测温度训练模型会过度依赖温度特征的“精确值”实际预测时预报温度和实测温度存在误差模型就会偏保守。解决训练时对温度特征加入扰动模拟预报误差。常见做法是对温度加 ±1 度的随机噪声让模型学会对温度不确定性保持鲁棒。rng np.random.default_rng(42) temp_noise rng.normal(0, 1.0, sizelen(df_hourly)) df_hourly[temp_c_perturbed] df_hourly[temp_c] temp_noise5.5 设备加装变频器后模型突然全面失灵现象某条产线完成了变频改造后连续一周的预测 MAPE 全部超过 15%过去表现良好的特征重要性排行也发生了剧烈变化。原因变频设备的负荷曲线特征和工频设备不同——工频设备启动即满载负荷曲线有明显的方波特征变频设备有平滑的加减速段负荷曲线更圆滑。原先的滞后特征和滑动窗口特征在方波曲线下学习到的转折点规律在平滑曲线下不再成立。解决训练窗口不能简单用全部历史数据改成只取最近 60 到 90 天的数据来重新训练。如果预期未来还会引入新设备可以在模型中加入“设备改造标记”字段而不是用设备改造前的历史数据硬预测改造后的负荷。6. 从“能预测”到“能用”误差分段评估与业务侧验证模型训练完成并拿到 6% 到 8% 的整体 MAPE 之后真正的落地验证才刚刚开始。整体 MAPE 低不代表每个时段都可靠。工业场景里能耗费用最集中的时段恰恰是误差最大的时段所以需要把预测误差按小时分段评估。我的做法是生成一个分小时误差表按hour分组分别计算每个小时的 MAPE 和平均绝对误差。通常会发现午夜时段的绝对误差很小但 MAPE 很高因为基数低下午高峰时段的绝对误差最大对电费预算的影响也最大。如果下午高峰时段的绝对误差超过某个阈值比如 200kW调度员就无法根据预测做设备启停决策这时就要单独对这个时段增加温度特征或者提高排班特征的权重。# 把预测结果和真实值合并成结果表按小时分段评估 eval_result pd.DataFrame({ hour: df_hourly[hour].dt.hour.values, actual: y_val, pred: y_pred_lgb }) hourly_eval eval_result.groupby(hour).apply( lambda g: pd.Series({ mape: mean_absolute_percentage_error(g[actual], g[pred]), mae_kw: (g[actual] - g[pred]).abs().mean() }) ) print(hourly_eval)分段评估能看出模型的误差结构但还不能证明预测结果对业务有用。我习惯再做一步“阈值验证”把预测值按从大到小排序看是否能准确定位每日的负荷尖峰时段。具体来说取每天实际负荷最高的 3 个小时看模型预测的 Top 3 小时中有多少是重合的。如果重合率低于 70%说明模型整体 MAPE 不错但尖峰时段预测能力不足这对设备容量规划来说等于没有用。这个验证脚本用 Python 写很快每天一个分组按预测值取 top 3 再与真实 top 3 求交集统计整个验证周期的平均重合率。除了准确率还要验证稳定性。工业现场最怕的不是模型偶尔不准而是时准时不准——前一天误差 3%后一天误差 20%调度员根本无法建立对系统的信任。我的建议是记录每天的中位数误差做成一个简单控制图算出历史 30 天中位数误差的均值与标准差当某一天的中位数误差超过均值加两倍标准差时触发告警让工程师介入排查当天是否有特殊工况。这样做很朴素但能有效拦截因排程临时变更、设备故障引起的预测漂移。还有一个容易忽略的落地细节预测结果的输出格式。模型本身输出的是 numpy 数组但业务系统需要的是带时间戳的表格最好直接生成 CSV让一线调度人员能直接导入到 Excel 或现有的能源管理平台里。# 导出 24 小时预测结果供调度系统使用 future_hours pd.date_range(start2024-03-01 00:00:00, periods24, freqh) forecast_df pd.DataFrame({time: future_hours, pred_kw: y_pred_24h}) forecast_df.to_csv(forecast_20240301.csv, indexFalse)在那之后我形成了一种习惯每次训练完模型都强制走一遍分小时误差和尖峰重合率验证并把结果截图存到项目文档里。哪怕整体 MAPE 再低这两项不达标也绝不说“模型已经可用”。工业预测的最终评判标准不在指标而在现场的人愿不愿意根据你的预测做调整。拿到这份资源建议你从自己工厂的某一条产线开始跑先用两周数据做一个准实时验证看看模型在你的生产节拍下误差结构是什么样的。希望帮到你。本文还有配套的精品资源点击获取