简介一份基于CatBoost算法的电力短期负荷预测研究文档面向电力系统调度、负荷预测及相关机器学习方向的研究者与工程师。内容系统梳理了短期负荷预测的核心挑战对比时间序列法、人工神经网络、专家系统、灰色数据理论等传统方法的不足指出其超参数调优困难、标签信息偏移、对计算机性能要求过高等局限并重点介绍CatBoost作为GBDT框架机器学习库的结构特点、类别特征识别优势以及对超参数依赖度低的优点。文档还给出梯度提升算法的数学推导以及利用CatBoost进行负荷预测的实验验证过程有助于读者理解从历史负荷与气象数据处理、日期类型特征利用到模型构建与误差分析的整体思路。资源为单个docx文件压缩包约276KB版式清晰便于查阅。已有114人学习该资源适合需要快速获取CatBoost预测方案、开展短期负荷预测实验或撰写相关论文的电力与数据挖掘从业者参考。1. 电力短期负荷预测为什么值得用 CatBoost中小样本表格回归的稳妥选择做电力短期负荷预测我和不少同行一样最早从随机森林和 LSTM 入手后来才把 CatBoost 放进候选并在连续几轮滚动实验里固定成主力模型。原因不复杂负荷预测本质是带日历和气象特征的表格回归样本量在几万到几十万之间CatBoost 在中小样本上很少翻车不需要标准化特征还自带类别特征编码。这篇文章就顺着这条路线把数据整理、参数设置、验证切分和踩坑记录拆开讲适合正在做短期负荷预测研究或者想把机器学习算法换进现有流程的工程师参考。2. CatBoost 在负荷预测里站住脚的三个理由有序提升、对称树与自动类别特征把 CatBoost 和其他机器学习算法放在一起对比之前我先说结论它对表格型特征的处理方式正好踩在负荷预测的需求点上。下面三个设计是我在实际实验里能明显感受到差异的地方。2.1 有序提升为什么负荷数据里的异常点没把模型带偏普通梯度提升每轮用同一批样本计算梯度并更新模型当样本量不大、噪声又强的时候模型容易记住训练集里的异常波动。负荷数据的异常点很多一次突发降温、一个工厂临时停产都会让当天负荷明显偏离历史规律。CatBoost 的有序提升Ordered Boosting改变了梯度的计算方式——每个样本的梯度只由它之前的样本得到避免模型把当前样本的标签信息提前泄漏到特征里这个设计让训练过程对噪声样本更抗噪。我在实验里观察到的一个直接表现是同样的特征和迭代次数XGBoost 在验证集上偶尔会出现某个时刻的预测被前几天的一个尖峰带歪CatBoost 很少出现这种情况。它对短时异常点的响应更温和代价是训练时间比 LightGBM 略长但对负荷预测这个量级的数据来说多出的时间完全可以接受。顺带说一个我踩过的坑有序提升不等于不会过拟合。迭代次数拉高到几千之后验证曲线照样会翘尾真正有用的是把 early_stopping_rounds 用起来让它在验证误差停止下降时自动停下来。后面第 5 章会专门展开这一点这里先记住结论。2.2 对称树与自动目标编码少做一半特征工程CatBoost 的基学习器是对称树oblivious tree同一层所有节点使用同一个分裂特征树的形状更规整。相比普通决策树对称树单棵表达能力弱一些但通过 boosting 叠加后不容易过拟合推理时还能做向量化加速。对负荷预测来说这意味着模型不太依赖特别深的树结构迭代次数和深度可以控制在一个比较小的范围。更实用的部分在类别特征处理。星期几、小时、节假日、地区编号这些在负荷预测里最常见的离散变量CatBoost 可以直接以字符串形式传入它会用 ordered target statistics 做编码把类别出现频率和对应目标值结合起来不产生像 One-Hot 那样的维度爆炸。我一般会把星期几、小时、是否节假日这三列直接标成 category 类型其余数值特征保持 float。这里有个很容易被忽略的细节ordered target statistics 同样依赖样本排序所以训练时 CatBoost 会对类别特征自动引入随机排列。如果你发现同一份数据每次训练结果有小幅波动这是正常现象不是因为代码写错要复现就固定 random_seed。具体到代码训练时不需要单独编码只要在构造 CatBoostRegressor 时传入 cat_features 参数指定哪些列是类别特征即可。这个参数接受的是列索引或列名列表顺序必须和特征表里的位置一致。我在项目里习惯把星期几、小时、节假日三个字段提前拼到特征表最前面再用一个列表统一指定避免索引错位。2.3 和随机森林回归算法、深度学习算法对比什么时候选 CatBoost选型阶段我做过一组对比把随机森林回归算法、XGBoost、LSTM 和 CatBoost 放在同一份负荷数据上跑。它们不是同一个量级的工具——LSTM 是深度学习算法更吃数据和调参随机森林适合做基线XGBoost 与 CatBoost 才是直接竞品。我用一张表记录当时的判断对比维度随机森林回归算法XGBoostLSTM深度学习算法CatBoost是否需要特征标准化不需要不需要需要不需要类别特征支持需手动编码需手动编码需手动编码原生支持中小样本几万行可靠但精度一般中上易过拟合容易欠拟合不容易过拟合精度较高可解释性高中低中高训练速度快快慢中这张表对应的是我常用的数据量大约三万行日负荷记录。当样本量超过二十万并且特征里含大量时序窗口时我可能会考虑换成 LightGBM但在短期负荷预测这个场景几万到十几万的样本量加上日历特征CatBoost 是省事且可靠的选择。如果你已经在用深度学习算法做负荷序列建模也不一定要替换。我的建议是把 CatBoost 当成新需求的默认候选尤其是预测结果需要向调度人员解释、特征里含有明显的离散业务变量时。下面这段是当时对比实验的骨架代码用同一特征集跑 5 折训练只比较验证集 RMSEfrom catboost import CatBoostRegressor from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import TimeSeriesSplit # X 是特征表y 是真实负荷按时间排序 tscv TimeSeriesSplit(n_splits5) for train_idx, valid_idx in tscv.split(X): X_train, X_valid X.iloc[train_idx], X.iloc[valid_idx] y_train, y_valid y.iloc[train_idx], y.iloc[valid_idx] # 两次训练都用同一份数据只有模型不同 cb CatBoostRegressor(iterations500, verbose0) rf RandomForestRegressor(n_estimators200)这段代码只搭了框架关键点是 TimeSeriesSplit 保证验证集永远在训练集之后不会把未来信息带进训练RF 和 CatBoost 都不需要归一化省去了 pipelines 里的标准化步骤。实际跑的时候我会把两个模型的 RMSE 打出来对比再按星期几和小时分组看误差来源。另外越是做研究性质的项目越要把对比实验的随机种子固定。CatBoost 的 random_seed 默认会随机生成不固定的话同一份数据两次训练的结果会有细微差别写论文或做技术报告时容易被人追问。实际对比结果在我的数据上是 CatBoost 的验证 RMSE 比随机森林低 6% 左右但更重要的是高温日的误差分布更集中这个现象后面第 5 章会解释。3. 把历史负荷整理成 CatBoost 能直接吃的样本特征表与预处理代码模型选型完成后真正决定精度的往往不是算法而是特征表。短期负荷预测里时间、气温、历史负荷三件事必须同时出现在特征里缺一个都会让误差变大。下面是我通常使用的特征工程流程。3.1 最小可用数据表时间戳、负荷、气温与日历字段我习惯把原始表清理成四列起步time时间戳、load负荷、temp气温、holiday是否节假日。如果是多地区预测再加一列 area_id。时间戳要精确到小时负荷和气温的单位保持统一节假日字段先记 0/1后面再细化成节前节后。最小特征表的构建代码如下import pandas as pd df pd.read_csv(load_history.csv, parse_dates[time]) df df.sort_values(time).reset_index(dropTrue) # 基础日历特征直接从时间戳提取 df[hour] df[time].dt.hour df[weekday] df[time].dt.weekday df[month] df[time].dt.month # 节假日特征外部维护一个节假日列表这里只做标记 holiday_dates set(pd.to_datetime([2024-02-10, 2024-10-01])) df[holiday] df[time].dt.date.isin([d.date() for d in holiday_dates]).astype(int) # 气温特征短期预测一般用日最高最低温也可以直接用工点温度 df[temp_high] df.groupby(df[time].dt.date)[temp].transform(max) df[temp_low] df.groupby(df[time].dt.date)[temp].transform(min)基础变量的选取只有一条原则预测时能拿到的信息才能进特征。hour 和 weekday 来自日历安全holiday 需要提前维护法定节假日表temp_high 和 temp_low 在训练时用实测值上线预测时则要换成气象台的预报值这两列在训练和上线时口径必须一致。如果一个地区空调负荷占比高temp_high 的重要性会排到前面这是后面看特征重要性时可以验证的。3.2 滑动平均滤波算法做平滑还是保留原始尖峰负荷序列天然有噪声很多初学做预测的人第一反应是用滑动平均滤波算法把曲线磨平再建模。我做过这个尝试结论是不要直接对目标列做滑动平均。原因很简单评估的时候你要用真实负荷做对比模型学的是一个被平滑过的目标预测结果天然滞后RMSE 看着低了实际追不上真实曲线。正确的做法是把滑动平均放在特征侧。滞后特征、滑动平均特征、滑动标准差特征都能帮助模型理解最近的负荷水平但目标值始终保持原始负荷。我常用的窗口是 24 小时一天、168 小时一周代码如下# 历史负荷滞后特征t-1、t-24、t-168 分别代表上小时、昨天同时刻、上周同时刻 for lag in [1, 24, 168]: df[fload_lag_{lag}] df[load].shift(lag) # 滑动平均特征只统计预测时刻之前的数据窗口内不含未来 df[load_ma_24] df[load].shift(1).rolling(24).mean() df[load_ma_168] df[load].shift(1).rolling(168).mean() # 滑动的波峰信息过去 24 小时的最高负荷用于午间尖峰判断 df[load_max_24] df[load].shift(1).rolling(24).max()这里的 shift(1) 是关键。滚动窗口从 shift(1) 开始而不是直接用 rolling是为了保证特征里没有任何未来信息。load_lag_168 对七天为周期的负荷曲线特别重要因为工作日效应会带来周期性的重复模式。窗口大小可以按地区调整工业占比高的地方168 小时窗口更稳定商业和居民负荷占比高的地方24 小时窗口更敏感。加入这些特征后模型会对最近一天的走势更加敏感但前提是保留原始 load 作为目标列预测出来的曲线才不会被平滑掉。注意滚动窗口里的任何特征都不能包含未来信息否则训练指标会虚高第 5 章会专门说这个泄漏点。3.3 节假日与气温特征不加这两组上限低一截如果只做最小特征表模型能跑通但节假日的预测基本不可用。春节、国庆负荷会降到正常工作日的 60% 到 80%靠 weekday 完全无法区分这些特殊日期。我维护一张带节前节后标记的日期表比单纯 0/1 更有效。逻辑是节前两天的工厂陆续停工负荷开始下滑节中三天处于低谷节后三天恢复。用三列 one-hot 特征表达这段过渡过程。气温特征的坑在“当天最高温”什么时候拿到。训练时可以用实测值但预测时如果气象预报只给了逐小时温度最高温需要从预报序列里取。我一般把气温拆成三列预测点温度、日最高温、日最低温全部来自同一份预报源避免训练和上线特征分布不一致。这也是第 5 章会再次提到的一个泄漏点。下面是一段节假日细化特征的代码import numpy as np # 核心思想把连续日期映射成阶段而不是只标记 0/1 def mark_holiday_period(dates, holiday_center): # 简易示例节前2天-1节日当天0节后2天1其余2 delta (dates - holiday_center).days return np.where( delta -2, 2, np.where(delta 2, delta, 2) )这个示例把日期差值映射成阶段-2 到 2 分别代表节前到节后的不同状态其余日期统一为普通日。实际项目里我会为春节单独维护一张表因为它的公历日期每年都变程序化推算容易出错元旦、劳动节、国庆则可以用固定日期硬编码按当地法定节假日替换即可。气温特征同理“今日是否高温”不要只看单点温度要看 14 点到 17 点的平均温度这个时段才是午尖峰的真正成因。4. 用 CatBoost 训练电力短期负荷预测模型一套可复现的 Python 代码特征表准备好之后训练本身并不复杂。CatBoost 的 Python 接口把大部分细节封装好了我实际项目中写的主训练代码只做四件事切分数据、初始化模型、拟合、看特征重要性。下面按顺序拆开。4.1 按时间切分训练集和验证集别随机打散时序预测里最常犯的错误是用 KFold 随机切分把未来样本混进训练集。负荷数据有连续性和周期性随机打散后模型看到的是“未来的相似片段”验证分数虚高上线后立刻现形。我固定用时间顺序切分前 80% 训练、后 20% 验证如果数据跨度超过两年验证集里必须包含一个完整的冬夏周期。split_date df[time].quantile(0.8) train df[df[time] split_date].copy() valid df[df[time] split_date].copy() # 类别特征列提前把离散变量变成字符串 cat_cols [hour, weekday, holiday] for col in cat_cols: train[col] train[col].astype(str) valid[col] valid[col].astype(str) feature_cols [c for c in train.columns if c not in [time, load]] X_train, y_train train[feature_cols], train[load] X_valid, y_valid valid[feature_cols], valid[load]这里没有用 TimeSeriesSplit 做交叉验证是因为最终评估只需要一个贴近真实部署的验证集。时间切分的阈值用 quantile(0.8) 是为了避免手写日期的麻烦但如果数据里有明显的季节性缺口要手动指定日期。类别特征转成 str 是 CatBoost 的标准用法不转的话它会把 int 当成数值特征处理。4.2 五个必调参数iterations、learning_rate、depth、l2_leaf_reg、loss_functionCatBoost 的默认参数能跑通但想拿到能用的精度下面五个参数至少要过一遍。网上很多教程一上来就让你跑网格搜索或者粒子群算法我的经验是先用默认参数跑 300 轮看验证误差曲线再按下面的顺序调参数我的常用范围作用调整方向iterations500~3000提升轮数配合早停不用手动纠结learning_rate0.03~0.1每轮步长越小越安全但要增加迭代轮数depth6~10对称树深度负荷特征 10 列左右8 够用l2_leaf_reg3~10叶子权重 L2 正则过拟合时往上加loss_functionRMSE 或 Quantile:alpha0.7损失函数高温尖峰明显时换分位数损失from catboost import CatBoostRegressor model CatBoostRegressor( iterations1000, learning_rate0.05, depth8, l2_leaf_reg5, loss_functionRMSE, eval_metricRMSE, random_seed42, early_stopping_rounds100, verbose50 ) model.fit( X_train, y_train, eval_set(X_valid, y_valid), cat_featurescat_cols, use_best_modelTrue )early_stopping_rounds 设 100意思是验证误差连续 100 轮不下降就提前终止use_best_modelTrue 保证保存最佳迭代的模型而不是最后一轮。loss_function 默认 RMSE适合大多数情况如果高温日尖峰总是被低估可以改成 Quantile:alpha0.7让模型在分位数上做回归。粒子群算法这类自动调参方法我一般不用它适合参数空间大、数据量大的场景负荷预测的特征和参数空间用人工调已经够快。如果你用的是 GPU 训练iterations 可以适当加大因为 CatBoost 在 GPU 上的对称树加速明显。训练完成后我习惯先把验证集上的整体 RMSE 和 MAPE 打出来作为后续所有改进的基线。RMSE 对大幅偏差更敏感MAPE 适合向业务方解释平均误差百分比如果 MAPE 超过 5%先回头检查特征而不是急着调参数。4.3 用特征重要性看模型到底学了什么训练结束后不要只看验证分数要先跑一遍特征重要性。CatBoost 返回的 feature importance 反映每个特征对预测的平均贡献能帮你快速发现特征工程里的遗漏。正常情况下降序前几名应该是 load_lag_24、load_lag_168、temp_high、hour 这几类。如果某个滞后特征占比超过 60%后面大概率会有滞后问题对应第 5 章的避坑记录。from catboost import Pool importance model.get_feature_importance(Pool(X_valid, y_valid)) for name, score in zip(feature_cols, importance): print(f{name}: {score:.2f})特征重要性只能告诉你“哪个特征重要”不能告诉你“为什么重要”所以最好配合 4.1 的验证集做交叉确认把模型的预测误差按小时和星期几分组看模型是在哪些时段分心。这一步不是可选步骤我在项目里把它当成模型验收的一部分。如果你想进一步压缩误差可以把模型输出结果保存成 CSV然后分析哪些日期的误差最大。最常见的结果是节假日和极端气温日霸榜这会直接引导你回到第 3 章补充特征。这种迭代路径比盲目调参有效得多。5. 用 CatBoost 做短期负荷预测的避坑记录5 个高频翻车现场下面五条都是我在负荷预测项目里实际踩过、并且不止一次踩过的坑。每一条按现象、原因、解决来写你可以直接对照自己的验证误差曲线判断中了哪一条。5.1 预测曲线整体滞后一天看着误差不大实际已经失效现象把预测值和真实负荷画在同一张图上模型的曲线和真实曲线形状几乎一样就是整体往后拖了一两个小时。RMSE 可能只有几十兆瓦但调度没法用这种预测因为峰时位置全部错了。原因滞后特征load_lag_1、load_lag_24权重太高模型学到的是“昨天的这个时刻大概是多少今天也差不多”。短期负荷虽然强周期但它不是严格重复单靠历史负荷无法预判今天的气温冲击和负荷爬坡速度。解决第一加入当天的温度预报特征让模型能根据气温修正趋势第二把滞后特征从“昨天同时刻”扩展成“昨天同一时刻前后一小时的平均值”减轻单点噪声第三评估指标除了 RMSE一定要加一个峰时误差记录每天实际峰时和预测峰时的偏差。峰时偏了半小时比偏 3% 的负荷更严重。5.2 训练误差极低、验证误差偏高迭代次数和正则没配对现象训练集误差接近 0验证集误差明显偏高验证误差曲线随迭代次数先降后升形成 U 形。原因iterations 太高、learning_rate 太大或者滞后特征窗口加得太多模型把训练集里的历史噪声背了下来。有序提升能减缓这个问题但减缓不等于免疫。解决先开 early_stopping_rounds100 并打开 use_best_model再调大 l2_leaf_reg。我常用的一组起始值是 learning_rate0.05、iterations1000、l2_leaf_reg5如果验证误差曲线还在翘尾把 l2_leaf_reg 加到 10学习率降到 0.03。另一个技巧是减少滞后特征数量保留 load_lag_1、load_lag_24、load_lag_168 三列足够加太多滑窗只会让模型更依赖历史模式。5.3 节假日预测被当成普通工作日星期特征救不了法定假日现象春节、国庆这几天模型预测负荷明显偏高真实负荷掉到平时的 60%模型还按工作日水平输出。误差曲线在这些日期上出现孤立尖峰。原因weekday 只有 0 到 6无法表达“2024 年 2 月 10 日是春节”这种不规则事件。模型在训练集里见过的工作日模式里没有哪一天和春节相似。解决把 holiday 特征细化成节前、节中、节后三段。我在 3.3 里维护了一张节假日阶段表遇到春节、国庆这类长假期按日期范围生成三列特征。如果历史数据还不够长至少要保证验证集里出现过节假日样本否则模型上线遇到节假日会直接翻车。5.4 用当天的真实温度训练预测时却只有预报温度现象训练阶段验证 RMSE 很漂亮模型上线后误差明显变大而且不是季节性变化能解释的。原因训练时用了当天的实测温度或实测最高温预测时气象接口只给未来预报值。训练和预测的特征分布不一致这是典型的特征泄漏。更隐蔽的版本是用“预测日当天的实测最高温”做特征——训练时这个数值已知预测时当天还没过完拿不到。温度特征必须统一用预报值。解决把训练特征对齐到预测时点的信息边界。温度统一用预报源并且在训练阶段就模拟预报误差比如给历史温度加上小幅随机噪声。负荷类特征统一用 shift(1) 保证只看过去。时刻、星期、月份这类日历特征不会泄漏可以放心使用。5.5 高温日午间尖峰被系统性低估MSE 损失对尖峰不敏感现象普通日误差正常夏季 35 度以上的高温日午间 13 点到 16 点的真实负荷明显高于模型输出误差集中在那几个小时。原因RMSE 或 MSE 是均值回归损失模型会优先拟合大多数普通日样本极端尖峰在样本总量里占的比例小对整体损失的贡献被摊薄。解决把损失函数从 RMSE 换成 Quantile:alpha0.7让模型在 0.7 分位数上拟合预测结果会整体偏高但在高温尖峰日的表现更接近实际。如果这个策略让普通日预测偏高太多可以按温度做模型切换超过 33 度具体阈值按当地负荷特性标定时用分位数模型其余用 RMSE 模型。这种规则在上线阶段不难实现对调度业务也很友好。6. 短期负荷预测上线前我用三个对账检查代替误差指标滞后、峰时与场景切片验证集的 RMSE 和 MAPE 只能说明平均偏差不能说明预测能不能用。我一般会在模型上线前加三个对账检查把预测误差拆开看。第一个是滞后检查把逐时预测误差和前一天同时刻的负荷变化做对照如果误差在负荷快速上升时段系统性为负、在快速下降时段系统性为正说明预测存在滞后。第二个是峰谷时刻检查把验证集的误差按小时聚合成 24 个平均值峰时段的平均误差如果明显偏离零就要回到特征表确认温度预报特征是否到位。第三个是场景切片按晴天、雨天、高温日、节假日分别统计误差模型短板会在切片里暴露出来。下面这段代码是第二个检查的简化实现valid[err] valid[load] - model.predict(X_valid) hour_err valid.groupby(hour)[err].mean() # 结果为负的时段说明预测偏低持续偏高要警惕滞后我把这段分析写进每周的预测复盘脚本里比单看 RMSE 更能发现问题。经验是RMSE 很难分出 0.1% 的差距但小时误差曲线很容易暴露峰谷系统性偏移。这个习惯帮我挡掉过好几次“整体指标漂亮、峰时全偏”的模型上线。希望帮到你。本文还有配套的精品资源点击获取