【发布时间】:2017-02-06 02:47:36
【问题描述】:
首先讲一下背景:聚类之间比较的几种方法依赖于所谓的配对计数。我们在相同的n 实体上有两个平面聚类向量a 和b。在对所有可能的实体对进行配对计数时,我们检查它们是否属于两者中的同一个集群,或者在a 中属于相同但在b 中不同,或者相反,或者属于两者中的不同集群。这样我们得到 4 个计数,我们称它们为 n11, n10, n01, n00。这些是不同指标的输入。
当实体的数量在 10,000 左右,并且要比较的聚类数量是几十个或更多时,性能就会成为一个问题,因为每次比较的对数为 10^8,而对于这需要执行10^4 次的所有聚类比较。
对于一个幼稚的 Python 实现,它真的很耗时,所以我求助于 Cython 和 numpy。这样,我可以将一次比较的时间缩短到 0.9-3.0 秒左右。在我的情况下,这仍然意味着半天的运行时间。我想知道您是否看到任何通过一些聪明的算法或 C 库或其他方式实现性能的可能性。
这是我的尝试:
1) 在不为所有对分配巨大数组的情况下进行计数,采用长度为 n 的 2 个成员向量 a1, a2,并返回计数:
cimport cython
import numpy as np
cimport numpy as np
ctypedef np.int32_t DTYPE_t
@cython.boundscheck(False)
def pair_counts(
np.ndarray[DTYPE_t, ndim = 1] a1,
np.ndarray[DTYPE_t, ndim = 1] a2,
):
cdef unsigned int a1s = a1.shape[0]
cdef unsigned int a2s = a2.shape[0]
cdef unsigned int n11, n10, n01, n00
n11 = n10 = n01 = n00 = 0
cdef unsigned int j0
for i in range(0, a1s - 1):
j0 = i + 1
for j in range(j0, a2s):
if a1[i] == a1[j] and a2[i] == a2[j]:
n11 += 1
elif a1[i] == a1[j]:
n10 += 1
elif a2[i] == a2[j]:
n01 += 1
else:
n00 += 1
return n11, n10, n01, n00
2) 这首先为 2 个聚类中的每一个计算共同成员向量(长度 n * (n-1) / 2,每个实体对一个元素),然后计算这些向量的计数。它在每次比较时分配约 20-40M 内存,但有趣的是,它比以前更快。注意:c 是一个围绕集群的包装类,具有通常的成员向量,但还有一个 c.members dict,其中包含 numpy 数组中每个集群的实体索引。
cimport cython
import numpy as np
cimport numpy as np
@cython.boundscheck(False)
def comembership(c):
"""
Returns comembership vector, where each value tells if one pair
of entites belong to the same group (1) or not (0).
"""
cdef int n = len(c.memberships)
cdef int cnum = c.cnum
cdef int ri, i, ii, j, li
cdef unsigned char[:] cmem = \
np.zeros((int(n * (n - 1) / 2), ), dtype = np.uint8)
for ci in xrange(cnum):
# here we use the members dict to have the indices of entities
# in cluster (ci), as a numpy array (mi)
mi = c.members[ci]
for i in xrange(len(mi) - 1):
ii = mi[i]
# this is only to convert the indices of an n x n matrix
# to the indices of a 1 x (n x (n-1) / 2) vector:
ri = n * ii - 3 * ii / 2 - ii ** 2 / 2 - 1
for j in mi[i+1:]:
# also here, adding j only for having the correct index
li = ri + j
cmem[li] = 1
return np.array(cmem)
def pair_counts(c1, c2):
p1 = comembership(c1)
p2 = comembership(c2)
n = len(c1.memberships)
a11 = p1 * p2
n11 = a11.sum()
n10 = (p1 - a11).sum()
n01 = (p2 - a11).sum()
n00 = n - n10 - n01 - n11
return n11, n10, n01, n00
3) 这是一个纯粹的基于 numpy 的解决方案,它创建了一个 n x n 实体对的共同成员关系布尔数组。输入是成员向量 (a1, a2)。
def pair_counts(a1, a2):
n = len(a1)
cmem1 = a1.reshape([n,1]) == a1.reshape([1,n])
cmem2 = a2.reshape([n,1]) == a2.reshape([1,n])
n11 = int(((cmem1 == cmem2).sum() - n) / 2)
n10 = int((cmem1.sum() - n) / 2) - n11
n01 = int((cmem2.sum() - n) / 2) - n11
n00 = n - n11 - n10 - n01
return n11, n10, n01, n00
编辑:示例数据
import numpy as np
a1 = np.random.randint(0, 1868, 14440, dtype = np.int32)
a2 = np.random.randint(0, 484, 14440, dtype = np.int32)
# to have the members dicts used in example 2:
def get_cnum(a):
"""
Returns number of clusters.
"""
return len(np.unique(a))
def get_members(a):
"""
Returns a dict with cluster numbers as keys and member entities
as sorted numpy arrays.
"""
members = dict(map(lambda i: (i, []), range(max(a) + 1)))
list(map(lambda m: members[m[1]].append(m[0]),
enumerate(a)))
members = dict(map(lambda m:
(m[0], np.array(sorted(m[1]), dtype = np.int)),
members.items()))
return members
members1 = get_members(a1)
members2 = get_members(a2)
cnum1 = get_cnum(a1)
cnum2 = get_cnum(a2)
【问题讨论】:
-
您能添加一个我们可以玩的有代表性的示例案例吗?
-
好的,给我一点时间,我会补充的
-
可能并行化是要走的路...对于此类操作,您的 GPU 可能比您的 CPU 更快,这可以为您节省大量时间
-
在您的第一次尝试中,您没有输入
i和j。它可能会设法自动推断出这些,但如果不是,它会花费你很多。 -
GPU 加速 Python 最简单的方法可能是使用 Numba(但我个人认为我无法给出示例)
标签: python algorithm numpy cluster-analysis cython