【问题标题】:Efficiently using 1-D pyfftw on small slices of a 3-D numpy array在 3-D numpy 数组的小切片上有效地使用 1-D pyfftw
【发布时间】:2019-08-29 19:45:10
【问题描述】:

我有一个大小约为 10,000x512x512 的 3D 数据立方体。我想重复解析沿 dim[0] 的向量窗口(例如 6)并有效地生成傅立叶变换。我想我正在将数组复制到 pyfftw 包中,这给了我巨大的开销。我现在正在查看文档,因为我认为我需要设置一个选项,但我可以在语法上使用一些额外的帮助。

这段代码最初是由另一个人用 numpy.fft.rfft 编写的,并用 numba 加速。但是该实现在我的工作站上不起作用,所以我重新编写了所有内容并选择使用 pyfftw。

import numpy as np
import pyfftw as ftw
from tkinter import simpledialog
from math import ceil
import multiprocessing

ftw.config.NUM_THREADS = multiprocessing.cpu_count()
ftw.interfaces.cache.enable()

def runme():
    # normally I would load a file, but for Stack Overflow, I'm just going to generate a 3D data cube so I'll delete references to the binary saving/loading functions:
    # load the file
    dataChunk = np.random.random((1000,512,512))
    numFrames = dataChunk.shape[0]
    # select the window size
    windowSize = int(simpledialog.askstring('Window Size',
        'How many frames to demodulate a single time point?'))
    numChannels = windowSize//2+1
    # create fftw arrays
    ftwIn = ftw.empty_aligned(windowSize, dtype='complex128')
    ftwOut = ftw.empty_aligned(windowSize, dtype='complex128')
    fftObject = ftw.FFTW(ftwIn,ftwOut)
    # perform DFT on the data chunk
    demodFrames = dataChunk.shape[0]//windowSize
    channelChunks = np.zeros([numChannels,demodFrames,
        dataChunk.shape[1],dataChunk.shape[2]])
    channelChunks = getDFT(dataChunk,channelChunks,
        ftwIn,ftwOut,fftObject,windowSize,numChannels)
    return channelChunks          

def getDFT(data,channelOut,ftwIn,ftwOut,fftObject,
        windowSize,numChannels):
    frameLen = data.shape[0]
    demodFrames = frameLen//windowSize
    for yy in range(data.shape[1]):
        for xx in range(data.shape[2]):
            index = 0
            for i in range(0,frameLen-windowSize+1,windowSize):
                ftwIn[:] = data[i:i+windowSize,yy,xx]
                fftObject() 
                channelOut[:,index,yy,xx] = 2*np.abs(ftwOut[:numChannels])/windowSize
                index+=1
    return channelOut

if __name__ == '__main__':
    runme()

我得到了一个 4D 数组;变量通道块。我将每个通道保存到二进制文件中(上面的代码中不包含,但保存部分工作正常)。

这个过程适用于我们的解调项目,然后将 4D 数据立方体 channelChunks 解析为 eval(numChannel) 3D 数据立方体(电影),并且根据我们的实验设置,我们能够通过颜色分离电影。我希望我可以绕过编写一个通过 pyfftw 调用矩阵上的 fft 的 C++ 函数。

实际上,我在给定的 1 轴和 2 轴索引处沿 dataChunk 的 0 轴获取 windowSize=6 个元素并执行 1D FFT。我需要在整个 3D 数据块中执行此操作以生成解调电影。谢谢。

【问题讨论】:

  • 您对此进行了分析吗?这应该让您很好地了解实际花费的时间。在大多数情况下,编写 C++ 函数不会加快速度。
  • 是的,我运行了 cProfiler。大约 98% 的时间都花在了 getDFT 调用上。问题似乎出在 for 循环中:for i in range(0,frameLen-windowSize+1,windowSize): 但是,分析器没有详细说明,所以我需要获得更好的统计数据,也许可以调用另一个选项。无论如何,我认为问题出在 ftwIn[:] = data[i:i+windowSize,yy,xx] 行的 for 循环中。在axis0中10,000帧需要40分钟,我很肯定如果内存管理得当,我可以得到1-2个数量级的改进。
  • cProfiler.run 应该提供每个函数调用所花费的时间——这只是了解那里有什么的问题。函数getDFT 是最明显的,因为它循环,与其他所有函数相比,它需要很长时间。您可以在 fft 函数中用 axis 参数替换 xxyy 上的循环。

标签: python numpy numpy-slicing pyfftw


【解决方案1】:

FFTW advanced plans 可以由 pyfftw 自动构建。 可以通过以下方式修改代码:

  • 实数到复数的转换可以用来代替复数到复数的转换。 使用 pyfftw,它通常会这样写:

    ftwIn = ftw.empty_aligned(windowSize, dtype='float64')
    ftwOut = ftw.empty_aligned(windowSize//2+1, dtype='complex128')
    fftObject = ftw.FFTW(ftwIn,ftwOut)
    
  • 向 FFTW 规划器添加一些标志。例如,FFTW_MEASURE 将计时不同的算法并选择最佳的。 FFTW_DESTROY_INPUT 表示可以修改输入数组:可以使用一些实现技巧。

    fftObject = ftw.FFTW(ftwIn,ftwOut, flags=('FFTW_MEASURE','FFTW_DESTROY_INPUT',))
    
  • 限制分割数。除法的成本高于乘法。

    scale=1.0/windowSize
    for ...
        for ...
            2*np.abs(ftwOut[:,:,:])*scale  #instead of /windowSize
    
  • 通过 pyfftw 使用 FFTW advanced plan 避免多个 for 循环。

    nbwindow=numFrames//windowSize
    # create fftw arrays
    ftwIn = ftw.empty_aligned((nbwindow,windowSize,dataChunk.shape[2]), dtype='float64')
    ftwOut = ftw.empty_aligned((nbwindow,windowSize//2+1,dataChunk.shape[2]), dtype='complex128')
    fftObject = ftw.FFTW(ftwIn,ftwOut, axes=(1,), flags=('FFTW_MEASURE','FFTW_DESTROY_INPUT',))
    
    ...
    for yy in range(data.shape[1]):
        ftwIn[:] = np.reshape(data[0:nbwindow*windowSize,yy,:],(nbwindow,windowSize,data.shape[2]),order='C')
        fftObject()
        channelOut[:,:,yy,:]=np.transpose(2*np.abs(ftwOut[:,:,:])*scale, (1,0,2))
    

这是修改后的代码。我还将帧数减少到 100,设置随机生成器的种子以检查结果是否未修改并注释 tkinter。 窗口的大小可以设置为 2 的幂,也可以设置为 2、3、5 或 7 的乘积,从而可以有效地应用 Cooley-Tukey 算法。避免使用大素数。

import numpy as np
import pyfftw as ftw
#from tkinter import simpledialog
from math import ceil
import multiprocessing
import time


ftw.config.NUM_THREADS = multiprocessing.cpu_count()
ftw.interfaces.cache.enable()
ftw.config.PLANNER_EFFORT = 'FFTW_MEASURE'

def runme():
    # normally I would load a file, but for Stack Overflow, I'm just going to generate a 3D data cube so I'll delete references to the binary saving/loading functions:
    # load the file
    np.random.seed(seed=42)
    dataChunk = np.random.random((100,512,512))
    numFrames = dataChunk.shape[0]
    # select the window size
    #windowSize = int(simpledialog.askstring('Window Size',
    #    'How many frames to demodulate a single time point?'))
    windowSize=32
    numChannels = windowSize//2+1

    nbwindow=numFrames//windowSize
    # create fftw arrays
    ftwIn = ftw.empty_aligned((nbwindow,windowSize,dataChunk.shape[2]), dtype='float64')
    ftwOut = ftw.empty_aligned((nbwindow,windowSize//2+1,dataChunk.shape[2]), dtype='complex128')

    #ftwIn = ftw.empty_aligned(windowSize, dtype='complex128')
    #ftwOut = ftw.empty_aligned(windowSize, dtype='complex128')
    fftObject = ftw.FFTW(ftwIn,ftwOut, axes=(1,), flags=('FFTW_MEASURE','FFTW_DESTROY_INPUT',))
    # perform DFT on the data chunk
    demodFrames = dataChunk.shape[0]//windowSize
    channelChunks = np.zeros([numChannels,demodFrames,
        dataChunk.shape[1],dataChunk.shape[2]])
    channelChunks = getDFT(dataChunk,channelChunks,
        ftwIn,ftwOut,fftObject,windowSize,numChannels)
    return channelChunks          

def getDFT(data,channelOut,ftwIn,ftwOut,fftObject,
        windowSize,numChannels):
    frameLen = data.shape[0]
    demodFrames = frameLen//windowSize
    printed=0
    nbwindow=data.shape[0]//windowSize
    scale=1.0/windowSize
    for yy in range(data.shape[1]):
        #for xx in range(data.shape[2]):
            index = 0

            ftwIn[:] = np.reshape(data[0:nbwindow*windowSize,yy,:],(nbwindow,windowSize,data.shape[2]),order='C')
            fftObject()
            channelOut[:,:,yy,:]=np.transpose(2*np.abs(ftwOut[:,:,:])*scale, (1,0,2))
            #for i in range(nbwindow):
                #channelOut[:,i,yy,xx] = 2*np.abs(ftwOut[i,:])*scale

            if printed==0:
                      for j in range(channelOut.shape[0]):
                          print j,channelOut[j,0,yy,0]
                      printed=1

    return channelOut

if __name__ == '__main__':
    seconds=time.time()
    runme()
    print "time: ", time.time()-seconds

让我们知道它在多大程度上加快了您的计算速度!我在电脑上从 24 秒到不到 2 秒...

【讨论】:

  • 哦,是的,它至少快了一个数量级,我在几分钟内处理了 10K 帧,而之前需要一个小时。我必须做更多的事情,因为我正在加载任意长度的数据集,所以最后一个数据块需要一个新的 ftw 对象调用及其数据大小。我会在周二公布一些统计数据。非常感谢!
  • 我检查了 cprofiler 输出。所以实际的 pyfft 调用只需要我 36 秒来处理 10,000 帧,窗口大小为 6,总执行时间为 237 秒。之前我花了 2001 秒完成所有事情,在循环 DFT 调用上花费了大约 1800 秒。我现在的开销是加载数据——这很容易修复。再次感谢!
猜你喜欢
  • 1970-01-01
  • 2015-11-23
  • 2018-07-18
  • 1970-01-01
  • 2021-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-19
相关资源
最近更新 更多