ARTICLE DETAIL

资讯详情

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

K-means聚类+LSTM:多工况传感器数据下的多输出回归实战

K-means聚类+LSTM:多工况传感器数据下的多输出回归实战 最近在处理一组设备多传感器数据时我遇到了一个典型问题用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 EarlyStopping3.2 第一步把原始时间序列构造成滑动窗口样本假设原始数据是一个二维数组raw_data每一行是一个时刻三列分别是三个指标。用窗口长度为input_steps构造样本def make_samples(raw_data, input_steps5): 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_clusters3, random_state42, n_init10) cluster_labels kmeans.fit_predict(cluster_features_scaled)这里n_clusters3对应业务上可能存在的三种工况。先按业务判断给定一个值再用轮廓系数等指标验证比纯粹依赖肘部法则更可靠。注意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(fcluster {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_steps5, n_features3, output_dim3) model.fit( X_train
返回列表