使用 k-Means 聚类尝试识别 2D 离群值显示根本没有离群值(而不是一个)

Using k-Means Clustering to try to identify a 2D outlier shows no outliers at all (instead of one)

我正在学习 Charu Aggarwal 的异常值分析简介,并完成第 1 章中的练习 7。

我正在尝试使用 k-Means 聚类来识别数据中的异常值。我试图做的是创建两个集群并测量每个数据点到其各自集群中心的距离,以确定哪些项目是异常值。

这是我的数据的直方图(使用 Matlab 生成):

这是我用来创建直方图的代码(很抱歉该位是在 Matlab 中而不是 Python):

x = [1, 2, 3, 10, 10, 9, 10];
y = [9, 9, 9, 10, 3, 1, 2];
histogram2(x, y)

根据直方图,我认为唯一的异常值是 (10, 10)

我尝试了以下代码:

from sklearn.cluster import KMeans
import numpy as np
import math

data = np.array([(1, 9), (2, 9), (3, 9), (10, 10), (10, 3), (9, 1), (10, 2)])

kmeans = KMeans(n_clusters = 2, random_state = 0).fit(data)

print(kmeans.predict(data))

# Based on my understanding of the documentation, this should give a matrix
# of the distances to the centroids for each data point
temp = kmeans.transform(data)

# Calculate Euclidean Distance
for x, y in temp:
    print('x: ' + str(x) + ' y: ' + str(y) + ' dist: ' + str(math.sqrt((x * x) + (y * y))))

同样,根据直方图,我预计会有一个异常值。但是,我的代码打印以下内容:

x: 10.077822185373186 y: 1.0 dist: 10.127314550264547
x: 9.222933372848358 y: 0.0 dist: 9.222933372848358
x: 8.40014880820572 y: 1.0 dist: 8.459462157844314
x: 6.005206074732157 y: 8.06225774829855 dist: 10.05298463144155
x: 1.0307764064044151 y: 10.0 dist: 10.05298463144155
x: 3.092329219213245 y: 10.63014581273465 dist: 11.070794912742265
x: 2.0155644370746373 y: 10.63014581273465 dist: 10.819542504191201

(在有人指出这一点之前,是的,我确实意识到 xy 在这里的使用有些误导)。我原以为对应于 (10, 10) 的点距中心的欧几里德距离比其余点高得多,但所有距离都非常接近。

我有点困惑我的错误是什么。我对最后一步(欧氏距离)有些怀疑。这是在这里使用的正确程序吗?还是我遗漏了一些其他错误?

此外,期望 k-Means 聚类首先揭示异常值是否合理?

不要认为 KMeans 有 outliers/noise。如果你看看 https://scikit-learn.org/stable/modules/clustering.html#clustering 有一个很好的图形表示 - 黑点代表噪声或“异常值”。

也许 DBSCAN 更适合您的需求

kmeans.transform 的解释不太正确。

我认为问题是在问:“如何计算到最近的簇质心的距离?”应该通过 kmeans.cluster_centers_ 属性。

例如returns原始数据中每个点与使用kmeans找到的两个聚类中心之间的2范数(欧式距离)的最小值:

>>> for a in data:
...   print(a, np.min([np.linalg.norm(a - kmeans.cluster_centers_[0]), np.linalg.norm(a - kmeans.cluster_centers_[1])]))
... 
[1 9] 1.0
[2 9] 0.0
[3 9] 1.0
[10 10] 6.005206074732157
[10  3] 1.0307764064044151
[9 1] 3.092329219213245
[10  2] 2.0155644370746373

由此,我们可能会注意到大多数“内点”落在距中心 0-4 个单位的范围内,而“离群点”落在距中心 6.0 个单位左右。

(编辑: 漏了第二题)

Also, is it reasonable to expect that k-Means Clustering would reveal that outlier in the first place?

这里:是的。一般而言:这取决于 2 范数是否是您 space 中的一个好的指标,以及(在某种程度上)您正在使用的维数(在高维 space 中,一切都是远离其他一切)。

似乎对 kmeans.transform 的工作方式有一点误解。 kmeans.transform returns 一组欧氏距离。这些列不对应于二维中的距离。相反,每一列代表两个聚类中心。所以row = 0col = 0中的元素对应的是第一个元素到第一个簇中心的距离。要找到异常值,只需查看元素与聚类中心之间的最小距离。

np.min(temp,1)

returns

array([1.        , 0.        , 1.        , 6.00520607, 1.03077641,
   3.09232922, 2.01556444])

表示第4个元素(或[10,10])是异常值。