当我使用 Spark 和 sklearn 对相同的数据和相同的预测值计算轮廓分数时,我得到了不同的结果。
以下是使用 Spark 的代码:
>>> prediction.show()+---+---+---------+----------+| a| b| features|prediction|+---+---+---------+----------+| 1| 1|[1.0,1.0]| 1|| 2| 2|[2.0,2.0]| 1|| 3| 3|[3.0,3.0]| 0|| 4| 4|[4.0,4.0]| 0|+---+---+---------+----------+>>> from pyspark.ml.evaluation import ClusteringEvaluator>>> evaluator = ClusteringEvaluator()>>> silhouette = evaluator.evaluate(prediction)>>> silhouette0.7230769230769223
以下是使用 sklearn 的代码:
>>> from sklearn.cluster import KMeans>>> from sklearn import metrics>>> x=[[1,1],[2,2],[3,3],[4,4]]>>> prediction = KMeans(n_clusters=2,max_iter=1000,random_state=123).fit_predict(x)>>> predictionarray([1, 1, 0, 0], dtype=int32)>>> silhouette = metrics.silhouette_score(x, prediction)>>> silhouette0.46666666666666673
如上所示,尽管输入相同,分数却大不相同。这是为什么呢?
回答:
主要区别在于使用了不同的距离度量。
Spark 使用平方欧几里得距离作为距离度量,而 sklearn 默认使用普通欧几里得距离。
Spark 选择这种距离度量的原因是为了实现更高效和并行计算。方程的一部分可以预先计算,将计算复杂度从 O(N^2^*D)
降低到 O(C*D*N/W)
,其中 N
是点的数量,D
是它们的维度,W
是工作者的数量,C
是集群的数量(假设相当低)。Spark 关于轮廓分数的数学推导和实现已在 github 上记录(此处)。
证明:
我们可以分析问题中的例子,并使用欧几里得距离和平方欧几里得距离手动计算轮廓分数。
我们有第一个集群,点为 (1,1)
和 (2,2)
,集群中心位于 (1.5,1.5)
,第二个集群点为 (3,3)
和 (4,4)
,集群中心位于 (3.5,3.5)
。
最终的轮廓分数是所有样本轮廓分数的平均值。由于问题中的四个点完全对称并且只有两个集群,计算其中一个集群的分数就足够了(这里我选择了第一个集群)。
下面 a
是集群内平均距离(与同一集群中所有点的平均距离),b
是集群间平均距离(与点不属于的最接近集群中所有点的平均距离)。分数计算公式为 (b-a) / max(b,a)
。
欧几里得距离:
-
点
(1,1)
:a
= sqrt((2-1)^2 + (2-1)^2) = sqrt(2)b
= (sqrt((3-1)^2 + (3-1)^2) + sqrt((4-1)^2 + (4-1)^2))) / 2 = (sqrt(8) + sqrt(18)) / 2 = 3.5355- 点分数 = (3.5355 – sqrt(2)) / 3.5355 = 0.6
-
点
(2,2)
:a
= sqrt((2-1)^2 + (2-1)^2) = sqrt(2)b
= (sqrt((3-2)^2 + (3-2)^2) + sqrt((4-2)^2 + (4-2)^2))) / 2 = (sqrt(2) + sqrt(8)) / 2 = 2.1213- 点分数 = (2.1213 – sqrt(2)) / 2.1213 = 0.33333
轮廓分数 = (0.6 + 0.33333) / 2 = 0.4666667
平方欧几里得距离:
-
点
(1,1)
:a
= (2-1)^2 + (2-1)^2 = 2b
= ((3-1)^2 + (3-1)^2 + (4-1)^2 + (4-1)^2) / 2 = (8 + 18) / 2 = 13- 点分数 = (13 – 2) / 13 = 0.84615
-
点
(2,2)
:a
= (2-1)^2 + (2-1)^2 = 2b
= ((3-2)^2 + (3-2)^2 + (4-2)^2 + (4-2)^2) / 2 = (2 + 8) / 2 = 5- 点分数 = (5 – 2) / 5 = 0.6
轮廓分数 = (0.84615 + 0.6) / 2 = 0.723075