简介:这份例程面向电力系统负荷预测方向的学习者与研究人员,聚焦支持向量机在短期负荷预测中的落地应用。与直接将影响因素送入模型的常规做法不同,它在训练前对各影响因素进行加权预处理,以突出关键特征、削弱弱相关因素干扰,从而改善预测精度。压缩包共2个文件,约19KB,包含1个m脚本与1个xls数据表:脚本承担数据读取、特征加权、训练集与测试集划分、SVM建模、参数寻优及误差评估等流程,数据表则提供历史负荷及相关影响因素样本,便于直接运行与调试。内容涉及SVM与支持向量回归原理、核函数选择、惩罚参数与核参数调整,以及RMSE、MAE等评价指标,适合作为课程设计、毕业设计或算法入门的实践案例。目前已有1134人学习下载,可帮助读者理解特征加权对预测性能的影响,并掌握从数据预处理到模型评估的完整建模思路。
1. 从一份 SVM 短期负荷预测例程说起:它到底能跑出什么
如果你手头有一份电力系统短期负荷预测的 SVM 例程,第一反应大概率是:这玩意儿到底能不能直接套到我的数据上?我拿到的这份例程,核心就是用支持向量机做未来 24 小时的负荷曲线预测,输入是历史负荷序列加上日期类型、温度这类外生变量,输出是逐点预测值。它解决的不是“发论文级”的创新问题,而是让一个刚接触负荷预测的工程师能在半天内跑通一条完整的基线:数据怎么组织、特征怎么构造、SVM 参数怎么设、误差怎么评估。适合谁?适合做电网调度辅助、微网能量管理、或者课程设计里需要一份能复现的 SVM 回归代码的人。不适合指望它直接上生产环境做超短期滚动预测——那是另一个量级的事。这份例程的价值在于,它把 SVM 做回归时最容易翻车的几个环节——归一化、核函数选择、参数寻优——都摆在了明面上,你照着改就能用。
2. 例程拆解:数据流、特征工程与 SVM 回归的接法
2.1 负荷数据的组织方式与归一化边界
短期负荷预测的原始数据通常是一张时间序列表:时间戳、有功负荷、温度、湿度、日类型(工作日/周末/节假日)。例程里一般会把数据整理成“输入-输出”对的形式,用前 N 天同一时刻的负荷加上当天已知的外生变量,去预测第 N+1 天对应时刻的负荷。这里第一个容易踩的坑是归一化。SVM 对特征尺度极度敏感,负荷数值可能在几百到几千兆瓦之间,温度在零下十几到四十度之间,如果不做归一化,核函数计算出来的距离会被负荷量级完全主导,温度特征等于白给。
常见做法是用最小最大归一化把每个特征压到 [0,1],但要注意:归一化的上下界必须用训练集算出来,然后原封不动地应用到测试集。我见过有人图省事,把全量数据一起归一化再切分,结果测试集的“未来信息”泄漏到了训练阶段,评估指标好看得离谱,一上线就崩。例程里如果写了MinMaxScaler,一定要检查fit和transform的调用顺序。
import numpy as np from sklearn.preprocessing import MinMaxScaler # 假设 raw_data 形状为 (样本数, 特征数),最后一列是负荷 # 按时间顺序切分,前 80% 做训练,后 20% 做测试 split_idx = int(len(raw_data) * 0.8) train_raw = raw_data[:split_idx] test_raw = raw_data[split_idx:] scaler = MinMaxScaler(feature_range=(0, 1)) # 关键:只用训练集拟合归一化器 scaler.fit(train_raw) train_scaled = scaler.transform(train_raw) test_scaled = scaler.transform(test_raw) # 反归一化时,负荷列对应的 min/max 要单独取出来 load_min = scaler.data_min_[-1] load_max = scaler.data_max_[-1]这段代码的逻辑说明:fit只在训练集上做,得到每个特征的最小值和最大值;transform分别应用到训练集和测试集。反归一化时,因为负荷在最后一列,直接取data_min_[-1]和data_max_[-1]就能把预测值还原回原始量纲。参数上唯一需要改的是feature_range,一般保持 (0,1) 即可,如果数据里有极端离群点,可以考虑用 (0.1, 0.9) 留一点余量,但这不是必须的。
2.2 特征构造:把时间戳变成 SVM 能吃的数值
SVM 本身不认识“周一”和“周六”的区别,也不认识“八月”和“十二月”的差异。例程里通常会用独热编码或者周期编码来处理日类型和月份。独热编码简单直接,但维度会膨胀;周期编码用正弦余弦把月份映射到单位圆上,维度低且保留了周期性,更适合负荷预测这种强周期场景。
我一般会构造这几类特征:前一日同一时刻负荷、前两日同一时刻负荷、前七日同一时刻负荷、当日最高温度、当日最低温度、日类型独热码、月份正弦余弦值。前七日负荷这个特征特别重要,因为它捕捉了周周期的模式。如果例程里只用了前一日负荷,预测精度在周末和周一附近会明显掉下来。
import pandas as pd def build_features(df): df = df.copy() # 滞后特征:前 1、2、7 天同一时刻的负荷 df['lag_1'] = df['load'].shift(24) df['lag_2'] = df['load'].shift(48) df['lag_7'] = df['load'].shift(24 * 7) # 日类型独热编码,假设 day_type 取值 0/1/2 df = pd.get_dummies(df, columns=['day_type'], prefix='dt') # 月份周期编码 df['month_sin'] = np.sin(2 * np.pi * df['month'] / 12) df['month_cos'] = np.cos(2 * np.pi * df['month'] / 12) # 丢弃因滞后产生的空值行 df = df.dropna().reset_index(drop=True) return df逻辑说明:shift(24)表示向前推 24 个时间点,如果数据是小时级,正好是一天。get_dummies把日类型展开成多个 0/1 列。月份正弦余弦把 1 到 12 月映射到周期坐标上,十二月和一月在数值上就靠近了。参数上唯一要注意的是shift的步长必须和采样频率匹配,半小时级数据就要用shift(48)。构造完特征后,dropna会丢掉前面若干行,这是正常的,不要试图填充这些行,因为填充出来的滞后特征没有物理意义。
2.3 SVM 回归器的参数设置与核函数选择
例程里用的通常是sklearn.svm.SVR,核心参数有三个:C、gamma、kernel。C是惩罚系数,越大对训练误差容忍度越低,容易过拟合;gamma控制核函数的影响范围,越大影响越局部,也容易过拟合。负荷预测这种任务,数据噪声不小,C一般从 1 到 100 之间试,gamma从 0.01 到 1 之间试。核函数首选 RBF,因为负荷和特征之间的关系是非线性的,线性核欠拟合会很明显。
参数寻优不要用网格搜索硬扫,太慢。我一般用RandomizedSearchCV配合时间序列交叉验证。注意不能用普通的 KFold,因为负荷数据有时间顺序,随机切分会导致未来数据泄漏到训练集。TimeSeriesSplit是更稳妥的选择。
from sklearn.svm import SVR from sklearn.model_selection import RandomizedSearchCV, TimeSeriesSplit from scipy.stats import uniform param_dist = { 'C': uniform(1, 100), 'gamma': uniform(0.01, 1), 'epsilon': uniform(0.01, 0.5) } tscv = TimeSeriesSplit(n_splits=5) svr = SVR(kernel='rbf') search = RandomizedSearchCV( svr, param_dist, n_iter=50, cv=tscv, scoring='neg_mean_absolute_error', random_state=42, n_jobs=-1 ) search.fit(X_train, y_train) best_svr = search.best_estimator_逻辑说明:uniform(1, 100)表示在 1 到 101 之间均匀采样,n_iter=50表示随机试 50 组参数。TimeSeriesSplit把数据按时间顺序切成 5 折,每折用前面的数据训练、后面的数据验证,模拟真实预测场景。scoring用负平均绝对误差,因为负荷预测里 MAE 比 MSE 更直观。n_jobs=-1用满所有 CPU 核。这套下来,一般几分钟能跑完,比网格搜索快一个数量级。
3. 从训练到评估:把预测结果落成可看的曲线和指标
3.1 训练流程的完整串联
把前面几步串起来,整个流程就是:读数据、构造特征、切分训练测试、归一化、随机搜索调参、用最优参数在训练集上重新拟合、在测试集上预测、反归一化、算指标、画曲线。这里有一个容易忽略的点:随机搜索时内部已经做了交叉验证,但最终模型还是要在全部训练数据上重新fit一次,否则你只用了部分数据训练。RandomizedSearchCV的best_estimator_默认已经在全部训练数据上拟合过了,直接拿来预测即可,不需要再手动fit。
# 用最优模型预测 y_pred_scaled = best_svr.predict(X_test) # 反归一化,注意 y 的归一化参数要和特征分开处理 y_pred = y_pred_scaled * (load_max - load_min) + load_min y_true = y_test_scaled * (load_max - load_min) + load_min from sklearn.metrics import mean_absolute_error, mean_squared_error mae = mean_absolute_error(y_true, y_pred) rmse = np.sqrt(mean_squared_error(y_true, y_pred)) print(f'MAE: {mae:.2f} MW, RMSE: {rmse:.2f} MW')逻辑说明:如果训练时把负荷也当作一个特征一起归一化了,那么预测出来的值也是归一化后的,需要单独用负荷列的 min/max 还原。MAE反映平均偏差,RMSE对大误差更敏感。电力系统里通常还会看 MAPE,但负荷接近零时 MAPE 会爆炸,所以例程里用 MAE 和 RMSE 更稳妥。
3.2 评估指标的选择与曲线诊断
MAE 和 RMSE 是标量,只能告诉你“平均差多少”,看不出误差在时间上的分布。我习惯把真实曲线和预测曲线画在一起,再画一张残差图。如果残差在某个时间段系统性偏正或偏负,说明特征里缺少那个时间段的模式信息。比如每天傍晚高峰时段残差一直偏正,说明模型低估了高峰,可能需要加入“是否高峰时段”的标记特征。
import matplotlib.pyplot as plt fig, axes = plt.subplots(2, 1, figsize=(12, 8)) axes[0].plot(y_true, label='Actual', linewidth=1) axes[0].plot(y_pred, label='Predicted', linewidth=1, linestyle='--') axes[0].legend() axes[0].set_ylabel('Load (MW)') residual = y_true - y_pred axes[1].plot(residual, color='gray', linewidth=0.8) axes[1].axhline(0, color='red', linestyle=':') axes[1].set_ylabel('Residual (MW)') plt.tight_layout() plt.show()逻辑说明:上图看整体拟合,下图看残差是否围绕零波动。如果残差有明显的周期pattern,说明模型没学到那个周期。参数上figsize按需调,linewidth调细一点避免曲线糊在一起。这张图比任何数字指标都直观,建议每次调完参数都看一眼。
3.3 预测结果的后处理与业务约束
SVM 输出的是连续值,但实际负荷有物理约束:不能为负,相邻时刻的变化率不会突变。例程里如果直接输出原始预测值,偶尔会出现负值或者尖刺。常见做法是加一个简单的后处理:把负值截断到零,再用滑动平均平滑一下。但平滑窗口不能太大,否则会把真实的负荷爬坡也抹掉。我一般用 3 点滑动平均,只对预测值做,真实值不动。
# 截断负值 y_pred = np.clip(y_pred, 0, None) # 3 点滑动平均 y_pred_smooth = np.convolve(y_pred, np.ones(3)/3, mode='same') # 注意边界处理,两端用原始值替代 y_pred_smooth[0] = y_pred[0] y_pred_smooth[-1] = y_pred[-1]逻辑说明:np.clip把负值压到零,np.convolve做滑动平均,mode='same'保证输出长度不变。边界两个点因为卷积核覆盖不全,直接用原始预测值替代。这一步不是必须的,但如果你的业务方看到负负荷会质疑模型,加上它省去很多解释成本。
4. 避坑与排查:SVM 负荷预测里最容易翻车的五个地方
4.1 现象:测试集 MAE 远小于训练集 MAE
原因:归一化时用了全量数据,测试集信息泄漏到了训练阶段。或者切分数据时没有按时间顺序,随机打乱后未来数据混入了训练集。
解决:检查fit和transform的调用对象,确保归一化器只见过训练数据。切分时用时间顺序切分,不要用train_test_split的默认随机打乱。
4.2 现象:预测曲线整体平移,形状对但数值偏高或偏低
原因:反归一化时用错了 min/max。如果训练时对负荷列单独做了归一化,预测时却用了全局的 min/max,就会产生系统性偏移。
解决:把负荷列的data_min_和data_max_单独存下来,反归一化时只用这两个值。如果特征和标签是分开归一化的,更要确认对应关系。
4.3 现象:模型训练极慢,跑一次要几十分钟
原因:C和gamma的搜索范围设得太大,或者n_iter设了几百。SVM 的训练复杂度对样本数很敏感,样本超过一万条时 RBF 核会明显变慢。
解决:先把n_iter降到 30 左右,观察最优参数落在哪个区间,再在那个区间附近细化搜索。如果样本量确实大,考虑先用LinearSVR快速筛特征,再用 RBF 核精调。
4.4 现象:周末和节假日的预测误差明显大于工作日
原因:日类型特征没有构造好,或者训练数据里周末样本太少,模型没学到周末的模式。
解决:检查独热编码是否覆盖了所有日类型,确认周末样本在训练集里的比例。如果周末样本确实少,可以考虑对周末样本做过采样,或者单独训练一个周末模型。
4.5 现象:换了新数据后预测完全失效
原因:新数据的量纲和训练数据不一致,比如负荷整体上了一个台阶,但归一化参数还是旧的。
解决:归一化参数要随数据更新。如果业务场景里负荷水平会缓慢漂移,建议用滚动窗口重新拟合归一化器,或者改用对量纲不敏感的标准化方法。但标准化同样要用训练集的均值和方差,不能全量算。
5. 进阶技巧:用滚动预测和误差反馈把例程变成能用的工具
例程跑通之后,下一步是把它变成能持续用的东西。最直接的进阶是滚动预测:每次预测未来 24 小时,等真实值出来之后,把最新的真实数据追加到历史序列里,重新构造特征,再预测下一个 24 小时。这样模型能跟上负荷的缓慢变化。实现上不需要每次重新训练 SVM,只需要更新输入特征,用已经训练好的模型推理即可。但如果误差持续偏大,就要触发重新训练。
def rolling_forecast(model, scaler, history_df, horizon=24): predictions = [] current_df = history_df.copy() for step in range(horizon): features = build_features(current_df) last_row = features.iloc[[-1]].drop(columns=['load']) last_scaled = scaler.transform(last_row) pred_scaled = model.predict(last_scaled)[0] pred = pred_scaled * (load_max - load_min) + load_min predictions.append(pred) # 把预测值当作已知负荷追加,继续预测下一时刻 new_row = current_df.iloc[[-1]].copy() new_row['load'] = pred current_df = pd.concat([current_df, new_row], ignore_index=True) return predictions逻辑说明:这个函数每次只预测一个点,然后把预测值填回历史序列,继续预测下一个点。build_features会重新计算滞后特征,所以预测值会被当作真实值使用。这种做法在短期预测里叫“递归预测”,优点是简单,缺点是误差会累积。如果发现预测曲线越往后越飘,可以改成“直接预测”,即一次性输出 24 个值,每个值都用真实历史特征,不依赖前面的预测值。
另一个进阶方向是误差反馈:记录每次预测的残差,如果连续几个周期残差同号,说明模型有系统性偏差,可以给预测值加一个修正项。修正项用最近 N 个周期的残差均值,N 一般取 7 到 14。这个技巧在负荷预测里很实用,能把 MAE 再压下去几个百分点。
还有一个容易被忽略的点是特征重要性的粗筛。SVM 本身不输出特征重要性,但可以用排列重要性或者先用一个线性模型快速看哪些特征系数大。把明显无关的特征去掉,SVM 的训练速度和泛化能力都会改善。我一般会保留滞后负荷、温度、日类型这三类,其他特征先加进去试,如果排列重要性接近零就删掉。
从那以后我每次拿到一份新的负荷预测例程,都会先检查归一化的 fit/transform 顺序,再确认切分是不是按时间来的,最后才看参数。这三步走完,基本能避开八成以上的翻车场景。希望帮到你。
本文还有配套的精品资源,点击获取