【问题标题】:How do I build a numpy array from a generator?如何从生成器构建一个 numpy 数组?
【发布时间】:2010-09-26 22:09:49
【问题描述】:

如何从生成器对象构建一个 numpy 数组?

让我来说明问题:

>>> import numpy
>>> def gimme():
...   for x in xrange(10):
...     yield x
...
>>> gimme()
<generator object at 0x28a1758>
>>> list(gimme())
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> numpy.array(xrange(10))
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> numpy.array(gimme())
array(<generator object at 0x28a1758>, dtype=object)
>>> numpy.array(list(gimme()))
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

在这种情况下,gimme() 是我想将其输出转换为数组的生成器。但是,数组构造函数不会遍历生成器,它只是存储生成器本身。我想要的行为是来自numpy.array(list(gimme())),但我不想支付同时在内存中拥有中间列表和最终数组的内存开销。有没有更节省空间的方法?

【问题讨论】:

  • 这是一个有趣的问题。我通过from numpy import *; print any(False for i in range(1)) 遇到了这个问题 - 它遮住了内置的any() 并产生了相反的结果(据我所知)。
  • @moooeeeep 这太糟糕了。如果numpy 不能(或不想)像 Python 那样对待生成器,至少它应该在接收到生成器作为参数时引发异常。
  • @max 我踩到了完全相同的地雷。显然这是由on the NumPy list(和earlier)提出的,结论是这不会被更改为引发异常,并且应该始终使用命名空间。

标签: python numpy generator


【解决方案1】:

虽然您可以使用 numpy.fromiter() 从生成器创建一维数组,但您可以使用 numpy.stack 从生成器创建 N 维数组:

>>> mygen = (np.ones((5, 3)) for _ in range(10))
>>> x = numpy.stack(mygen)
>>> x.shape
(10, 5, 3)

它也适用于一维数组:

>>> numpy.stack(2*i for i in range(10))
array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])

请注意,numpy.stack 在内部使用生成器并使用arrays = [asanyarray(arr) for arr in arrays] 创建中间列表。实现可以在here找到。

[警告] 正如@Joseh Seedy 所指出的,Numpy 1.16 提出了一个警告,该警告会阻止将此类函数与生成器一起使用。

【讨论】:

  • 这是一个很好的解决方案,感谢您的指出。但它似乎比使用np.array(tuple(mygen)) 慢很多(在我的应用程序中)。以下是测试结果:%timeit np.stack(permutations(range(10), 7)) 1 loop, best of 3: 1.9 s per loop 对比 %timeit np.array(tuple(permutations(range(10), 7))) 1 loop, best of 3: 427 ms per loop
  • 这看起来很棒并且对我有用。但是使用 Numpy 1.16.1 我收到了这个警告:FutureWarning: arrays to stack must be passed as a "sequence" type such as list or tuple. Support for non-sequence iterables such as generators is deprecated as of NumPy 1.16 and will raise an error in the future.
  • 在旧 imac 上的 numpy 1.21.2 中,time np.array( list( np.ndindex( 1000, 1000 ))) # CPU times: user 1.43 stime np.stack( list( np.ndindex( 1000, 1000 ))) # CPU times: user 3.42 s
【解决方案2】:

vstackhstackdstack 函数可以作为产生多维数组的输入生成器。

【讨论】:

  • 你能举个例子,以防链接发生变化或什么? :)
  • 这些函数可以使用数组生成器,而不是值生成器
【解决方案3】:

Numpy 数组需要在创建时明确设置它们的长度,这与 python 列表不同。这是必要的,以便可以在内存中连续分配每个项目的空间。连续分配是 numpy 数组的关键特性:这与本机代码实现相结合,让对它们的操作执行得比常规列表快得多。

记住这一点,技术上不可能将生成器对象转换为数组,除非您:

  1. 可以预测运行时会产生多少元素:

    my_array = numpy.empty(predict_length())
    for i, el in enumerate(gimme()): my_array[i] = el
    
  2. 愿意将其元素存储在中间列表中:

    my_array = numpy.array(list(gimme()))
    
  3. 可以制作两个完全相同的生成器,遍历第一个求总长度,初始化数组,然后再次遍历生成器求每个元素:

    length = sum(1 for el in gimme())
    my_array = numpy.empty(length)
    for i, el in enumerate(gimme()): my_array[i] = el
    

1 可能是您正在寻找的。 2 空间效率低,而 3 时间效率低(您必须通过生成器两次)。

【讨论】:

  • 内置的array.array是一个连续的非链表,你可以简单的array.array('f', generator)。说不可能是误导。这只是动态分配。
  • 为什么 numpy.array 不像内置的 array.array 那样分配内存,正如 Cuadue 所说。什么是交易?我问是因为在两个示例中都分配了连续的内存。还是不行?
  • numpy 假定其数组大小不会改变。它严重依赖于同一块内存的不同视图,因此允许扩展和重新分配数组将需要额外的间接层来启用视图。
  • 使用空会快一点。由于您将以任何方式初始化值,因此无需执行两次。
  • 另见下面@dhill 的答案,它比 1 更快。
【解决方案4】:

有点切题,但如果你的生成器是列表推导式,你可以使用numpy.where 更有效地得到你的结果(我在看到这篇文章后在自己的代码中发现了这一点)

【讨论】:

    【解决方案5】:

    这个stackoverflow结果后面google了一下,发现有一个numpy.fromiter(data, dtype, count)。默认的 count=-1 从可迭代对象中获取所有元素。它需要明确设置dtype。就我而言,这很有效:

    numpy.fromiter(something.generate(from_this_input), float)

    【讨论】:

    • 你会如何将这个应用于这个问题? numpy.fromiter(gimme(), float, count=-1) 不起作用。 something 代表什么?
    • @Matthias009 numpy.fromiter(gimme(), float, count=-1) 为我工作。
    • 解释为什么fromiter 仅适用于一维数组的线程:mail.scipy.org/pipermail/numpy-discussion/2007-August/…
    • fwiw,count=-1 不需要指定,因为它是默认值。
    • 如果您事先知道可迭代的长度,请指定count 以提高性能。这样,它会在填充内存之前分配内存,而不是按需调整大小(请参阅numpy.fromiter 的文档)
    猜你喜欢
    • 2018-03-27
    • 1970-01-01
    • 2023-03-06
    • 1970-01-01
    • 1970-01-01
    • 2017-03-17
    • 1970-01-01
    • 2016-09-22
    • 2017-12-30
    相关资源
    最近更新 更多