tslearn时间序列KMeans聚类报x/y数组至少需2个条目错误
问题说明
使用tslearn库的TimeSeriesKMeans对110个文件的时间序列做基于DTW的聚类,遍历2、3、4三类聚类数,通过轮廓系数选最优结果。数据编码固定为-1(否)、0(不确定)、1(是),不能替换NaN值,无需归一化。处理部分文件时抛出错误:ValueError: x and y arrays must have at least 2 entries,尝试过对to_time_series_dataset(x)输出做/不做squeeze操作都无法解决。
执行squeeze后的样例数据:
[[ 0. 1. -1. nan] [-1. 1. 0. -1.] [ 0. -1. nan nan] [ 0. 0. -1. nan] [ 0. 1. 0. -1.] [ 0. -1. nan nan] [ 0. -1. -1. nan] [ 0. 0. -1. nan] [ 0. -1. nan nan] [ 0. -1. nan nan] [ 0. 0. -1. nan] [-1. -1. nan nan] [ 1. 1. -1. nan] [ 1. -1. nan nan] [ 0. -1. nan nan] [ 1. -1. nan nan] [ 0. -1. -1. nan] [ 0. -1. nan nan] [ 1. -1. nan nan] [ 0. 0. -1. nan] [ 0. -1. -1. nan] [ 0. 1. -1. nan] [ 0. 0. -1. nan] [ 1. -1. nan nan]]
不执行squeeze的标准tslearn三维输入格式样例:
[[[ 0.] [ 1.] [-1.] [nan]] [[-1.] [ 1.] [ 0.] [-1.]] [[ 0.] [-1.] [nan] [nan]] ... ]
原有聚类代码:
for j in [2,3,4]: km = TimeSeriesKMeans(n_clusters=j, metric="dtw") labels = km.fit_predict(x) silhouetteScore = silhouette_score(x, labels, metric="dtw") num_of_clusters_list.append(j) silhouetteScore_list.append(silhouetteScore) print(f"{j} clusters, score is {silhouetteScore}")
报错根因
- 首先
squeeze操作是多余的,tslearn要求输入形状为(n_samples, n_timestamps, n_features),单变量时间序列最后一维为1是符合规范的,squeeze反而会破坏输入形状。 - 原生DTW度量不支持带NaN的输入,当单条时间序列非NaN有效值少于2个,或两条序列的非NaN位置无足够重叠时,DTW距离计算会因有效计算点对不足2个抛出上述值错误。
- 即使没有触发报错,带NaN的输入传入原生DTW计算逻辑也会得到全NaN的距离结果,导致后续聚类、轮廓系数计算完全失效。
解决步骤
- 过滤无效样本
加载每个文件的数据后,先逐样本统计非NaN值数量,剔除有效长度小于2的样本,这类样本本身信息量不足,既无法参与距离计算,也会干扰聚类结果。过滤后如果剩余有效样本数不足2,直接跳过该文件的聚类流程(样本量不足无法完成聚类)。 - 替换支持缺失值的距离度量
不要使用默认的dtw度量,换成支持部分观测序列计算的softdtw度量,不需要替换NaN值即可完成距离计算,符合数据编码要求。如果对DTW的硬对齐需求很高,可以自行实现带掩码的DTW逻辑:计算两个序列的距离时,仅对两个序列同一时间步均不为NaN的位置计算点对距离,跳过所有含NaN的位置,预计算完全部样本对的距离矩阵后,传入聚类和轮廓系数接口时指定metric="precomputed"即可。 - 动态调整聚类数遍历范围
聚类数必须小于有效样本总数(轮廓系数计算要求每个簇至少有1个样本,且聚类数不能等于样本数),过滤完有效样本后,从[2,3,4]里筛选出小于有效样本数的值作为实际遍历的聚类数,比如有效样本只有3个时,仅遍历聚类数为2的情况即可,避免触发新的参数错误。
调整后的参考代码:
# 不要执行squeeze,保留tslearn要求的三维输入格式 x = to_time_series_dataset(raw_x) # 过滤有效样本:单序列非NaN值≥2 valid_mask = np.array([np.count_nonzero(~np.isnan(seq.squeeze())) >= 2 for seq in x]) x_valid = x[valid_mask] n_samples = len(x_valid) # 有效样本不足直接跳过 if n_samples < 2: print(f"当前文件有效样本不足,跳过聚类") continue # 动态确定待测试的聚类数 test_cluster_list = [k for k in [2,3,4] if k < n_samples] num_of_clusters_list = [] silhouetteScore_list = [] for j in test_cluster_list: # 用支持缺失值的softdtw作为度量 km = TimeSeriesKMeans(n_clusters=j, metric="softdtw", random_state=42) labels = km.fit_predict(x_valid) silhouetteScore = silhouette_score(x_valid, labels, metric="softdtw") num_of_clusters_list.append(j) silhouetteScore_list.append(silhouetteScore) print(f"{j} clusters, score is {silhouetteScore}")
内容的提问来源于stack exchange,提问作者Paul Jiang
相关产品推荐
相关产品推荐

