从iterable中设置numpy数组值的有效方法

时间:2016-11-30 23:01:48

标签: python arrays numpy

我已经分配了一个给定大小的大numpy数组。例如

my_array = numpy.empty(10000, numpy.float)

可以通过(模拟示例)

生成数组的值
k * val ** 2 for val in range(0, 10000)

设置数组值的这一步已完成多次。例如,for k in range(0,1000)。我不想做任何其他分配,而不是开始时numpy.empty()所做的分配。

我考虑过,

my_array = numpy.array([k*val**2 for val in range(0,10000)])

但看起来至少会有列表[k * val ** 2 for val in range(0, 10000)]的分配。是吗?

我也看到numpy.fromiter,但这似乎是用于构建数组。

my_array = numpy.fromiter((k*val**2 for val in range(0,10000)), numpy.float, 10000)

这里有进一步的分配是真的吗?

要查看numpy.fromiter是否分配了一个数组,我尝试了以下内容

import numpy as np

iterable1 = (x*x for x in range(5))
iterable2 = (x*x + 1.0 for x in range(5))
my_array = np.fromiter(iterable1, np.float)
print(my_array)
print(hex(id(my_array)))

my_array = np.fromiter(iterable2, np.float)
print(my_array)
print(hex(id(my_array)))

在输出I中,打印的两个地址不同。这是不是意味着np.fromiter分配了一个新的数组,然后将其分配给my_array

3 个答案:

答案 0 :(得分:2)

首先确保您了解变量赋值的作用:

 my_array = numpy.empty(10000, numpy.float)
 my_array = numpy.fromiter(...)

第二个作业取代了第一个作业。 my_array最初引用的对象是免费的并且被垃圾收集。这只是基本的Python变量处理。要挂起原始数组(可变对象),您必须更改其值

my_array[:] = <new values>

但是,生成<new values>的过程很可能会创建一个临时缓冲区(或两个或三个)。然后将这些值复制到目标。即使x += 1执行缓冲计算。几乎没有就地的numpy操作。

一般来说,第二次猜测numpy的内存分配并不起作用。效率只能通过时间测试来衡量,而不是通过猜测幕后发生的事情。

不要为预先分配&#39;而烦恼。除非你需要迭代填写它:

In [284]: my_array = np.empty(10, int)
In [285]: for i in range(my_array.shape[0]):
     ...:     my_array[i] = 2*i+3
In [286]: my_array
Out[286]: array([ 3,  5,  7,  9, 11, 13, 15, 17, 19, 21])

与以下相比,这是一种创建阵列的可怕方式:

In [288]: np.arange(10)*2+3
Out[288]: array([ 3,  5,  7,  9, 11, 13, 15, 17, 19, 21])

fromiter方法更漂亮,但速度更快。

In [290]: np.fromiter((i*2+3 for i in range(10)),int)
Out[290]: array([ 3,  5,  7,  9, 11, 13, 15, 17, 19, 21])

一些时间:

In [292]: timeit np.fromiter((i*2+3 for i in range(10000)),int)
100 loops, best of 3: 4.76 ms per loop
# giving a count drops the time to 4.28 ms

In [293]: timeit np.arange(10000)*2+3
The slowest run took 8.73 times longer than the fastest. This could mean that an intermediate result is being cached.
10000 loops, best of 3: 47.4 µs per loop

In [294]: %%timeit 
     ...: my_array=np.empty(10000,int)
     ...: for i in range(my_array.shape[0]):
     ...:     my_array[i] = 2*i+3
     ...:     
100 loops, best of 3: 4.72 ms per loop

In [303]: timeit np.array([i*2+3 for i in range(10000)],int)
100 loops, best of 3: 4.48 ms per loop

fromiter与显式循环一样长,而纯粹的numpy解决方案的速度要快几个数量级。时间方面,np.array与列表理解和fromiter与生成器之间几乎没有差异。

从预先存在的列表创建数组大约需要1/3的时间。

In [311]: %%timeit alist=[i*2+3 for i in range(10000)]
     ...: x=np.array(alist, int)
     ...: 
1000 loops, best of 3: 1.63 ms per loop

将列表分配给现有的empty数组并不快。

In [315]: %%timeit alist=[i*2+3 for i in range(10000)]
     ...: arr = np.empty(10000,int)
     ...: arr[:] = alist
1000 loops, best of 3: 1.65 ms per loop
In [316]: %%timeit alist=[i*2+3 for i in range(10000)]; arr=np.empty(10000,int)
     ...: arr[:] = alist
1000 loops, best of 3: 1.63 ms per loop

有一些numpy函数采用out参数。通过这种方式重用数组可以节省一些时间。 np.cross是一个利用此功能的函数(代码是Python并且可读)。

另一个&#39;矢量化&#39;从标量函数创建值的方法:

In [310]: %%timeit f=np.frompyfunc(lambda i: i*2+3,1,1)
     ...: f(range(10000))
     ...: 
100 loops, best of 3: 8.31 ms per loop

答案 1 :(得分:2)

鉴于评论中的解释,似乎问题如下:

  • 需要经常更新大型阵列,并尽可能高效地进行更新;
  • 更新源不仅包括其他numpy数组,还包含任意Python对象(可以即时生成)。

第二项是问题:只要您的值来自Python,将它们放入一个numpy数组中永远不会真正有效。这是因为你必须遍历解释代码中的每个值。

  

我希望找到已经打包在内置函数中的表达式for ind, elem in enumerate(iterable): my_array[ind] = elem。你知道Python解释器是否将该表达式编译成一个整体吗?

CPython的虚拟机与C ++模型有很大不同;具体而言,编译器不能内联表达式或将其解释为一个整体,以使其显着提高效率。即使它支持在C中执行此特定操作的字节码指令,它仍然需要调用生成器的next方法,该方法在执行Python字节后将每个值生成为堆分配的Python对象-码。在任何一种情况下,每次迭代都会涉及解释代码,而您确实希望避免这种情况。

解决问题的有效方法是从头开始设计,永不留下numpy。正如其他人在评论中所解释的那样,分配成本(如果通过numpy有效地完成)与在Python中逐个实际处理数据的成本相比微不足道。我会按如下方式设计:

  • 从头开始将尽可能多的代码转换为本地使用numpy数组;返回一个接口的numpy数组部分,不用担心分配成本。在numpy本身内尽可能多地执行循环,因此它们是在本机代码中完成的。永远不要在Python中迭代大型数组的所有值。
  • 如果无法使用numpy,请尽快使用numpy.fromiter将迭代器转换为numpy数组。
  • 使用my_array[:] = new_array[:]my_array = new_array将新值引入数组。 (前者将在微观上花费更多时间,但在数据模型中的许多地方共享my_array时更有意义。)
  • 您感兴趣的
  • 基准操作。不要认为复制速度很慢&#34; - 可能会发现C ++中的操作会变慢&#34;比C ++中有效的操作的Python再现速度快了几个数量级。

如果在执行上述操作后numpy不支持某些操作,并且测量结果显示效率非常低,则可以使用Python/C API创建一个有效执行计算的扩展模块,并将结果返回为在C中创建的numpy数组。

答案 2 :(得分:1)

np.fromiter不做任何进一步的分配。它只是从一个可迭代创建一个数组。这是该功能的全部精髓。它还接受count参数,允许fromiter预先分配输出数组,而不是根据需要调整它。

此外,如果您想一次更改所有项目,则无需使用np.empty

毕竟,如果你通过对一系列数字进行一些数学运算来构建新数组,你也可以简单地对Numpy数组进行操作。

以下是一个例子:

In [4]: a = np.arange(10)

In [6]: a**2 + 10
Out[6]: array([10, 11, 14, 19, 26, 35, 46, 59, 74, 91])