简介:面向电力系统负荷预测场景,这份MATLAB资源基于支持向量机(SVM)与支持向量回归(SVR)实现电网负荷预测,代码完整、附有数据与注释,适合本科及以上学历的研究者、电力从业者进行算法复现或扩展改造。资源包共96个文件,约5.36MB,以m源码为主,另有c/cpp核心接口、mexw64/mexw32编译文件、mat数据集、txt说明、pdf教程及xls竞赛负荷样本等,便于在MATLAB中直接运行并理解SVM建模、参数寻优与回归预测流程。内容覆盖SVMcgForRegress参数寻优、SVR回归、PCA预处理、FASTICA及libsvm接口调用等模块,并收录SVM分类与回归模板、LibSVM程序代码注释PDF。已有233人学习下载,尤其适合需要在现有框架上做创新改进的读者;下载后可按目录结构快速定位主程序、工具函数与数据文件,参照注释即可完成从数据载入、模型训练到预测评估的完整流程,是一套可直接落地使用的电网负荷预测参考实现。
1. 用SVM给电网负荷做预测:为什么这个二十年前的老模型仍然值得跑一遍
电网调度值班员明早需要一条未来24小时的负荷曲线,摆在面前的选择很多:LSTM、XGBoost、Prophet,而我一般先选SVM。基于支持向量机SVM的电网负荷预测,核心是用SVR(支持向量回归)把历史负荷、温度、日期类型映射到未来负荷上。项目标题自带“代码完整、数据齐全”,意味着不需要自备数据就能完整跑通一版可用基线,这本笔记就把这条链路拆开讲透。
当样本只有几千到几万条、没有GPU、交期又紧时,调好参数的SVR在精度上完全能和大模型掰手腕,而且训练快、可解释、好排查。适合三类人:刚转电力数据方向想跑通全流程的工程师;需要一个可信基线的算法岗;做毕业设计需要快速复现、扩展的学生。读完你会知道为什么SVM在这个任务上没被淘汰、一段能用的SVR代码怎么拆、以及哪些数据坑会让模型输出“指数平滑都不如”的结果。
2. 先把原理讲透:SVM如何从“分两类”变成“回归一条负荷曲线”
2.1 从最大间隔到不敏感损失:三个数学直觉,足够干活
表面上看,支持向量机SVM的官方设定是分类器:找一个超平面把正负样本分开,同时让间隔最大化。回归版本的SVR做的事情稍微绕一点,它不是在“拟合所有点”,而是允许预测值落在真实值附近的一个不敏感带内。这个带子的宽度由超参数epsilon定义,凡是误差绝对值小于等于epsilon的点,损失算0;超出带子的点,按超出量产生线性损失,并由C控制惩罚强度。
换句话说,SVR只对“错得比较严重”的样本较真,所以模型更平滑,不容易被个别异常负荷拉偏。理解这一点之后,调参数就不玄学了:C越大,越不能容忍离群误差;epsilon越大,误差带越宽,拟合曲线越平滑,但预测偏差也可能变大。我在前面的项目里经常遇到新手把epsilon设成0.1,结果模型几乎只会输出平均负荷,峰谷全被削平,后面第5章会专门讲这个坑。
再回到“支持向量”这个词。无论是分类还是回归,最终模型只由落在间隔边界上、或超出误差带的少数样本决定。电力负荷数据里,这些向量往往是早高峰、晚高峰和极端天气日的样本。正因为模型只依赖少数关键点,SVM训练快、泛化记录好,在小样本上特别有优势——这也是它二十年了还在工业界存活的原因。
2.2 核函数与样本量的关系:为什么RBF是电力负荷的默认选择
线性核相当于在原空间做回归,多项式核能捕捉简单非线性,RBF(高斯径向基核)对应一个无限维映射,是四者里表达力最强的。负荷和温度的关系并不单调:空调负荷在高温侧飙升,电采暖在低温侧增加,中间温度段相对平缓。这种U型、倒U型关系用线性核很难刻画,而RBF天然能处理。对比下来我的选择习惯是:
- 线性核:样本量极大、特征维度极高时用来打底;
- RBF核:负荷预测这种中小样本、强非线性的回归任务,默认首选;
- 多项式核:很少用,阶数一高就容易在边界处震荡;
- sigmoid核:本质类似带隐层的神经网络,调参难度更大,不推荐。
在optdigits手写数字分类那类小样本任务里,研究者对比过核函数与参数对精度的影响,结论是核函数的选择和参数组合直接决定结果上界。电网负荷预测也是同一类样本量有限的任务:同样一份5000点数据,线性核与RBF的MAPE能差出2到3个百分点。所以不用纠结,起步阶段固定kernel="rbf",把精力留给C、epsilon和gamma。
2.3 为什么不用LSTM或ARIMA:SVR的价值由场景决定
从实战角度看,三者并不互斥。ARIMA擅长描述线性时间序列,但对节假日、温度突变无能为力;LSTM理论上能端到端拟合,却需要足够多样本、耗GPU、超参数也多;SVR夹在中间:它对特征工程要求高,但特征到位后训练速度快、结果可复现、可解释性强。
| 模型 | 样本量需求 | 训练时间 | 是否需GPU | 超参数 | 可解释性 |
|---|---|---|---|---|---|
| SVR (RBF) | 千级到十万级 | 分钟级 | 否 | C、epsilon、gamma | 中 |
| ARIMA | 百级到万级 | 秒级 | 否 | p、d、q | 高 |
| LSTM | 数万级起步 | 小时级 | 是 | 层数、单元数、dropout | 低 |
如果你手里是12个月左右的15分钟级数据(约3.5万个样本)或更小,SVR性价比明显更高;如果手里有三年以上的小时级历史,而且温度、气象、节假日字段齐全,再上LSTM也不迟。多数团队真正缺的不是更贵的模型,而是可控的基线和能解释的误差。SVR就是那个“先跑通、再决定要不要升级”的锚点。
3. 数据准备:负荷预测里真正决定上限的环节
3.1 一份“齐全”的负荷数据应该长什么样
标题里的“数据齐全”,我理解为至少包含三组字段:负荷目标值(有功功率,单位一般是MW或kW)、日期时间(连续、无跳变)、外部影响因素(温度、湿度、日期类型、节假日标记、是否有极端天气)。粒度可以是15分钟或1小时,时间跨度至少要覆盖12个月,否则季节周期性学不完整。
拿到原始数据先做三件检查。第一,检查重复时间戳,同一时刻出现多条记录说明采集端或合并环节出了问题,需要按平均值或丢弃处理;第二,检查缺失段,是采集空档还是真实停电,直接空着不处理会让滞后特征出现断层;第三,检查量纲,负荷是MW还是kW、温度是摄氏还是华氏,这类单位错乱会让评估指标看起来特别好或特别离谱,还很难察觉。
3.2 特征工程:从日期与天气里榨出周期信息
只拿历史负荷直接预测未来也能跑,但结果通常在峰谷处“平滑过头”。实战中一定要加入周期特征。我常用的特征分成四类:
滞后项:前一天同一时刻、上周同一时刻。负荷有极强的日周期和周末-工作日差异,这两列相当于把历史曲线平移过来做参考。时间编码:星期几0到6、是否周末0/1、是否节假日0/1、当天小时序号。天气项:温度、湿度、风速,温度最好做一阶差分,温度变化率对空调负荷的启动现象影响很明显。滚动统计项:近三小时均值、近三小时最大值,用来刻画短时趋势和惯性。
有些做法还会做负荷去趋势:对历史序列做STL分解,把趋势项、季节项、残差项分开,再让模型去拟合残差。我自己的经验是,当滞后特征加得足够多时,是否去趋势对最终MAPE影响不大,但如果你发现模型在年度慢漂移上跟着很紧、在天气突变上反应很慢,那加一步分解反而有效。
3.3 时序划分与数据泄露:为什么不能直接随机打散
电网负荷预测有一个铁律:测试集在时间上必须严格晚于训练集。如果直接调用train_test_split默认的shuffle=True,测试集里混入了过去的样本,模型相当于提前见过未来,指标会虚高到让你自我怀疑。
另一个隐蔽的坑是归一化器。MinMaxScaler如果对全量数据再fit,再transform,就等于训练集提前看到了测试集的最大值和最小值。SVR对输入尺度和输出范围很敏感,这种“未来信息”会被它抓得特别牢。正确做法是先按时间切分,scaler只在训练集上fit,再分别transform训练集和测试集。下一步的代码就是这个顺序,照抄不会出事。
4. 代码落地:一套可直接复现的sklearn实现
4.1 全流程骨架:从CSV到可预测的最小链路
常见做法是用sklearn.svm.SVR,pandas整理特征,单机训练即可。我的骨架代码如下:
import pandas as pd import numpy as np from sklearn.svm import SVR from sklearn.preprocessing import MinMaxScaler from sklearn.metrics import mean_absolute_error, mean_squared_error # 1. 读取与基础清洗 df = pd.read_csv("load_hourly.csv", parse_dates=["datetime"]) df = df.sort_values("datetime").reset_index(drop=True) # 严格按时间排序 df["load"] = pd.to_numeric(df["load"], errors="coerce").interpolate() # 2. 构造特征 def build_features(df): df = df.copy() df["hour"] = df["datetime"].dt.hour df["weekday"] = df["datetime"].dt.weekday # 0=周一 df["lag_24"] = df["load"].shift(24) # 昨天同一时刻 df["lag_168"] = df["load"].shift(168) # 上周同一时刻(小时粒度) df["roll_mean_3"] = df["load"].rolling(3).mean().shift(1) return df df = build_features(df) df = df.dropna().reset_index(drop=True) # 前几行滞后未填充,直接丢弃逻辑说明:排序用datetime列而不是CSV里的原始顺序,防止采集端乱序。interpolate()处理缺失段比fillna(0)安全得多,负荷不会凭空掉到零。lag_24和lag_168利用负荷的强周期关联,是整组特征里信息量最大的两个。roll_mean_3是前三小时滑动均值,加shift(1)是为了保证预测当前时刻时,没有用到当前时刻自身的信息。
这里有两个参数需要根据数据粒度调整:小时粒度下滞后窗口是24和168;换成15分钟粒度时要改成96和672,否则滞后特征会对不上相位。滚动窗口长度我一般取3到6个点,太大会把早高峰“磨平”,太小又起不到平滑噪声的作用。
4.2 切分、归一化与模型训练:SVR三个参数按这些值起步
接下来把序列串行切成训练集和测试集,再归一化训练:
# 3. 串行切分 feature_cols = ["hour", "weekday", "lag_24", "lag_168", "roll_mean_3"] X = df[feature_cols].values y = df["load"].values split_idx = int(len(X) * 0.8) X_train, X_test = X[:split_idx], X[split_idx:] y_train, y_test = y[:split_idx], y[split_idx:] # 4. 仅用训练集fit归一化器 scaler_x = MinMaxScaler() scaler_y = MinMaxScaler() X_train_s = scaler_x.fit_transform(X_train) X_test_s = scaler_x.transform(X_test) y_train_s = scaler_y.fit_transform(y_train.reshape(-1, 1)).ravel() # 5. 训练SVR model = SVR(kernel="rbf", C=1.0, epsilon=0.01, gamma="scale") model.fit(X_train_s, y_train_s)逻辑说明:split采用时间顺序,80%训练、20%测试。scaler_x只在X_train上fit,再transform测试集,避免把测试集的最小最大值泄露进缩放器;scaler_y同理,只接触训练集。y_train_s被压缩到0到1区间,和X特征的范围一致,SVR的默认参数才有意义。
参数说明:C=1.0是sklearn默认值,起步阶段可以接受,后续网格搜索再放开;epsilon=0.01是针对归一化后负荷量纲的经验值,负荷在0到1区间里,0.01相当于允许1%级别的误差不惩罚;gamma="scale"会自动根据特征标准差设定,比固定gamma=0.1稳妥。在手写数字分类那类小样本任务里,gamma一旦取值不当,核函数几乎区分不开样本,直接用scale能少踩一个坑。
4.3 反归一化与误差评估:先回到真实量纲再谈好坏
模型输出的y是0到1区间,要还原成MW再评估:
# 6. 预测与反归一化 pred_s = model.predict(X_test_s) pred = scaler_y.inverse_transform(pred_s.reshape(-1, 1)).ravel() # 7. 误差指标 mae = mean_absolute_error(y_test, pred) rmse = np.sqrt(mean_squared_error(y_test, pred)) mape = np.mean(np.abs((y_test - pred) / y_test)) * 100 print(f"MAE={mae:.2f} MW, RMSE={rmse:.2f} MW, MAPE={mape:.2f}%")逻辑说明:逆变换用的是之前fit好的scaler_y,不是重新fit一次。RMSE对峰谷异常更敏感,适合电网这种“尖峰误差更贵”的场景;MAPE用来和文献、同行结果对比。我习惯把y_test和pred画在同一张图上,按日期分隔查看,观察早晚高峰段有没有系统性滞后,这比只看三个数字可靠得多。
提示:MAPE对真实值接近0的时刻会飙到极大值。电网负荷在夜间低谷通常不会归零,问题不大;如果数据里真有接近0的时段,建议剔除负荷小于某个阈值的点再计算,否则一个低谷点能把整体误差拉高好几个百分点。
这套代码从读数据到评估大约30行,复制到自己的数据上,需要调整的只有列名、粒度和滞后窗口。跑通后自然知道哪里该加特征、哪里该换参数。
5. 常见问题排查:五个让负荷预测翻车的坑与补救办法
5.1 模型“只会输出平均值”,峰谷全部消失
现象:预测曲线几乎是一条水平线,真实负荷的早高峰、晚高峰全被削平。
原因:epsilon设得太大。负荷归一化到0到1后,epsilon取0.1甚至更高,相当于允许正负10%的误差不产生惩罚,学习器就没有动力去拟合峰谷。C如果同时设得很小,那些误差大的尖峰点也得不到足够的惩罚,模型会选择“躺平”。
解决:把epsilon降到0.005到0.02之间,C设为1到10起步。调整后如果预测曲线仍然偏平,优先检查是否做过归一化,以及特征里是否真的包含了滞后项——如果只用小时和星期几两个特征,SVR很难拟合出陡峭的负荷形态。
5.2 离线指标异常漂亮,线上预测却完全不准
现象:离线测试MAPE只有2%,第二天上线预测误差却到了8%以上。
原因:这是典型的泄露性预处理。最常见两个来源:MinMaxScaler用了全量数据拟合,把测试集的最大最小值提前暴露给训练过程;或者特征工程里滚动均值没有shift,用了当前时刻之后的数据。
解决:严格按“先切分,再fit,再transform”的顺序写代码;滚动特征一律shift(1);构造特征时确认预测时刻t只用t-1及之前的信息。验证方法很简单:把预测值逐点延迟一个时刻再计算相关性,如果延迟1小时的相关性仍然很高,就说明特征里残留了未来信息。
5.3 用预测值接力预测后,误差越滚越大
现象:单步预测的MAE很漂亮,但连续预测48小时时,从第12小时起曲线开始明显偏移,后面几乎完全跑偏。
原因:SVR是静态映射,把上一步的预测结果当输入再预测下一步,误差会沿递归路径累积。尤其当特征里有lag_24这种强周期项时,一步失真就会带动后续所有点集体失真,这就是典型的“自喂误差”滚雪球。
解决:多步预测不要用滚动接力。常用的做法是构建多个单步模型,分别预测未来第1小时、第2小时、第24小时,最后拼接成一条长曲线;或者用真实的近期观测值(比如现场有SCADA实时数据)滚动刷新输入。记住一句话:SVR适合做单步或稀疏多步预测,不适合做递归多步预测。
5.4 RBF参数网格搜索跑很久不收敛
现象:GridSearchCV跑了上千组C和gamma,最佳结果和跑几百组时差不多,训练时间却按小时计。
原因:网格点选得没有章法。C和gamma在SVR里是近似指数级敏感的量,在1到100之间均匀取10个点,远不如在对数尺度上取点高效。
解决:用np.logspace(-2, 2, 9)生成C序列,用np.logspace(-3, 1, 9)生成gamma序列;先粗搜一轮,再在最佳点附近加密一轮。如果数据量超过两三万点,SVR的二次规划求解会明显变慢,可以先在抽样数据上粗搜,再用全量数据精训。网格搜索不是越密越好,找到方向比找到“精确值”更重要。
5.5 节假日和极端天气时段误差激增
现象:整体指标还能看,但春节、寒潮、暴雨这些日子的预测值比真实值低20%以上,有时甚至反向。
原因:这些日子的负荷分布和普通工作日、周末差异太大,训练集中相似样本太少。SVR只靠支持向量做决策,极端样本占比太低时,模型会把它们当噪声处理、干脆忽略。
解决:把“是否节假日”做成0/1特征,不要只依赖星期几编码,因为节假日跨周且不定;极端天气日如果有气象预警字段,也单独做成0/1特征。如果落地场景对节假日要求很高,一个可用套路是:对节假日样本单独训练一个小模型,与主模型按日期类型切换使用。这样虽然多维护一个模型,但能避免“平时准、关键节点翻车”的尴尬。
6. 进阶与收尾:用滚动验证与网格搜索守住模型可信度
起步参数足够跑通流程,但要交付,还需要对C、epsilon、gamma做一轮验证式搜索。常见做法是TimeSeriesSplit和GridSearchCV搭配,既搜索参数,又不破坏时序性:
from sklearn.model_selection import GridSearchCV, TimeSeriesSplit tscv = TimeSeriesSplit(n_splits=5) param_grid = { "C": np.logspace(-2, 2, 9), "epsilon": [0.005, 0.01, 0.02], "gamma": np.logspace(-3, 1, 9), } search = GridSearchCV(SVR(kernel="rbf"), param_grid, cv=tscv, scoring="neg_mean_squared_error", n_jobs=-1) search.fit(X_train_s, y_train_s) print(search.best_params_, search.best_score_)TimeSeriesSplit的n_splits=5表示用前一段递增训练、后一段验证,最终取平均,比K折更适合时间序列。注意这里评分是负均方误差,数值越大越好;如果想直接和业务对齐,可以取出best_estimator_,在测试集上预测后重新计算MAPE。
我习惯在网格搜索结束后再做一步残差自相关检查:如果预测残差在滞后1小时仍有明显自相关,说明模型丢掉了某些周期信息,此时回去补特征远比继续调参有意义。这帮我避免了很多次“指标挺好、部署就翻车”的情况。另外,epsilon对最终误差的影响往往比gamma更大,低于0.005时模型会开始过拟合个别尖峰点,高于0.03时峰谷被磨平,推荐在0.005到0.02之间固定后再调其余两个参数。
参数搜索不是终点。跑完一轮后,把模型参数、特征列表、误差指标记录成基线;后续引入XGBoost、LSTM时,用同一套训练测试划分和评估口径做对比,才能看清新模型带来的真实增益。希望帮到你。
本文还有配套的精品资源,点击获取