简介:基于MATLAB实现的支持向量机SVM电网负荷预测项目,面向电力系统调度、机器学习应用研究及本科以上学习者,解决小样本、非线性负荷序列的回归预测问题。资源共96个文件,约5.36MB,主体为.m脚本,涵盖SVM/SVR核心建模、网格搜索、遗传算法与粒子群算法参数寻优等模块;同时包含LibSVM底层C/C++源码、编译好的mex运行库、.mat与.xls样本数据,以及LibSVM程序代码注释和教程PDF,便于直接运行与二次开发。代码结构清晰、关键处有注释,并附有欧洲智能技术网络(EUNITE)竞赛负荷预测样本数据,可帮助读者快速复现“数据预处理—SVM建模—参数优化—负荷预测”完整流程,也可灵活替换数据用于其他回归场景。目前已有231人学习,适合需要上手SVM回归预测、撰写相关论文或进行算法扩展的研究者。
1. 电网负荷预测为何绕不开SVM:一个老模型还在生产环境的原因
调度值班长把明天96点负荷预测曲线摔在我桌上,前一晚深度学习模型在晚高峰偏了将近8%,那一刻我意识到:电网负荷预测不是比谁的网络更深,而是比谁在有限样本下更稳。基于支持向量机SVM的电网负荷预测,听起来像十年前的老题,但它至今仍是电力系统里做回归预测的可靠基线——样本量只有几千条、特征维度几十维的时候,SVM的小样本泛化能力反而能把深度学习按在地上摩擦。这篇笔记把完整落地路径拆开讲:数据怎么清洗、特征怎么构造、sklearn里SVR的核心参数怎么调、哪些坑会让你线上翻车。适合电力系统算法工程师、做能源数据分析的同学,也适合刚接触负荷预测但项目里必须快速出结果的人。
2. SVM建模前的选型课:硬间隔、核函数与lasso/cnn的真实边界
2.1 硬间隔SVM的梯度下降:为什么你几乎不会在代码里见到它
很多资料讲SVM,开口就是硬间隔、最大化间隔、支持向量,然后推导到对偶问题。但真实项目里,没人会手写硬间隔SVM的梯度下降,原因很简单:硬间隔是一个带不等式约束的优化问题,原始形式里那个margin约束让梯度下降很难处理。你当然可以把目标函数改写成hinge loss加上正则项,用次梯度下降去优化,这在理论上成立,但收敛速度和对参数初始化的敏感度都远不如成熟求解器。
sklearn里的SVC和SVR底层走的是libsvm,用的是SMO(序列最小优化),不是梯度下降。SMO每次挑两个拉格朗日乘子做解析更新,避开了梯度下降在高维约束空间里打转的问题。所以如果你只是在做负荷预测,不要纠结硬间隔SVM的梯度下降怎么写,你应该理解的是:SVM最终决策只依赖支持向量,也就是那些离决策边界最近的样本点,这个特性决定了它对小样本和噪声的鲁棒性。
这个理解对负荷预测有直接影响。电力负荷数据里,工作日的早晚高峰形态相对稳定,但偶尔会有异常突变(天气骤变、临时检修、大用户启停),这些样本如果落在间隔边界附近,就会成为支持向量并影响模型。SVM的损失函数对远离边界的样本不敏感,这意味着那些正常的负荷点不会过度拉扯模型,模型更专注于“难分”的边界样本。
2.2 核函数选择:RBF为什么是负荷预测的默认项
SVM做回归(SVR)时,核函数决定了模型能把原始特征空间映射到什么形状的更高维空间里。常见选项就三个:线性核、多项式核、RBF径向基核。线性核适合特征和目标关系本来就接近线性的场景,比如用前一天同时刻负荷直接预测今天同时刻负荷;多项式核有全局特性,但阶数高了容易在边界处震荡;RBF核是局部核,只关心样本之间的欧氏距离,对局部结构拟合能力强。
在负荷预测里我默认选RBF,原因有三个:第一,负荷与温度、湿度、光照、前一天负荷之间普遍存在非线性关系,RBF的映射能力够用;第二,RBF只有一个gamma参数,网格搜索好搜,多项式核还有coef0、degree两个额外参数要一起调,搜索空间成倍扩大;第三,RBF数值稳定性好,不像多项式核那样容易在特征量纲不一致时产生巨大内积。用一句话记忆:线性核是RBF的极端情况,多项式核是RBF的廉价替代,生产环境直接RBF起步。
2.3 lasso和svm,特征筛选与回归能力怎么配合
lasso和svm放在一起聊,因为它们解决的是同一类问题的两个阶段。lasso(L1正则线性回归)的天然属性是特征选择:它会把不重要的特征系数压成精确的0,所以你手里有40个原始特征但不知道哪些有效时,先跑一遍lasso,留下的特征一般是真正的信号。SVM擅长的是“特征已经选好,但关系复杂”的回归拟合,它不内置特征筛选,你在RBF核里塞一堆无关特征,只会让核矩阵计算变慢,还会引入噪声。
我一般的做法是:先用lasso在标准化后的特征上跑一轮,看哪些特征的系数非零,或者用交叉验证选一个合适的L1惩罚系数,把特征从40个筛到15个以内;然后把这15个特征喂给SVR。这个流程尤其适合“数据齐全但不知道用什么”的情况:负荷数据里往往既有温度、湿度、风速,又有电价、节假日、星期几,甚至还有上一时刻的负荷,lasso能帮你快速判断哪些是冗余项。
需要注意的是,lasso筛选出的特征对线性关系敏感,如果某个特征与负荷的关系是纯非线性的(比如温度对负荷的影响在高温和低温两侧都上升,中间反而低),lasso可能低估它。这种情况我会把温度的高温段、低温段拆成两个特征再做筛选,或者在lasso和svm中间加一步:先看单变量与目标的相关性和互信息,再决定保留与否。
2.4 svm和cnn原理对比:什么时候别用SVM
svm和cnn原理对比是个常被问的问题。SVM通过核函数把数据隐式映射到高维空间,在小样本下找一个最大间隔的决策面;CNN则靠卷积核在原始数据上逐层学习局部特征,数据量越大、层次越深,它能捕捉的复杂模式越多。原理上的本质差异是:SVM的假设空间由核函数和有限支持向量决定,CNN的假设空间由网络结构和海量参数决定。前者需要你手动做特征工程,后者试图自动学习特征。
负荷预测场景里,什么时候别用SVM?两个信号:第一,训练样本超过5万条且特征维度上百,SVR的核矩阵计算和求解时间会让人失去耐心,这时候CNN或LSTM的大批量训练优势就出来了;第二,你手里有连续多天的15分钟级负荷曲线,想让模型自己学出“早高峰—午休—晚高峰”的时序形态,CNN或循环结构天然适合。反过来,当样本只有两三千条、特征靠人工构造已经够用、模型需要快速迭代上线时,SVM就是那个不容易翻车的选择。
3. 把“数据齐全”变成训练集:负荷数据清洗与特征工程落地
3.1 一份标准负荷数据的字段长什么样
“数据齐全”这四个字在负荷预测项目里意味着历史负荷序列、气象要素、日历特征三类数据都齐。下表是我见过的最常见字段组合:
| 字段名 | 类型 | 说明 |
|---|---|---|
| time | datetime | 时间戳,常见粒度为15分钟或1小时 |
| load | float | 有功负荷,单位MW,预测目标 |
| temperature | float | 干球温度,摄氏度 |
| humidity | float | 相对湿度,百分比 |
| wind_speed | float | 风速,m/s |
| rainfall | float | 降雨量,mm/h |
| is_holiday | int | 是否节假日,0/1 |
| day_of_week | int | 星期几,1-7 |
| hour | int | 小时,0-23 |
拿到数据先别急着建模,第一步是看时间范围和粒度。电网负荷预测最常做的是96点日预测(15分钟粒度)或24点日预测(小时粒度)。如果原始数据是15分钟粒度但后面想预测小时负荷,直接重采样聚合即可;反过来,如果做15分钟预测但只有小时级气象数据,需要向前填充或插值,而这个操作会引入未来信息,必须在时间对齐时格外小心。
3.2 缺失值、异常点与时间对齐的处理顺序
处理顺序固定为:时间对齐 → 缺失值 → 异常点。先做时间对齐是因为数据来源往往不止一个:SCADA系统的负荷数据可能是准点采集,气象站数据可能有延迟,节假日表又是另一份人工维护的Excel。先把所有表统一到同一个时间索引上,再做后续处理才不会脏。
import pandas as pd import numpy as np # 假设三份表:load_df, weather_df, calendar_df # 统一时间索引,15分钟粒度 load_df['time'] = pd.to_datetime(load_df['time']) load_df = load_df.set_index('time').resample('15min').ffill() weather_df['time'] = pd.to_datetime(weather_df['time']) weather_df = weather_df.set_index('time').resample('15min').ffill() # 对齐合并,取并集时间索引 df = load_df.join(weather_df, how='outer') df = df.join(calendar_df, how='left') df = df.sort_index() # 缺失值:负荷列优先用前向+后向均值兜底,气象列用前向填充 df['load'] = df['load'].fillna(method='ffill').fillna(method='bfill') df['temperature'] = df['temperature'].fillna(method='ffill')这段代码的逻辑是:三张表先各自重采样到15分钟网格,避免因采集时间偏差导致join错位;how='outer'保留所有时间点,避免漏点;负荷缺失用前后向填充组合,因为负荷序列短期自相关性极强,用均值填会抹平峰谷。气象数据短时间缺失用前向填充,因为气象变化是慢变量,前一个时刻的值是最合理的估计。
注意:如果缺失段超过两小时,前向填充会失效,我一般会直接丢弃该时段样本,不让模型去学一段被“伪造”出来的负荷曲线。异常点检测放到缺失值之后,是因为缺失填充可能引入孤立尖峰,需要再统一清洗。
3.3 滚动窗口与滞后特征:让SVM“看到”昨天和上周
SVM不像LSTM有记忆单元,它只能看到输入特征。所以必须把“历史信息”通过滞后特征显式喂给模型。滞后特征的核心思路是:预测t时刻负荷时,把t-1、t-24、t-168时刻的负荷作为特征,分别对应上一时刻、昨天同时刻、上周同时刻。这一步几乎是负荷预测中最关键的特征工程,比调C和gamma作用大得多。
# 构造滞后特征和滚动统计特征 df = df.sort_index() for lag in [1, 2, 48, 96, 336]: # 15分钟粒度下:15min前、30min前、昨天、前天、上周同期 df[f'load_lag_{lag}'] = df['load'].shift(lag) # 滚动窗口均值:过去24小时(96个点)的滑动平均,用于捕捉趋势 df['load_rolling_mean_24h'] = df['load'].rolling(window=96, min_periods=1).mean() # 差分特征:当前与昨天同时刻的差值,反映日间增长趋势 df['load_diff_daily'] = df['load'] - df['load_lag_96'] # 删除构造特征过程中产生的NaN行 df = df.dropna().reset_index(drop=True)滞后步长的选择要和预测粒度匹配。如果你做的是小时级预测,滞后步长应改为1、24、168;如果做15分钟级,1、96、672分别对应15分钟前、昨天、上周。滚动窗口的窗口长度也要对应时间粒度:24小时在15分钟粒度下是96个点,小时粒度下是24个点。差分特征的作用是消除负荷序列的周期性趋势,让SVM更容易学到“今天的负荷相对昨天的增量与温度的关系”。
如果数据跨了多年,考虑加入年积日或季节标签,避免让模型把冬天的负荷模式套到夏天上。这一步常被忽略,但光伏渗透率高的地区,春秋两季的午间负荷形状和冬夏差异巨大,没有季节特征,SVM很容易在过渡月份翻车。
3.4 归一化的玄学:为什么SVM对量纲这么敏感
RBF核函数内部计算的是样本间的欧氏距离,公式里直接用了特征向量的二范数。如果某个特征量纲特别大(比如负荷是几百MW,湿度是几十百分比),距离计算会被这个特征主导,其他特征等于白给。这不是调参能救的,必须在特征工程阶段归一化。
from sklearn.preprocessing import StandardScaler # 分离特征与目标 feature_cols = [c for c in df.columns if c not in ['time', 'load']] X = df[feature_cols].values y = df['load'].values.reshape(-1, 1) # 注意:先切分训练集/测试集,再分别fit和transform # 这里仅演示标准化流程,实际必须先切分再fit,原因见第5章避坑第1条 scaler_X = StandardScaler() scaler_y = StandardScaler() X_scaled = scaler_X.fit_transform(X) y_scaled = scaler_y.fit_transform(y).ravel()这里有两个选择:StandardScaler还是MinMaxScaler。SVR对目标值y做标准化也是必要的,因为epsilon参数和C的取值依赖目标值的尺度;如果y是几百MW的原始值,epsilon=0.01的默认值会相当于没设误差带。目标值标准化后,epsilon可以理解成“标准化后的绝对误差”,量纲统一后网格搜索的数值才好设。输入特征标准化后还要再检查一遍:是否有特征在标准化后依然方差极小,如果有,说明它几乎是个常数,直接删掉,留着只会增加核矩阵计算量。
4. 用sklearn跑通SVM负荷预测:从训练到回测的最小代码
4.1 完整代码:数据加载、切分、训练、评估
下面这段代码是完整的、可以直接替换数据路径跑通的流程。前提是你的数据集已经整理成第3章那样的宽表,且包含time和load两列。如果没有真实数据,可以用make_demo_data()函数生成一组带周期和噪声的模拟负荷来验证流程,真实场景换成pd.read_csv即可。
import numpy as np import pandas as pd from sklearn.svm import SVR from sklearn.preprocessing import StandardScaler from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import mean_absolute_error, mean_absolute_percentage_error # 若没有真实数据,用模拟数据跑通流程:周期+趋势+噪声 def make_demo_data(n=3000): t = np.arange(n) trend = 0.02 * t season = 50 * np.sin(2 * np.pi * t / 96) + 20 * np.sin(2 * np.pi * t / (96 * 7)) noise = np.random.default_rng(42).normal(0, 8, n) load = 300 + trend + season + noise return pd.DataFrame({'time': pd.date_range('2023-01-01', periods=n, freq='15min'), 'load': load, 'temperature': 20 + 10 * np.sin(2 * np.pi * t / (96 * 365))}) # 加载数据(真实场景改成自己的CSV路径) # df = pd.read_csv('load_data.csv', parse_dates=['time']) df = make_demo_data() # 构造滞后特征与日历特征 df['hour'] = df['time'].dt.hour df['day_of_week'] = df['time'].dt.dayofweek for lag in [1, 2, 96, 192, 672]: df[f'load_lag_{lag}'] = df['load'].shift(lag) df['load_rolling_mean_24h'] = df['load'].rolling(96, min_periods=1).mean() df = df.dropna().reset_index(drop=True) # 按时间顺序切分:前80%训练,后20%测试,禁止随机切分 split_idx = int(len(df) * 0.8) train_df = df.iloc[:split_idx].copy() test_df = df.iloc[split_idx:].copy() feature_cols = ['hour', 'day_of_week', 'temperature', 'load_lag_1', 'load_lag_2', 'load_lag_96', 'load_lag_192', 'load_lag_672', 'load_rolling_mean_24h'] X_train = train_df[feature_cols].values y_train = train_df['load'].values.ravel() X_test = test_df[feature_cols].values y_test = test_df['load'].values.ravel() # 标准化:在训练集上fit,测试集只transform scaler_X = StandardScaler() scaler_y = StandardScaler() 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() y_test_s = scaler_y.transform(y_test.reshape(-1, 1)).ravel() # 训练SVR model = SVR(kernel='rbf', C=10.0, epsilon=0.02, gamma='scale') model.fit(X_train_s, y_train_s) # 预测并反标准化 y_pred_s = model.predict(X_test_s) y_pred = scaler_y.inverse_transform(y_pred_s.reshape(-1, 1)).ravel() # 评估指标 mae = mean_absolute_error(y_test, y_pred) mape = mean_absolute_percentage_error(y_test, y_pred) print(f'MAE: {mae:.2f} MW') print(f'MAPE: {mape*100:.2f}%') # 输出前5个预测点对照 for i in range(5): print(f'实际 {y_test[i]:.2f} MW, 预测 {y_pred[i]:.2f} MW')这段代码把前面所有要点串起来了:模拟数据生成、滞后特征构造、按时间顺序切分、标准化、SVR训练、反标准化、指标输出。make_demo_data里的周期用了96点日周期和672点周周期,能模拟出“每天两个峰”的基本负荷形态,方便你对流程建立直观感受。真实数据只需要替换load_data.csv的读取,并保证字段名与feature_cols一致即可。
4.2 参数逐行说明
代码里SVR(kernel='rbf', C=10.0, epsilon=0.02, gamma='scale')这几个参数需要逐个理解。
kernel='rbf':径向基核函数,对应第2章的选择理由,适合非线性负荷曲线。C=10.0:惩罚系数,控制“违反边缘”的容忍度。C越大,模型越努力拟合训练集,越容易过拟合;C越小,模型越平滑。负荷预测里C的合理范围通常在1到100之间,具体靠网格搜索。epsilon=0.02:SVR特有的参数,定义了一个“不管误差”的管道宽度。目标是标准化后的值,epsilon=0.02意味着标准化后的预测值与真实值误差在0.02以内不计损失。epsilon设得越大,模型越平滑、支持向量越少,训练越快;但太大时预测会跟不上负荷的尖峰。gamma='scale':RBF核的宽度参数,'scale'表示根据特征数量自动计算为1/(n_features * X.var())。实际项目中这个自动值很少是最优的,后面网格搜索会覆盖它。
4.3 输出怎么看:指标先看MAE还是MAPE
负荷预测行业里,MAPE(平均绝对百分比误差)是汇报给调度和领导的第一指标,因为它直观:晚高峰2000MW的负荷,偏了50MW是2.5%,一听就懂。但MAPE有个陷阱——当实际负荷接近0时,百分比会被放大,夜间谷值时段一个小小的绝对误差都能让MAPE很难看。所以我会同时打印MAE和MAPE,并且按时段分段统计:峰时段(8:00-11:00、18:00-21:00)看重MAE,谷时段看MAPE是否被异常值带偏。
# 按小时分段评估,定位误差来源 test_df_copy = test_df.copy() test_df_copy['y_test'] = y_test test_df_copy['y_pred'] = y_pred test_df_copy['hour'] = test_df_copy['time'].dt.hour hourly_mape = test_df_copy.groupby('hour').apply( lambda g: np.mean(np.abs(g['y_test'] - g['y_pred']) / g['y_test']) ) print(hourly_mape)这段代码按小时聚合MAPE,能快速看出模型是不是只在某个时段失效。比如光伏渗透率高的地区,午间1小时内光伏出力剧变,负荷预测误差通常集中在11:00-13:00;如果误差集中在凌晨,大概率是滞后特征没把夜间负荷的缓慢变化捕捉到。看到分时段误差再去调特征,比盲目网格搜索高效得多。
5. SVM参数调优与避坑:网格搜索、学习曲线和四个翻车现场
5.1 网格搜索C与gamma:搜索范围怎么定
SVR的RBF核真正需要调的就两个参数:C和gamma。网格搜索范围参考libsvm的经验:C在2的指数网格上取,gamma在2的负指数网格上取。C从2^-5到2^15,gamma从2^-15到2^3,每个维度取10-15个值,组合起来用TimeSeriesSplit做交叉验证。
from sklearn.model_selection import GridSearchCV param_grid = { 'C': [0.1, 1, 10, 50, 100, 200], 'gamma': [0.001, 0.01, 0.1, 1, 'scale'], 'epsilon': [0.01, 0.02, 0.05] } tscv = TimeSeriesSplit(n_splits=5) gs = GridSearchCV(SVR(kernel='rbf'), param_grid, cv=tscv, scoring='neg_mean_absolute_percentage_error', n_jobs=-1, verbose=1) gs.fit(X_train_s, y_train_s) print(f'最优参数: {gs.best_params_}') print(f'最优MAPE: {-gs.best_score_*100:.2f}%')这段代码用了TimeSeriesSplit而不是普通KFold,这是时序预测的硬性要求。scoring选了负MAPE,因为GridSearchCV默认是最大化分数,负数才能让“MAPE越小越好”和“分数越大越好”对齐。n_jobs=-1会让所有核参与并行搜索,C和gamma的9个组合乘以3个epsilon,在几千条样本上通常几分钟内能跑完。
5.2 避坑1:归一化泄漏——把整个数据集fit后再切分
现象:训练集和测试集上的MAPE都很好看(2%以内),但模型部署到第二天预测时误差飙到8%以上。原因:你在切分之前就对全量数据做了fit_transform,StandardScaler的均值和方差包含了测试集(也就是“未来”)信息,模型在测试时相当于提前知道了未来的统计量,指标虚高。解决:严格按第4章的写法,先切分,再在训练集上fit,测试集只transform。这不是小细节,这是线下验证可信度的底线。
5.3 避坑2:随机切分让模型“偷看”未来
现象:用了train_test_split默认的随机切分,测试集MAPE只有1.8%,你觉得模型神了,换成按时间顺序回测,MAPE变成4.5%。原因:负荷序列有强自相关性,随机切分会把t时刻样本和t+1时刻样本分到训练集和测试集两侧,而这两个样本的滞后特征几乎一样,模型等于在做“开卷考试”。解决:一律用TimeSeriesSplit或者手动按时间百分比切分。记住一句话:负荷预测里,测试集必须严格晚于训练集,否则你的精度没有任何说服力。
5.4 避坑3:SVR在样本量破万后训练慢到怀疑人生
现象:把两年的15分钟级数据(约7万条)直接喂给SVR,fit跑了20分钟还没结束。原因:libsvm的求解复杂度在样本量上是O(n^2)到O(n^3),支持向量的数量会随样本量线性增长,7万条样本的核矩阵计算是灾难。解决:优先做特征筛选和样本降采样。负荷预测不需要把每一天都喂进去,按“相似的日类型”抽样:工作日保留,周末保留,特殊节假日保留,其他普通日可以隔天采样,样本量降到一万以内。如果还想用全量数据,改用LinearSVR或SGDRegressor,但会牺牲非线性拟合能力。我的经验是:超过3万条样本,RBF核的SVR就不适合继续硬扛,要么降采样,要么换模型。
5.5 避坑4:节假日特征当成普通数值
现象:模型在春节和国庆假期的预测误差比其他日子高一倍,而且总是把假期预测成工作日形态。原因:is_holiday这个字段只有0和1,在RBF核里它和其他连续特征一起参与距离计算,1和0之间的距离只有1,而负荷在工作日和节假日的差异可能是几百MW,这个特征的“权重”在距离计算里被稀释了。解决:把节假日特征拆成交叉特征,比如is_holiday * load_lag_672(去年或上周同期的负荷),让模型学到“节假日叠加历史同期负荷”的交互效应。也可以按日类型分模型:工作日一个SVR、周末一个SVR、长假用专门模型。这里顺带说一句,lasso和svm在这个问题上的差异很明显:lasso因为线性加权的特性,可以给is_holiday一个较大的负系数,效果反而比SVM直接把0/1丢进RBF核要好。所以如果你发现SVM在节假日上翻车,先别急着调C,试试把节假日特征改用lasso做一版对照。
6. 从单步到多步:SVM负荷预测的最后一公里
6.1 多步预测的两种做法与选型
单步预测只预测下一个时刻,但调度需要未来24小时甚至72小时的完整曲线。常见做法有两种:递归预测和直接多输出预测。
| 做法 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| 递归预测 | 用t的预测值作为t+1的滞后特征,循环推进 | 只训练一个模型,实现简单 | 误差随步长累积,预测第48步时误差明显放大 |
| 直接多输出 | 同时预测未来96个点,SVR用MultiOutputRegressor包装 | 每个预测点独立训练,误差不累积 | 需要训练96个模型,计算量大,且忽略相邻点间的相关性 |
实际项目中我倾向递归预测,但要限制步长。负荷预测的滞后特征主要靠昨天和上周同期,而不是上一时刻的预测值,所以在递归到第4步之后,把预测值替换成“上一轮预测的最近时刻值”即可,误差累积效应在很多天前就已经通过滞后特征固定住了。如果你需要直接输出96点曲线且资源充足,用MultiOutputRegressor(SVR(...))包一层也能跑,但训练时间会乘以输出维度,通常配合第5章的降采样策略一起使用。
6.2 验证方法:把预测曲线画出来看
数值指标之外,我保留的最后一道验证是画曲线。分别挑三个典型日:工作日、周末、节假日,画出实际负荷和预测负荷的对比线。曲线图能看出指标看不出的东西——峰现时刻是否偏移、午间光伏段的形状是否贴合、夜间谷底是否被预测成平台段。
import matplotlib.pyplot as plt # 取测试集最后一天,画出实际与预测曲线 plt.figure(figsize=(12, 5)) plt.plot(test_df_copy['time'], test_df_copy['y_test'], label='actual', linewidth=2) plt.plot(test_df_copy['time'], test_df_copy['y_pred'], label='predicted', linestyle='--') plt.legend() plt.ylabel('Load (MW)') plt.show()峰现时刻偏移是最容易忽略的问题:MAPE只有3%,但每条预测曲线都比实际晚到15分钟,这种系统性偏差调度员一眼就能看出来。如果出现这个现象,检查滞后特征里是否有太多短时滞,或者C设得太大导致模型过度拟合训练集的相位特征。
我做负荷预测这些年养成的习惯是:每换一版数据或参数,就用同一套时间切分脚本跑一遍全流程,把结果存成带时间戳的CSV作为“后悔药”。哪天调度说新版预测不如旧版,我可以立刻翻出旧结果对比是数据问题还是参数问题,而不是凭记忆争论。希望这个习惯和这篇笔记里的流程,能帮你少踩几个我已经踩过的坑。
本文还有配套的精品资源,点击获取