对于我博士的侧面项目,我参与了使用Python建模一些系统的任务。效率方面,我的程序遇到了以下问题的瓶颈,我将在最小工作示例中公开。
我处理由其3D起点和终点编码的大量段,因此每个段由6个标量表示。
我需要计算成对最小的段间距离。在source中可以找到两个段之间最小距离的解析表达式。致MWE:
import numpy as np
N_segments = 1000
List_of_segments = np.random.rand(N_segments, 6)
Pairwise_minimal_distance_matrix = np.zeros( (N_segments,N_segments) )
for i in range(N_segments):
for j in range(i+1,N_segments):
p0 = List_of_segments[i,0:3] #beginning point of segment i
p1 = List_of_segments[i,3:6] #end point of segment i
q0 = List_of_segments[j,0:3] #beginning point of segment j
q1 = List_of_segments[j,3:6] #end point of segment j
#for readability, some definitions
a = np.dot( p1-p0, p1-p0)
b = np.dot( p1-p0, q1-q0)
c = np.dot( q1-q0, q1-q0)
d = np.dot( p1-p0, p0-q0)
e = np.dot( q1-q0, p0-q0)
s = (b*e-c*d)/(a*c-b*b)
t = (a*e-b*d)/(a*c-b*b)
#the minimal distance between segment i and j
Pairwise_minimal_distance_matrix[i,j] = sqrt(sum( (p0+(p1-p0)*s-(q0+(q1-q0)*t))**2)) #minimal distance
现在,我意识到这是非常低效的,这就是我在这里的原因。我已经广泛研究了如何避免循环,但我遇到了一些问题。显然,这种计算最好使用cdist的python进行。但是,它可以处理的自定义距离函数必须是二进制函数。在我的情况下这是一个问题,因为我的向量具有6的长度,并且必须分成它们的第一个和最后3个组件。我认为我不能将距离计算转换为二元函数。
赞赏任何意见。
答案 0 :(得分:6)
您可以使用numpy的矢量化功能来加速计算。我的版本一次计算距离矩阵的所有元素,然后将对角线和下三角形设置为零。
def pairwise_distance2(s):
# we need this because we're gonna divide by zero
old_settings = np.seterr(all="ignore")
N = N_segments # just shorter, could also use len(s)
# we repeat p0 and p1 along all columns
p0 = np.repeat(s[:,0:3].reshape((N, 1, 3)), N, axis=1)
p1 = np.repeat(s[:,3:6].reshape((N, 1, 3)), N, axis=1)
# and q0, q1 along all rows
q0 = np.repeat(s[:,0:3].reshape((1, N, 3)), N, axis=0)
q1 = np.repeat(s[:,3:6].reshape((1, N, 3)), N, axis=0)
# element-wise dot product over the last dimension,
# while keeping the number of dimensions at 3
# (so we can use them together with the p* and q*)
a = np.sum((p1 - p0) * (p1 - p0), axis=-1).reshape((N, N, 1))
b = np.sum((p1 - p0) * (q1 - q0), axis=-1).reshape((N, N, 1))
c = np.sum((q1 - q0) * (q1 - q0), axis=-1).reshape((N, N, 1))
d = np.sum((p1 - p0) * (p0 - q0), axis=-1).reshape((N, N, 1))
e = np.sum((q1 - q0) * (p0 - q0), axis=-1).reshape((N, N, 1))
# same as above
s = (b*e-c*d)/(a*c-b*b)
t = (a*e-b*d)/(a*c-b*b)
# almost same as above
pairwise = np.sqrt(np.sum( (p0 + (p1 - p0) * s - ( q0 + (q1 - q0) * t))**2, axis=-1))
# turn the error reporting back on
np.seterr(**old_settings)
# set everything at or below the diagonal to 0
pairwise[np.tril_indices(N)] = 0.0
return pairwise
现在让我们来看看吧。以您的示例N = 1000
为例,我得到了
%timeit pairwise_distance(List_of_segments)
1 loops, best of 3: 10.5 s per loop
%timeit pairwise_distance2(List_of_segments)
1 loops, best of 3: 398 ms per loop
当然,结果是一样的:
(pairwise_distance2(List_of_segments) == pairwise_distance(List_of_segments)).all()
返回True
。我也很确定在算法的某处隐藏了矩阵乘法,所以应该有进一步加速(以及清理)的潜力。
顺便说一下:我尝试过简单地使用numba而没有成功。不过不确定原因。
答案 1 :(得分:2)
这更像是一个元回答,至少对于初学者而言。您的问题可能已经存在,并且我的计划遇到了瓶颈问题。和#34;我意识到这是非常低效的"。
非常低效?通过什么衡量?你有比较吗?您的代码是否太慢而无法在合理的时间内完成?什么是合理的时间?你能为这个问题投入更多的计算能力吗?同样重要的是 - 您是否使用适当的基础架构来运行代码(使用供应商编译器编译的numpy / scipy,可能具有OpenMP支持)?
然后,如果您对上述所有问题都有答案,并且需要进一步优化您的代码 - 那么您当前代码的瓶颈在哪里?你有资料吗?它的循环体可能比环路本身的评估重得多吗?如果是这样,那么"循环"不是你的瓶颈,你首先不需要担心嵌套循环。首先优化身体,可能通过提出数据的非正统矩阵表示,以便您可以一步完成所有这些单一计算 - 例如通过矩阵乘法。如果您的问题无法通过有效的线性代数运算来解决,您可以开始编写C扩展或使用Cython或使用PyPy(最近刚刚获得了一些基本的numpy支持!)。优化的可能性无穷无尽 - 问题实际上是:您已经有多接近实际解决方案,您需要需要多少进行优化,以及您愿意投入多少努力。
免责声明:我已经为我的博士学位做了非规范的成对距离和scipy / numpy的东西;-)。对于一个特定的距离度量,我最终编码"成对"部分简单的Python(即我也使用了双嵌套循环),但花了一些精力让身体尽可能高效(结合使用i)我的问题的密码矩阵乘法表示和ii)使用{{3 }})。
答案 2 :(得分:0)
您可以使用以下内容:
def distance3d (p, q):
if (p == q).all ():
return 0
p0 = p[0:3]
p1 = p[3:6]
q0 = q[0:3]
q1 = q[3:6]
... # Distance computation using the formula above.
print (distance.cdist (List_of_segments, List_of_segments, distance3d))
但它似乎没有更快,因为它在内部执行相同的循环。