增长numpy数字数组的最快方法


80

要求:

  • 我需要从数据中任意增大一个数组。
  • 我可以猜测大小(大约100-200),但不能保证每次都适合该数组
  • 一旦增长到最终大小,我就需要对其进行数值计算,因此我更希望最终使用二维numpy数组。
  • 速度至关重要。例如,对于300个文件之一,update()方法被称为4500万次(大约需要150秒),而finalize()方法被称为500k次(总共需要106s)……总共需要250s或者。

这是我的代码:

def __init__(self):
    self.data = []

def update(self, row):
    self.data.append(row)

def finalize(self):
    dx = np.array(self.data)

我尝试过的其他操作包括以下代码...但这速度会慢一些。

def class A:
    def __init__(self):
        self.data = np.array([])

    def update(self, row):
        np.append(self.data, row)

    def finalize(self):
        dx = np.reshape(self.data, size=(self.data.shape[0]/5, 5))

这是如何称呼它的示意图:

for i in range(500000):
    ax = A()
    for j in range(200):
         ax.update([1,2,3,4,5])
    ax.finalize()
    # some processing on ax

2
完成之前是否需要是一个numpy数组?如果没有,请使用列表列表,然后在完成后进行转换。
Andrew Jaffe

1
@AndrewJaffe列表列表是否匹配numpy的内存效率?
AturSams 2015年

Answers:


96

我尝试了一些不同的事情,并进行了时间安排。

import numpy as np
  1. 您提到的缓慢方法:(32.094秒)

    class A:
    
        def __init__(self):
            self.data = np.array([])
    
        def update(self, row):
            self.data = np.append(self.data, row)
    
        def finalize(self):
            return np.reshape(self.data, newshape=(self.data.shape[0]/5, 5))
    
  2. 常规ol Python列表:(0.308秒)

    class B:
    
        def __init__(self):
            self.data = []
    
        def update(self, row):
            for r in row:
                self.data.append(r)
    
        def finalize(self):
            return np.reshape(self.data, newshape=(len(self.data)/5, 5))
    
  3. 尝试在numpy中实现arraylist:(0.362秒)

    class C:
    
        def __init__(self):
            self.data = np.zeros((100,))
            self.capacity = 100
            self.size = 0
    
        def update(self, row):
            for r in row:
                self.add(r)
    
        def add(self, x):
            if self.size == self.capacity:
                self.capacity *= 4
                newdata = np.zeros((self.capacity,))
                newdata[:self.size] = self.data
                self.data = newdata
    
            self.data[self.size] = x
            self.size += 1
    
        def finalize(self):
            data = self.data[:self.size]
            return np.reshape(data, newshape=(len(data)/5, 5))
    

这就是我的计时方式:

x = C()
for i in xrange(100000):
    x.update([i])

因此,看起来常规的旧Python列表相当不错;)


1
我认为使用6000万个更新和500K个完成调用可以使比较更加清晰。在此示例中,您似乎没有调用finalize。
fodon 2011年

1
@fodon我实际上确实调用了finalize-每次运行一次(所以我认为影响不大)。但是,这让我觉得也许我误解了数据的增长方式:如果在更新中获得6000万数据,我想这将为下一次定稿提供至少6000万数据?
欧文

@Owen 60M和500K分别意味着有6,000万和50万次呼叫updatefinalize。见我的修订定时它测试一个100:1的比例的updatefinalize
PRASHANT库马尔

我已经用一个简短的脚本(可能在语法上不正确)更新了该问题,以了解其工作原理。
fodon 2011年

3
请注意,当您的内存不足时,第三个选项更为出色。第二个选项需要大量内存。原因是Python的列表是值的引用数组,而NumPy的数组是值的实际数组。
Fabianius

20

np.append()每次都复制数组中的所有数据,但列表将容量增加一个因子(1.125)。列表速度很快,但是内存使用量比数组大。如果您关心内存,则可以使用python标准库的数组模块。

这是关于此主题的讨论:

如何创建动态数组


2
有没有办法改变列表增长的因素?
fodon 2011年

1
np.append()消耗的时间随元素数量呈指数增加。
钟钟

1
^线性(即总累积时间为二次),而不是指数。
user1111929 '19

15

使用Owen帖子中的类声明,这是经过修改的时间,并具有一定的确定效果。

简而言之,我发现C类提供的实现比原始文章中的方法快60倍以上。(对文字墙表示歉意)

我使用的文件:

#!/usr/bin/python
import cProfile
import numpy as np

# ... class declarations here ...

def test_class(f):
    x = f()
    for i in xrange(100000):
        x.update([i])
    for i in xrange(1000):
        x.finalize()

for x in 'ABC':
    cProfile.run('test_class(%s)' % x)

现在,产生的计时:

A:

     903005 function calls in 16.049 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.000    0.000   16.049   16.049 <string>:1(<module>)
100000    0.139    0.000    1.888    0.000 fromnumeric.py:1043(ravel)
  1000    0.001    0.000    0.003    0.000 fromnumeric.py:107(reshape)
100000    0.322    0.000   14.424    0.000 function_base.py:3466(append)
100000    0.102    0.000    1.623    0.000 numeric.py:216(asarray)
100000    0.121    0.000    0.298    0.000 numeric.py:286(asanyarray)
  1000    0.002    0.000    0.004    0.000 test.py:12(finalize)
     1    0.146    0.146   16.049   16.049 test.py:50(test_class)
     1    0.000    0.000    0.000    0.000 test.py:6(__init__)
100000    1.475    0.000   15.899    0.000 test.py:9(update)
     1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
100000    0.126    0.000    0.126    0.000 {method 'ravel' of 'numpy.ndarray' objects}
  1000    0.002    0.000    0.002    0.000 {method 'reshape' of 'numpy.ndarray' objects}
200001    1.698    0.000    1.698    0.000 {numpy.core.multiarray.array}
100000   11.915    0.000   11.915    0.000 {numpy.core.multiarray.concatenate}

B:

     208004 function calls in 16.885 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.001    0.001   16.885   16.885 <string>:1(<module>)
  1000    0.025    0.000   16.508    0.017 fromnumeric.py:107(reshape)
  1000    0.013    0.000   16.483    0.016 fromnumeric.py:32(_wrapit)
  1000    0.007    0.000   16.445    0.016 numeric.py:216(asarray)
     1    0.000    0.000    0.000    0.000 test.py:16(__init__)
100000    0.068    0.000    0.080    0.000 test.py:19(update)
  1000    0.012    0.000   16.520    0.017 test.py:23(finalize)
     1    0.284    0.284   16.883   16.883 test.py:50(test_class)
  1000    0.005    0.000    0.005    0.000 {getattr}
  1000    0.001    0.000    0.001    0.000 {len}
100000    0.012    0.000    0.012    0.000 {method 'append' of 'list' objects}
     1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
  1000    0.020    0.000    0.020    0.000 {method 'reshape' of 'numpy.ndarray' objects}
  1000   16.438    0.016   16.438    0.016 {numpy.core.multiarray.array}

C:

     204010 function calls in 0.244 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.000    0.000    0.244    0.244 <string>:1(<module>)
  1000    0.001    0.000    0.003    0.000 fromnumeric.py:107(reshape)
     1    0.000    0.000    0.000    0.000 test.py:27(__init__)
100000    0.082    0.000    0.170    0.000 test.py:32(update)
100000    0.087    0.000    0.088    0.000 test.py:36(add)
  1000    0.002    0.000    0.005    0.000 test.py:46(finalize)
     1    0.068    0.068    0.243    0.243 test.py:50(test_class)
  1000    0.000    0.000    0.000    0.000 {len}
     1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
  1000    0.002    0.000    0.002    0.000 {method 'reshape' of 'numpy.ndarray' objects}
     6    0.001    0.000    0.001    0.000 {numpy.core.multiarray.zeros}

更新破坏了A类,终结器破坏了B类。C级面对他们两个都是健壮的。


更新完成了一次,然后完成一次被调用。整个过程进行了m次(否则,没有数据可完成)。另外,与原始帖子进行比较时...是指第一个(array.append + numpy转换)还是(numpy.append +重塑)?
fodon 2011年

1
cProfile。这是我的代码片段中调用的第一个导入和最后一行。
Prashant Kumar

5

用于完成的功能在性能上有很大的不同。考虑以下代码:

N=100000
nruns=5

a=[]
for i in range(N):
    a.append(np.zeros(1000))

print "start"

b=[]
for i in range(nruns):
    s=time()
    c=np.vstack(a)
    b.append((time()-s))
print "Timing version vstack ",np.mean(b)

b=[]
for i in range(nruns):
    s=time()
    c1=np.reshape(a,(N,1000))
    b.append((time()-s))

print "Timing version reshape ",np.mean(b)

b=[]
for i in range(nruns):
    s=time()
    c2=np.concatenate(a,axis=0).reshape(-1,1000)
    b.append((time()-s))

print "Timing version concatenate ",np.mean(b)

print c.shape,c2.shape
assert (c==c2).all()
assert (c==c1).all()

使用串联的速度似乎是第一个版本的两倍,比第二个版本快十倍以上。

Timing version vstack  1.5774928093
Timing version reshape  9.67419199944
Timing version concatenate  0.669512557983

1

如果要通过列表操作提高性能,请查看blist库。它是python列表和其他结构的优化实现。

我尚未对其进行基准测试,但他们页面中的结果似乎很有希望。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.