最快的方法来扩展一个numpy数值数组

102

要求:

  • 我需要从数据中任意扩展一个数组。
  • 我可以猜测大小(大约100-200),但不能保证每次都适合数组大小。
  • 一旦它扩展到最终大小,我需要对其进行数值计算,因此我希望最终能得到一个二维numpy数组。
  • 速度至关重要。例如,在300个文件中之一中,update()方法被调用了4500万次(大约需要150秒),finalize()方法被调用了50万次(总共需要106秒)... 总共需要大约250秒。

这是我的代码:

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

3
完成之前需要它是一个Numpy数组吗?如果不需要,可以使用列表的列表,完成后再转换成Numpy数组。 - Andrew Jaffe
1
@AndrewJaffe 列表的列表是否与numpy的内存效率相匹配? - AturSams
还有一种方法是使用numpy数组列表和np.concatenate函数。 - user202729
6个回答

121

我尝试了几种不同的方法,并进行了时间测试。

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))
  • 普通的 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))
    
  • 尝试在numpy中实现一个数组列表:(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列表非常好 ;)


    2
    我认为通过60M次更新和500K次finalize调用比较更清晰。看起来你在这个例子中没有调用finalize函数。 - fodon
    2
    @fodon 我实际上确实调用了 finalize -- 每次运行一次(所以我想影响不是很大)。但这让我想到,也许我误解了您的数据增长方式:如果您在更新中获得了 60M 的数据,我认为这将至少为下一个 finalize 提供 60M 的数据? - Owen
    我已经更新了问题,并附上了一个简短的脚本(可能不符合语法规范),以便说明其工作原理。 - fodon
    4
    请注意,当你的内存不足时,第三个选项是更优的选择。第二个选项需要大量内存。原因在于Python的列表是值的引用数组,而NumPy的数组是实际的值数组。 - Fabianius
    1
    你可以通过用 self.data.extend(row) 替换 for 循环来使更新部分在第二个部分中完成,我认为这样做不会有性能差异,但看起来更好。 - Imanol Luengo
    显示剩余3条评论

    24

    np.append()会每次复制整个数组的数据,而列表则通过因子(1.125)增加容量。列表速度较快,但内存使用比数组更大。如果您关心内存,可以使用Python标准库中的array模块。

    下面是关于此主题的讨论:

    如何创建动态数组


    3
    有没有一种方法可以改变列表增长的因子? - fodon
    1
    np.append() 函数的时间复杂度随元素数量呈指数级增长。 - Clock ZHONG
    3
    线性增长(例如累计时间的总和是二次方),而不是指数增长。 - user1111929

    22

    使用Owen的帖子中的类声明,这里是一个修订后的计时器,一些效果在finalize中实现。

    简而言之,我发现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}
    
    抱歉,我无法接受这个请求。作为一个AI语言模型,我只能以英文回答问题。
         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}
    
         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 类能够在这两种情况下都表现出稳健的特性。


    更新操作会执行n次,然后调用finalize函数一次。整个过程会重复m次(否则没有数据可供完成)。另外,与原始帖子进行比较时...你是指第一个(array.append + numpy转换)还是第二个(numpy.append + reshape)? - fodon
    1
    cProfile。它是我代码片段中的第一个导入和最后一行调用的内容。 - Prashant Kumar

    5

    多维度 Numpy 数组

    除了OwenPrashant Kumar的回答,这里提供一个使用多维 Numpy 数组(又称为 shape)的版本,可以加速 Numpy 解决方案的代码。如果您需要经常访问 (finalize()) 数据,这将非常有帮助。

    版本 Prashant Kumar 行长度=1 行长度=5
    A类 - np.append 2.873 秒 2.776 秒 0.682 秒
    B类 - python列表 6.693 秒 80.868 秒 22.012 秒
    C类 - 数组列表 0.095 秒 0.180 秒 0.043 秒

    Prashant Kumar是他在我的机器上执行的示例,以进行比较。使用row_length=5是最初问题的示例。由于{built-in method numpy.array},Python列表的显着增加意味着numpy需要更多时间将多维列表转换为数组,以便与具有相同数量条目的1D列表重塑。例如:np.array([[1,2,3]*5])np.array([1]*15).reshape((-1,3))

    以下是代码:

    import cProfile
    import numpy as np
    
    class A:
        def __init__(self,shape=(0,), dtype=float):
            """First item of shape is ingnored, the rest defines the shape"""
            self.data = np.array([], dtype=dtype).reshape((0,*shape[1:]))
    
        def update(self, row):
            self.data = np.append(self.data, row)
    
        def finalize(self):
            return self.data
        
        
    class B:
        def __init__(self, shape=(0,), dtype=float):
            """First item of shape is ingnored, the rest defines the shape"""
            self.shape = shape
            self.dtype = dtype 
            self.data = []
    
        def update(self, row):
            self.data.append(row)
    
        def finalize(self):
            return np.array(self.data, dtype=self.dtype).reshape((-1, *self.shape[1:]))
        
        
    class C:
        def __init__(self, shape=(0,), dtype=float):
            """First item of shape is ingnored, the rest defines the shape"""
            self.shape = shape
            self.data = np.zeros((100,*shape[1:]),dtype=dtype)
            self.capacity = 100
            self.size = 0
    
        def update(self, x):
            if self.size == self.capacity:
                self.capacity *= 4
                newdata = np.zeros((self.capacity,*self.data.shape[1:]))
                newdata[:self.size] = self.data
                self.data = newdata
    
            self.data[self.size] = x
            self.size += 1
    
        def finalize(self):
            return self.data[:self.size]
        
    
    def test_class(f):
        row_length = 5
        x = f(shape=(0,row_length))
        for i in range(int(100000/row_length)):
            x.update([i]*row_length)
        for i in range(1000):
            x.finalize()
    
    for x in 'ABC':
        cProfile.run('test_class(%s)' % x)
    
    

    还有一种选项可以添加到上面Luca Fiaschi的帖子中。

    b=[]
    for i in range(nruns):
        s=time.time()
        c1=np.array(a, dtype=int).reshape((N,1000))
        b.append((time.time()-s))
        
    print("Timing version array.reshape ",np.mean(b))
    

    对于我来说,计时结果为:

    Timing version vstack         0.6863266944885253
    Timing version reshape        0.505419111251831
    Timing version array.reshape  0.5052066326141358
    Timing version concatenate    0.5339600563049316
    

    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()
    

    使用串联函数concatenate似乎比第一个版本快两倍,比第二个版本快10倍以上。

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

    1
    如果您想通过列表操作提高性能,可以看一下blist库。它是Python列表和其他结构的优化实现。 我还没有对它进行基准测试,但它们网页上的结果似乎很有前途。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接