Python加速大型嵌套数组处理

3

我想知道是否有更快的方法来完成这个任务。

"""
Structure
 -data[]
  -data[0] 
    -data[number, number, number, number, number, number, number]
    - ... ect X 12000
  -data[1]
    -data[number, number, number, number, number, number, number]
    - ... ect X 12000
  -data[2]
    -data[number, number, number, number, number, number, number]
    - ... ect X 12000
  -data[3] 
    -data[number, number, number, number, number, number, number]
    - ... ect X 12000
x and y are the first two numbers in each data array.
"""

我需要扫描第1、2、3层中的每个项目,针对第一层(0)中的每个项目进行比较,查看它们是否在给定的搜索半径内。这需要一些时间。

    for i in range (len(data[0])):

        x = data[0][i][0]
        y = data[0][i][1]

        for x in range (len(data[1])):
            x1 = data[1][x][0]
            y1 = data[1][x][1]

            if( math.pow((x1 -x),2) + math.pow((y1 - y),2) < somevalue):
                matches1.append(data[0][i])
                matches2.append(data[1][x])
                continue
            else:
                continue

感谢您的任何帮助!

3
你应该使用NumPy。 - Daniel
我尝试将数组转换为numpy数组:data2 = np.array(data)。似乎没有什么区别。我应该使用特定的numpy运算符进行处理吗? - Rankinstudio
1
@rankind 将其转换为数组并不会使常规的Python操作更快。有很多专门针对数组的NumPy方法存在。 - roganjosh
你的数据是什么样子的?坐标范围如何分布,somevalue有多大,预计有多少匹配项? - Stefan Pochmann
4个回答

1

首先,你应该编写更易读的Python代码:

for x,y in data[0]:
    for x1, y1 in data[1]:
        if (x1 - x)**2 + (y1 - y)**2 < somevalue:
            matches1.append((x,y))
            matches2.append((x1,y1))

您可以使用numpy对内部循环进行向量化:
for x,y in data[0]:
    x1, y1 = data[1].T
    indices = (x1 - x)**2 + (y1 - y)**2 < somevalue
    matches.append(((x,y), data[1][indices]))

在第一个例子中,如何从data[0]中提取x,y?我必须按位置引用它们,因为它们不叫x,y。它们总是在位置0和1上 x = data[0][i][0] y = data[0][i][1]。数组中还有其他数据与它们一起。也许我没有清楚地说明数据结构。 - Rankinstudio
1
@rankind 这只是序列解包,xy的名称由您选择。如果在位置0和1后面有更多的内容,则可以使用for x, y, *dummy in data[0]:等等。请参见这里 - Paul Panzer
1
@rankind 别忘了 for 循环本身会进行一层解包:data[0] 是 "2D",for 语句将其分解为一个个 1D 切片。然后每个 1D 切片都会被 x, y, *catchall 解包。 - Paul Panzer
1
@rankind,你的Python版本是什么,你是如何出现这个错误的?-除非你正在积极修改它,否则循环或解包不会影响其结构。 - Paul Panzer
我已经取得了让它工作的良好进展。我忘了提到这些数组长度不同。有什么简单的方法可以解决吗?“布尔索引与沿维度0索引的数组不匹配” - Rankinstudio
显示剩余3条评论

1
针对这个特定问题,scipy.spatial.KDTree 或者其 Cython 版本 scipy.spatial.cKDTree 似乎是量身定制的解决方案:
import numpy as np
from scipy.spatial import cKDTree

# create some random data
data = np.random.random((4, 12000, 7))

# in each record discard all but x and y
data_xy = data[..., :2]

# build trees
trees = [cKDTree(d) for d in data_xy]

somevalue = 0.001

# find all close pairs between reference layer and other layers
pairs = []
for tree in trees[1:]:
    pairs.append(trees[0].query_ball_tree(tree, np.sqrt(somevalue)))

这个例子只需要不到一秒钟的时间。请注意,输出格式与您的脚本生成的格式不同。对于三个非参考层中的每一个,它都是一个列表的列表,其中索引为k的内部列表包含靠近参考列表中点k的点的索引。

0
我建议将此创建为一个函数,并使用numba库和装饰器@jit(nopython=True)。
另外,正如建议的那样,您应该使用numpy数组,因为numba专注于利用numpy操作。
from numba import jit

@jit(nopython=True)
def search(data):

  matches1 = []
  matches2 = []

  for i in range (len(data[0])):

    x = data[0][i][0]
    y = data[0][i][1]

    for x in range (len(data1[1])):
      x1 = data[1][x][0]
      y1 = data[1][x][1]

      if( math.pow((x1 -x),2) + math.pow((y1 - y),2) < somevalue):
          matches1.append(data[0][i])
          matches2.append(data[1][x])
          continue
      else:
          continue

  return matches1, matches2

if __name__ == '__main__':
  # Initialize
  # import your data however.
  m1, m2 = search(data)

关键是要确保只使用 numba 支持的允许函数。

我曾经看到过速度提高了 100 倍快,甚至达到了 ~300 倍快。


1
在这种情况下,速度增加了多少?从你的措辞中看来,你只是随意添加了“@jit”装饰器,希望能得到最好的结果。 - roganjosh
3
@rankind 我对这个答案有些怀疑,因为它还没有经过测试,而且很多东西无法被JIT编译。在这种情况下,如果您能在问题中提供一个玩具示例,那将会很有帮助。 - roganjosh
@roganjosh,我觉得这是一个有趣的争议点。我想进一步探讨一下。我们可以去Python聊天室讨论更多,6号房间可以吗? - Kyle Swanson
可能是这样,但实际上你只需要看一下这个的例子。如果在带有@jit(nopython=True)的函数中遇到不支持的内容,整个函数就会回退到Python速度。 - roganjosh
确实...我自己还没有做过那个...但是这里有一个好的答案 - Kyle Swanson
显示剩余7条评论

0

这也是使用GPGPU计算的好地方。从Python中,您可以根据底层硬件选择pycuda和pyopencl。如果您没有GPU,则OpenCL还可以使用CPU上的一些SIMD指令。
如果您不想走GPGPU路线,那么像之前提到的numpy或numba也会很有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接