【发布时间】:2019-03-10 00:41:04
【问题描述】:
我知道如何计算数组中点之间的欧几里得距离 scipy.spatial.distance.cdist
类似于这个问题的答案: Calculate Distances Between One Point in Matrix From All Other Points
但是,我想在假设循环边界条件的情况下进行计算,例如因此在这种情况下,点 [0,0] 与点 [0,n-1] 的距离为 1,而不是 n-1 的距离。 (然后,我将为目标细胞阈值距离内的所有点制作一个蒙版,但这不是问题的核心)。
我能想到的唯一方法是重复计算 9 次,域索引在 x、y 和 x&y 方向上添加/减去 n,然后堆叠结果并在 9 个切片中找到最小值.为了说明需要 9 次重复,我整理了一个简单的示意图,其中只有 1 个 J 点,并用圆圈标记,它显示了一个示例,在这种情况下,由三角形标记的单元格在域中的最近邻居反映到左上角。
这是我使用 cdist 为此开发的代码:
import numpy as np
from scipy import spatial
n=5 # size of 2D box (n X n points)
np.random.seed(1) # to make reproducible
a=np.random.uniform(size=(n,n))
i=np.argwhere(a>-1) # all points, for each loc we want distance to nearest J
j=np.argwhere(a>0.85) # set of J locations to find distance to.
# this will be used in the KDtree soln
global maxdist
maxdist=2.0
def dist_v1(i,j):
dist=[]
# 3x3 search required for periodic boundaries.
for xoff in [-n,0,n]:
for yoff in [-n,0,n]:
jo=j.copy()
jo[:,0]-=xoff
jo[:,1]-=yoff
dist.append(np.amin(spatial.distance.cdist(i,jo,metric='euclidean'),1))
dist=np.amin(np.stack(dist),0).reshape([n,n])
return(dist)
这有效,并产生例如:
print(dist_v1(i,j))
[[1.41421356 1. 1.41421356 1.41421356 1. ]
[2.23606798 2. 1.41421356 1. 1.41421356]
[2. 2. 1. 0. 1. ]
[1.41421356 1. 1.41421356 1. 1. ]
[1. 0. 1. 1. 0. ]]
零显然标记了 J 点,并且距离是正确的(此 EDIT 更正了我之前不正确的尝试)。
请注意,如果您更改最后两行以堆叠原始距离,然后只使用一个最小值,如下所示:
def dist_v2(i,j):
dist=[]
# 3x3 search required for periodic boundaries.
for xoff in [-n,0,n]:
for yoff in [-n,0,n]:
jo=j.copy()
jo[:,0]-=xoff
jo[:,1]-=yoff
dist.append(spatial.distance.cdist(i,jo,metric='euclidean'))
dist=np.amin(np.dstack(dist),(1,2)).reshape([n,n])
return(dist)
对于较小的 n (10) 则相当慢
...但无论哪种方式,对于我的大型数组(N=500 和 J 点数约为 70)来说,它都是 慢,此搜索占用了大约 99% 的计算时间,(而且使用循环也有点难看)-有更好/更快的方法吗?
我想到的其他选择是:
- scipy.spatial.KDTree.query_ball_point
通过进一步搜索我发现有一个功能 scipy.spatial.KDTree.query_ball_point 直接计算我的 J 点半径内的坐标,但它似乎没有任何使用周期性边界的工具,所以我认为仍然需要以某种方式使用 3x3 循环,堆叠然后使用amin 就像我上面做的那样,所以我不确定这是否会更快。
我使用这个函数编写了一个解决方案,而不用担心周期性边界条件(即这不能回答我的问题)
def dist_v3(n,j):
x, y = np.mgrid[0:n, 0:n]
points = np.c_[x.ravel(), y.ravel()]
tree=spatial.KDTree(points)
mask=np.zeros([n,n])
for results in tree.query_ball_point((j), maxdist):
mask[points[results][:,0],points[results][:,1]]=1
return(mask)
也许我没有以最有效的方式使用它,但这已经和我的基于 cdist 的解决方案一样慢,即使没有周期性边界。在两个 cdist 解决方案中包含 mask 函数,即在这些函数中将 return(dist) 替换为 return(np.where(dist<=maxdist,1,0)),然后使用 timeit,我得到以下 n=100 的时序:
from timeit import timeit
print("cdist v1:",timeit(lambda: dist_v1(i,j), number=3)*100)
print("cdist v2:",timeit(lambda: dist_v2(i,j), number=3)*100)
print("KDtree:", timeit(lambda: dist_v3(n,j), number=3)*100)
cdist v1: 181.80927299981704
cdist v2: 554.8205785999016
KDtree: 605.119637199823
-
为 [0,0] 的设定距离内的点创建一个相对坐标数组,然后手动循环 J 点,使用此相对点列表设置掩码 - 这具有“相对距离”的优点计算只执行一次(我的 J 点每个时间步都会改变),但我怀疑循环会很慢。
-
为 2D 域中的每个点预先计算一组掩码,因此在模型集成的每个时间步中,我只需选择 J 点的掩码并应用。这将使用大量内存(与 n^4 成正比),并且可能仍然很慢,因为您需要循环 J 点以组合掩码。
【问题讨论】:
-
也许您可以提供自己的指标,因为
metric参数也可以是callable,但我同意文档对此有点枯燥。一眼看去,我什至无法重现euclidean指标。 -
您是否尝试过基于 numba 的方法?有问题的问题是您必须大量重复计算的类型。我觉得使用 numba JIT 包装的函数,即使是丑陋的 for 循环,也可能会提供令人惊讶的收益。
-
好图! :) 我旁边有那幅图,我需要修复我的代码。我相信我可以安全地“剪切”一些副本,但它有点复杂,我猜它对性能的影响很小。
-
@Mercury 感谢您的建议,不,我没有 - 请有机会制作一些建议的代码,我不知道 JIT 包装的函数是什么,请原谅我的无知!
标签: python performance numpy scipy distance