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
    在所有情况下,主要是指,但可以随意突出显示允许其他距离计算的方法。

    1。所有距离
    • 仅使用
      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 
    
    • 任何套餐
    是最直观的内置函数,比bare
    numpy

    from scipy.spatial.distance import cdist
    D = cdist(X, Y)
    
    cdist
    还可以处理很多很多距离度量以及用户定义的距离度量(尽管这些度量没有优化)。有关详细信息,请查看上面链接的文档

    • Y
      X
    对于自参考距离,其工作原理与cdist类似,但返回一维压缩距离数组,通过每个项只包含一次,节省了对称距离矩阵上的空间。您可以使用

    2.K最近邻(KNN)
    • 仅使用
      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)
    
    与:

    • 任何套餐
    是一种查找邻居和受约束距离的更快的方法。请注意,虽然KDTree通常比上述3d暴力解决方案快得多(只要oyu有8个以上的点),但如果您有
    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)
    
    • 任意指标
    BallTree具有与KDTree相似的算法属性。我不知道Python中有并行/矢量化/快速的BallTree,但是使用scipy,我们仍然可以对用户定义的度量进行合理的KNN查询。如果可用,内置指标将更快

    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