在 Python 中矢量化平方欧氏距离的掩码

Vectorize mask of squared euclidean distance in Python

我正在 运行ning 代码生成一个掩码,其中 B 中的位置比 A 中的位置的距离 D 更近。

N = [[0 for j in range(length_B)] for i in range(length_A)]    
dSquared = D*D

for i in range(length_A):
    for j in range(length_B):
        if ((A[j][0]-B[i][0])**2 + (A[j][1]-B[i][1])**2) <= dSquared:
            N[i][j] = 1

对于数万个位置长的A和B列表,此代码需要一段时间。我很确定有一种方法可以对其进行矢量化,但可以使其 运行 快得多。谢谢。

使用二维数组索引更容易可视化此代码:

for j in range(length_A):
    for i in range(length_B):
        dist = (A[j,0] - B[i,0])**2 + (A[j,1] - B[i,1])**2 
        if dist <= dSquared:
            N[i, j] = 1

那个dist表达式看起来像

((A[j,:] - B[i,:])**2).sum(axis=1)

对于 2 个元素,这个数组表达式可能不会更快,但它应该可以帮助我们重新思考问题。

我们可以执行i,joutter广播问题

A[:,None,:] - B[None,:,:]  # 3d difference array

dist=((A[:,None,:] - B[None,:,:])**2).sum(axis=-1)  # (lengthA,lengthB) array

将其与 dSquared 进行比较,并使用生成的布尔数组作为将 N 的元素设置为 1 的掩码:

N = np.zeros((lengthA,lengthB))
N[dist <= dSquared] = 1

我没有测试过这段代码,所以可能有错误,但我认为基本思想是存在的。并且可能足以让您为其他案例制定详细信息的思考过程。

您可以使用 scipy's cdist,据称它对于此类距离计算非常有效,例如 -

from scipy.spatial.distance import cdist

N = (cdist(A,B,'sqeuclidean') <= dSquared).astype(int)

中所建议,可以使用也可以使用broadcasting。现在,从问题中发布的代码来看,我们似乎正在处理 Nx2 形状的数组。因此,我们基本上可以对第一列和第二列进行切片,然后分别对它们执行广播减法。好处是我们不会去 3D 并因此保持它的内存效率,这也可能转化为性能提升。因此,平方欧氏距离将像这样计算 -

sq_eucl_dist = (A[:,None,0] - B[:,0])**2 + (A[:,None,1] - B[:,1])**2

让我们为计算平方欧几里得距离的所有这三种方法计时。

运行时测试 -

In [75]: # Input arrays
    ...: A = np.random.rand(200,2)
    ...: B = np.random.rand(200,2)
    ...: 

In [76]: %timeit ((A[:,None,:] - B[None,:,:])**2).sum(axis=-1) # @hpaulj's solution
1000 loops, best of 3: 1.9 ms per loop

In [77]: %timeit (A[:,None,0] - B[:,0])**2 + (A[:,None,1] - B[:,1])**2
1000 loops, best of 3: 401 µs per loop

In [78]: %timeit cdist(A,B,'sqeuclidean')
1000 loops, best of 3: 249 µs per loop

我支持上面使用 Numpy 的建议。循环代码也对 A 做了比它需要的更多的索引。你可以使用类似的东西:

import numpy as np

dimension = 10000
A = np.random.rand(dimension, 2) + 0.0
B = np.random.rand(dimension, 2) + 1.0
N = []
d = 1.0

for i in range(len(A)):
    distances = np.linalg.norm(B - A[i,:], axis=1)
    for j in range(len(distances)):
        if distances[j] <= d:
            N.append((i,j))

print(len(N))

要从纯粹的 Python 中获得像样的性能是相当困难的。我还要指出,多维数组解决方案将需要...很多...内存。

只要你的矩阵 N 可能是稀疏的,scipy.spatial.cKDTree 将提供比基于计算所有距离蛮力的任何方法更好的时间复杂度:

cKDTree(A).sparse_distance_matrix(cKDTree(B), max_distance=D)