K-means聚类+LSTM:多工况传感器数据下的多输出回归实战
2026/8/27 11:01:19 网站建设 项目流程

最近在处理一组设备多传感器数据时,我遇到了一个典型问题:用LSTM做多输出回归,训练集loss一路降到0.05,验证集loss却长期停在0.2附近。一开始我怀疑是模型结构不够深,把LSTM神经元从32加到128,结果验证集loss反而更高。后来我把预测目标单独拆开看,才发现问题不在网络,而在数据本身:这批数据虽然按时间连续采样,但设备实际上会跨越三种完全不同的运行工况——正常负载、满载冲击、停机冷却。三种工况下,输入到输出的映射关系完全不同,混在一起用一个LSTM模型去拟合,模型只能学到一个两头都靠不住的折中结果。

后来我把流程改成“先用K-means对样本做状态聚类,再为每一类分别训练一个LSTM多输出回归模型”,问题才明显改善。这算是我处理时间序列多指标预测时,比较值得记录的一次改造。这篇文章就把这套思路的完整实战过程拆开讲一遍,包括为什么这样做、代码怎么写、参数怎么调,以及哪些地方最容易翻车。

1. 为什么单靠LSTM做多输出回归,常常不够

1.1 多输出回归不是“多一个输出神经元”这么简单

LSTM做多输出回归的典型写法,是在LSTM层之后接一个Dense层,输出维度等于目标数量。这种写法可以跑通,也经常出现在教程里。但真实项目里,多输出回归隐含着一个问题:任务期望一个模型同时学会多个目标变量与输入之间的映射。如果这几个目标变量之间的相对关系在不同状态下会变,一个模型很难同时让它们都准。

举例来说,设备振动和温度,在正常运行下,温度升高,振动可能保持在较低水平;在满载冲击下,温度继续升高,振动会突然变大。同一个输入时序特征,在不同状态下对应的输出关系不同。这个不同无法通过增加LSTM层数或神经元数量解决,因为问题不是模型容量不够,而是训练数据来自多个不同分布。

1.2 单模型隐藏了一个假设:数据属于同一个“运行模式”

如果只训练一个LSTM模型,它在计算损失时会把所有样本的误差平均起来。常见的现象是:

  • 某类样本数量多,模型偏向学习那一类;
  • 数量少的工况很容易被当成噪声;
  • 整体loss看起来可以接受,但分时间段看,某几个时段的预测结果明显偏离。

这在业务上是很大的隐患。工业生产中的异常工况往往就是少数样本,如果模型把它们忽略了,那这套预测就基本失去意义。你真正想捕捉的恰恰是少数状态下的变化规律,而不是把多数状态的平均表现优化得漂亮。

1.3 什么情况下应该考虑“聚类+分组建模”

不是所有多输出回归都需要用K-means。如果数据来源单一,工况稳定,一个LSTM足够。出现下面几个信号,可以试试:

  • 把验证集按时间段切块评估,误差忽高忽低;
  • 某些输入组合下,预测值长期偏离一个固定偏移;
  • 业务上本身就能列举出多种运行模式;
  • 样本量足够,按模式拆分后每个子集不会太小。

这些信号说明,问题不是“LSTM不够努力”,而是数据里天然存在多个模式,需要先切分。这也是K-means容易被忽视、但在实践中往往很有效的理由。

2. K-means在这里到底起什么作用

2.1 它不是对“要预测的目标”聚类,而是对“状态特征”聚类

一个常见误区是,看到“聚类+LSTM”,以为是把标签y或者预测目标聚成几类,然后对每一类做分类问题。这里不是。

我们要聚类的是“描述当前运行状态的样本特征”。对时间序列来说,就是每个滑动窗口内提取出来的统计量,比如最近5个时间步里温度的平均值、标准差、最大值、最小值,振动最后时刻的值等。K-means会把这些样本分成K组,每一组对应一种相似的历史状态。然后再看这些分组有没有业务含义,比如是不是分别对应正常、满载、待机。

2.2 K-means和LSTM的三种结合方式

  • 方式一:先聚类,再分组建模。对每个簇单独训练LSTM,预测时先用KMeans判断新样本属于哪个簇,再调用对应模型。这是本文采用的方式,逻辑最直接,也最容易排查问题。
  • 方式二:把簇编号作为额外输入特征。整体只训练一个LSTM,输入除了原始窗口序列,再加入簇ID的向量。适合不想维护多个模型的情况,但簇的分类信息对LSTM来说只是辅助,模型仍需要在一套参数中同时拟合所有模式。
  • 方式三:用聚类结果做预测加权融合。预测时不硬性选择某一个模型,而是根据样本与各簇中心的距离,把多个模型的预测结果做加权。效果可能更平滑,但整体链路复杂,调试成本高。

如果你只想要一个能快速落地的方案,优先尝试方式一。不要一上来就做加权融合。

顺带提一句:搜LSTM相关材料时,常会看到social LSTM。它主要解决多个移动对象之间的交互建模,比如行人轨迹预测,和这里讨论的多输出回归不是同一个问题。如果你要做的是行人相对位置预测,social LSTM才值得关注;如果只是在做设备多指标回归,那不需要引入交互建模。

2.3 为什么选K-means而不是其他聚类

K-means在工业场景里最实用的原因是:可解释、参数少、速度快、结果稳定。它不需要复杂度很高的概率模型,也没有太多超参数需要调。

但它也有短板:

  • K值必须提前指定;
  • 对噪声和离群点敏感;
  • 只能发现凸形簇;
  • 对初始化敏感,需要固定随机种子并多次运行。

如果数据状态是连续变化的,比如“负载程度从0到100%平滑变化”,K-means切出来的边界可能是强制的,这时不如先用一个LSTM整体建模,再考虑按负载区间做分段建模。

2.4 这个方案真正改变的是什么

从工作流来看,它把“一个模型负责所有情况”改成“先判断当前处于什么状态,然后让更专注的模型负责预测”。换句话说,K-means不是用来提升LSTM精度的魔法,而是把复杂任务拆解成若干个小任务。拆解之后,每个LSTM模型只需要学一种运行规律,训练和目标都更清晰。

这种工作方式还有一个隐藏好处:当某种新状态出现时,不需要重新训练全套模型。可以先判断新样本是不是明显落在已知簇之外,如果是,再考虑新增一个簇,用新数据单独训练一个模型。从长期维护看,这比反复训练一个大模型更容易控制。

3. 完整实战:K-means聚类+LSTM多输出回归的六步流程

3.1 场景和数据约定

我用一个比较常见的场景来说明:设备每隔一定时间记录一组传感器数据,包括温度、振动、压力三个连续指标。现在要根据最近5个时刻的3个指标,预测下一时刻的3个指标。这是一个典型的多输出回归问题,因为一个样本要输出3个连续值。

下面代码是可以在常见Python 3.9/3.10环境中运行的示例结构。TensorFlow和scikit-learn的版本以你本机实际环境为准,不建议追求最新版本,稳定API即可。

import numpy as np import pandas as pd from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense from tensorflow.keras.callbacks import EarlyStopping

3.2 第一步:把原始时间序列构造成滑动窗口样本

假设原始数据是一个二维数组raw_data,每一行是一个时刻,三列分别是三个指标。用窗口长度为input_steps构造样本:

def make_samples(raw_data, input_steps=5): X, y = [], [] for i in range(len(raw_data) - input_steps): X.append(raw_data[i:i + input_steps]) y.append(raw_data[i + input_steps]) return np.array(X), np.array(y)

这样,每个样本的输入形状是(input_steps, 3),输出形状是(3,)y是下一时刻的三个指标。如果你希望预测未来多个时刻,可以把y改成未来若干个时刻的拼接,输出维度会变成(3 * horizon,)。这里先以最简单的“下一步预测”为例。

3.3 第二步:提取聚类特征,做K-means聚类

这里的核心是,聚类特征只能从当前窗口内的历史信息中提取,不能使用未来时刻的数据。一个简单可用的特征是每个指标在窗口内的均值、标准差、最大值减最小值、最后时刻值:

def extract_cluster_features(X): feature_list = [] for window in X: row = [] col_count = window.shape[1] for c in range(col_count): col = window[:, c] row.extend([ col.mean(), col.std(), col.max() - col.min(), col[-1], ]) feature_list.append(row) return np.array(feature_list)

然后做标准化和聚类:

cluster_features = extract_cluster_features(X) cluster_scaler = StandardScaler() cluster_features_scaled = cluster_scaler.fit_transform(cluster_features) kmeans = KMeans(n_clusters=3, random_state=42, n_init=10) cluster_labels = kmeans.fit_predict(cluster_features_scaled)

这里n_clusters=3对应业务上可能存在的三种工况。先按业务判断给定一个值,再用轮廓系数等指标验证,比纯粹依赖肘部法则更可靠。

注意:K-means的目标是发现不同运行状态,而不是直接优化LSTM的预测误差。聚类特征必须只依赖历史窗口,否则预测阶段根本拿不到同样的信息。

3.4 第三步:为每个簇切分数据并标准化

聚类完成后,cluster_labels给每个样本分配了一个簇ID。接下来把X和y按簇拆分,并对每个簇分别做LSTM输入的标准化。

这里有一个非常容易踩坑的点:如果一开始就对全部数据做标准化,然后用标准化后的数据训练,等于让所有簇共用了同一个输入分布。在预测阶段,新样本进来后,如果它被分到某个簇,理应用该簇自己的输入scaler,而不是全局scaler。更稳妥的做法是,聚类特征和LSTM输入各用一套标准化,并且每个簇维护自己的LSTM输入scaler。

下面是一个完整的按簇切分、标准化、训练模型的过程:

cluster_models = {} cluster_scalers = {} for cluster_id in range(kmeans.n_clusters): idx = np.where(cluster_labels == cluster_id)[0] if len(idx) < 100: print(f"cluster {cluster_id} 样本太少,跳过或使用兜底模型") continue X_cluster = X[idx] y_cluster = y[idx] X_scaler = StandardScaler() original_shape = X_cluster.shape X_cluster_2d = X_cluster.reshape(-1, original_shape[-1]) X_cluster_scaled = X_scaler.fit_transform(X_cluster_2d).reshape(original_shape) cluster_scalers[cluster_id] = X_scaler split_pos = int(len(X_cluster) * 0.8) X_train, X_val = X_cluster_scaled[:split_pos], X_cluster_scaled[split_pos:] y_train, y_val = y_cluster[:split_pos], y_cluster[split_pos:] model = build_lstm(input_steps=5, n_features=3, output_dim=3) model.fit( X_train

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询