Python 如何在numpy阵列上进行n-D距离和最近邻计算
此问题旨在成为标准的重复目标 给定形状Python 如何在numpy阵列上进行n-D距离和最近邻计算,python,arrays,numpy,scikit-learn,scipy,Python,Arrays,Numpy,Scikit Learn,Scipy,此问题旨在成为标准的重复目标 给定形状(i,n)和(j,n)的两个数组X和Y,表示n的维度坐标列表 def test_data(n, i, j, r = 100): X = np.random.rand(i, n) * r - r / 2 Y = np.random.rand(j, n) * r - r / 2 return X, Y X, Y = test_data(3, 1000, 1000) 最快的方法是什么: 具有形状的(i,j)的D距离X中的每个点和Y 对于
(i,n)
和(j,n)
的两个数组X
和Y
,表示n
的维度坐标列表
def test_data(n, i, j, r = 100):
X = np.random.rand(i, n) * r - r / 2
Y = np.random.rand(j, n) * r - r / 2
return X, Y
X, Y = test_data(3, 1000, 1000)
最快的方法是什么:
(i,j)
的D
距离X
中的每个点和Y
Y
中的每个点,k
最近邻相对于X
中所有点的索引k_i
和距离k_d
r\u i
、r\u j
和X
中每个点的距离r
在Y
中每个点的距离j
内的指数r\u i
- 仅使用
numpy
- 使用任何
包python
是Y
X
- 仅使用
numpy
D = np.sqrt(np.sum((X[:, None, :] - Y[None, :, :])**2, axis = -1))
但是,创建(i,j,n)
形状的中间矩阵会占用大量内存,而且速度非常慢
然而,多亏了@Divakar(package,)的一个技巧,我们可以使用一些代数并进行分解:(X-Y)**2=X**2-2*X*Y+Y**2
D = np.sqrt( # (X - Y) ** 2
np.einsum('ij, ij ->i', X, X)[:, None] + # = X ** 2 \
np.einsum('ij, ij ->i', Y, Y) - # + Y ** 2 \
2 * X.dot(Y.T)) # - 2 * X * Y
是Y
X
XX = np.einsum('ij, ij ->i', X, X)
D = np.sqrt(XX[:, None] + XX - 2 * X.dot(X.T))
注意,使用这种方法时,浮点不精确会使对角线项稍微偏离零。如果需要确保它们为零,则需要显式设置:
np.einsum('ii->i', D)[:] = 0
- 任何套餐
numpy
from scipy.spatial.distance import cdist
D = cdist(X, Y)
cdist
还可以处理很多很多距离度量以及用户定义的距离度量(尽管这些度量没有优化)。有关详细信息,请查看上面链接的文档
是Y
X
- 仅使用
numpy
np.argpartition
来获得k-最近的
索引,并使用这些索引来获得相应的距离值。因此,使用D
作为保存上述距离值的数组,我们将-
if k == 1:
k_i = D.argmin(0)
else:
k_i = D.argpartition(k, axis = 0)[:k]
k_d = np.take_along_axis(D, k_i, axis = 0)
但是,我们可以通过在减少数据集之前不求平方根来加快速度D_sq = np.einsum('ij, ij ->i', X, X)[:, None] +\
np.einsum('ij, ij ->i', Y, Y) - 2 * X.dot(Y.T)
if k == 1:
k_i = D_sq.argmin(0)
else:
k_i = D_sq.argpartition(k, axis = 0)[:k]
k_d = np.sqrt(np.take_along_axis(D_sq, k_i, axis = 0))
现在,np.argpartition
执行间接分区,不一定按排序顺序给我们元素,只确保第一个k
元素是最小的。因此,对于排序输出,我们需要对上一步的输出使用argsort
-
sorted_idx = k_d.argsort(axis = 0)
k_i_sorted = np.take_along_axis(k_i, sorted_idx, axis = 0)
k_d_sorted = np.take_along_axis(k_d, sorted_idx, axis = 0)
如果只需要,k_i
,则根本不需要平方根:
D_sq = np.einsum('ij, ij ->i', X, X)[:, None] +\
np.einsum('ij, ij ->i', Y, Y) - 2 * X.dot(Y.T)
if k == 1:
k_i = D_sq.argmin(0)
else:
k_i = D_sq.argpartition(k, axis = 0)[:k]
k_d_sq = np.take_along_axis(D_sq, k_i, axis = 0)
sorted_idx = k_d_sq.argsort(axis = 0)
k_i_sorted = np.take_along_axis(k_i, sorted_idx, axis = 0)
是X
Y
D_sq = np.einsum('ij, ij ->i', X, X)[:, None] +\
np.einsum('ij, ij ->i', Y, Y) - 2 * X.dot(Y.T)
与:
- 任何套餐
n
-维度,KDTree只有在2**n
以上的点时才能很好地伸缩。有关高维的讨论和更高级的方法,请参见
实现KDTree最推荐的方法是使用scipy
,或
不幸的是,scipy
的KDTree实现速度很慢,而且对于更大的数据集,它倾向于进行故障隔离。正如@HansMusgrave所指出的,性能提高了很多,但不像scipy
那样常见,目前只能处理欧几里德距离(而scipy
中的KDTree
可以处理任意阶数的Minkowsi p-范数)
是X
Y
k_d, k_i = X_tree.query(X, k = k)
- 任意指标
def d(a, b):
return max(np.abs(a-b))
tree = sklearn.neighbors.BallTree(X, metric=d)
k_d, k_i = tree.query(Y)
如果d()
不是正确答案,则该答案将是错误的。BallTree比暴力更快的唯一原因是因为度量的属性允许它排除某些解决方案。对于真正的任意函数,蛮力实际上是必要的
3.半径搜索
- 仅使用
numpy
mask = D_sq < r**2
r_i, r_j = np.where(mask)
r_d = np.sqrt(D_sq[mask])
或者scipy.spatial.KDTree.query\u ball\u tree
Y_tree = KDTree(Y)
r_ij = X_tree.query_ball_tree(Y_tree, r = r)
不幸的是,r_ij
最终成为一个索引数组列表,这些数组有点难以解开以供以后使用
更简单的方法是使用cKDTree
的稀疏距离矩阵
,它可以输出coo\u矩阵
from scipy.spatial.distance import cKDTree
X_cTree = cKDTree(X)
Y_cTree = cKDTree(Y)
D_coo = X_cTree.sparse_distance_matrix(Y_cTree, r = r, output_type = `coo_matrix`)
r_i = D_coo.row
r_j = D_coo.column
r_d = D_coo.data
这是一种非常灵活的距离矩阵格式,因为它保持一个实际的矩阵(如果转换为
csr
),也可以用于许多矢量化操作。根据我使用numpy的经验,使用重载运算符w
mask = D_sq < r**2
r_i, r_j = np.where(mask)
r_d = np.sqrt(D_sq[mask])
r_ij = X_tree.query_ball_point(Y, r = r)
Y_tree = KDTree(Y)
r_ij = X_tree.query_ball_tree(Y_tree, r = r)
from scipy.spatial.distance import cKDTree
X_cTree = cKDTree(X)
Y_cTree = cKDTree(Y)
D_coo = X_cTree.sparse_distance_matrix(Y_cTree, r = r, output_type = `coo_matrix`)
r_i = D_coo.row
r_j = D_coo.column
r_d = D_coo.data