【问题标题】:How to best optimize calculations iterated over NxM grid in Python如何在 Python 中最好地优化迭代 NxM 网格的计算
【发布时间】:2019-08-21 11:50:32
【问题描述】:

在 Python 中工作时,我正在对 NxM 值网格进行一些物理计算,其中 N 从 1 变为 3108,M 从 1 变为 2304(这对应于大图像)。我需要在这个空间中的每个点计算一个值,总计约 700 万次计算。我目前的方法非常缓慢,我想知道是否有办法完成这项任务并且不需要几个小时......

我的第一种方法只是使用嵌套的 for 循环,但这似乎是解决我的问题的效率最低的方法。我曾尝试使用 NumPy 的 nditer 并单独迭代每个轴,但我读到它实际上并没有加快我的计算速度。我没有单独遍历每个轴,而是尝试制作一个 3-D 数组并遍历外轴,如 Brian 的回答所示 How can I, in python, iterate over multiple 2d lists at once, cleanly? 。这是我的代码的当前状态:

import numpy as np
x,y = np.linspace(1,3108,num=3108),np.linspace(1,2304,num=2304) # x&y dimensions of image
X,Y = np.meshgrid(x,y,indexing='ij')
all_coords = np.dstack((X,Y)) # moves to 3-D
all_coords = all_coords.astype(int) # sets coords to int

作为参考,all_coords 如下所示:

array([[[1.000e+00, 1.000e+00],
        [1.000e+00, 2.000e+00],
        [1.000e+00, 3.000e+00],
        ...,
        [1.000e+00, 2.302e+03],
        [1.000e+00, 2.303e+03],
        [1.000e+00, 2.304e+03]],

       [[2.000e+00, 1.000e+00],
        [2.000e+00, 2.000e+00],
        [2.000e+00, 3.000e+00],
        ...,
        [2.000e+00, 2.302e+03],
        [2.000e+00, 2.303e+03],
        [2.000e+00, 2.304e+03]],

等等。回到我的代码...

'''
- below is a function that does a calculation on the full grid using the distance between x0,y0 and each point on the grid.
- the function takes x0,y0 and returns the calculated values across the grid
'''
def do_calc(x0,y0):
    del_x, del_y = X-x0, Y-y0
    np.seterr(divide='ignore', invalid='ignore')
    dmx_ij = (del_x/((del_x**2)+(del_y**2))) # x component
    dmy_ij = (del_y/((del_x**2)+(del_y**2))) # y component
    return dmx_ij,dmy_ij

# now the actual loop

def do_loop():
    dmx,dmy = 0,0
    for pair in all_coords:
        for xi,yi in pair:
            DM = do_calc(xi,yi)
            dmx,dmy = dmx+DM[0],dmy+DM[1]
    return dmx,dmy

如您所见,此代码需要非常长的时间才能运行...如果有任何方法可以修改我的代码以使其不需要数小时即可完成,我会非常有兴趣知道如何做那。在此先感谢您的帮助。

【问题讨论】:

  • 你给出的代码是你实际计算的吗?可能不会,因为它只会返回一堆NaNs。目前的代码(在修复NaN 问题之后)很容易加速(使用平移不变性),但这可能无法推广到更现实的每点函数。因此,您必须提供更多信息。例如,一切都只依赖于坐标吗?还是像素值也进入计算?真正的计算或至少部分转换是不变的吗?
  • 上述计算会产生 NaN 的唯一位置是 (x0,y0)。在网格的其余部分,有真正的价值。 NaN 位置是我在实际代码中正确处理的。我在代码中使用的完整计算几乎与上面描述的完全一样,除了前面的另一个由 (x0,y0) 索引的术语,为了简单起见,我没有费心提及或描述。您可以认为上面的代码(除了我适当处理的 NaN 问题)是完整的以及我正在尝试优化的代码。谢谢。
  • 有趣的是,当我运行你的代码时,除了NaNs 什么都没有。提示:网格的每个点在迭代中都是 x0, y0 一次,单个 NaN 项足以构成整个总和 NaN。无论如何,我会尽快发布答案。
  • 太棒了,我对你的解决方案很感兴趣。是的,您对 NaN 的看法是正确的。如果您在除以零时捕获它并将其设置为其他值,那么一切正常。再次感谢。

标签: python performance numpy optimization iteration


【解决方案1】:

这是一种在N=310, M=230 处提供 10,000 倍加速的方法。由于该方法的扩展性比原始代码更好,因此我预计在完整问题规模下会超过一百万。

该方法利用了问题的移位不变性。例如,del_x**2 在每次调用 do_calc 时基本相同,因此我们只计算一次。

如果do_calc 的输出在求和之前被加权,则问题不再是完全平移不变的,并且此方法不再有效。然而,结果可以用线性卷积来表示。在N=310, M=230,这仍然给我们带来了超过 1,000 倍的加速。而且,这将是更完整的问题规模

原始问题的代码

import numpy as np

#N, M = 3108, 2304
N, M = 310, 230

### OP's code

x,y = np.linspace(1,N,num=N),np.linspace(1,M,num=M) # x&y dimensions of image
X,Y = np.meshgrid(x,y,indexing='ij')
all_coords = np.dstack((X,Y)) # moves to 3-D
all_coords = all_coords.astype(int) # sets coords to int

'''
- below is a function that does a calculation on the full grid using the distance between x0,y0 and each point on the grid.
- the function takes x0,y0 and returns the calculated values across the grid
'''
def do_calc(x0,y0):
    del_x, del_y = X-x0, Y-y0
    np.seterr(divide='ignore', invalid='ignore')
    dmx_ij = (del_x/((del_x**2)+(del_y**2))) # x component
    dmy_ij = (del_y/((del_x**2)+(del_y**2))) # y component
    return np.nan_to_num(dmx_ij), np.nan_to_num(dmy_ij)

# now the actual loop

def do_loop():
    dmx,dmy = 0,0
    for pair in all_coords:
        for xi,yi in pair:
            DM = do_calc(xi,yi)
            dmx,dmy = dmx+DM[0],dmy+DM[1]
    return dmx,dmy

from time import time

t = [time()]

### pp's code

x, y = np.ogrid[-N+1:N-1:2j*N - 1j, -M+1:M-1:2j*M - 1J]
den = x*x + y*y
den[N-1, M-1] = 1
xx = x / den
yy = y / den
for zz in xx, yy:
    zz[N:] -= zz[:N-1]
    zz[:, M:] -= zz[:, :M-1]
XX = xx.cumsum(0)[N-1:].cumsum(1)[:, M-1:]
YY = yy.cumsum(0)[N-1:].cumsum(1)[:, M-1:]
t.append(time())

### call OP's code for reference

X_OP, Y_OP = do_loop()
t.append(time())

# make sure results are equal

assert np.allclose(XX, X_OP)
assert np.allclose(YY, Y_OP)
print('pp {}\nOP {}'.format(*np.diff(t)))

示例运行:

pp 0.015251636505126953
OP 149.1642508506775

加权问题代码:

import numpy as np

#N, M = 3108, 2304
N, M = 310, 230

values = np.random.random((N, M))
x,y = np.linspace(1,N,num=N),np.linspace(1,M,num=M) # x&y dimensions of image
X,Y = np.meshgrid(x,y,indexing='ij')
all_coords = np.dstack((X,Y)) # moves to 3-D
all_coords = all_coords.astype(int) # sets coords to int

'''
- below is a function that does a calculation on the full grid using the distance between x0,y0 and each point on the grid.
- the function takes x0,y0 and returns the calculated values across the grid
'''
def do_calc(x0,y0, v):
    del_x, del_y = X-x0, Y-y0
    np.seterr(divide='ignore', invalid='ignore')
    dmx_ij = (del_x/((del_x**2)+(del_y**2))) # x component
    dmy_ij = (del_y/((del_x**2)+(del_y**2))) # y component
    return v*np.nan_to_num(dmx_ij), v*np.nan_to_num(dmy_ij)

# now the actual loop

def do_loop():
    dmx,dmy = 0,0
    for pair, vv in zip(all_coords, values):
        for (xi,yi), v in zip(pair, vv):
            DM = do_calc(xi,yi, v)
            dmx,dmy = dmx+DM[0],dmy+DM[1]
    return dmx,dmy

from time import time
from scipy import signal

t = [time()]
x, y = np.ogrid[-N+1:N-1:2j*N - 1j, -M+1:M-1:2j*M - 1J]
den = x*x + y*y
den[N-1, M-1] = 1
xx = x / den
yy = y / den
XX, YY = (signal.fftconvolve(zz, values, 'valid') for zz in (xx, yy))

t.append(time())
X_OP, Y_OP = do_loop()
t.append(time())
assert np.allclose(XX, X_OP)
assert np.allclose(YY, Y_OP)
print('pp {}\nOP {}'.format(*np.diff(t)))

示例运行:

pp 0.12683939933776855
OP 158.35225439071655

【讨论】:

  • 太棒了.. 我很快就会在我的完整代码中对此进行测试。在求和之前,将计算字段乘以由 (x0,y0) 索引的值是微不足道的,对吗?实际上,我在每个点都有另一个网格,因此在求和之前,我需要将 do_calc(x0,y0) 中的每个计算字段按点 (x0,y0) 的质量进行缩放。为了清楚起见,我想我应该将其包含在原始帖子中。
  • @astro_lukas 这不是微不足道的,而是直截了当的。第二个版本解决了这个问题,values 是一个与您的群众相对应的随机数组。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-21
  • 2021-06-06
  • 2017-04-14
  • 2017-06-04
  • 2012-02-06
相关资源
最近更新 更多