【问题标题】:creating distance matrix iteratively efficiently while preserving iteration process in Python 3 / Numpy在 Python 3 / Numpy 中保留迭代过程的同时有效地迭代创建距离矩阵
【发布时间】:2020-10-15 20:47:12
【问题描述】:

我有一个矩阵 A 并想从它迭代计算距离矩阵 D。想要一步一步计算的原因是为了以后在迭代过程中包含一些if语句。

我现在的代码如下所示:

import numpy as np
from scipy.spatial import distance

def create_data_matrix(n,m):
    mean = np.zeros(m)
    cov = np.eye(m, dtype=float)
    data_matrix = np.random.multivariate_normal(mean,cov,n)
    return(data_matrix)
def create_full_distance(A):
    distance_matrix = np.triu(distance.squareform(distance.pdist(A,"euclidean")),0)
    return(distance_matrix)

matrix_a = create_data_matrix(1000,2)
distance_from_numpy = create_full_distance(matrix_a)
matrix_b = np.empty((1000,1000))
for idx, line in enumerate(matrix_a):
    for j, line2 in enumerate(matrix_a):
        matrix_b[idx][j] = distance.euclidean(matrix_a[idx],matrix_a[j])

现在矩阵 "distance_from_numpy" 和 "matrix_b" 是相同的,尽管 matrix_b 需要更长的时间来计算尽管 matrix_a 只是一个 (100x2) 矩阵,而且我知道 "distance.pdist()" 方法非常快但我不确定是否可以在迭代过程中实现它。
我的问题是,为什么双 for 循环这么慢,我怎样才能提高速度,同时仍然保留迭代过程(因为我想在那里包含 if 语句)?

编辑:对于上下文:我想保留迭代,因为如果其中一个距离小于特定数字,我想停止迭代。

【问题讨论】:

  • 你的矩阵有多大?如果是 100x2,使用 np.linalg.norm 计算所有距离然后检查阈值可能会更快。
  • 现在的数据矩阵是 100x2,但在某些时候我会有一个 (100000x1000) 数据矩阵。所以相应的距离矩阵将是 100x100 和 100000x100000
  • 这是相当大的。对distance.euclidean 的调用可能需要足够长的时间,因此Python for 循环不再重要。我建议你先比较 distance_from_numpymatrix_b 的时间,比如 (100x1000)。
  • 在上面给出的例子中,计算速度的差异是“感觉的”,因为 numpy 创建的全距离矩阵“distance_from_numpy”是立即计算的,而 for 循环创建的距离矩阵“matrix_b”需要一秒钟或两个都只有 1000x1000
  • 这是相当大的。尝试 numba 并自己计算距离。

标签: python-3.x performance numpy matrix iteration


【解决方案1】:

Python 是一种高级语言,因此循环本身就很慢。它只需要处理很多开销。随着嵌套循环数量的增加,这种情况会变得越来越糟。另一方面,Numpy 使用快速的 Fortran 代码。

为了加快 Python 的实现速度,例如,您可以使用 Cython 实现循环部分,它将您的代码转换为 C,然后编译它以加快执行速度。其他选项是 Numba,或者在 Fortran 中编写循环。

【讨论】:

  • 由于 numpy 使用 fortran 代码,我不应该能够在某种 numpy 方法中转换 for 循环吗?我会调查 Cython,谢谢。
  • 你这是什么意思?这与您的 create_full_distance() 函数有何不同?
  • 我可以使用 while 循环代替 if 语句,这有帮助吗?
  • 而不是在每个计算的距离值 d(i,j) 之后检查它是否小于某个数字,我可以使用“while x > d(i,j): ...”循环
  • 我必须查看实际代码,才能知道您究竟想在什么时候放置 while 循环。但我认为这只有在您有很多以这种方式跳过的案例时才会有所帮助。如果只是 1% 的情况,那也没多大帮助。
【解决方案2】:

正如 Ehsan 在评论中提到的,我使用 numba 来提高计算速度。

from numba import jit
import numpy as np
from scipy.spatial import distance

def create_data_matrix(n,m):
    mean = np.zeros(m)
    cov = np.eye(m, dtype=float)
    data_matrix = np.random.multivariate_normal(mean,cov,n)
    return(data_matrix)
    
def create_full_distance(A):
    distance_matrix = np.triu(distance.squareform(distance.pdist(A,"euclidean")),0)
    return(distance_matrix)

@jit(nopython=True) # Set "nopython" mode for best performance, equivalent to @njit
def slow_loop(matrix_a):
    matrix_b = np.empty((1000,1000))
    for i in range(len(matrix_a)):
        for j in range(len(matrix_a)):
            #matrix_b[i][j] = distance.euclidean(matrix_a[i],matrix_a[j])
            matrix_b[i][j] = np.linalg.norm(matrix_a[i]-matrix_a[j])
    print("matrix_b: ",matrix_b)
    return()

def slow_loop_without_numba(matrix_a):
    matrix_b = np.empty((1000,1000))
    for i in range(len(matrix_a)):
        for j in range(len(matrix_a)):
            matrix_b[i][j] = np.linalg.norm(matrix_a[i]-matrix_a[j])
    return()
    
matrix_a = create_data_matrix(1000,2)

start = time.time()
ergebnis = create_full_distance(matrix_a)
#print("ergebnis: ",ergebnis)  
end = time.time()
print("with scipy.distance.pdist = %s" % (end - start))

start2 = time.time() 
slow_loop(matrix_a)
end2 = time.time()
print("with @jit onto np.linalg.norm = %s" % (end2 - start2))

start3 = time.time()
slow_loop_without_numba(matrix_a)
end3 = time.time()
print("slow_loop without numba = %s" % (end3 - start3))

我执行了代码并产生了以下结果:

with scipy.distance.pdist = 0.021986722946166992
with @jit onto np.linalg.norm = 0.8565070629119873
slow_loop without numba = 6.818004846572876

虽然 scipy 仍然快得多,但 numba 将计算速度提高了很多。距离矩阵越大,这将越有趣。我不能在带有 scipy 方法的函数上使用 numba。

【讨论】:

  • 对于 Numba,只需写出循环而不使用 np.linalg.norm。例如。 stackoverflow.com/a/60854278/4045774 还要测量第二个调用而不是第一个调用,以获得运行时。 (第一次运行是编译时间+运行时)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-26
  • 2017-01-30
  • 2014-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多