ARTICLE DETAIL

资讯详情

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

DTW-Kmeans与Transformer-GRU结合:多变量时间序列预测精度提升新路径

DTW-Kmeans与Transformer-GRU结合:多变量时间序列预测精度提升新路径 简介面向工业物联网、金融量化、能源调度与智慧城市等场景的多变量时间序列预测这份docx文档完整呈现了DTW-KMeans聚类算法与Transformer-GRU组合模型的实战项目。方案先以动态时间规整衡量序列形状相似性完成聚类再通过Transformer编码器与GRU回归头分层建模兼顾异步对齐、非线性与非平稳特征适合具备一定深度学习基础的研发人员。内容涵盖数据预处理、模型构建与训练、性能评估、GUI设计以及部署应用全流程并对异步采样缺失值处理、DTW计算复杂度控制、聚类数量与模型容量选择等工程难点给出针对性解决方案。资源为单个docx文件压缩包约81KB已有79人学习。文档目录清晰从项目背景、模型架构、聚类先验路由到推理监控逐层展开可帮助读者快速复用这套“聚类先行、分布感知、深度细化”的预测范式作为多变量回归与组合深度学习的落地参考。1. 用DTW-Kmeans给Transformer-GRU分簇多变量预测精度上不去的另一条路如果你手里有一批多变量时间序列比如电力负荷、股价五维特征、传感器温压数据直接丢进Transformer-GRU组合模型训练往往会发现测试误差一直下不来预测曲线像是把所有样本“平均”了一遍。问题多半不在模型容量而在于序列形态差异太大有的陡升陡降有的缓慢波动有的带明显周期。用动态时间规整DTW做序列聚类把形态相似的窗口分到同一簇再在每个簇上单独训练Transformer-GRU组合模型很多场景能把RMSE压掉10%到20%。这套方案不挑行业适合所有“样本不算多但形态复杂”的多变量回归预测。接下来从为什么非DTW不可讲起逐步落到能跑的Python代码和具体的坑。2. 序列聚类为什么非DTW不可欧氏距离的局限与DBA质心平均2.1 相位偏移与局部伸缩欧氏距离在时间序列上翻车的根源Kmeans的核心是距离度量加质心更新。对普通向量欧氏距离简单直接但时间序列有平移、伸缩和噪声两个形状几乎完全相同、只是相位错开的序列欧氏距离会非常大。多变量场景更麻烦每个维度的弯曲位置不一样逐点相减再求和结果基本被相位差和噪声主导聚类出来的标签和直觉完全对不上。用一个最小例子说明。生成两条正弦波相位相差90度形态一模一样只是左右错开import numpy as np from fastdtw import fastdtw t np.linspace(0, 4 * np.pi, 200) s1 np.sin(t) s2 np.sin(t np.pi / 2) euclid np.sqrt(((s1 - s2) ** 2).sum()) dtw_dist, path fastdtw(s1, s2) print(欧氏距离:, round(euclid, 2)) print(DTW距离:, round(dtw_dist, 2))fastdtw的第一个返回值是累计距离第二个是路径列表。dist参数默认用欧氏距离计算两个点之间的代价这里没有显式传所以是按点值做的。跑完后你会看到欧氏距离是DTW距离的好几倍但这两条曲线本质是同一个波形。这就是为什么直接拿欧氏距离跑Kmeans时时间序列聚类总是不符合直觉。DTW的思想是允许一个点对应另一条序列的多个点通过动态规划寻找累计代价最小的对齐路径。每条路径从矩阵左上走到右下就定义了一种非线性弯折。复杂度是O(n*m)n和m是两条序列长度长序列直接算很慢FastDTW用多层粗化加细化把复杂度降到接近线性工程上够用。多变量情况下tslearn内部会把每个特征维的代价加总再套同一套动态规划。你也可以在fastdtw里传入自定义dist函数比如对三维向量计算曼哈顿距离。点间度量的选择会影响聚类结果通常欧氏距离就够但各维尺度差异大时得先做标准化。2.2 Kmeans的质心问题DBA动态时间规整平均传统Kmeans更新质心时对簇内样本逐点求平均。但DTW下序列长度可能不同逐点平均没有意义而且即便长度相同逐点平均也会把相位信息磨平。tslearn里的TimeSeriesKMeans用的质心更新方式是DBA全称DTW Barycenter Averaging。它不采用算术平均而是构造一条“质心序列”使得它到簇内所有序列的DTW距离平方和最小。迭代过程大致是先随机选一条序列作为初始质心然后对每个簇内样本求DTW对齐路径把质心上的每个点映射到对应的多个样本点再把这些点的均值作为质心新值重复到收敛。tslearn的调用非常短from tslearn.clustering import TimeSeriesKMeans kmeans TimeSeriesKMeans(n_clusters3, metricdtw, n_init2, max_iter50, random_state42) labels kmeans.fit_predict(X_3d)X_3d是形状为(n_samples, n_timestamps, n_features)的三维数组。metricdtw告诉模型用动态时间规整质心自然走DBA。n_init是随机初始化的次数每次跑完后保留损失最小的结果建议设2到4max_iter是单次迭代上限50对大多数数据够用。random_state保证你能复现同一次划分。为什么序列聚类仍然可以选Kmeans而不是密度聚类因为Kmeans每次迭代只需要计算每个样本到K个质心的距离不需要预先算全量两两距离矩阵。样本量到几千时DBSCAN类方法要n^2的距离矩阵直接爆内存Kmeans的n*K次距离计算反而可行。代价是它对噪声敏感而且质心都是DBA迭代算出来的比普通Kmeans慢得多。序列长度超过500后这种慢会被放大第5章会讲压缩手段。还有一点容易被忽略Kmeans假设簇的分布比较均匀如果时间序列形态本身极度不平衡比如90%是缓慢上升、10%是剧烈震荡Kmeans会因为距离量纲的问题把大簇再切一刀。我一般会先跑一版聚类看簇样本量分布如果出现明显的大簇吞小簇就得检查标准化策略而不是急着调K。后续的3.1节会针对这个问题给一个可复现的处理方式。3. Python数据准备与DTW-Kmeans聚类从滑窗到簇划分的可执行代码3.1 构造多变量回归样本滑窗切片与多步标签假设原始数据是一张没有索引的二维表行按时间排列列是多种特征其中一列或多列是预测目标。要做过去L步预测未来H步最常见做法是滑窗每个输入样本是连续L行特征标签是紧接其后的H行目标。下面的函数把这种转换封装好import numpy as np def sliding_window(data, input_cols, target_cols, lookback, horizon): X, y [], [] n len(data) for start in range(n - lookback - horizon 1): X.append(data[start:start lookback, input_cols]) y.append(data[start lookback:start lookback horizon, target_cols]) return np.array(X), np.array(y)data是二维numpy数组input_cols和target_cols是列索引列表lookback是历史窗口长度horizon是预测步数。比如data有10000步lookback取48horizon取12那么X的每个元素是48行输入特征y的每个元素是12行目标值形状分别为(n_samples, 48, 输入特征数)和(n_samples, 12, 目标列数)。这种形状正好能被tslearn和Keras接受。滑窗时要注意两个问题。一是步长相邻窗口如果完全重叠样本之间的自相关极强聚类和训练都容易过拟合到重叠信息完全不重叠又会损失样本量。我一般让相邻窗口重叠50%也就是步长取lookback//2。二是切分必须先沿时间划分训练集和测试集再分别滑窗绝不能先滑窗再随机打乱。如果把未来的窗口混进训练集验证指标会虚高上线后立刻露馅。多个独立序列的场景还要按序列ID分组切分详见5.5。标准化要在滑窗之后做但只能在训练集上fit。这里有两种标准化容易混聚类的标准化建议用样本内独立z-score即每个窗口减自身均值除自身标准差这样聚类只关心形态不受绝对数值影响而训练Transformer-GRU用的标准化建议合并所有训练样本做全局StandardScaler保留不同窗口之间的绝对尺度。tslearn的TimeSeriesScalerMeanVariance专门做第一种代码如下from tslearn.preprocessing import TimeSeriesScalerMeanVariance scaler TimeSeriesScalerMeanVariance(mu0., std1.) X_cluster scaler.fit_transform(X_train)mu和std是每个样本缩放后的目标均值和标准差设0和1代表每窗口独立标准化。fit_transform只作用于训练集测试集预测新样本时调用scaler.transform即可。注意这个scaler和后面训练模型用的StandardScaler是两套对象别混。3.2 用TimeSeriesKMeans实现DTW序列聚类把滑窗后的X_train交给TimeSeriesKMeans它对形状(n_samples, n_timestamps, n_features)直接处理。这里的关键是聚类对象是“窗口”而不是整条长序列。窗口级的含义是每个局部形态都被单独归类后续模型能针对不同局部模式分别抓规律。from tslearn.clustering import TimeSeriesKMeans import numpy as np kmeans TimeSeriesKMeans(n_clusters3, metricdtw, n_init2, max_iter30, random_state42) labels kmeans.fit_predict(X_cluster) unique, counts np.unique(labels, return_countsTrue) print(dict(zip(unique, counts)))TimeSeriesKMeans的metric参数除dtw外还有softdtw和euclidean。softdtw是可微版本速度略快但需要额外参数第一版先用标准dtw。n_init和max_iter按数据量调整窗口数量几千时n_init2、max_iter30足够。cluster结果如果出现某个簇只有十几个样本后面对应模型的训练集就太小此时要么增大K要么调整标准化让簇分配更均衡。聚类完成之后需要按簇拆分训练集和测试集做法很直接X_by_cluster [] y_by_cluster [] for cid in range(kmeans.n_clusters): idx np.where(labels cid)[0] X_by_cluster.append(X_train[idx]) y_by_cluster.append(y_train[idx])这里用kmeans.n_clusters而不是硬编码3避免后面改参数时漏改。labels是模型直接输出的整数数组长度等于n_samples。拆分后X_by_cluster和y_by_cluster长度相同每个元素是一个二维或三维数组。后面训练模型时只需要遍历这两个列表。3.3 选K的实用方法肘部法则与样本量下限序列聚类的轮廓系数计算成本高因为每个样本到所有样本的DTW距离都算一遍才能得到轮廓值几千窗口根本跑不动。更实际的选K方法是画“簇内平均距离”的肘部曲线。TimeSeriesKMeans的transform方法返回每个样本到每个质心的DTW距离矩阵取每行的最小值就是该样本到所属簇的距离。import matplotlib.pyplot as plt K_range range(2, 8) inners [] for k in K_range: km TimeSeriesKMeans(n_clustersk, metricdtw, n_init1, max_iter20, random_state42) km.fit(X_cluster) dist_to_center km.transform(X_cluster).min(axis1) inners.append(dist_to_center.mean()) print(fK{k}, 簇内平均DTW距离{inners[-1]:.4f}) plt.plot(list(K_range), inners, markero) plt.xlabel(K) plt.ylabel(簇内平均DTW距离) plt.show()肘部曲线下降变缓的位置是合理K。别只追求曲线拐点后继续增大K因为每个簇样本量会被切薄。比如总窗口数量是3000K5时每簇平均600个K8时每簇只有370个如果某个簇只分到150个Transformer-GRU这种容量不小的模型很容易过拟合。实际选择时我会取肘部右侧一位同时保证最小簇样本量不低于batch_size的10倍。如果你发现某一簇的样本量明显偏少不要急着换K先看这些窗口是不是形态过于特殊。可以打印几个质心序列看形状确认是真实模式还是噪声。噪声主导的小簇可以直接并入距离最近的大簇合并方式是把该簇样本标签改为最近质心所属簇再重新训练模型。4. Keras实现Transformer-GRU组合模型分簇训练多变量回归预测器4.1 模型结构Transformer编码层加GRU层为什么不是相反很多人看transformer模型详解时注意力集中在多头注意力的矩阵计算上却忽略了它在时序回归里的一个短板位置编码是绝对的对局部连续变化不敏感。GRU擅长捕捉短期的上升下降趋势但长序列上容易遗忘早期信息。把两者串起来先用Transformer编码层处理整段输入得到每个时间步携带全局依赖的上下文表示再把这个表示序列交给GRU让GRU在全局背景下提取局部演化规律最后输出未来H步预测。结构依次是输入特征经过Dense层映射到d_model维加上正弦位置编码再接一个MultiHeadAttention自注意力层带残差和LayerNorm然后GRU只输出最后一个时间步的隐藏状态最后通过Dense输出horizon乘out_dim个数reshape成(batch, horizon, out_dim)。如果去掉GRU直接对Transformer输出做全局池化短期预测会明显钝化如果去掉Transformer只留GRU长序列的滞后问题又会回来。组合的价值正在于此。基于Keras的实现如下import tensorflow as tf from tensorflow.keras import layers, Model class PositionalEncoding(layers.Layer): def __init__(self, d_model): super().__init__() self.d_model d_model def call(self, inputs): seq_len tf.shape(inputs)[1] pos tf.range(seq_len, dtypetf.float32)[:, None] i tf.range(self.d_model, dtypetf.float32)[None, :] angle pos / tf.pow(10000.0, (2 * (i // 2)) / self.d_model) pe tf.where(i % 2 0, tf.sin(angle), tf.cos(angle)) return tf.cast(pe, inputs.dtype) inputs def build_combined_model(feature_dim, d_model64, num_heads4, gru_units32, horizon1, out_dim1): inputs tf.keras.Input(shape(None, feature_dim)) x layers.Dense(d_model)(inputs) x PositionalEncoding(d_model)(x) attn layers.MultiHeadAttention(num_headsnum_heads, key_dimd_model // num_heads) attn_out attn(x, x) x layers.Add()([x, attn_out]) x layers.LayerNormalization(epsilon1e-6)(x) x layers.GRU(gru_units, return_sequencesFalse)(x) x layers.Dense(horizon * out_dim)(x) outputs layers.Reshape((horizon, out_dim))(x) model Model(inputs, outputs) return modelPositionalEncoding实现的是经典sin/cos位置编码d_model必须能被num_heads整除否则key_dim算出来是小数Keras会直接报错。MultiHeadAttention的key_dim是每个头的投影维度令它等于d_model//num_heads让不同头关注不同子空间。attn(x, x)表示query、key、value都是同一个输入即自注意力。残差和LayerNorm放在注意力之后是Transformer标准做法。GRU的return_sequencesFalse表示只输出最后一个时间步的隐藏状态作为整个序列的摘要后续Dense层拿这个摘要生成未来预测。有个容易被忽略的点滑窗数据都是等长的所以这里不需要mask。如果以后换成不定长序列用padding补齐必须给MultiHeadAttention手动传mask否则注意力会看到padding位置。Keras的GRU本身能自动继承mask但Attention层不会。4.2 按簇训练模型时间顺序切分、编译与保存训练各簇模型时一定要按时间顺序切分训练集和验证集。随机切分会把未来窗口混进训练指标好看但没有参考价值。下面的函数接收第3章产出的X_by_cluster和y_by_cluster逐簇构建模型并训练def train_by_clusters(X_by_cluster, y_by_cluster, d_model64, num_heads4, gru_units32, horizon1, out_dim1, epochs30, batch_size64): cluster_models {} for cid, (Xc, yc) in enumerate(zip(X_by_cluster, y_by_cluster)): split int(len(Xc) * 0.8) X_tr, X_val Xc[:split], Xc[split:] y_tr, y_val yc[:split], yc[split:] model build_combined_model(feature_dimX_tr.shape[2], d_modeld_model, num_headsnum_heads, gru_unitsgru_units, horizonhorizon, out_dimout_dim) model.compile(optimizertf.keras.optimizers.Adam(learning_rate1e-3), lossmse, metrics[mae]) history model.fit(X_tr, y_tr, validation_data(X_val, y_val), epochsepochs, batch_sizebatch_size, verbose1) cluster_models[cid] model return cluster_modelssplit取整后Xc[:split]是较早的窗口Xc[split:]是较晚的窗口。如果数据来自多条独立序列这里不能直接按行切而应按序列ID分组保证同一序列的尾部窗口不会出现在训练集、头部窗口出现在验证集。Adam初始学习率1e-3对Transformer编码层来说偏大如果loss震荡先看第5.3节。训练结束后保存模型可以用model.save(fcluster_{cid}.h5)。加载时因为自定义了PositionalEncoding层需要传custom_objects更省事的做法是用model.export(cluster_0)新版TensorFlow直接把整层代码打包加载不需要额外参数。每簇样本量不同训练epochs可以相同但batch_size建议维持一致避免某些簇因样本太少导致每个epoch步骤数过少。实际项目里我会先看每簇样本量最少的那一簇适当增大epochs或做轻微数据增强。时间序列增强常用缩放和加噪声但要注意不能破坏窗口的时间顺序。4.3 多变量回归预测评估RMSE、MAE与分步误差曲线每个簇的模型训练完成后在对应验证集上评估。多步预测的输出是三维数组sklearn的指标大多不接受三维需要先reshapefrom sklearn.metrics import mean_squared_error, mean_absolute_error def evaluate_clusters(cluster_models, X_by_cluster, y_by_cluster): for cid, model in cluster_models.items(): y_pred model.predict(X_by_cluster[cid], verbose0) y_true y_by_cluster[cid] y_pred_flat y_pred.reshape(-1, y_pred.shape[-1]) y_true_flat y_true.reshape(-1, y_true.shape[-1]) rmse np.sqrt(mean_squared_error(y_true_flat, y_pred_flat)) mae mean_absolute_error(y_true_flat, y_pred_flat) eps 1e-6 mape np.mean(np.abs((y_true - y_pred) / (np.abs(y_true) eps))) * 100 print(fcluster {cid}: RMSE{rmse:.4f}, MAE{mae:.4f}, MAPE{mape:.2f}%)MAPE对接近零的真实值非常敏感如果目标序列本身有零值MAPE会变大到失去意义这时候改用SMAPE或MASE更合适。多步预测还应该按预测步单独看误差下面的代码计算未来H步各自的RMSEstep_rmse np.sqrt(((y_true - y_pred) ** 2).mean(axis(0, -1))) print(Each horizon RMSE:, step_rmse)如果输出是[0.10, 0.15, 0.22, 0.31]这样的递增序列说明模型对远期预测确实更难这是正常的。但如果你看到最后一步误差突然比前一步大一倍以上下一步要检查训练标签是否构造错误或者模型是否在输出历史均值。真正合格的组合模型误差增长通常是缓慢的不会在第1步到第4步之间直接翻三倍。5. 避坑笔记DTW-KmeansTransformer-GRU的5个翻车现场与解决参数5.1 坑DTW距离计算慢到跑不完聚类半小时没结果现象是TimeSeriesKMeans.fit无限等待训练集只有几千个窗口也迟迟不返回。原因在于每次迭代要计算每个样本到每个质心的DTW距离质心更新又要跑多轮DBA序列长度一旦超过1000复杂度立刻上来。解决办法是下采样和加约束窗。下采样就是每隔一个点取一个比如原本序列长度96下采样到48聚类结果通常不变DTW本身是按全局形态对齐的局部细节砍掉一半影响不大。约束窗用Sakoe-Chiba bandkmeans TimeSeriesKMeans( n_clusters3, metricdtw, metric_params{sakoe_chiba_global_constraint: True, sakoe_chiba_radius: 16}, n_init1, max_iter20, random_state42)sakoe_chiba_radius是带宽单位是时间步。设16意味着任一时刻的点最多只能对齐到另一条序列偏移16个位置范围内的点超出范围直接不允许。这个参数先试序列长度的10%如果聚类结果和原来差不多就保持能省一半以上计算量。n_init设1避免重复随机初始化速度翻倍。窗口约束还有一个额外好处过滤掉那些距离很远但碰巧对齐的顽固样本让聚类更稳健。5.2 坑聚类后预测误差不降反升还不如一个全局模型现象是加了DTW-Kmeans之后每个簇单独训练验证误差反而比不聚类更大。原因有两层一是数据本身可能同质强行分簇只是把样本切薄模型每个簇的数据量不足二是聚类用的输入特征和预测目标不一定相关两个簇的形态差异明显但目标分布几乎一样分簇就白做了。解决是先跑一个不聚类的全量Transformer-GRU做基线再来看分簇收益。然后检查每个簇的目标值分布简单做一步for cid in range(kmeans.n_clusters): yc y_by_cluster[cid] print(cid, np.mean(yc), np.std(yc), len(yc))如果不同簇的y均值、标准差差异很小说明聚类没有为目标预测提供有效切分此时应该改用聚类特征增强方式而不是硬分簇训练多个模型。第6.3节的方案就是给全量模型拼接聚类距离特征效果通常比硬分簇稳定。5.3 坑Transformer加GRU训练时loss震荡验证集不收敛现象是训练前几百步loss剧烈摆动到后面也停不下来。原因是Transformer的注意力层对学习率过于敏感固定1e-3的Adam在浅层时可能发散加上多变量特征尺度不统一即使做了StandardScalerDense层输入的梯度也会抖动。解决是用学习率衰减和梯度裁剪lr_schedule tf.keras.optimizers.schedules.ExponentialDecay( initial_learning_rate1e-3, decay_steps500, decay_rate0.96) optimizer tf.keras.optimizers.Adam(learning_ratelr_schedule, clipnorm1.0) model.compile(optimizeroptimizer, lossmse, metrics[mae])ExponentialDecay每500步衰减4%能缓解后期震荡。clipnorm1.0把梯度的L2范数裁剪到1GRU虽然比LSTM温和但长序列上梯度爆炸仍然可能发生。如果loss还是不稳把初始学习率降到5e-4或者扩大batch_size。还有一个很多人踩的坑不要一上来就加多层Transformer编码层。一层自注意力对大多数回归任务已经够用层数加深只会让训练更脆。5.4 坑多步预测后面几步输出接近均值曲线像“冬眠”现象是第1步预测有起伏第2到第H步逐渐变成一条水平线。原因通常是标签构造时采用了递归预测方式模型在训练时看到的都是上一步预测作为下一步输入误差被一步步放大最后学会输出一个保守的均值以降低远期loss。更重要的一种情况是模型输出的多步维度与标签维度不一致loss在reshape时被错误平均模型被迫对远期目标采取最保险的策略。解决方式是确认训练标签是直接多步输出y[i]存的是未来H步真实值模型一次输出H步不做递归。然后在损失函数里给近期步更高权重weights np.linspace(1.5, 0.5, horizon) def weighted_mse(y_true, y_pred): return tf.reduce_mean(tf.square(y_true - y_pred) * weights)传给compile的loss时用weighted_mse。这样模型会优先把近期步拟合准远期步随权重自然放松。注意weights要在loss函数外定义好如果horizon是1这个函数退化成语义MSE没有副作用。5.5 坑验证集指标漂亮上线后偏差大到没法用现象是本地按KFold随机切分出来的RMSE很低实际部署时误差翻倍。原因是时间序列窗口之间天然有自相关随机切分后训练集和验证集会出现同一个长序列的相邻窗口模型实质上见过验证集的“近亲”信息。解决是严格按时间顺序切分验证集必须在时间上晚于训练集所有窗口。遇到多条独立序列时还要按序列ID分组切分绝不能让同一设备的窗口同时落在两侧group_ids np.array([seq_id[i] for i in range(n_samples)]) # 按时间顺序取后20% group_id作为验证集 val_mask group_ids group_ids_sorted[int(len(group_ids) * 0.8)] train_mask ~val_mask这段示意代码强调一个原则分组切分按业务实体走而不是按行走。聚类本身也可能加速泄露因为聚类把不同设备中形态相似的窗口聚到同一簇训练集和验证集如果来自同一设备的不同时段模型就很容易“记住”设备的特征。所以我在实践里先分组再滑窗滑窗切分后把组ID传下去每一步都带着它做评估。6. 进阶两段式簇推断、滚动验证与聚类特征扩展6.1 新样本到达时先选簇再预测推断阶段与训练阶段不同新来一个窗口要先用kmeans找它属于哪个簇再调用对应模型预测。tslearn的scaler同样要保存顺序是先样本内标准化再predictdef predict_new(kmeans, scaler, cluster_models, X_new): X_scaled scaler.transform(X_new) cid kmeans.predict(X_scaled)[0] return cluster_models[cid].predict(X_new, verbose0)注意这里不能对新样本做fit_transform因为scaler已经在训练集上fit过。TimeSeriesScalerMeanVariance是逐样本独立标准化fit阶段几乎没有统计量但为了保证API一致你仍然要复用同一个scaler对象。如果你在聚类前用的是StandardScaler那必须保存fit后的均值方差推断时用transform而不是重新fit。6.2 用滚动时间序列交叉验证替代随机切分滚动验证模拟真实上线过程在测试区间中逐窗口滑动每次都只看没被训练过的窗口。实现简单但能测出模型在时间漂移面前的稳定表现def rolling_validation(X, y, kmeans, scaler, cluster_models, test_start, test_len): preds, trues [], [] for i in range(test_start, test_start test_len): X_new X[i:i1] y_new y[i:i1] cid kmeans.predict(scaler.transform(X_new))[0] pred cluster_models[cid].predict(X_new, verbose0) preds.append(pred[0, 0, 0]) trues.append(y_new[0, 0, 0]) return np.array(preds), np.array(trues)这个循环里没有重新训练模型验证的是固定模型在新数据上的表现。如果你想模拟模型定期更新可以把循环改成每滑N步重训一次但成本会很高。我通常先把固定模型滚一遍和单模型基线做对比如果分簇方式在滚动验证下没有正向收益就果断放弃多模型策略。做这个对比时要保证两种方案使用完全相同的滚动窗口和损失指标。6.3 把簇ID和质心距离作为特征扩展除了硬分簇还可以把聚类信息拼进输入特征让全量模型感知当前窗口与历史典型形态的距离。这一步相当于给Transformer-GRU加了一个先验门控在样本量不足时往往比硬分簇更稳。dist_to_centers kmeans.transform(X_cluster) dist_feat np.repeat(dist_to_centers[:, None, :], X_train.shape[1], axis1) X_aug np.concatenate([X_train, dist_feat], axis-1)dist_to_centers形状是(n_samples, n_clusters)每个样本到每个质心的DTW距离。np.repeat把它沿时间轴复制使每个时间步都携带当前窗口到质心的编码。原始输入特征维度F变成Fn_clusters其余结构和4.1的build_combined_model完全一致。训练时用X_aug预测新样本时也要用同一个kmeans.transform生成距离再拼接。这种方式的好处是不需要维护多个模型一个模型全搞定缺点是聚类误差会直接传导进模型输入所以聚类本身要稳。我的习惯是先用6.2的滚动验证跑一个不加聚类特征的全量模型做基线再跑分簇多模型最后试聚类特征增强。每次只加一个变量看增量收益是否在5%以上。如果只有特征增强提升而分簇模型没提升说明硬分簇过于武断边界样本在两个簇之间来回切换反而吃亏。这套检查逻辑帮我在多个项目里避免了为复杂度而复杂度的方案。希望帮到你。本文还有配套的精品资源点击获取
返回列表