在 CPU 上寻找 Python 中最快的 2D 卷积

信息处理 图像处理 Python 麻木的
2022-01-02 23:33:11

卷积是神经网络、图像处理、计算机视觉中许多算法的基本组成部分……但这些也是计算方面的瓶颈……在python生态系统中,存在不同的现有解决方案,使用numpy,scipytensorflow,但哪个是最快的?

只是为了解决问题,卷积应该在两个二维矩阵上运行。我们将在这里始终考虑计算机视觉中最典型的情况:

  • 第一矩阵一种是输入,通常很大 (ñ×ñ在哪里ñ通常大于210=1024),
  • 第二个矩阵是模板,通常更小(比如=128),
  • 卷积的结果C=一种*被填充,使其大小相同一种.

    通常,您需要对许多内核上的许多图像执行此操作,因此在许多内核上执行此操作的方法有一个好处。

感谢您的任何提示!

PS:目标是在某个时候在这篇博客文章中总结这些结果,其中已经包含一些例子......

3个回答

为了提供更量化的方法,我制作了一个Jupyter notebook (可以在这里看成一个网页)。

结果可以总结在下图中: 增加尺寸的结果

在实践中,我发现它numpy总是快很多。然而,似乎新的替代方案正在出现,我必须包括(并非详尽无遗):特定于架构的优化 (IPP)、深度学习算法中的简化图(例如PyTorch)等等。欢迎评论!

这取决于图像和过滤器的大小。
有时它还取决于过滤器本身和所需的质量。

假设所有任意(即过滤器没有特殊属性,但它们的大小,其中一些是 HPF,一些 LPF,一些都不是,它们不可分离,不允许近似等......)可以遵循以下原则:

  • 如果过滤器与图像相比较小,如果过滤器使用一次,通常直接计算是要走的路。
  • 如果滤波器很长或对许多图像多次使用,最好在频域中进行。请注意,您需要填充才能使用频域乘法(循环卷积)应用线性卷积。还要尝试利用所有真实数据(频域中的对称性)。
  • 如果允许近似,滤波器的可分离近似可能会产生很大的加速。
  • 如果您使用利用英特尔 IPP 的直接卷积将产生最快的结果。
  • 如果您使用频域,则枯萎的 IPP 或 FFTW 将产生最快的结果(在 FFTW 的情况下,您仍然需要使用 IPP 或手动编码有效地进行频域乘法)。

这些指南将轻松让您在性能方面接近您的系统优势。
通常带有小内核的卷积是内存有限的操作。

顺便说一句,英特尔 IPP 中的过滤速度可能最快。
您可以通过安装Intel Python Distribution在 Cython 中使用 IPP 来使用它

由于您专门指定 CPU,因此可能值得考虑 pyFFTW 和 python 子进程。在过去,我已经成功地将数组拆分为子数组。然后为子数组的子集启动并发进程,这些子数组又将运行 pyFFTW 的实例。PyFFTW 是 FFTW 上的 python 包装器,根据我的经验,它比 numpy fft 快。请阅读有关启用缓存等以优化性能的 pyFFTW 文档。稍后我在工作计算机上时将在此处发布一些代码。但是有一个问题,太多的进程会减慢速度。下面是我一直在讨论的一个例子。fft_split 函数将问题(dat)拆分为 num_prcss 个进程,并让多个进程将它们作为一个批次进行处理。我在这里使用 pyFFTW,但如果您决定运行它,可以使用 numpy fft。

import numpy as np
import pyfftw as pyfftw
import multiprocessing as mp
import time as time

dat = np.random.rand(50000).reshape(50,-1)

num_prcss = 8
threads = 8

output = mp.Queue()

def fft_sub_process(dat_in, row, output):
    match_out = pyfftw.interfaces.numpy_fft.ifft(dat_in, threads=threads)
    match_abs = np.abs(match_out)
    max_match_fltr = np.max(match_abs)
    print([row, max_match_fltr])
    output.put([row, max_match_fltr])

def fft_split(dat, num_prcss):
    results = []
    for r in xrange(0, dat.shape[-2], num_prcss):    
        if r == dat.shape[-2] - dat.shape[-2]%num_prcss:
            processes = [mp.Process(target=fft_sub_process, args=(dat[row], row, output)) for row in xrange(r, r+dat.shape[-2]%num_prcss)]
            for p in processes:
                p.start()
            for p in processes:
                p.join()    
            results.append([output.get() for p in processes])
        else:
            processes = [mp.Process(target=fft_sub_process, args=(dat[row], row, output)) for row in xrange(r, r+num_prcss)]
            for p in processes:
                p.start()
            for p in processes:
                p.join()
            results.append([output.get() for p in processes])

    return results

t = time.time()

search_res = fft_split(dat, num_prcss)

elapsed = time.time() - t

print(elapsed)