如何使用 KNN /K-means 在数据框中对时间序列进行聚类
Posted
技术标签:
【中文标题】如何使用 KNN /K-means 在数据框中对时间序列进行聚类【英文标题】:How can I use KNN /K-means to clustering time series in a dataframe 【发布时间】:2017-12-10 05:40:08 【问题描述】:假设一个数据框包含 1000 行。每行代表一个时间序列。
然后我构建了一个 DTW 算法来计算 2 行之间的距离。
我不知道接下来要做什么来为数据帧完成一个无监督的分类任务。
如何标注数据框的所有行?
【问题讨论】:
没有knn聚类。 【参考方案1】:定义
KNN 算法 = K-最近邻分类算法
K-means = 基于质心的聚类算法
DTW = Dynamic Time Warping 一种用于时间序列的相似性测量算法
我将在下面逐步展示如何构建两个时间序列以及如何计算动态时间规整 (DTW) 算法。您可以使用 scikit-learn 构建无监督 k-means 聚类,而无需指定质心的数量,然后 scikit-learn 就会知道使用名为 auto
的算法。
构建时间序列并计算 DTW
你有两个时间序列,你计算 DTW 使得
import pandas as pd
import numpy as np
import random
from dtw import dtw
from matplotlib.pyplot import plot
from matplotlib.pyplot import imshow
from matplotlib.pyplot import cm
from sklearn.cluster import KMeans
from sklearn.preprocessing import MultiLabelBinarizer
#About classification, read the tutorial
#http://scikit-learn.org/stable/tutorial/basic/tutorial.html
def createTs(myStart, myLength):
index = pd.date_range(myStart, periods=myLength, freq='H');
values= [random.random() for _ in range(myLength)];
series = pd.Series(values, index=index);
return(series)
#Time series of length 30, start from 1/1/2000 & 1/2/2000 so overlap
myStart='1/1/2000'
myLength=30
timeS1=createTs(myStart, myLength)
myStart='1/2/2000'
timeS2=createTs(myStart, myLength)
#This could be your dataframe but unnecessary here
#myDF = pd.DataFrame([x for x in timeS1.data], [x for x in timeS2.data])#, columns=['data1', 'data2'])
x=[xxx*100 for xxx in sorted(timeS1.data)]
y=[xx for xx in timeS2.data]
choice="dtw"
if (choice="timeseries"):
print(timeS1)
print(timeS2)
if (choice=="drawingPlots"):
plot(x)
plot(y)
if (choice=="dtw"):
#DTW with the 1st order norm
myDiff=[xx-yy for xx,yy in zip(x,y)]
dist, cost, acc, path = dtw(x, y, dist=lambda x, y: np.linalg.norm(myDiff, ord=1))
imshow(acc.T, origin='lower', cmap=cm.gray, interpolation='nearest')
plot(path[0], path[1], 'w')
使用 KNN 对时间序列进行分类
关于应该标记什么以及使用哪些标签的问题并不明显?所以请提供以下详细信息
我们应该在数据框中标记什么? DTW算法计算的路径? 哪种类型的标签?二进制?多类?之后我们可以决定我们的分类算法,可能是所谓的KNN算法。它的工作原理是你有两个独立的数据集:训练集和测试集。通过训练集,您可以教算法标记时间序列,而测试集是一种工具,我们可以通过它来衡量模型与 AUC 等模型选择工具的配合情况。
小谜题在提供有关问题的详细信息之前一直打开
#PUZZLE
#from tutorial (#http://scikit-learn.org/stable/tutorial/basic/tutorial.html)
newX = [[1, 2], [2, 4], [4, 5], [3, 2], [3, 1]]
newY = [[0, 1], [0, 2], [1, 3], [0, 2, 3], [2, 4]]
newY = MultiLabelBinarizer().fit_transform(newY)
#Continue to the article.
关于分类器的 Scikit-learn 比较文章在下面的第二个枚举项中提供。
使用 K-means 进行聚类(与 KNN 不同)
K-means 是聚类算法及其无监督版本,您可以这样使用
#Unsupervised version "auto" of the KMeans as no assignment for the n_clusters
myClusters=KMeans(path)
#myClusters.fit(YourDataHere)
这是与 KNN 算法非常不同的算法:这里我们不需要任何标签。我在第一个枚举项中为您提供了有关以下主题的更多材料。
进一步阅读
Does K-means incorporate the K-nearest-neighbour algorithm?
scikit learn中分类器的比较here
【讨论】:
@gump 正如我所写:我们应该标记什么?你想在 DTW 中分类什么? 谢谢!兄弟!是的,我想对不同的序列进行排序和标记。现在我有 50k 行关于虚拟机工作负载的时间序列数据。我想用一些方法为他们做一个无监督的分类工作。据我所知,我尝试使用 dtw (计算每个 2 个时间序列之间的距离)或者使用 rnn/lstm 来完成这个目标(但现在我没有进一步的解决方案。所以我需要你的帮助,想想吧!)。 然后,对时间序列数据进行分类标注(基于波动幅度和波动幅度的相似度)。我想为他们建立一些优化组合。因为这些时间序列是虚拟机的 cpu 工作负载。我想找到一种方法来构建[负载平衡的云服务器优化]。 请原谅我糟糕的英文表达。谢谢!这是一项非常紧迫的任务。 我们应该标记一个分类,如 1,2,3,4.. 等,只标记每个时间序列。但这些数字没有得到证实。首先,我想使用 dtw 或 rnn 方法来执行无监督标签任务。然后我尝试对这些时间序列做一个优化组合,做一些优化组合。所以这些虚拟机工作负载的组合会做一个负载平衡。我表达清楚了吗?【参考方案2】:您可以使用DTW。事实上,我的一个项目也遇到了同样的问题,我用 Python 编写了自己的类。
这是逻辑;
-
创建所有集群组合。 k 代表簇数,n 代表系列数。返回的项目数应为
n! / k! / (n-k)!
。这些可能类似于潜在的中心。
对于每个系列,计算每个集群组中每个中心的距离并将其分配给最小值。
对于每个集群组,计算各个集群内的总距离。
选择最小值。
还有代码;
import numpy as np
import pandas as pd
from itertools import combinations
import time
def dtw_distance(x, y, d=lambda x,y: abs(x-y), scaled=False, fill=True):
"""Finds the distance of two arrays by dynamic time warping method
source: https://en.wikipedia.org/wiki/Dynamic_time_warping
Dependencies:
import numpy as np
Args:
x, y: arrays
d: distance function, default is absolute difference
scaled: boolean, should arrays be scaled before calculation
fill: boolean, should NA values be filled with 0
returns:
distance as float, 0.0 means series are exactly same, upper limit is infinite
"""
if fill:
x = np.nan_to_num(x)
y = np.nan_to_num(y)
if scaled:
x = array_scaler(x)
y = array_scaler(y)
n = len(x) + 1
m = len(y) + 1
DTW = np.zeros((n, m))
DTW[:, 0] = float('Inf')
DTW[0, :] = float('Inf')
DTW[0, 0] = 0
for i in range(1, n):
for j in range(1, m):
cost = d(x[i-1], y[j-1])
DTW[i, j] = cost + min(DTW[i-1, j], DTW[i, j-1], DTW[i-1, j-1])
return DTW[n-1, m-1]
def array_scaler(x):
"""Scales array to 0-1
Dependencies:
import numpy as np
Args:
x: mutable iterable array of float
returns:
scaled x
"""
arr_min = min(x)
x = np.array(x) - float(arr_min)
arr_max = max(x)
x = x/float(arr_max)
return x
class TrendCluster():
def __init__(self):
self.clusters = None
self.centers = None
self.scale = None
def fit(self, series, n=2, scale=True):
'''
Work-flow
1 - make series combination with size n, initial clusters
2 - assign closest series to each cluster
3 - calculate total distance for each combinations
4 - choose the minimum
Args:
series: dict, keys can be anything, values are time series as list, assumes no nulls
n: int, cluster size
scale: bool, if scale needed
'''
assert isinstance(series, dict) and isinstance(n, int) and isinstance(scale, bool), 'wrong argument type'
assert n < len(series.keys()), 'n is too big'
assert len(set([len(s) for s in series.values()])) == 1, 'series length not same'
self.scale = scale
combs = combinations(series.keys(), n)
combs = [[c, -1] for c in combs]
series_keys = pd.Series(series.keys())
dtw_matrix = pd.DataFrame(series_keys.apply(lambda x: series_keys.apply(lambda y: dtw_distance(series[x], series[y], scaled=scale))))
dtw_matrix.columns, dtw_matrix.index = series_keys, series_keys
for c in combs:
c[1] = dtw_matrix.loc[c[0], :].min(axis=0).sum()
combs.sort(key=lambda x: x[1])
self.centers = c:series[c] for c in combs[0][0]
self.clusters = c:[] for c in self.centers.keys()
for k, _ in series.items():
tmp = [[c, dtw_matrix.loc[k, c]] for c in self.centers.keys()]
tmp.sort(key=lambda x: x[1])
cluster = tmp[0][0]
self.clusters[cluster].append(k)
return None
def assign(self, serie, save=False):
'''
Assigns the serie to appropriate cluster
Args:
serie, dict: 1 element dict
save, bool: if new serie is stored to clusters
Return:
str, assigned cluster key
'''
assert isinstance(serie, dict) and isinstance(save, bool), 'wrong argument type'
assert len(serie) == 1, 'serie\'s length is not exactly 1'
tmp = [[c, dtw_distance(serie.values()[0], self.centers[c], scaled=self.scale)] for c in self.centers.keys()]
tmp.sort(key=lambda x: x[1])
cluster = tmp[0][0]
if save:
self.clusters[cluster].append(serie.keys()[0])
return cluster
如果你想看到它的实际效果,你可以参考我关于Time Series Clustering的存储库。
【讨论】:
我也将编写自己的 DTW 聚类算法 - 您对 DTW 距离的实现是我见过的最直接的。但是,有两个问题(这些是基于存储库中的代码的问题):1)为什么要在集群类的 fit 函数中计算每个 DTW 的扭曲路径,而不是在距离函数中? 2)我在理解您的聚类逻辑时遇到了一些麻烦——这是一种层次聚类的形式吗?我是这个主题的新手,我正在尝试了解更多信息,我想了解更多关于您的具体实施的信息。谢谢! 我在fit
方法中使用dtw_distance
计算每个样本对的距离,nxn
次。这就是为什么有一个方阵。距离函数只是一个工具,fit
方法是使用该工具的地方。对于第二个问题,我认为它更接近于分区。我在样本中选择最好的中心(而不是 K-means 的随机创建和迭代)。然后,我只是通过选择最接近的来分配其余部分。最佳的定义是组合的最小总距离。这也是它有点慢的原因。以上是关于如何使用 KNN /K-means 在数据框中对时间序列进行聚类的主要内容,如果未能解决你的问题,请参考以下文章