K-Means:将聚类分配给新数据点

时间:2018-04-17 03:12:19

标签: python python-3.x numpy k-means unsupervised-learning

我已经在python中实现了k-means聚类算法,现在我想用我的算法得到的聚类标记一个新数据。我的方法是迭代每个数据点和每个质心,以找到最小距离和与之相关的质心。但我想知道是否有更简单或更短的方法来做到这一点。

def assign_cluster(clusterDict, data):

    clusterList = []
    label = []
    cen = list(clusterDict.values())

    for i in range(len(data)):
        for j in range(len(cen)):
            # if cen[j] has the minimum distance with data[i]
            # then clusterList[i] = cen[j]

其中clusterDict是一个字典,其中键为标签,[0,1,2,....],值为质心坐标。

有人可以帮我实现吗?

2 个答案:

答案 0 :(得分:1)

这是numba的一个很好的用例,因为它可以让你将它表达为一个简单的双循环而不会造成很大的性能损失,从而可以避免使用np.tile来复制跨越第三维的数据只是为了以矢量化方式进行。

从另一个答案借用标准的矢量化numpy实现,我有这两个实现:

import numba
import numpy as np


def kmeans_assignment(centroids, points):
    num_centroids, dim = centroids.shape
    num_points, _ = points.shape

    # Tile and reshape both arrays into `[num_points, num_centroids, dim]`.                                                                      
    centroids = np.tile(centroids, [num_points, 1]).reshape([num_points, num_centroids, dim])
    points = np.tile(points, [1, num_centroids]).reshape([num_points, num_centroids, dim])

    # Compute all distances (for all points and all centroids) at once and                                                                       
    # select the min centroid for each point.                                                                                                    
    distances = np.sum(np.square(centroids - points), axis=2)
    return np.argmin(distances, axis=1)


@numba.jit
def kmeans_assignment2(centroids, points):
    P, C = points.shape[0], centroids.shape[0]
    distances = np.zeros((P, C), dtype=np.float32)
    for p in range(P):
        for c in range(C):
            distances[p, c] = np.sum(np.square(centroids[c] - points[p]))
    return np.argmin(distances, axis=1)

然后对于一些样本数据,我做了几个计时实验:

In [12]: points = np.random.rand(10000, 50)

In [13]: centroids = np.random.rand(30, 50)

In [14]: %timeit kmeans_assignment(centroids, points)
196 ms ± 6.78 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [15]: %timeit kmeans_assignment2(centroids, points)
127 ms ± 12.1 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

我甚至不敢说numba版本肯定比np.tile版本更快,但很明显它非常接近而不会产生额外的内存成本{{1 }}

事实上,我注意到我的笔记本电脑在制作形状较大时使用(10000,1000)形状为np.tile和(200,1000)形状points,然后centroids生成np.tile,同时MemoryError函数在5秒内运行,没有内存错误。

另外,我实际上注意到在第一个版本(使用numba)上使用numba.jit时速度减慢,这可能是由于jitted函数内部额外的数组创建以及&# 39;当你已经调用所有向量化函数时,numba可以进行优化。

在尝试使用广播缩短代码时,我也没有注意到第二版的任何重大改进。例如。缩短双循环

np.tile

并没有真正帮助任何事情(并且在所有for p in range(P): distances[p, :] = np.sum(np.square(centroids - points[p, :]), axis=1) 重复广播points[p, :]时会占用更多内存。

这是numba非常好的好处之一。你真的可以用一种非常直接的,基于循环的方式编写算法,它与算法的标准描述相一致,并允许更精确地控制语法如何解压缩到内存消耗或广播...所有这些都不会放弃运行时性能。

答案 1 :(得分:0)

执行分配阶段的有效方法是执行矢量化计算。此方法假设您从两个2D数组开始:点和质心,具有相同的列数(空间维度),但可能有不同的行数。通过使用平铺(np.tile),我们可以计算批处理中的距离矩阵,然后选择每个点最近的聚类。

以下是代码:

def kmeans_assignment(centroids, points):
  num_centroids, dim = centroids.shape
  num_points, _ = points.shape

  # Tile and reshape both arrays into `[num_points, num_centroids, dim]`.
  centroids = np.tile(centroids, [num_points, 1]).reshape([num_points, num_centroids, dim])
  points = np.tile(points, [1, num_centroids]).reshape([num_points, num_centroids, dim])

  # Compute all distances (for all points and all centroids) at once and 
  # select the min centroid for each point.
  distances = np.sum(np.square(centroids - points), axis=2)
  return np.argmin(distances, axis=1)

有关完整的可运行示例,请参阅this GitHub gist