【问题标题】:Advice on vectorizing block-wise operations in Numpy在 Numpy 中向量化块操作的建议
【发布时间】:2020-10-07 03:04:03
【问题描述】:

我正在尝试实现一系列统计操作,我需要帮助我的代码矢量化。

这个想法是从两个图像中提取NxN 补丁,计算这两个补丁之间的距离度量。

为此,首先我使用以下循环构造补丁:

params = []
for i in range(0,patch1.shape[0],1):
    for j in range(0,patch1.shape[1],1):
        window1 = np.copy(imga[i:i+N,j:j+N]).flatten()
        window2 = np.copy(imgb[i:i+N,j:j+N]).flatten()
        params.append((window1, window2))
print(f"We took {time()- t0:2.2f} seconds to prepare {len(params)/1e6} million patches.")

这需要大约 10 秒才能完成,我并不太关心预处理时间。下面的步骤是我要优化的步骤。

之后,为了加快处理速度,我使用了多池来计算实际结果。包含实际计算的函数如下:

@njit
def cauchy_schwartz(imga, imgb):
    p, _ = np.histogram(imga, bins=10)
    p = p/np.sum(p)
    q, _ = np.histogram(imgb, bins=10)
    q = q/np.sum(q)

    n_d = np.array(np.sum(p * q)) 
    d_d = np.array(np.sum(np.power(p, 2) * np.power(q, 2)))
    return -1.0 * np.log10( n_d, d_d)

我使用这个结构来处理所有的补丁:

def f(param):
    return cauchy_schwartz(*param)

with Pool(4) as p:
    r = list(tqdm.tqdm(p.imap(f,params), total=len(params)))

我确信必须有更优雅的方法来执行此操作,因为如果我将整个 10Kpx x 10Kpx 图像发送到 cauchy_schwartz 函数,它会在一秒钟内处理所有内容,但使用我的方法,即使在 4 核上这需要很长时间。

我的心智模型是 blockproc 在 matlab 中的工作方式 - 我最终以这种模式编写了这段代码。如有任何关于改进此代码性能的建议,我将不胜感激。

【问题讨论】:

    标签: python performance numpy scipy scientific-computing


    【解决方案1】:

    通过使用apply_along_axis,您可以摆脱cauchy_schwartz。由于您不太关心预处理时间,假设您已获得包含扁平补丁的数组params

    params = np.random.rand(3,2,100)

    你可以看到params的形状是(3,2,100),这三个数字3、2、100只是随机选择来创建一个辅助数组来演示使用apply_along_axis的逻辑。 3 对应于您拥有的补丁数量(由补丁形状和图像大小确定),2 对应于两个图像,100 对应于扁平补丁。因此,params 的轴是 (idx of patches, idx of images, idx of entries of a flattened patch),这与您的代码创建的列表 params 完全匹配

    params = []
    for i in range(0,patch1.shape[0],1):
        for j in range(0,patch1.shape[1],1):
            window1 = np.copy(imga[i:i+N,j:j+N]).flatten()
            window2 = np.copy(imgb[i:i+N,j:j+N]).flatten()
            params.append((window1, window2))
    

    使用辅助数组params,这是我的解决方案:

    hist = np.apply_along_axis(lambda x: np.histogram(x,bins=11)[0],2,params)
    hist = hist / np.sum(hist,axis=2)[...,None]
    
    n_d = np.sum(np.product(hist,axis=1),axis=1)
    d_d = np.sum(np.product(np.power(hist,2),axis=1),axis=1)
    res = -1.0 * np.log10(n_d, d_d)
    

    【讨论】:

    • 谢谢 - 这正是我想要的,它也帮助我学习逻辑。有点好奇为什么您选择 3 作为random.rand(3,2,100) 中参数的第一个维度。让我进行实验和思考,并尝试理解这一点。但是,如果你能帮忙解释一下——我就不用猜了。在您的解决方案中是 N(窗口大小)3、深度 2(对于 2 个补丁)和 100 个样本/块?
    • @shaunakde 欢迎您 :) 很高兴我的回答对您有所帮助,我已经更新了我的回答以解释数组的轴 params
    • 感谢您的更新。我来这里是说通过一些试验和错误我能够理解,但感谢您的更新!我还学到了很多关于编写好的 numpy 代码的知识。谢谢!
    【解决方案2】:

    首先,分析您的代码以确定瓶颈。您可以使用https://mg.pov.lt/profilehooks/。我认为瓶颈在于创建补丁,因为您正在为流程创建补丁的副本。您可以通过仅传递补丁的索引来使用更少的内存:

    params = []
    for i in range(0,patch1.shape[0],1):
        for j in range(0,patch1.shape[1],1):
            start, end = (i,i+N), (j,j+N)
            params.append((start, end))
    

    那么,假设imgaimgb 是全局的,您可以从cauchy_schwartz 函数创建补丁,如下所示:

    @njit
    def cauchy_schwartz(start, end):
    
        a,b = start; c,d = end
        window1 = np.copy(imga[a:b, c:d]).flatten()
        window2 = np.copy(imgb[a:b, c:d]).flatten()
    
        # process patches window1 and window2
    

    【讨论】:

    • 谢谢你的回答,但这不是慢的一步。我能够 Numba 编译预处理代码。与后续步骤中较慢的直方图计算和补丁处理相比,它微不足道。 np.copy 让我从内存中删除原始数组。我可以将它们更改为引用,但它不会改变计算时间。
    • 也许你的代码会导致错误共享:错误共享发生在不同处理器上的线程修改驻留在同一缓存行source上的变量时。
    • 有趣!你对如何衡量有什么建议吗?或者我可以采取哪些措施来缓解它?
    • 也许您可以使用不同的补丁大小(代码中的patch1.shape)。我假设更大的补丁大小可能会带来更好的性能(更短的执行时间)。
    • 感谢您的建议。我认为您在该评估中是正确的。如果我将整个图像传递给函数,它会在一秒钟内计算出来。但是如果我通过许多小块 - 它需要更长的时间(~120s)。我假设这与 python 的内部数据模型有关。我正在尝试考虑聚合或构造这个问题的方法,以便我可以使用 numba 的矢量化或使用 numpy 执行整个操作。将随时更新这篇文章的任何发现!感谢您抽出宝贵时间提供帮助! [1/2]
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-31
    • 2011-08-12
    • 2015-07-05
    • 1970-01-01
    • 1970-01-01
    • 2022-11-20
    相关资源
    最近更新 更多