为什么Python代码要花这么长时间才能在数据集中找到接近点?

为什么Python代码要花这么长时间才能在数据集中找到接近点?,python,performance,numpy,Python,Performance,Numpy,我试图制作一个高效的Python代码,在给定表单中的一组数据点的情况下 a1 a2 a3 ... an (point 1) b1 b2 b3 ... bn (point 2) . . . 将查找其中哪些太接近(在阈值内) 我已经有了以下Fortran例程来解决这个问题: program check_closeness implicit none

我试图制作一个高效的Python代码,在给定表单中的一组数据点的情况下

a1 a2 a3 ... an (point 1)
b1 b2 b3 ... bn (point 2)
         .
         .
         .
将查找其中哪些太接近(在阈值内)

我已经有了以下Fortran例程来解决这个问题:

program check_closeness                                           
    implicit none
    integer, parameter :: npoints = 10000, ndis = 20
    real(8), parameter :: r = 1e0, maxv = 3e0, minv = 0e0
    real(8), dimension(npoints,ndis) :: dis
    real(8) :: start, ende

    call RANDOM_NUMBER(dis)
    dis = (maxv - minv) * dis + minv

    call cpu_time(start)
    call remove_close(npoints, ndis,dis, r)
    call cpu_time(ende)
    write(*,*) 'Time elapsed', ende - start
endprogram

subroutine remove_close(npoints, ndis, points, r)
    implicit none
    integer, intent(in) :: npoints, ndis
    integer :: i, i_check
    real(8), dimension(npoints, ndis), intent(in) :: points
    real(8), intent(in) :: r
    logical :: is_close

    do i=1,npoints
       is_close = .FALSE.
       i_check = i

       do while (.not. is_close .and. i_check < npoints)
          i_check = i_check + 1
          is_close = all(abs(points(i,:) - points(i_check,:)) < r)
       enddo
    enddo
end subroutine
此执行耗时102.60617995262146(秒),比Fortran版本慢得多

我尝试了这个例程的另一个Python版本,它的速度要快得多,但仍然无法接近Fortran版本:

import numpy as np                        
import time

def check_close(a, r):
    for i, vec1 in enumerate(a[:-1]):
        d = np.abs(a[i+1:,:] - vec1)
        is_close = np.any(np.all(d < r, axis=1))

maxv, minv = 3, 0
a = np.random.rand(10000, 20)
a = (maxv - minv) * a + minv
r = 1e0

ini = time.time()
check_close(a, r)
fin = time.time()
print('Time elapsed {}'.format(fin - ini))
测量它们和更新时间如下:

Python实现1:经过的时间98.63728801719844(秒)


Python实现2:耗时3.3923211600631475(秒)

之所以创建Numpy,是因为Python速度非常慢。正如您所展示的,fortran代码在没有太多算法差异的情况下速度更快。 因此,要回答您的问题:

  • Python是一种脚本语言,并不是围绕速度构建的。如果需要速度,可以像现在这样使用numpy之类的库,或者尝试在需要的地方将本机C/C++代码合并到程序中
  • 为了使它和fortran一样快,试着找到一种只使用numpy例程的方法,去掉for循环。这将大大提高您的性能
  • 试着思考你的问题,以及你如何能够不经过循环地解决它们;这将是在python中解锁速度的关键


    顺便说一句,我不确定您使用
    np.abs()
    的目的是什么,但它不是在计算两点之间的欧几里德距离。使用
    np.linalg.norm()

    我在我的机器上尝试了两种方法,都不到3秒钟,但还没有FORTRAN那么快

    我生成了类似的随机数据,但增加了r,否则我得到了零个案例

    import time
    
    import numpy as np
    
    maxv, minv = 3, 0
    
    a = np.random.rand(10000, 20)
    a = (maxv - minv) * a + minv
    r = 3e0
    
    1-BallTree(sklearn) 给

    Balltree (sklearn) based - time elapsed 1.5845096111297607
    
    pdist (scipy) based - time elapsed 0.6769788265228271
    
    编辑:奇怪(有人能解释吗?)一个疯狂的高
    leafsize=5000
    在我的情况下效果最好。通常
    leafsize
    约为10

    2-pdist(scipy)
    pdist
    返回点的压缩形式标识,因此需要执行一些
    (i,j)
    索引管理,但这是通过vectors=fast完成的。

    您可以在Python中做得更好,但这需要一些工作。令人惊讶的是,我认为您仅使用NumPy的方法与您在不涉及其他模块的情况下所能获得的方法一样好。然而,如果你愿意使用numba,你甚至可以比Fortran做得更好

    下面的函数返回闭合向量。它实际上是用Python编写的C代码,然后使用numba将其编译为本机代码

    将numpy导入为np
    从数学进口工厂
    来自numba import njit
    @njit
    def检查关闭2(a,r):
    n=a.形状[0]
    m=a.形状[1]
    #选择数组,如果以后要使用它
    close=np.zero(n,dtype=np.bool_u2;)
    对于范围(n)中的i:
    对于范围(i+1,n)内的j:
    对于范围内的k(m):
    #短路比较
    last=fabs(a[i,k]-a[j,k])>=r
    如果最后:
    打破
    #如果我们到了这里,我们就找到了一条近路
    如果k==(m-1)且不是最后一个:
    #一旦找到关闭值,停止查找
    关闭[i]=1
    打破
    回程结束
    
    当我在我的机器上使用
    %timeit
    运行它时,我得到

    %timeit check_close_nb2(a, r)
    544 ms ± 4.07 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    
    当我运行上面的代码时,我看到

    %timeit check_close(a, r)
    4.91 s ± 69.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    因此,我的比较表明,我的机器比你的机器慢一点,建议numba版本的时间大约为400ms。

    不要使用
    time.time
    对代码进行基准测试,因为它可以测量墙时间(即,它包括系统上运行的所有内容)。要么使用,要么使用。然后,请用新的数字更新您的问题。@a_客人,我不认为缩短10%的时间会有什么帮助。请注意,这可能不是一种特别有效的方法。首先将点放入宽度为r的箱子中。然后,唯一可以靠近的点位于与参考点相同的箱子中,或者位于相邻的箱子中。生成的算法将是O(N),而不是这里的O(N**2)。对于大N,这可能会使代码的数量级比您在这里的速度快,无论您选择何种语言。我使用的是may,因为我只在3D中使用过这种方法,而您的维度更高一些。@Pytity我不理解您的评论。通过
    time报告的数字。time
    不可靠,因此我要求更新这些数字。还要注意,real(8)在Fortran中不可移植,对于某些编译器将失败。了解内部iso_fortran_env模块,并参见类似的fortran norm2计算数组的2-范数。但是如果你真的想要性能,你会避开平方根,比较长度的平方,不知道你从哪里得到了欧几里德距离。OP显然实现了切比雪夫距离。这可能与问答无关。非常感谢。注意,我刚刚更新了Fortran时间,我犯了一个错误,所以您的第二个方法实际上在时间上更好。它的问题是内存。对于我的典型问题,我必须加载大约1e5个数据点,因此距离矩阵无法存储在内存中。@pablo您应该在问题帖中提到约束(距离矩阵无法存储在内存中)。pdist不是存储完整的距离矩阵,而是大约一半的距离矩阵。可能正是内存问题出现的时候。balltree和pdist支持多个距离度量,这也是一个好消息。我对所使用的metic做出了(错误的?)假设。pdist解决方案给出的答案与原始帖子的答案不一样。您需要更改度量值吗?看起来应该是“切比雪夫”。这也会在我的机器上大大加快解决方案的速度(670ms->400ms),并生成正确的答案(在
    np.unique
    之后,用于重复数据消除
    i
    )。
    from scipy.spatial.distance import pdist
    
    
    ini = time.time()
    
    condensed_distance_small = pdist(a) <= r
    condensed_indici = np.argwhere(condensed_distance_small)
    
    n = 10000
    
    #
    # i,j contain the indici of points that are within r of each other
    # based on condensed-indici formulas of
    # https://stackoverflow.com/questions/13079563/how-does-condensed-distance-matrix-work-pdist
    
    i = np.ceil((1/2.) * (- (-8*condensed_indici + 4 *n**2 -4*n - 7)**0.5 + 2*n -1) - 1).astype(int)
    elem_in_i_rows = (i+1) * (n - 1 - (i+1)) + ((i+ 1)*(i + 2))//2
    j = (n - elem_in_i_rows + condensed_indici)
    
    fin = time.time()
    
    print('pdist (scipy) based - time elapsed {}'.format(fin - ini))
    
    pdist (scipy) based - time elapsed 0.6769788265228271
    
    %timeit check_close_nb2(a, r)
    544 ms ± 4.07 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    
    %timeit check_close(a, r)
    4.91 s ± 69.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)