Python中基于DTW的大规模时间序列距离矩阵计算效率优化
你的代码跑一年是正常的——109489个样本的全量两两DTW配对总共有近60亿次计算,哪怕单次DTW计算只花0.1毫秒,全量跑完也要近70天,更别说你现在的代码还有大量无效开销。下面按收益从高到低给可行的优化方案,入门也能直接落地。
1 零逻辑改造成本的基础提效(先做,直接提效2-3倍)
先把代码里的纯浪费部分砍掉:
- 提前把所有时间序列从pandas里抽出来存成numpy数组列表:你现在每次循环都用
cycles.iloc[i]取数,pandas行索引的额外开销很高,提前一次性把cycle_info_scaled列转成纯列表存好,不要在循环里碰pandas接口 - 利用DTW的对称性减少一半计算:DTW满足
d(i,j)=d(j,i),且d(i,i)=0,你现在每一行都算所有j从0到总长度,等于每对距离算两遍,对角线的0值也在重复计算,只需要算j>i的部分,最后对称补全矩阵即可 - 修正矩阵数据类型:DTW返回的是浮点数,你现在把矩阵dtype设为
object,内存占用是float32的2倍以上,且连续读写性能差,直接改成dtype=np.float32即可 - 换原生支持并行的DTW实现:你现在自己写joblib并行的方式有很大的进程通信开销,CPU根本跑不满,直接用
dtaidistance库的dtw.distance_matrix_fast接口,它是C实现,内置多进程并行,自动只算对称矩阵的一半,比你手写的并行版本快5-10倍。
2 算法层面减计算量(核心优化,提效100-1000倍)
哪怕你把代码优化到极致,60亿次DTW计算的总量摆在那,普通消费级硬件不可能在几天内跑完。你的最终目标是聚类,根本不需要算全量距离矩阵,这是最核心的优化点:
- 优先用时间序列嵌入替代原始DTW计算:用MiniRocket/ROCKET工具把每个时间序列转换成几千维的特征向量,10万条序列的转换只需要几分钟,转完之后用欧氏距离就能得到和DTW接近的聚类效果,距离计算速度比DTW快3个数量级
- 如果必须用精确DTW,不要算全量矩阵:聚类用HDBSCAN这类基于近邻图的算法即可,只需要计算每个样本最近的10-30个邻居的DTW距离,总计算量从60亿次降到200-300万次,普通机器几个小时就能跑完
- 数据量大的时候先做预聚类:先随机抽5%-10%的样本做聚类得到类中心,剩下的样本只需要计算和类中心的DTW距离归到最近类,再在类内做细聚类,总计算量可以降到原来的1%以下
3 可直接运行的参考实现
3.1 小样本测试用(1000条以内,验证DTW计算逻辑)
import numpy as np from dtaidistance import dtw import joblib # 加载数据,提前抽成numpy数组列表,脱离pandas cycles = joblib.load('new_cycles.pkl') series_list = [np.array(x, dtype=np.float32) for x in cycles['cycle_info_scaled'].values] # 自动并行计算对称DTW矩阵,warp窗口和你原来设的0.05一致 dist_matrix = dtw.distance_matrix_fast( series_list, window=0.05, parallel=True, use_mp=True, only_triu=True # 只算上三角,减少一半计算 )
3.2 全量10万样本聚类方案(普通机器半天内跑完,效果和DTW聚类接近)
如果序列长度不一致,先统一补零到相同长度即可:
import numpy as np import joblib from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from minirocket import MiniRocket from hdbscan import HDBSCAN # 加载并预处理数据 cycles = joblib.load('new_cycles.pkl') series_list = [np.array(x, dtype=np.float32) for x in cycles['cycle_info_scaled'].values] max_len = max(len(s) for s in series_list) # 补零到统一长度,适配MiniRocket输入要求 X = np.stack([ np.pad(s, (0, max_len - len(s)), constant_values=0) for s in series_list ], axis=0) # 时间序列转特征向量 embed_pipe = make_pipeline( MiniRocket(num_kernels=10000, random_state=42), StandardScaler() ) X_embedded = embed_pipe.fit_transform(X) # 无监督聚类,不需要全量距离矩阵 clusterer = HDBSCAN(min_cluster_size=10, n_jobs=-1) cluster_labels = clusterer.fit_predict(X_embedded)
4 原代码的具体问题汇总
- 重复计算对称位置的距离,浪费50%算力
- 循环内反复调用pandas的
iloc接口取数,产生大量不必要的开销 - 距离矩阵用
object类型存储,内存占用高、读写慢 - 手写joblib并行的粒度过粗,进程间数据传输开销大,CPU利用率低
- 最核心的问题:10万级样本计算全量两两DTW本身就不具备工程可行性,必须从算法层面压缩计算量,否则无论怎么优化代码逻辑都不可能在可接受时间内跑完
内容的提问来源于stack exchange,提问作者josseossa
相关产品推荐
相关产品推荐

