news 2026/10/2 5:15:28

基于K-means聚类与LSTM的电能质量预测:从原理到工程落地

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于K-means聚类与LSTM的电能质量预测:从原理到工程落地

简介:这份PDF文献面向电力系统、智能电网及数据分析方向的研究生与工程技术人员,聚焦主动配电网电能质量预测这一难题。针对电能质量数据在较长时间跨度上的时序性与非线性特征,文中提出将K-means聚类与长短期记忆网络相结合的预测方法:先对历史电能质量数据、环境因素及负荷数据做多维向量聚类,再对每类数据分别训练LSTM模型并评估性能,最后通过IEEE-13节点含分布式电源的仿真算例,验证其优于时间序列法、BP神经网络和标准LSTM。资源包共1个PDF文件,约2.73MB,内容完整呈现论文的摘要、系统框架、算法伪代码与实验对比,适合深度学习与数据研究方向的读者研读。目前已有218人学习,可作为电能质量预测建模、聚类与LSTM融合思路的参考文献与专业指导。

1. 一份把 K-means 和 LSTM 串起来的电能质量预测论文,到底能跑出什么

配电网里做电能质量预测的人,大多经历过这种尴尬:手上攒了几个月甚至一年的监测数据,温度、光照、负荷、电压偏差、谐波畸变率全都有,但真到要预测未来某时段的稳态指标时,用 ARIMA 拟合出来的曲线要么滞后、要么在峰值点直接跑偏。这份《基于聚类LSTM深度学习模型的主动配电网电能质量预测》给出的思路是:别拿全量数据硬训一个 LSTM,先用 K-means 把历史数据按欧式距离聚成几类,再对每一类单独训一个 LSTM,预测时先判断未来工况属于哪一类,再调用对应模型。论文用 IEEE-13 节点含光伏 DG 的仿真算例做了对比,聚类 LSTM 的 RMSE 和 MAE 都优于 ARIMA、BP 和标准 LSTM,48 小时预测里近 70% 的相对误差落在 5% 以内。适合正在做电能质量态势感知、负荷预测或时间序列多变量预测的从业者,尤其是想搞清楚“聚类到底加在哪一步、LSTM 参数怎么设”的人。

2. 聚类 LSTM 预测框架拆解:四个模块和 K 值怎么定

2.1 从数据采集到预测输出的完整链路

论文把整个预测流程拆成四个功能模块,这个拆法本身就值得借鉴,因为它把“数据从哪来、怎么分、怎么训、怎么用”四件事分得很清楚,不会在实现时把预处理和建模搅在一起。

第一个模块是 PQ 数据获取。在含分布式电源的主动配电网里,需要在合适位置布置环境变量监测装置、负荷监测仪和电能质量监测仪,采集较长时间跨度内的光照强度、温度、负荷数据以及对应的电能质量稳态指标项,按相同时间标记关联保存。这里的关键是“相同时间标记”——如果环境数据和电能质量数据的时间戳对不齐,后面聚类出来的类别就是错的。

第二个模块是 PQ 数据聚类。拿到关联历史数据后,先做归一化预处理,然后用轮廓系数法确定最优分类数 K,再用 K-means 把数据按欧式距离分成 K 类。论文里每个时刻的数据被组织成[温度, 光照, 用电负荷, 电能质量指标]的 4 维向量,这个结构直接决定了聚类的输入维度。

第三个模块是 LSTM 模型训练与测试。把每一类数据转成监督学习序列,按 70% 和 30% 划分训练集和测试集,分别构建 LSTM 网络进行训练和性能评估,直到每一类都有自己确定好的预测模型。

第四个模块是 PQ 数据预测。获取未来某时段的环境因素预测数据和负荷预测数据,先判断它属于哪个聚类类别,然后调用对应类别的 LSTM 模型输出电能质量稳态指标项的预测值。

注意:这四个模块的顺序不能乱。我见过有人先把全部数据训一个 LSTM,再拿预测结果去聚类,那是完全反了——聚类必须在训练之前,而且是对输入特征加输出指标一起聚。

2.2 轮廓系数法确定 K 值:为什么是 3 而不是 2 或 4

K-means 最让人头疼的就是 K 值怎么选。论文用的是轮廓系数法,这个方法的逻辑不复杂,但实操时容易算错。

对数据集中的某个样本点 $P_i$,定义其轮廓系数:

$$S_i = \frac{c - a}{\max(a, c)}$$

其中 $a$ 是凝聚度,表示 $P_i$ 与同簇其他样本的平均距离;$c$ 是分离度,表示 $P_i$ 与最近簇中所有样本的平均距离。最近簇的定义是:

$$q = \arg\min \frac{1}{n} \sum |q - P_i|$$

把所有样本的轮廓系数求平均,就得到平均轮廓系数 $\bar{S}$,取值范围在 $[-1, +1]$。簇内样本距离越近、簇间样本距离越远,$\bar{S}$ 越大,聚类效果越好。

论文在 $K \in [2, 8]$ 范围内逐一计算平均轮廓系数,发现当 $K=3$ 时 $\bar{S}$ 最大,所以最优类别数取 3。这个结论不是拍脑袋来的,是算出来的。

实操时用 Python 复现这段逻辑:

import numpy as np from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score from sklearn.preprocessing import MinMaxScaler # 假设 data 是 [温度, 光照, 负荷, 电压偏差] 的 N x 4 矩阵 scaler = MinMaxScaler() data_norm = scaler.fit_transform(data) silhouette_scores = {} for k in range(2, 9): kmeans = KMeans(n_clusters=k, init='k-means++', n_init=10, random_state=42) labels = kmeans.fit_predict(data_norm) score = silhouette_score(data_norm, labels) silhouette_scores[k] = score print(f"K={k}, 平均轮廓系数={score:.4f}") best_k = max(silhouette_scores, key=silhouette_scores.get) print(f"最优类别数 K={best_k}")

这段代码里几个参数需要说明。init='k-means++'是让初始聚类中心尽量分散,避免陷入局部最优;n_init=10表示用不同随机种子跑 10 次取最好的结果,因为 K-means 对初始中心敏感;random_state=42是为了结果可复现。归一化用MinMaxScaler把各变量压到 $[0,1]$,这一步不能省——温度和光照的量纲差了好几个数量级,不归一化的话聚类结果会被大数值变量主导。

2.3 LSTM 细胞单元的门控机制与 BPTT+Adam 训练

论文对 LSTM 内部结构的描述比较细,这里只挑实现时真正影响参数设置的部分说。

LSTM 细胞单元包含遗忘门、输入门、输出门三个门控结构。遗忘门以上一个单元输出 $h_{t-1}$ 和本单元输入 $x_t$ 为输入,通过 Sigmoid 函数为上一单元状态 $C_{t-1}$ 的每一项产生一个 $[0,1]$ 内的值,控制遗忘程度:

$$f_t = \sigma(W_f \cdot [h_{t-1}, x_t] + b_f)$$

输入门配合 tanh 函数控制新信息的加入:

$$i_t = \sigma(W_i \cdot [h_{t-1}, x_t] + b_i)$$ $$\tilde{C}t = \tanh(W_C \cdot [h{t-1}, x_t] + b_C)$$ $$C_t = f_t \cdot C_{t-1} + i_t \cdot \tilde{C}_t$$

输出门控制当前单元状态有多少被过滤:

$$o_t = \sigma(W_o \cdot [h_{t-1}, x_t] + b_o)$$ $$h_t = o_t \cdot \tanh(C_t)$$

训练过程用 BPTT 算法加 Adam 梯度优化。BPTT 的四个步骤是:前向计算各细胞输出值、反向计算每个细胞的误差项、根据误差项计算权重梯度、用 Adam 更新权值。Adam 的优势在于适合处理大规模数据和参数、对噪声和稀疏度不敏感,这在电能质量数据里很实用——监测数据里总有缺失值和异常波动。

在 Keras 里搭建这个 LSTM 网络,核心代码大概长这样:

from keras.models import Sequential from keras.layers import LSTM, Dense from keras.optimizers import Adam model = Sequential() # 隐藏层用 50 个 LSTM 细胞,输入维度为 4(温度、光照、负荷、历史PQ指标) model.add(LSTM(50, input_shape=(time_steps, 4), return_sequences=False)) # 全连接输出层,输出 1 个预测值(如电压偏差) model.add(Dense(1)) # 损失函数用 mae,优化器用 adam model.compile(loss='mae', optimizer='adam') model.fit(X_train, y_train, epochs=100, batch_size=32, validation_split=0.1, verbose=1)

LSTM(50)里的 50 是隐藏层神经元数量,论文没有明确给出这个值,但根据同类时间序列预测的经验,50 到 128 之间比较常见,数据量小的时候取小一点防止过拟合。input_shape=(time_steps, 4)里的time_steps是时间窗口长度,论文用series_to_supervised()把原始时间序列转成监督学习格式,这个窗口长度需要根据数据采样频率和预测跨度来调。loss='mae'对应论文里的平均绝对误差,optimizer='adam'对应适应性动量估计算法。epochs=100和batch_size=32是常见起点,实际训练时看验证集 loss 曲线来定早停。

3. 从原始数据到预测输出:可复现的完整实现步骤

3.1 数据归一化与反归一化的参数选择

论文用式(9)把温度、光照、用电负荷和电能质量指标项都归算到 $[0,1]$ 之间:

$$X_{norm} = \frac{X - X_{min}}{X_{max} - X_{min}}$$

预测完成后用式(10)反归一化:

$$X = X_{norm} \times (X_{max} - X_{min}) + X_{min}$$

这里有个容易翻车的地方:$X_{max}$ 和 $X_{min}$ 必须用历史数据集里的最大最小值,不能用训练集和测试集各自的最大最小值。如果用测试集自己的极值做反归一化,预测结果会被人为拉回测试集范围,看起来误差很小,实际上模型根本没有预测能力。我一般会把训练集的 min/max 存下来,预测时直接用这组参数做反归一化。

# 保存训练集的归一化参数 train_min = data_train.min(axis=0) train_max = data_train.max(axis=0) # 归一化 data_train_norm = (data_train - train_min) / (train_max - train_min) data_test_norm = (data_test - train_min) / (train_max - train_min) # 预测后反归一化 pred_original = pred_norm * (train_max[target_idx] - train_min[target_idx]) + train_min[target_idx]

3.2 监督学习序列构造与训练测试集划分

论文用series_to_supervised()把时间序列转成监督学习格式。这个函数的核心逻辑是:用前 $n$ 个时间步的 $m$ 个特征预测下一个时间步的目标值。比如用过去 24 小时的温度、光照、负荷和电压偏差,预测第 25 小时的电压偏差。

import pandas as pd def series_to_supervised(data, n_in=24, n_out=1, dropnan=True): """ 将时间序列转换为监督学习格式 data: DataFrame 或 array,每列是一个变量 n_in: 输入时间步数 n_out: 输出时间步数 """ n_vars = 1 if type(data) is list else data.shape[1] df = pd.DataFrame(data) cols = [] # 构造输入序列列名 for i in range(n_in, 0, -1): cols.append(df.shift(i)) # 构造输出序列列名 for i in range(0, n_out): cols.append(df.shift(-i)) agg = pd.concat(cols, axis=1) if dropnan: agg.dropna(inplace=True) return agg.values

n_in=24表示用过去 24 个时间步作为输入,这个值需要根据数据的自相关性和预测跨度来调。论文里一天有 24 组稳态数据,所以取 24 作为输入窗口是合理的。n_out=1表示预测下一个时间步。dropnan=True会删掉因为 shift 产生的 NaN 行,这是必须的,否则训练时会报错。

数据分割按时间顺序取前 70% 做训练集、后 30% 做测试集。注意不能随机打乱——时间序列的先后顺序本身就是信息,打乱之后 LSTM 学到的时序依赖就是假的。

3.3 分类训练与预测调用的完整流程

论文表 1 给出了聚类 LSTM 预测算法的伪代码,翻译成可执行的 Python 逻辑:

# 第一步:读取历史数据并归一化 data_norm = normalize(raw_data) # 第二步:轮廓系数法确定最优 K best_k = find_optimal_k(data_norm, k_range=range(2, 9)) # 第三步:K-means 聚类 kmeans = KMeans(n_clusters=best_k, init='k-means++', n_init=10, random_state=42) labels = kmeans.fit_predict(data_norm) # 第四步:对每一类分别训练 LSTM models = {} for i in range(best_k): class_data = data_norm[labels == i] # 转监督学习格式 supervised = series_to_supervised(class_data, n_in=24, n_out=1) # 按 70/30 划分 train_size = int(len(supervised) * 0.7) train, test = supervised[:train_size], supervised[train_size:] X_train, y_train = train[:, :-1], train[:, -1] X_test, y_test = test[:, :-1], test[:, -1] # 重塑为 LSTM 需要的 3D 输入 X_train = X_train.reshape((X_train.shape[0], 24, 4)) X_test = X_test.reshape((X_test.shape[0], 24, 4)) # 构建并训练 LSTM model = build_lstm_model(time_steps=24, n_features=4) model.fit(X_train, y_train, epochs=100, batch_size=32, verbose=0) models[i] = model # 第五步:预测时先判断类别再调用对应模型 future_data_norm = normalize(future_data) future_label = kmeans.predict(future_data_norm) pred = models[future_label[0]].predict(future_X) pred_original = inverse_normalize(pred, train_min, train_max)

这段代码里最关键的是第五步——预测时不能直接拿未来数据去调模型,必须先判断它属于哪个聚类类别。判断方法是用训练好的 K-means 模型对未来的环境因素和负荷数据做predict,得到类别标签后再调用对应的 LSTM。如果未来数据跨越了多个类别,就需要分段预测再拼接。

3.4 性能评估指标 RMSE 与 MAE 的计算

论文用均方根误差和平均绝对误差来评估预测性能:

$$RMSE = \sqrt{\frac{1}{N} \sum_{i=1}^{N} (y_i - \hat{y}_i)^2}$$

$$MAE = \frac{1}{N} \sum_{i=1}^{N} |y_i - \hat{y}_i|$$

from sklearn.metrics import mean_squared_error, mean_absolute_error import numpy as np rmse = np.sqrt(mean_squared_error(y_true, y_pred)) mae = mean_absolute_error(y_true, y_pred) print(f"RMSE: {rmse:.4f}, MAE: {mae:.4f}")

论文的对比结果里,聚类 LSTM 的 RMSE 平均为 5.59,MAE 平均为 4.40;标准 LSTM 的 RMSE 平均为 6.62,MAE 平均为 5.40;BP 网络最差,RMSE 平均 9.61,MAE 平均 7.66;ARIMA 的 RMSE 平均 8.37,MAE 平均 6.75。聚类 LSTM 比标准 LSTM 的 RMSE 降低了约 15.6%,这个提升幅度在电能质量预测场景里是有实际意义的。

4. 避坑与排查:聚类 LSTM 落地时最容易翻车的五个地方

4.1 聚类前不归一化,K-means 被大数值变量带偏

现象:聚类结果里某一类样本数特别多,其他类样本极少,LSTM 训练时某一类数据不够导致模型欠拟合。

原因:温度可能在 10 到 40 之间,光照强度可能在 0 到 4000 之间,负荷可能在 0 到 5000 之间,量纲差了两三个数量级。K-means 用欧式距离算相似度,数值大的变量会主导距离计算,聚类结果实际上只反映了负荷的分布,温度和光照的信息被淹没了。

解决:聚类前必须对每个变量单独做归一化,把所有变量压到 $[0,1]$ 或标准化到均值为 0、方差为 1。论文里明确写了“在进行归一化预处理后”再做聚类,这一步不能省。

4.2 训练集和测试集随机划分,时序信息泄漏

现象:测试集上的 RMSE 很低,但拿实际未来数据预测时误差很大。

原因:用train_test_split随机划分时间序列数据,会把未来时刻的数据混进训练集,LSTM 在训练时已经“看到”了未来信息,测试集评估结果虚高。

解决:按时间顺序划分,前 70% 做训练、后 30% 做测试。论文里明确写了“按照时间顺序划分为训练集与测试集”,这是时间序列预测的基本纪律。

4.3 预测时忘记判断聚类类别,直接调用单一模型

现象:预测结果在某些时段误差突然变大,但模型在测试集上表现正常。

原因:未来工况可能属于不同的聚类类别,如果只用其中一个类别的 LSTM 模型做预测,遇到其他类别的工况时预测性能会明显下降。

解决:预测前先用训练好的 K-means 模型对未来的环境因素和负荷数据做类别判断,再调用对应类别的 LSTM。如果未来数据跨越多个类别,需要分段预测。

4.4 LSTM 输入维度与series_to_supervised输出不匹配

现象:model.fit()时报错,提示输入维度不对。

原因:series_to_supervised返回的是 2D 数组,但 Keras 的 LSTM 层需要 3D 输入(samples, time_steps, features)。很多人忘了 reshape 这一步。

解决:在fit之前加X_train = X_train.reshape((X_train.shape[0], time_steps, n_features))。time_steps要和series_to_supervised里的n_in一致,n_features是变量个数。

4.5 反归一化用错极值,预测结果被“压缩”

现象:预测曲线看起来太平滑,波动幅度明显小于实际值。

原因:反归一化时用了测试集或预测数据自己的最大最小值,而不是训练集的极值。这样会把预测值强行拉回一个很小的范围,看起来误差小,实际上模型没有预测能力。

解决:归一化和反归一化必须用同一组极值,也就是训练集的最大最小值。把这组参数存下来,预测时直接调用。

5. 进阶技巧:用滑动窗口和场景聚类把预测精度再推一步

论文在结论里提到,后续可以按季节、节假日、天气类型等不同场景进行更合理的聚类划分。这个方向在实际项目里比论文里的基础版更实用,因为电能质量的季节性差异和天气敏感性都很明显。

我一般会做两件事。第一件是把固定窗口改成滑动窗口。论文里用 24 个时间步做输入,但如果数据采样频率更高,比如 15 分钟一个点,一天就是 96 个点,这时候用 96 做输入窗口比 24 更合理。滑动窗口的代码实现:

def sliding_window(data, window_size, step=1): """ 生成滑动窗口样本 data: 归一化后的时间序列 window_size: 窗口长度 step: 滑动步长 """ X, y = [], [] for i in range(0, len(data) - window_size, step): X.append(data[i:i + window_size, :-1]) # 输入特征 y.append(data[i + window_size, -1]) # 目标值 return np.array(X), np.array(y)

window_size根据数据采样频率和预测跨度来定,step控制窗口滑动步长,取 1 表示逐点滑动,取大于 1 的值可以降采样减少计算量。

第二件事是按场景分别聚类。比如先按季节把数据分成四组,每组内部再用轮廓系数法确定 K 值做 K-means,然后每组单独训 LSTM。这样做的代价是模型数量变多、训练时间变长,但预测精度通常能再提升一截。论文里 48 小时预测中聚类 LSTM 有 67% 的相对误差在 5% 以内,按场景细分后这个比例还能往上走。

验证方法上,我习惯用滚动预测的方式做交叉验证:用前 3 个月数据训模型,预测第 4 个月;再用前 4 个月训模型,预测第 5 个月,以此类推。这样能看出模型在不同时间段的稳定性,比单次划分训练集测试集更有说服力。

从那以后我每次做时间序列预测项目,都强制走一遍“先归一化、再按时间划分、聚类后分类训练、预测前判类别”的流程,少一步都可能在后期的实际部署里翻车。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 5:14:36

Rust+STM32+VS Code嵌入式开发环境搭建指南

1. 为什么现在要认真搭一套 Rust STM32 VS Code 的嵌入式开发环境?Rust 进入嵌入式领域不是赶时髦,而是解决了一类长期被容忍却极其危险的“慢性病”——内存安全漏洞在裸机环境下的不可控蔓延。我从 2015 年开始用 C 写 STM32 项目,踩过太…

作者头像 李华
网站建设 2026/10/2 5:13:34

QuickBlue AI应用底座:Spring Cloud + JDK 21微服务架构实战

1. 从一堆散装微服务到"AI 应用底座":QuickBlue 到底在解决什么问题如果你最近一年在折腾企业级 AI 应用落地,大概率会遇到一个很尴尬的局面:模型能力不缺,缺的是把模型能力"接进"现有业务系统的那层地基。我…

作者头像 李华
网站建设 2026/10/2 5:13:22

Vite构建失败:Rollup无法解析import路径的根因与修复方案

前几天帮一个同事排查 Vue3 Vite 项目的时候,遇到了一个相当典型的坑:本地开发npm run dev一切正常,页面访问、热更新都好好的,结果一到 CI 里执行npm run build,Rollup 就开始刷红报错。控制台最核心的一行大概是这样…

作者头像 李华
网站建设 2026/10/2 5:12:52

RAG系统拆解:离线建库与在线查询两条链路全流程实战

1. 为什么 RAG 值得拆成两条链路来看很多人第一次接触 RAG,脑子里浮现的画面是:把文档丢进去,问一个问题,模型就能答出来。这个画面没错,但它把中间最关键的工程结构给藏起来了。真正在生产环境里跑过 RAG 的人都知道&…

作者头像 李华
网站建设 2026/10/2 5:12:30

Agent匹配分与人工判断负相关?Spearman分析实战与优化

1. 一次反直觉的评测结果:当匹配分和人工判断背道而驰28 条 JD,跑完 Agent 匹配打分,再拉人工逐条评估,最后算出来的 Spearman 相关系数是ρ -0.085。这个数字第一次出现在我面前的时候,我盯着屏幕看了大概十秒钟&…

作者头像 李华
网站建设 2026/10/2 5:11:46

VMware VMCI驱动手动安装指南:报错原因与解决方案

前几天给一台 Windows 11 的模板机装 VMware Tools,Workstation 16 Pro 弹出这行提示:“安装程序无法自动安装 Virtual Machine Communication Interface(VMCI) 驱动程序。必须手动安装此驱动程序。”第一反应是安装包坏了,重装一遍还是原样&…

作者头像 李华