【问题标题】:Numpy concatenate is slow: any alternative approach?Numpy 连接很慢:任何替代方法?
【发布时间】:2016-11-23 00:59:14
【问题描述】:

我正在运行以下代码:

for i in range(1000)
    My_Array=numpy.concatenate((My_Array,New_Rows[i]), axis=0)

上面的代码很慢。有没有更快的方法?

【问题讨论】:

    标签: arrays performance numpy concatenation


    【解决方案1】:

    这基本上是所有基于数组的算法中发生的事情。

    每次更改数组的大小时,都需要重新调整大小并且需要复制每个元素。这也发生在这里。 (一些实现会保留一些空槽;例如,随着每次增长,内存空间会增加一倍)。

    • 如果您在创建 np.array 时获得了数据,只需一次添加所有这些(内存将只分配一次!)
    • 如果没有,请使用链表之类的方式收集它们(允许 O(1) 附加操作)。然后一次在你的 np.array 中读取它(同样只有一个内存分配)。

    这不是一个特定于 numpy 的主题,而是更多关于数据结构的主题。

    编辑:因为这个相当模糊的答案得到了一些赞成,我觉得有必要明确我的链表方法是一个可能的例子。正如评论中所指出的,python 的列表更像数组(绝对不是链表)。但核心事实是:python 中的 list.append() 是 fast (摊销:O(1)),而对于 numpy-arrays 则不是这样! docs中还有一小部分关于内部的:

    列表是如何实现的?

    Python 的列表实际上是可变长度数组,而不是 Lisp 样式的链表实现使用对其他对象的连续引用数组,并将指向该数组的指针和数组的长度保存在列表头结构中。

    这使得索引列表 a[i] 的成本与列表的大小或索引的值无关。

    添加或插入项目时,引用数组的大小会调整。 应用了一些巧妙的方法来提高重复追加项目的性能;当数组必须增长时,会分配一些额外的空间,因此接下来的几次不需要实际调整大小。

    (我的粗体注释)

    【讨论】:

    • 我实际使用了列表追加,性能大大提升。
    • 请记住,pythons list 实际上是一个数组(内部)。它肯定会做一些技巧,例如在增长时保留空槽,并且具有良好的摊销复杂性,但经典的链表可能更快(取决于许多因素)。
    • Python 没有原生链表类。
    • 如何在 python 中做一个真正的链表?没有内置?只有tutorialspoint.com/python/python_linked_lists.htm这样的东西?
    • 集合模块中有双端队列,比列表快:stackoverflow.com/questions/14668769/…
    【解决方案2】:

    这取决于New_Rows[i]是什么,以及你想要什么样的数组。如果您从要首尾相连的列表(或一维数组)开始(以制作一个长的一维数组),只需一次将它们全部连接起来。连接采用任意长度的列表,而不仅仅是 2 个项目。

     np.concatenate(New_Rows, axis=0)
    

    或者可能使用中间列表推导(以获得更大的灵活性)

     np.concatenate([row for row in New_Rows])
    

    或者更接近你的例子。

     np.concatenate([New_Rows[i] for i in range(1000)])
    

    但如果New_Rows 元素的长度都相同,并且您想要一个二维数组,每行一个New_Rows 值,np.array 做得很好:

     np.array(New_Rows)
     np.array([i for i in New_Rows])
     np.array([New_Rows[i] for i in range(1000)])
    

    np.array 主要用于从列表列表构建数组。

    np.concatenate 也可以在 2d 中构建,但输入需要是 2d 才能开始。 vstackstack 可以解决这个问题。但是所有这些stack 函数都使用某种列表理解,然后是concatenate

    一般来说,迭代或附加列表会更好/更快,并且只应用一次np.array(或连接)。 appending 到列表很快;比创建一个新数组要快得多。

    【讨论】:

      【解决方案3】:

      也许创建一个大小正确的空数组而不是填充它? 如果你有一个相同维度的数组列表,你可以

      import numpy as np 
      arr = np.zeros((len(l),)+l[0].shape) 
      for i, v in enumerate(l):
         arr[i] = v
      

      对我来说工作得更快,它只需要分配一个内存

      【讨论】:

        【解决方案4】:

        假设您有一个大的二维 numpy 数组列表,具有相同的列数和不同的行数,如下所示:

        x = [numpy_array1(r_1, c),......,numpy_arrayN(r_n, c)]

        像这样连接:

        while len(x) != 1:
            if len(x) == 2:
                x = np.concatenate((x[0], x[1]))
                break
            for i in range(0, len(x), 2):
                if (i+1) == len(x):
                    x[0] = np.concatenate((x[0], x[i]))
                else:
                    x[i] = np.concatenate((x[i], x[i+1]))
        
            x = x[::2]
        

        【讨论】:

        • 我使用x = [np.zeros(2000) for _ in range(1000)] 运行了这个。这将在大约 50 毫秒内运行。 np.concatenate(x)(参见@hpaulj 的回答)在 2 毫秒内运行。您的答案肯定比问题中给出的方法快,但比 numpy 的最佳实践要慢得多。
        • 在最少的操作中合并数组对确实是一种聪明的方法,但连接接受任何长度的列表,所以你不限于对。
        • 我从未声称这是最佳的 Pythonic 解决方案,它只是有效且简单。我使用了 2 对,因为每个用于连接的新内存分配都是最低限度的。当我第一次按顺序运行 np.concatenate 时,我的电脑卡住了,我这样做了,它对我有用,所以我想分享它,因为当时我找不到任何适合我的情况的答案。也许我应该搜索更多.
        • 预分配 np.empty 数组是要走的路。在收集大量随机大小的 [N,5] 数据包然后需要保存为单个垂直堆叠数组的情况下,它的运行速度对我来说快了大约 100 倍。当然,这假设你有 RAM 来容纳所有东西。
        【解决方案5】:

        我认为@thebeancounter 的解决方案是可行的方法。 如果你事先不知道你的 numpy 数组的确切大小,你也可以采取类似于在 C++ 中实现向量类的方法。

        更具体地说,您可以将 numpy ndarray 包装到一个新类中,该类的默认大小大于您当前的需求。当 numpy 数组几乎填满时,将当前数组复制到更大的数组。

        【讨论】:

          猜你喜欢
          • 2017-07-04
          • 1970-01-01
          • 1970-01-01
          • 2021-10-27
          • 2015-11-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多