加快numpy阵列的分析

时间:2015-02-06 03:03:51

标签: python performance numpy multiprocessing fft

我有一个python代码,它用数字导入4列txt文件 前三列是x,y,z坐标,第四列是该坐标处的密度。

下面是读取,转换为ndarray,傅里叶变换该字段,计算距原点(k =(0,0,0))和变换坐标的距离,取平均值并绘制它们的代码。 感谢pandas(用于数据分析的python库)和python FFT,加载256 ^ 3行和傅里叶变换非常快,并在几秒钟内完成。

然而,将加载的txt转换为numpy ndarray,计算平均密度(每个坐标的平均值),以及计算距离原点的距离(k =(0,0,0))需要很长时间。

我认为问题是最后的部分,但我无法弄清楚如何优化它。

我有32台核心机器的资源。

有人可以教我如何加速,使它成为一个多进程代码,或类似的东西,以便那些可以很快完成?感谢。

(如果您是宇宙学家并且需要此代码,您可以使用它,但如果可以,请与我联系。谢谢)

from __future__ import division
import numpy as np

ngridx = 128
ngridy = 128    
ngridz = 128

maxK = max(ngridx,ngridy,ngridz)

#making input file
f = np.zeros((ngridx*ngridy*ngridz,4))

i = 0
for i in np.arange(len(f)):
    f[i][0] = int(i/(ngridy*ngridz))
    f[i][1] = int((i/ngridz))%ngridy
    f[i][2] = int(i%ngridz)
    f[i][3] = np.random.rand(1)
    if i%1000000 ==0:
        print i
#This takes forever
#end making input file

#Thanks to Mike,
a = f[:,3].reshape(ngridx,ngridy,ngridz)

avg =np.sum(f[:,3])/len(f)
a /= avg
p = np.fft.fftn(a)
#This part is much much faster than before (Original Post).

#Keeping track of corresponding wavenumbers (k_x, k_y,k_z) for each element in p
#This is just a convension on fourier transformation so you can ignore this part
kValx = np.fft.fftfreq( ngridx , (1.0 / ngridx ) )
kValy = np.fft.fftfreq( ngridy , (1.0 / ngridy ) )
kValz = np.fft.fftfreq( ngridz , (1.0 / ngridz ) )
kx = np.zeros((ngridx,ngridy,ngridz))
ky = np.zeros((ngridx,ngridy,ngridz))
kz = np.zeros((ngridx,ngridy,ngridz))
rangecolx = np.arange(ngridx)
rangecoly = np.arange(ngridy)
rangecolz = np.arange(ngridz)
for row in np.arange(ngridx):
    for column in np.arange(ngridy):
        for height in np.arange(ngridz):
            kx[row][column][height] = (kValx[row])
            ky[row][column][height] = (kValy[column])
            kz[row][column][height] = (kValz[height])
    if row%10 == 0:
        print row
print 'wavenumber generate complete!'

#Calculating the average powerspectrum in terms of fixed K (Distance from origin to a point in fourier space)
#by taking the spherical shell of thickness 1 and averaging out the values inside it.
#I am sure that this process can be optimised somehow, but I gave up.

qlen = maxK/2 #Nyquist frequency
q = np.zeros(((qlen),4),dtype=complex)
#q is a four column array with length maxK/2.
#q[:,0] is integer wavenumber (K, which is the distance from the origin = sqrt(kx^2+ky^2+kz^2))
#q[:,1] is the sum of square of the fourier transformed value 
#q[:,2] is the sum of the fourier transformed value, 
#and q[:,3] is the total number of samples with K=q[:,0]

for i in  np.arange(len(q)):
    q[i][0] = i
i = 0
for i in np.arange(len(p)):
    for r in np.arange(len(p[0])):
        for s in np.arange(len(p[0,0])):
            K = np.around(np.sqrt(kx[i,r,s]**2+ky[i,r,s]**2+kz[i,r,s]**2))
            if K < qlen:
                q[K][1]=q[K][1]+np.abs(p[i,r,s])**2
                q[K][2]=q[K][2]+p[i,r,s]
                q[K][3]=q[K][3]+1   
    if i%10 ==0:
        print 'i = ',i,' !'
print q

2 个答案:

答案 0 :(得分:5)

Numpy通常可以比普通的python快数百倍的事情,只需要很少的额外努力。您只需要知道编写代码的正确方法。仅仅列出我想到的第一件事:

索引

普通的python在计算机应该擅长的事物上通常很慢。一个例子是索引,所以像

这样的行
a[f[i,0]][f[i,1]][f[i,2]]=f[i,3]

让我非常怀疑。当你说“将加载的txt转换为numpy ndarray”需要很长时间时,这是你所指的那个吗?这不会让我感到惊讶,因为每次python看到a[f[i,0]]时,它必须首先索引f,确保i是一个整数,并且你没有跑掉边缘f;那么它必须确保f[i,0]是一个整数,并且你没有跑掉a的边缘。然后它必须重复这两次才能知道你要设置哪个元素。

一个改进是使用a[f[i,0],f[i,1],f[i,2]],因为使用这种索引时numpy会更快。

但我认为你的数据实际上是某种顺序。例如,f[i,2]是否从0循环到256,然后f[i,1]增加1,f [i,2]从0开始?如果是这样,你真正需要做的就是说出像

这样的东西
a = f[:,3].reshape(ngridx,ngridy,ngridz)

这是一个非常快速的操作,只需要几分之一毫秒。形状可能是错误的,所以你可能不得不改变参数的顺序,用转座做一些事情,但基本的想法肯定存在。您可以在the documentation中了解相关信息。

复制数据不好

您不需要复制所有内容,当您需要复制数组(或数组的一部分)时,您应该让numpy为您执行此操作。例如,只需使用Firstdel,而不是a[1:]函数。或者,如果你真的需要复制数据(你不只是为了绘图),请使用:

def Firstdel(a):
    return numpy.copy(a[1:])

但一般来说,你可以只使用numpy数组的“切片”,而不是复制它们。阅读它here

循环

循环也是臭名昭着的浪费时间。首先,while在python中对于简单循环并不常见。而不是

while i < len(f):
    # do stuff
    i = i+1

你应该使用

for i in range(len(f)):
    # do stuff

尽可能多地摆脱循环。要设置kxkykz,此代码比嵌套循环快10倍,但是缩放为N而不是N ^ 3(其中N = ngridx ngridy ngridz):

for row in range(ngridx):
    kx[row,:,:] = kValx[row]
for column in range(ngridy):
    ky[:,column,:] = kValy[column]
for height in range(ngridz):
    kz[:,:,height] = kValz[height]

切片对于设置值也很有用,因为循环进入numpy。而不是这段代码

i = 0
while i < len(q):
    q[i][0] = i
    i = i + 1

只需使用

q[:,0] = range(len(q))

在这里,我只是设置q的“切片”等于另一个数组。

循环之后的嵌套循环也可以加速,但它们会更复杂。

但你也想尽可能避免循环。这带给我们......

使用内置numpy函数

numpy存在的原因是将这些慢速python循环转换为快速C代码(我们不需要知道它存在)。所以有很多函数可以做你想要做的事情,已经内置到numpy。

而不是

while i < len(f):
    masstot = masstot + f[i,3]
    i = i+1

你应该使用像

这样的东西
masstot = np.sum(f[:,3])

读取起来比较简单,但也可能方式更快,因为numpy可以直接访问计算机内存中的数据,并且可以使用快速C函数来查找总和而不是使用慢速python函数。 (同样,您不需要了解C函数的任何信息;他们只会完成工作。)

而不是那个大的嵌套循环每次通过循环计算K的值,只需使K成为具有适当值的数组:

K = np.around(np.sqrt(kx**2+ky**2+kz**2))

K的大小与kx等相同。然后,​​您可以使用advanced indexing来设置q的值。我就是这样做的最后一节:

# Again, we get rid of nested loops, to get a large improvement in speed and scaling
K = np.around(np.sqrt(kx**2+ky**2+kz**2)).astype(int)
for i in range(qlen):
    indices = (K==i) # This will be an array of True/False values,
                     # which will be used for "advanced indexing" of p
    q[i,0] = i
    q[i,1] = sum(np.abs(p[indices])**2)
    q[i,2] = sum(p[indices])
    q[i,3] = sum(indices)
print q

综上所述,与你目前的问题相比,我的代码提高了35倍。

答案 1 :(得分:2)

也可以加快输入文件的创建速度:

size = ngridx*ngridy*ngridz
f = np.zeros((size,4))
a = np.arange(size)
f[:, 0] = np.floor_divide(a, ngridy*ngridz)
f[:, 1] = np.fmod(np.floor_divide(a, ngridz), ngridy)
f[:, 2] = np.fmod(a, ngridz)
f[:, 3] = np.random.rand(size)

要制作kxkykz,您可以使用broadcasting摆脱循环:

kx += kValx[:, np.newaxis, np.newaxis]
ky += kValy[np.newaxis, :, np.newaxis]
kz += kValz[np.newaxis, np.newaxis, :]