ARTICLE DETAIL

资讯详情

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

电力负荷预测实战:K-means聚类与Transformer-LSTM混合模型MATLAB实现

电力负荷预测实战:K-means聚类与Transformer-LSTM混合模型MATLAB实现 简介针对电力系统负荷预测中传统方法难以捕捉复杂模式与长期依赖的痛点这份资源提供了一种结合K均值聚类、Transformer自注意力机制与长短期记忆网络的智能预测方案。文档以论文与代码结合的形式完整介绍了模型原理与MATLAB实现包括数据清洗与归一化预处理、K均值聚类划分相似时段、Transformer编码层提取长程依赖、长短期记忆网络层学习时序特征以及将两者融合形成综合特征向量并完成预测的流程。训练采用均方误差损失与Adam优化器并给出可直接运行的完整源码涵盖数据生成、特征构造、模型定义、评估与可视化等环节资源为Word文档格式共一个文件约736KB便于复现与二次开发。适合具备机器学习基础的电力系统研究人员可用于电网负荷预测与调度优化。目前已有151人学习下载文档还讨论了参数优化与算法改进方向有助于进一步提升预测性能。1. 为什么电力负荷预测要同时用 K-means、Transformer 和 LSTMK-means-Transformer-LSTM 这套名字看着像三个模型排队站一起实际上它解决的是电力负荷预测里最头疼的问题负荷数据既有明显的日/周周期性又夹着突发天气、节假日这类长程突变。单靠 LSTM 能记住近期模式但遇到远离训练分布的突变场景容易钝单靠 Transformer 能捕捉长程依赖却对局部连续波动的拟合不如循环结构细腻。K-means 在这里干的是「分场景」的活先把历史负荷按曲线形态聚成几类再对每一类分别训练混合模型预测时先判场景、再选模型。适合正在做电力调度、微网能量管理或负荷预测开题且打算在 MATLAB 里闭环落地而不是只跑 Python 的工程师和研究生。2. 数据准备与 K-means 聚类先把负荷场景分成可分别建模的几类2.1 数据清洗与滑窗切分先解决缺失值和毛刺再造训练样本电力负荷数据从 SCADA 或电表采集过来第一条拦路虎不是模型而是数据质量。常见情况是某几个采集点掉线产生 NaN或者负荷曲线出现瞬间尖峰——空调集中启动、大设备投切都会造成这种毛刺。这些问题不处理后面聚类会把异常形状当成一个独立簇Transformer 的注意力权重也会被极端值带走。我一般先把序列读进来做两步清洗线性插值补缺失中值滤波去毛刺。data readtable(load_data.csv); ts_raw data.load_value; % 原始负荷序列单位 kW ts_fill fillmissing(ts_raw, linear); % 缺失点线性插值 ts_clean medfilt1(ts_fill, 5); % 窗口为 5 的中值滤波 % 15 分钟粒度一天 96 点 ptsPerDay 96; numDays floor(length(ts_clean) / ptsPerDay); ts_clean ts_clean(1 : numDays * ptsPerDay); ts_mat reshape(ts_clean, ptsPerDay, numDays);fillmissing的linear方式适合短缺失段超过连续 2 小时8 个点的缺失段建议直接用前后同类型日替换否则长插值段会造出虚假平台。medfilt1窗口取 5 比较保守只削单点尖峰不伤真实负荷形状如果你发现滤波后峰谷值被削平说明窗口太大改回 3。滑窗切分是另一个关键设计。预测下一个 15 分钟负荷点时输入不能是整天的曲线而是过去一段固定长度的连续序列。输入步长我一般取 24 或 48对应过去 6 小时或 12 小时。太短抓不住晚高峰的爬坡趋势太长会把两天前无关信息喂进来。inputSteps 24; % 用过去 24 个点6 小时预测下一点 numFeat 1; % 单变量负荷可扩展为温度/湿度多特征 X zeros(numDays - 1, inputSteps, numFeat); Y zeros(numDays - 1, 1); for d 1 : numDays - 1 seg ts_mat(d, 1 : inputSteps); % 前一天前 6 小时 X(d, :, 1) seg; Y(d) ts_mat(d, inputSteps 1); % 预测下一时刻 end这段代码的用意是把原始序列重排成「X 为连续历史窗口、Y 为待预测下一时刻」的监督学习格式。注意这里我用的是一天内部滑窗跨天边界先不处理否则会把深夜零点到凌晨的负荷跳变硬塞进训练样本里。实际项目中跨天窗口要单独加一个「日类型」特征这里先保持简单。2.2 用每日曲线做聚类特征K-means 的输入不是原始序列很多人直接把上面切好的滑窗样本丢给kmeans这不对。滑窗样本之间高度重叠相邻时间段的窗口形状几乎一样聚类结果会被局部波动主导根本分不出「工作日」「周末」「极端天气日」这种成规模的场景。更合理的做法是先把每一天归一化后的完整曲线作为特征向量对「日」做聚类再把聚类标签映射到该日产生的所有窗口样本上。% 每日曲线归一化保留形状去掉幅值 dayCurve zeros(numDays, ptsPerDay); for d 1 : numDays v ts_mat(d, :); dayCurve(d, :) (v - mean(v)) / std(v); end % K-means 聚类先按轮廓系数定 k这里以 k4 为例 k 4; rng(42); % 固定随机种子保证可复现 [idx, C] kmeans(dayCurve, k, Distance, sqeuclidean, ... Replicates, 10, MaxIter, 500); silh silhouette(dayCurve, idx); meanSilh mean(silh);特征选择上归一化每日曲线比直接用原始负荷值好很多——它去掉日峰谷绝对幅值差异保留「形状」信息。sqeuclidean是欧氏距离的平方对轮廓更敏感适合负荷曲线这种连续向量聚类Replicates设为 10 表示用 10 组不同初始中心跑 10 次取最优这是对抗 K-means 初始值敏感的标准手段。rng(42)必须写不写的话同一份数据两次运行结果可能不同后面模型复现会很痛苦。k 的选定不是拍脑袋。拿silhouette的平均值做参考k 从 2 试到 8平均轮廓系数出现拐点或开始下降的位置就是合理的 k。常见结论是 3 到 5 类之间对应工作日早高峰型、平稳型、周末型、极端天气型这几类典型负荷形态。2.3 按簇划分训练集和测试集防止时间泄漏的一个硬约束聚类做完下一步是给每个滑窗样本打上所属簇标签然后分簇训练模型。这里有一个容易忽略的硬约束测试集必须来自时间上靠后的数据不能随机打乱后按比例切。负荷预测是时间序列任务用未来数据训练、过去数据测试等于提前偷看了答案评估指标会虚高到没有参考价值。正确的划分方式是按时间顺序切分前 80% 天数作为训练时段后 20% 作为测试时段然后把两个时段内的样本按各自所属簇分别组织。trainRatio 0.8; splitDay floor(numDays * trainRatio); trainDays 1 : splitDay; testDays splitDay 1 : numDays; % 按簇索引整理训练集 XTrainByCluster cell(k, 1); YTrainByCluster cell(k, 1); for c 1 : k dayIdx trainDays(idx(trainDays) c); rows []; for d dayIdx rows [rows, d]; % 该日所有滑窗样本的行号由 2.1 节映射 end XTrainByCluster{c} X(rows, :, :); YTrainByCluster{c} Y(rows); end上面代码省略了滑窗样本行号到日期的精确映射实际实现时需要维护一个sampleDayID数组记录每个滑窗样本取自哪一天。核心原则就一条一个滑窗样本的簇标签必须等于它所属那天的簇标签测试样本绝不能在聚类阶段参与簇中心计算。如果用了全量数据做 K-means 再切分测试集的信息已经通过簇中心进入训练集这个泄漏在论文里看不出来一上线上预测就会现形。3. MATLAB 里搭 Transformer-LSTM 混合网络从自注意力到训练循环3.1 为什么是「Transformer LSTM」而不是二选一这两个模型单独用都有明显边界。LSTM 的优势在沿时间步顺序编码对负荷的连续爬坡、惯性延续拟合细腻劣势是序列一长前面信息经过多步门控衰减难以直接关联到几天前同类型的负荷形态。Transformer 的自注意力机制让任意两个时间步可以直接计算相关度适合捕捉「今天上午 10 点和上周六上午 10 点负荷相似」这种长程遥相关但它对局部连续波动的归纳偏置弱单独用时预测曲线容易偏平滑峰谷位置对但幅值差口气。把两者串起来是常见做法先用自注意力层对输入窗口做加权重组把与预测点相关的历史时刻显式放大再把重组后的序列送进 LSTM 沿时间顺序细化。这个结构在 PyTorch 里写很顺手MATLAB 里则需要用dlarray手动实现前向计算好处是不依赖工具箱是否提供 Transformer 层整个计算图掌握在自己手里。3.2 用 dlarray 自写自注意力层维度、残差和 mask 取舍MATLAB 的 Deep Learning Toolbox 在自定义网络上有两条路一是继承nnet.layer.Layer写自定义层需要自己实现predict和backward反向传播梯度手算容易出错二是用dlarray加dlfeval走自定义训练循环梯度交给自动微分。我强烈建议第二种代码看起来多一点但不用手推公式而且调试时能直接在中间变量上打断点。function z selfAttention(X, Wq, Wk, Wv, Wo, dk) % X: dlarray维度 [numFeat, inputSteps, batchSize] % Q, K, V 的维度: [dk, inputSteps, batchSize] Q Wq * X; K Wk * X; V Wv * X; % 注意力分数: [inputSteps, inputSteps, batchSize] scores pagemtimes(permute(Q, [2 1 3]), K) / sqrt(dk); weights softmax(scores, DataFormat, CBT); % 按最后一个时间维度归一化 z pagemtimes(V, weights); % 上下文向量 z Wo * z; z z X; % 残差连接 end这段代码对应 Transformer 架构里最核心的缩放点积注意力Q 和 K 做点积得到每两个时间步之间的相关度除以sqrt(dk)防止分数过大把 softmax 推到饱和区softmax 归一化后得到注意力权重作用在 V 上就是「按相关性加权汇总所有时刻的信息」。残差连接z z X是必须保留的没有它深层网络训练时梯度容易消失。参数初始化有个容易被忽视的地方Wq、Wk、Wv 的方差应该按1/sqrt(dk)缩放而不是用默认的随机初始化。MATLAB 里可以用dlarray(randn(dk, numFeat) / sqrt(dk))赋值这一步做好了训练初期的 loss 不会乱跳。单头注意力对负荷数据通常够用多头的作用是让不同头关注不同时间尺度比如一个头关注日内周期、另一个关注近邻趋势。如果数据量不大头数设 1 或 2 即可强行上 8 头只会让参数暴涨而精度不涨。3.3 LSTM 单元的前向与梯度全连接之外的另一种时序编码自注意力输出的是加权后的特征序列还需要 LSTM 沿时间轴逐步消化。MATLAB 里可以用内置lstmLayer但在自定义训练循环里手动实现 LSTM 单元反而更透明方便后面加梯度裁剪和参数检查。function [h, c] lstmStep(x, h, c, Wi, Wf, Wg, Wo, Ui, Uf, Ug, Uo, bi, bf, bg, bo) % x: [numFeat, 1, batchSize] 当前时刻输入 % h, c: [hiddenSize, 1, batchSize] 上一时刻状态 g size(h, 1); i sigmoid(Wi * x Ui * h bi); % 输入门 f sigmoid(Wf * x Uf * h bf); % 遗忘门 o sigmoid(Wo * x Uo * h bo); % 输出门 gt tanh(Wg * x Ug * h bg); % 候选状态 c f .* c i .* gt; % 更新细胞状态 h o .* tanh(c); % 输出隐藏状态 end这就是经典 LSTM 的三个门加一个候选态。sigmoid门控把信息流动控制在 0 到 1 之间tanh负责把候选值压在 -1 到 1。门控的意义在于让网络自己学会「记住多少、忘掉多少」——负荷序列里如果昨天同一时段负荷相似遗忘门会接近 1 让信息多留一步如果遇到节假日突变遗忘门会主动清空旧状态。手动实现的好处是你能确切知道每个参数的形状和作用代价是要自己做参数汇总。前向计算时沿时间步循环调用lstmStep把每一步的 h 收集起来最后一次的 h 接全连接层输出预测值。循环在 MATLAB 里写 for 循环即可因为自动微分能穿透循环。3.4 训练循环与超参设定学习率、batch、epochs 的起点值模型前向搭好后训练循环是最后一公里。用dlnetwork加adamupdate的组合比手写梯度下降收敛快很多。function [loss, grad] modelLoss(params, XBatch, YBatch) % 前向selfAttention LSTM 循环 全连接输出 z selfAttention(XBatch, params.Wq, params.Wk, params.Wv, params.Wo, params.dk); h zeros(hiddenSize, 1, size(XBatch, 3)); c zeros(hiddenSize, 1, size(XBatch, 3)); for t 1 : size(z, 2) [h, c] lstmStep(z(:, t, :), h, c, ... params.Wi, params.Wf, params.Wg, params.Wo, ... params.Ui, params.Uf, params.Ug, params.Uo, ... params.bi, params.bf, params.bg, params.bo); end yPred params.Wout * h params.bout; loss mean((yPred - YBatch).^2); % MSE 损失 grad dlgradient(loss, params); % 自动微分 end % 训练主循环 learnRate 5e-4; numEpochs 200; batchSize 64; averageGrad []; averageSqGrad []; for ep 1 : numEpochs for iter 1 : numIterPerEpoch [XBatch, YBatch] getBatch(XTrain, YTrain, batchSize); [loss, grad] dlfeval(modelLoss, params, XBatch, YBatch); [params, averageGrad, averageSqGrad] ... adamupdate(params, grad, averageGrad, averageSqGrad, ep, learnRate); end endadamupdate内部维护一阶动量averageGrad和二阶动量averageSqGrad这两个变量必须在整个训练过程中持续传入传出不能在每次迭代重新初始化。学习率 5e-4 是这套结构的常见起点Transformer 类模型比纯 LSTM 对学习率更敏感从 1e-3 起步大概率 loss 跳 NaN后文避坑章会细说。epoch 200 看起来多但负荷数据量通常不大分簇后单簇可能只有几千条样本训练很快。batchSize 64 适合中小数据集如果单簇样本量不足 1000 可以把 batch 调成 32 甚至直接用全批量。4. 训练避坑K-means、Transformer 和 LSTM 的 5 个典型翻车现场4.1 归一化做在聚类之前簇直接失效现象K-means 聚出来的几类曲线形状几乎一样只是幅值不同轮廓系数很高但分簇毫无意义。预测时按簇建模的精度和全局单模型差不多没有任何提升。原因对原始负荷序列先做了整体mapminmax归一化再拿归一化后的值做每日曲线聚类。整体归一化保留的是每天的绝对幅值差异K-means 用欧氏距离衡量相似度时幅值大的日子天然被归到一起曲线形状根本没参与决策。解决聚类特征必须用「每日内部归一化」后的曲线即 2.2 节里(v - mean(v)) / std(v)的做法让每条曲线只表达形状。先把幅值信息剥掉再做聚类聚类结果才能真正代表负荷形态差异。4.2 同一份数据两次 K-means结果却不一样现象代码没改数据没换昨天跑出来的簇标签和今天跑出来的完全不同连带着分簇训练的模型指标也差了几个百分点。复现实验结果时对不上。原因K-means 的初始中心是随机选的不同初始中心可能收敛到不同的局部最优。聚类本身不唯一这是算法特性而不是 bug。有人用Replicates, 1省时间翻车概率显著上升。解决固定随机种子rng(42)并把Replicates至少设到 10。固定种子保证可复现Replicates 让每次从不同起点找最优解。如果换数据后评估指标波动仍大检查是不是每日曲线里有异常日干扰质心先把明显异常的曲线剔除。4.3 dlarray 维度顺序不对训练到一半报维度错现象前向计算时报维度不匹配错误信息指向pagemtimes或softmax的某个维度。调了很久才发现是维度顺序搞反。原因Python 里张量维度习惯是[batch, time, feature]MATLAB 的dlarray默认顺序是[feature, time, batch]对应 C、T、B 三个字母。直接把 PyTorch 代码的思路套到 MATLAB 里注意力矩阵的转置方向就会反。解决在写selfAttention之前先用一行代码确认维度约定dlarray(randn(numFeat, inputSteps, 4))打印出来看size和dims是否分别是[1 2 3]。然后在自注意力里用permute(Q, [2 1 3])把 Q 从[dk, T, B]换成[T, dk, B]再做pagemtimes。这个坑只踩一次踩完就记住 MATLAB 的 C 永远在第一位。4.4 学习率稍微调大loss 直接变 NaN现象学习率从 5e-4 调到 1e-3第一个 epoch 的 loss 就是 NaN之后再也不恢复。换小学习率又能跑但收敛变慢。原因两个叠加因素。一是自注意力里 QK 点积的值域随序列长度放大softmax 之前除以sqrt(dk)只能部分压制极端值仍可能让梯度爆炸二是 LSTM 沿时间步连乘门控接近 1 时梯度回传像滚雪球。纯 LSTM 能扛 1e-3 的学习率加上 Transformer 分支后扛不住。解决把学习率压回 3e-4 到 5e-4 区间并在每个 batch 反传后手动裁剪梯度范数。MATLAB 里dlgradient返回的梯度是结构体遍历每个字段计算总范数超过阈值就按比例缩放。阈值一般取 1.0 或 5.0可以先从 5.0 试起。另外检查 Wq、Wk、Wv 的初始化方差是不是1/sqrt(dk)初始化方差不对会让损失在训练前期就发散。4.5 测试集里某个簇样本太少预测结果大面积跳变现象训练时按簇分别建模测试集某些时间段预测误差突然放大三倍以上。打开测试集一看这些时间段所属的簇在测试集里只有零星几个样本。原因按时间顺序切分训练测试后簇分布天然不均匀。比如「极端天气日」大多集中在夏季某个月如果切分点恰好把这个月划到测试集测试集里这一簇样本极少甚至没有分簇模型缺少对应数据验证只能拿相邻簇的模型硬顶。解决三个手段配合使用。第一切分后统计各簇在训练和测试集的样本比例发现某簇测试样本小于总测试集 5% 时把该簇预测退化为全局模型第二聚类时如果极端天气类样本太少把 k 减小让稀有簇合并到相邻簇第三引入增量式按簇预测——测试样本先算它和哪个簇中心最近再用对应模型不依赖样本事先打好标签这样至少保证每个测试点都有一个归属。5. 评估与调参用误差指标反推 K 值、窗口和注意力头数5.1 三个必看的误差指标MAE、RMSE、MAPE分簇模型训完第一件事不是看曲线漂不漂亮而是把三个数值算出来MAE、RMSE、MAPE。它们各自暴露不同的问题只看一个会被误导。YPred predictAll(models, XTest); % 按簇模型逐样本预测 YTrue YTest; rmse sqrt(mean((YPred - YTrue).^2)); mae mean(abs(YPred - YTrue)); mape mean(abs((YPred - YTrue) ./ YTrue)) * 100; fprintf(RMSE: %.3f kW, MAE: %.3f kW, MAPE: %.2f%%\n, rmse, mae, mape);RMSE 对大误差敏感一个偏离很大的点会把 RMSE 顶得很高。如果 RMSE 明显大于 MAE说明预测误差分布有长尾——大部分时刻预测得不错少数时刻严重偏离。这种长尾通常来自负荷突变日雷阵雨、临时检修而不是模型整体不行。MAPE 是相对误差对负荷低谷时刻特别苛刻凌晨负荷值很低即使只差 0.5 kW 相对误差也可能到 20%拉低整个 MAPE。所以看 MAPE 时最好按小时段分别统计或者只看 8 点到 22 点这段主要负荷区间。判断模型好坏时光看整体指标不够要对比「分簇模型」和「全局单一模型」的指标差异。如果分簇后的 RMSE 只降了不到 2%说明聚类没有带来实质收益问题多半出在聚类特征或者 k 值选择上而不是混合网络本身。5.2 预测曲线与残差分布定位「欠拟合」还是「簇错配」数值指标只能告诉你差多少不能告诉你在哪里差。拿一段典型日比如某工作日的预测曲线和真实曲线叠在一起画肉眼看三件事峰谷时刻是否对齐、幅值是否系统性偏低、有没有滞后。残差直方图是更客观的诊断工具。residual YPred - YTrue; histogram(residual, 50); xlabel(预测误差 (kW)); ylabel(样本数);残差分布近似零均值的高斯形态说明模型误差是随机噪声主导模型结构基本够用。如果残差分布明显左偏或右偏说明存在系统性偏差——比如整体预测偏高检查是不是归一化时用了训练集的最大最小值而测试集峰值超过训练范围。如果残差直方图出现双峰两座峰分别对应不同簇的样本说明某个簇的模型没有拟合好把该簇单独拎出来重新训练或者考虑这个簇是否需要进一步细分。另一个高频现象是预测曲线比真实曲线平滑峰被削平、谷被抬高。这是 Transformer 注意力把多个时间步的信息平均化后的典型副作用。解决方向不是换模型而是检查注意力权重分布如果所有时间步的权重都接近均匀说明自注意力没有学到有效聚焦可能是序列长度太长、dk 太小或者训练 epoch 不足。5.3 参数调优顺序先 K 值再窗口最后注意力头数三个参数互相牵连但调参有固定顺序能少走弯路。第一步固定窗口和头数只调 K 值。K 从 2 试到 6每档 K 跑一遍聚类加训练看验证集 RMSE。K 偏小时不同负荷形态挤在一个模型里误差下不来K 偏大时会出现样本稀少的簇模型过拟合。选 RMSE 开始下降趋缓的那个拐点。这一步能跑出结论的前提是聚类特征做对了否则调 K 没有意义。第二步固定 K调窗口长度。窗口从 12、24、48、96 四档里选。窗口太短晚高峰爬坡趋势看不到窗口太长自注意力的计算量随窗口平方增长训练时间明显变长而精度在超过 48 后基本不再提升。我一般会在 24 和 48 之间选数据粒度 15 分钟时 48 点代表 12 小时足够覆盖一个完整的负荷变化周期。第三步最后调注意力头数和 dk。头数从 1 试到 4每档跑同样的 epoch 和初始化种子。头数增加会带来参数量的增长小数据集上头数 2 和头数 4 的精度往往没差别但训练时间涨一截。dk 决定 Q/K/V 投影维度常用值是 16 或 32和输入特征维度保持同一个量级即可不需要精确匹配。整个调参过程建议用脚本批量跑而不是手动改参数重开。把 K、window、numHeads 三个变量放在最外层 for 循环里每次迭代记录 RMSE 到结果表最后统一比较。手动逐档调参容易忘记上次的随机种子和归一化范围对比结果失去公平性。6. 进阶多步负荷预测的一个实用技巧前面整个流程预测的都是下一个 15 分钟点实际调度场景更常需要未来 1 到 4 小时的负荷曲线也就是多步预测。常见做法有两种递归多步和直接多步。递归多步是把第一步的预测值作为第二步的输入继续预测实现简单但误差随步长累积第 4 步的误差常常大到不可用。直接多步则是在模型最后一层把输出维度从 1 改成预测步数一步到位输出未来 H 个点。负荷预测里我强烈建议用直接多步改动很小但效果可靠。把最后一层全连接输出改成H维即可代价是训练标签要从Y改成[Y(t1), Y(t2), ..., Y(tH)]的向量。需要注意 H 步的误差特征不同损失函数建议用加权 MSE越近的时刻权重越高比如权重从 1 到 0.5 线性递减这样模型不会为了迁就远期步长而牺牲近期精度。这套方案还有一个值得投入的改进按天滚动更新模型参数。每天凌晨用前一天新采集的负荷数据对模型做一步微调学习率降到初始值的十分之一epoch 设 5 到 10 就够了不需要重新聚类和全量训练。聚类中心和归一化参数每月重算一次即可因为它们变化缓慢。我早期做这套系统时把每日更新省了结果夏季空调负荷上来后预测误差在两周内从 6% 涨到 11%重新全量训练才救回来。最后留个个人教训这套 K-means-Transformer-LSTM 组合的收益上限七成在数据清洗和聚类特征设计上三成在模型结构里。模型代码可以照抄数据流水线必须按你自己的负荷特性调每个数据集上踩的坑都不一样。先把数据准备做厚模型部分反而快。希望帮到你。本文还有配套的精品资源点击获取
返回列表