ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

支持向量机SVM电力短期负荷预测:Matlab案例与避坑指南

支持向量机SVM电力短期负荷预测:Matlab案例与避坑指南 简介基于MATLAB的支持向量机电力短期负荷预测仿真项目面向电力系统研究人员、电气工程专业学生及机器学习入门者集中解决利用SVM根据历史负荷数据预测未来电力需求的问题。资源为RAR压缩包共含12个文件包括4个.m源代码、6个txt数据文件与2个.t数据文件整体仅24KB程序与数据相互配套便于直接运行与对照分析。已有753人学习。资料覆盖数据导入、缺失值处理、异常值识别、训练集与测试集划分、SVM模型构建与评估等环节选用EUNITE及1997年电力负荷数据集有助于理解线性、RBF等核函数差异掌握C与γ参数调整、交叉验证及MSE/MAE/R²评价指标的计算。整套代码结构简洁、步骤清晰既可作为电力负荷预测课程设计的参考实现也可作为深入理解SVM回归建模的入门实例。1. 支持向量机做电力短期负荷预测这个 Matlab 案例包能直接复现什么电力短期负荷预测是个典型的“小样本、高维、强时序”问题历史负荷数据往往只有几千行但影响预测的因素却很多。支持向量机SVM恰恰在小样本高维场景下比神经网络更稳这也是很多课程设计和实际项目仍然选它做电力负荷预测的原因。这个 Matlab 案例包把整套流程都装好了两套公开电力负荷数据eunite2001 和 1997 年负荷数据、数据导入脚本、SVR 训练与预测脚本、以及评估步骤。你拿到手不是看 PPT而是可以直接在 Matlab 里跑出预测曲线和指标。适合三类人正在做 SVM 课程设计的学生、想把 SVR 回归流程完整跑一遍的入门者、以及需要做负荷预测对比实验的工程师。数据规模不大模型训练秒级完成新手友好熟手也能拿它当参数调优的试验台。2. 先拆数据eunite2001 与 1997 两套负荷数据怎么读进来、怎么洗干净2.1 数据文件结构辨析txt、m、副本文件之间是什么关系拿到压缩包先别急着跑代码第一步是搞清楚每个文件是谁。这个案例包里的文件可以分成三类数据文件、脚本文件、冗余文件。下面这张表是我整理的文件角色对照按这个顺序去读代码会轻松很多。文件角色说明eunite2001.txt数据集EU NITE 负荷竞赛数据含负荷列和温度列用于带外部特征的预测实验eunite2001.m脚本对应 eunite2001.txt 的加载/训练主脚本eunite20012.txt数据变体可能是对 eunite2001.txt 做了特征增删后的版本eunite2001 - 副本.txt冗余备份原始数据备份内容应与 eunite2001.txt 一致可忽略bstrain1997.txt / bstest1997.txt训练/测试对1997 年负荷数据按时间切分好对应 svr2.mtrain1997.txt / test1997.txt训练/测试对另一组 1997 年数据对应 svr1997.msvr.m / svr2.m / svr1997.m核心脚本三个版本的 SVR 训练预测脚本逻辑相似区别在数据集与特征处理你可能会问为什么有这么多相似文件我的判断是这是作者在不同阶段留下的实验版本。eunite2001 - 副本.txt这种明显是 Windows 复制文件时自动生成的不用管eunite20012.txt和eunite2001.t大概率是某个预处理步骤的输出或截断版本。真正要盯住的是svr.m、svr2.m、svr1997.m三个脚本分别对应哪份数据。判断脚本和数据对应关系有个省事的方法用 Matlab 打开脚本看它load或importdata的是哪个文件名文件名对上了数据流就清楚了。我一般会先建一个data/目录把数据文件统一放进去再把副本文件移出工作区避免 Matlab 在当前目录搜索时加载到错误版本。2.2 数据清洗与训练/测试切分别让归一化范围污染预测电力负荷数据最常见的格式有两种一种是每行只有负荷值半小时采样另一种是每行包含日期、温度、负荷等多个字段。eunite2001.txt属于后者train1997.txt这类属于前者。不管哪种读进来以后都要做一次系统性检查。先看读取代码% 通用数据读取兼容空格和 Tab 分隔的纯数值文件 function data load_load_data(filename, has_header) if has_header % 第一行是列名时跳过表头 raw importdata(filename, , 1); data raw.data; else raw load(filename); data raw; end % 检查缺失值NaN 或 Inf 直接标记出来 nan_idx find(isnan(data) | isinf(data)); if ~isempty(nan_idx) fprintf(发现 %d 个缺失值用前后均值填充\n, length(nan_idx)); for i 1:length(nan_idx) [r, c] ind2sub(size(data), nan_idx(i)); if r 1 r size(data, 1) data(r, c) (data(r-1, c) data(r1, c)) / 2; else data(r, c) 0; % 首尾缺失直接置零 end end end end这段代码做了三件事跳过表头读取纯数值、扫描 NaN 和 Inf、用前后时刻均值填充空缺。电力负荷是连续变化的物理量用前后均值填充比用全局均值填充要合理得多——负荷在相邻半小时内不会突变这个假设在大多数情况下成立。读取完成后还有一个关键操作训练集和测试集必须分别归一化而且测试集要复用训练集的归一化参数。很多新手在这里翻车后面避坑章节会细讲。这里先给出正确写法% 训练集归一化记录 min 和 max xmin min(train_data); xmax max(train_data); train_norm (train_data - xmin) ./ (xmax - xmin eps); % 测试集用同一组参数归一化而不是重新算 min/max test_norm (test_data - xmin) ./ (xmax - xmin eps);eps加上的目的是防止某列最大值等于最小值时除零报错。这个细节在实际数据里很常见——比如某段时间负荷恒定的列归一化分母直接变成 0SVM 训练直接崩掉。2.3 时间特征构造给 SVM 喂“能预测未来”的输入原始负荷序列是一串数字直接扔给 SVR 做单步预测也可以但效果一般。因为电力负荷有明显的日周期性和周周期性今天上午 10 点的负荷大概率接近昨天上午 10 点的负荷而离明天凌晨 2 点的负荷很远。所以要把时序数据转换成“滞后特征”的形式。滞后特征构造的核心是用前p个时刻的负荷值预测下一时刻的负荷值。常见做法是滑动窗口窗口大小在 4 到 24 之间取值。半小时采样数据取 24 就是 12 小时窗口能覆盖一个完整日周期的一半整点采样数据取 24 则是完整一天。具体代码% 构造滞后特征矩阵X 的每一行是前 p 个时刻的负荷 function [X, y] make_lag_features(series, p) n length(series); X zeros(n - p, p); y zeros(n - p, 1); for i 1:(n - p) X(i, :) series(i : i p - 1); % 前 p 个点 y(i) series(i p); % 要预测的那一点 end end如果数据里有温度列比如 eunite2001我一般会把它作为额外特征拼到 X 后面因为温度是负荷的强相关因子——夏季空调负荷和冬季采暖负荷都随温度变化。拼特征的操作很简单X [X, temperature_series(p 1 : end)]; % 温度与预测点对齐对齐要特别注意第 i 行的预测目标是第ip时刻的负荷那么对应的温度也必须是第ip时刻的温度不能用第 i 时刻的。很多跑出来的曲线相位偏移就是这里没对齐。3. 构建 SVR 回归模型svr.m 与 svr2.m 的代码逻辑与参数语义3.1 SVR 与分类 SVM 的区别epsilon 不敏感损失是回归的关键摘要里讲的是分类 SVM 找最优超平面但本项目实际用的是 SVRSupport Vector Regression也就是 SVM 的回归版本。分类 SVM 的损失函数只惩罚错分样本而 SVR 引入了一个叫epsilon 不敏感损失的机制预测值与真实值的偏差在 epsilon 以内不计误差超出部分才计入损失。这个机制对负荷预测非常合适——负荷本身有测量误差你不可能要求模型预测得和实际值一模一样允许一个小范围内的偏差反而能抑制过拟合。epsilon 设置的常见范围是0.01到0.1之间归一化后。epsilon 太小模型会拼命拟合每一个点训练时间变长且容易过拟合epsilon 太大预测曲线会变得过于平滑峰谷都被削平。SVR 的损失函数形式如下L(y, f(x)) max(0, |y - f(x)| - epsilon)这个公式的意思很直观误差绝对值小于 epsilon损失算 0大于 epsilon按超出的量线性惩罚。线性惩罚而不是平方惩罚也是 SVR 对异常点不敏感的原因之一。电力负荷偶尔会有尖峰毛刺平方损失会被这些毛刺带偏线性损失就好很多。3.2 核函数选型对照RBF、多项式、线性在负荷预测里的取舍核函数决定了 SVR 在特征空间里怎么度量相似度。Matlab 里fitrsvm支持 linear、polynomial、rbf、gaussian 四种常用核不同版本写法略有差异。选核不是越复杂越好要看数据量和你对泛化能力的要求。核函数参数优势劣势适用场景linear无训练最快、可解释性强无法刻画非线性关系数据线性趋势明显时作为 baselinepolynomial阶数 d能拟合一定程度非线性阶数高时外推极易过拟合小样本、低噪声数据rbf / gaussiangamma局部拟合能力强、通用性最好gamma 敏感需调参大多数负荷预测场景的首选sigmoid缩放与偏移类似神经网络激活参数难收敛效果不稳定很少用于回归不建议尝试在这个案例包里svr.m和svr2.m的默认配置都是 RBF 核这也是绝大多数短期负荷预测论文里的选择。RBF 核的本质是把样本映射到无穷维空间但真正决定效果的是 gamma 参数的取值。gamma 越小单个样本的影响范围越大模型越平滑gamma 越大模型越倾向于在训练点附近剧烈波动泛化能力直线下降。后面避坑章节会具体展开。3.3 训练代码逐段解读从 svmtrain 到 fitrsvm 的兼容写法案例包里的脚本可能用了不同的 Matlab 版本编写。老版本的 MatlabR2015a 之前常用svmtrain和svmpredict这两个函数来自 bioinformatics 工具箱或 libsvm 接口新版本推荐用fitrsvm和predict。如果你直接拿老脚本跑到 2023b 上第一行就会报Undefined function svmtrain。这里给出一份兼容两种环境的训练代码先在脚本开头检测函数是否存在% 检测当前环境是否支持 svmtrain if exist(svmtrain, file) use_old_api true; else use_old_api false; end % 训练 SVR 模型 if use_old_api % 老接口需要设置 s3 表示 epsilon-SVRt2 表示 RBF 核 model svmtrain(train_y, train_X, ... -s 3 -t 2 -c 1 -g 0.5 -p 0.01); pred_y svmpredict(test_y, test_X, model); else % 新接口fitrsvmKernelFunction,rbf 指定高斯核 model fitrsvm(train_X, train_y, ... KernelFunction, rbf, ... BoxConstraint, 1, ... KernelScale, 0.5, ... Epsilon, 0.01, ... Standardize, false); pred_y predict(model, test_X); end老接口里-s 3是 epsilon-SVR 的固定标志-t 2是 RBF 核-c对应 C 参数-g对应 gamma-p对应 epsilon。新接口参数映射关系BoxConstraint对应 CKernelScale的平方倒数对应 gammaEpsilon对应 epsilon。Standardize设成 false 是因为我们在前面已经手动归一化过了再标准化就是重复操作。这份代码的关键不在于函数差异而在于参数一一对应。你可以在这份代码基础上做参数扫描把BoxConstraint和KernelScale换成循环变量就是下一章调参的基础工具。4. 避坑SVM 负荷预测的五个常见翻车现场4.1 现象运行脚本报错Undefined function svmtrain或svmpredict现象代码在第一行训练时就中断Matlab 提示找不到函数。部分用户装了 libsvm 但没编译 mex 文件报错信息会变成Cannot find mex file。原因svmtrain在 R2015a 及以后版本中被fitrsvm取代新装 Matlab 默认不带老接口libsvm 需要额外下载并mex编译不是开箱即用。解决二选一。要么按前文写的兼容检测代码直接改用fitrsvm和predict这是最省事的路要么保留老接口去 libsvm 官网下载对应 Matlab 版本的源码在 Matlab 里进入libsvm/matlab目录运行make编译。我建议直接用fitrsvm新接口支持KFold交叉验证、OptimizeHyperparameters自动调参老接口做不到这些。4.2 现象训练集指标很好看测试集误差突然爆炸现象MSE 在训练集上只有个位数测试集上一算变成几百预测曲线严重偏离真实负荷曲线。画出图来测试集曲线几乎是一条水平线或剧烈振荡。原因这是最典型的数据泄漏。很多人把整个数据集合并后做归一化再切训练集和测试集——这样测试集的 min/max 信息已经在归一化时泄漏给了模型。更隐蔽的版本是分层抽样随机把数据点打乱划分训练测试集导致相邻时间段的数据被拆到了两边测试集里包含白天数据训练集里也有同一天的白天数据模型“见过的信息”太多了。解决归一化分两步走先切分训练测试集再用训练集统计量归一化测试集也就是第 2.2 节给的代码。切分时坚持按时间顺序切前 80% 作为训练集后 20% 作为测试集。千万不要打乱顺序做随机划分。这是时序预测的铁律。4.3 现象gamma 参数没调预测曲线要么贴住训练集要么变成直线现象gamma 取默认值比如1/特征数或1时训练曲线和真实曲线几乎重合测试曲线剧烈振荡gamma 调小后测试曲线又变成一条接近水平的直线完全跟丢负荷变化。原因gamma 控制 RBF 核的影响半径。gamma 太大模型对局部波动过度敏感把噪声也学进去了gamma 太小所有样本之间的相似度都趋同模型退化为一个常数预测器。电力负荷的周期性强gamma 取0.1到1之间通常能找到平衡点。解决不要手试走一遍固定网格搜索。第 6 章给的网格搜索代码可以直接用。粗搜索 gamma 从0.01到10按对数间隔取C 从0.1到100按对数间隔取每组参数做 3 折时序交叉验证选验证集 MSE 最小的组合。4.4 现象用crossval随机 K 折验证结果每次跑都不一样现象同一份代码多次运行得到的评估指标波动很大最高和最低能差 30% 以上。原因Matlab 的crossval默认使用随机划分而随机划分对时序数据没有任何意义——模型可能在第 2 折里看到第 1 折测试段的真实数据信息泄露导致指标虚高且不稳定。解决手动实现时序折叠不能用crossval的随机模式。常见做法是把训练集按时间顺序切成等长的 4 段第 1 段训练、第 2 段测试第 12 段训练、第 3 段测试以此类推。每一折的测试数据永远在训练数据的后面。这样的验证结果才是可信的。4.5 现象load读文件报错或数据错位第一列全是 0现象用load(eunite2001.txt)读取时报错或读进来后数据变成了结构体字段取了半天或第一列全是 0和原始文件对不上。原因load对格式要求苛刻文件里只要有非数字内容表头、日期字符串、制表符混排它要么报错要么把整个文件读成 structure。部分 txt 文件末尾有空行load会把空行读成 NaN 行导致数据错位。解决统一用importdata加参数控制读取或者用fopentextscan手动解析。最常见的可靠写法是fid fopen(eunite2001.txt, r); C textscan(fid, %f %f %f, HeaderLines, 1, Delimiter, \t); fclose(fid); data [C{1}, C{2}, C{3}];textscan的Delimiter同时指定空格和 TabHeaderLines跳过表头%f强制按浮点数解析遇到解析不了的非数字字符会自动跳过不会让整个脚本崩掉。这是我处理老式 txt 数据的默认方案。5. 评估与验证MSE、MAE、R² 怎么算交叉验证怎么用5.1 三个评估指标的计算与解读预测做完不能只看曲线“像不像”要用三个指标量化MSE均方误差、MAE平均绝对误差、R²决定系数。三者的侧重点不一样MSE 对大误差敏感适合考察是否存在偏离严重的预测点MAE 反映平均绝对偏差更接近业务上的“平均错多少”R² 衡量模型对真实方差的解释比例越接近 1 说明预测曲线跟真实曲线的趋势越吻合。% 输入真实值 y_true预测值 y_pred已反归一化回原始量纲 function [MSE, MAE, R2] eval_regression(y_true, y_pred) n length(y_true); e y_true - y_pred; MSE mean(e .^ 2); MAE mean(abs(e)); SSR sum((y_pred - mean(y_true)) .^ 2); SST sum((y_true - mean(y_true)) .^ 2); R2 1 - SSR / SST; fprintf(MSE %.4f, MAE %.4f, R2 %.4f\n, MSE, MAE, R2); end计算 R² 时有一个要注意的细节mean(y_true)用真实值的均值而不是预测值的均值。有些代码会写错成mean(y_pred)导致 R² 结果虚高。R² 为负数也是正常情况——如果预测得比直接用历史均值还差R² 就会变成负的这说明模型根本没学到有效规律需要回头检查特征构造或参数。读取和评估之间还有个容易忽略的步骤预测值必须在原尺度上评估不能在归一化尺度上算完指标再报出来。反归一化很简单y_pred_real y_pred_norm * (xmax - xmin) xmin。这一点直接在代码里用训练时的xmin和xmax不要重新算测试集的。5.2 时序交叉验证与泛化能力检验前面避坑部分提到crossval随机划分不可信这里给出一个可以实际替换的时序折叠实现。假设训练集有 8000 个样本我们要做 4 折验证就把这 8000 个样本顺序切成长度递增的 4 段训练块每段后面紧跟一个固定长度的验证块。% 时序折叠交叉验证每折只用过去的数据预测未来 function cv_mse time_series_cv(features, target, n_fold, cfg) n length(target); fold_size floor(n / (n_fold 1)); cv_mse zeros(n_fold, 1); for k 1:n_fold train_end k * fold_size; val_start train_end 1; val_end min(val_start fold_size - 1, n); % 训练段从开头到 train_end trX features(1:train_end, :); trY target(1:train_end); % 验证段紧接着训练段之后 vaX features(val_start:val_end, :); vaY target(val_start:val_end); model fitrsvm(trX, trY, ... KernelFunction, cfg.kernel, ... BoxConstraint, cfg.C, ... KernelScale, cfg.gamma, ... Epsilon, cfg.epsilon); pred predict(model, vaX); cv_mse(k) mean((pred - vaY) .^ 2); end cv_mse mean(cv_mse); end这段代码的逻辑核心是第 k 折的训练集永远是1:train_end验证集永远在训练集之后绝对不允许未来数据混入训练。cfg是一个结构体用来传递 C、gamma、epsilon 和核函数类型方便后续做参数网格搜索时重复调用。交叉验证的结果比单次切分更可信。规则很简单多组参数跑同一套交叉验证选择平均 MSE 最小的一组如果两组参数的平均 MSE 很接近选参数更小的那组——C 小意味着模型更平滑更不容易在新数据上翻车。6. 进阶网格搜索调 C 和 gamma以及滚动多步预测的实操技巧SVR 的参数默认值能跑通流程但离“好用”还有距离。要拿到一个能上台面的结果C 和 gamma 必须搜一遍。C 控制对误差的惩罚强度C 越大模型越不愿意犯错越容易过拟合C 越小模型越宽容可能欠拟合。gamma 控制 RBF 核的影响范围前面避坑章节已经讲过危害。两者组合起来就是一个二维网格搜索问题。常见的搜索策略是两步走先粗搜确定范围再细搜精确定位。粗搜用对数网格C 取0.1, 1, 10, 100gamma 取0.01, 0.1, 1, 10锁定表现最好的区域后再在相邻区间各补两个点。调用第 5.2 节的time_series_cv函数C_list [0.1, 1, 10, 100]; gamma_list [0.01, 0.1, 1, 10]; best_mse inf; best_cfg struct(); for C C_list for gamma gamma_list cfg.C C; cfg.gamma 1 / gamma; % KernelScale 与 gamma 取倒数关系 cfg.epsilon 0.01; cfg.kernel rbf; mse time_series_cv(train_X, train_y, 4, cfg); fprintf(C%.2f gamma%.2f - MSE%.4f\n, C, gamma, mse); if mse best_mse best_mse mse; best_cfg cfg; end end end注意这里KernelScale和 gamma 的关系gamma 1 / KernelScale^2所以KernelScale 1 / sqrt(gamma)。很多人在这一步换算错导致网格搜索的结果完全对不上号。跑完以后用best_cfg重新训练完整训练集再在测试集上评估一次得到的指标才是最终能写进报告的数字。单步预测做完以后真正的需求往往是要预测未来 24 小时甚至 48 小时的负荷。这时候需要滚动预测先用真实历史数据预测第 1 步然后把预测值拼到历史序列末尾用这个“掺了预测值”的序列去预测第 2 步以此类推。实现方式% 滚动多步预测horizon 是预测步数p 是滞后窗口大小 function pred_seq rolling_forecast(model, history, p, horizon) pred_seq zeros(horizon, 1); buffer history(end - p 1 : end); % 取最后 p 个真实值作为起点 for h 1:horizon pred predict(model, buffer(:)); pred_seq(h) pred; buffer [buffer(2:end); pred]; % 丢掉最旧值拼入最新预测值 end end这里的buffer就是滑动窗口的状态每次预测完成后窗口整体前移一格最旧的真实值被丢掉最新的预测值补进来。误差会随着步数增加而累积——第 1 步误差最小第 24 步可能已经偏得比较远。实战里我习惯对滚动预测再做一次“逐小时模型”对 24 个预测时刻各训练一个独立模型用同样的滞后特征但不同的 C 和 gamma。这样每个时刻的参数都是单独调出来的整体误差通常比单一模型滚动 24 步小 15% 左右。我自己第一次跑这个案例包的时候也犯了第 4.2 节的错误——把整个序列一起归一化再切分测试集指标虚高到 0.98还以为是模型调得好后来换成时序切分立刻掉到 0.91那一刻才算真正看清了这个模型的水平。从那以后我每次跑时序预测都会强制走一遍“先切分、再归一化、再时序交叉验证”的流程宁慢勿快。希望这套流程也能帮你在电力负荷预测上少走几段弯路。本文还有配套的精品资源点击获取
返回列表