矩阵分配到位?

Matrix assignment in place?

假设我初始化一个矩阵如下:

import scipy
m = scipy.zeros((10, 10))

现在我做一些计算,我想把结果赋给m。赋值的时候,m的大小是不变的,所以我觉得原地赋值会更快

m = scipy.array([[i * j for j in range(10)] for i in range(10)])

我担心在上面的代码中,创建了一个临时矩阵来保存结果,然后将m赋值给这个值。这是低效的,因为它涉及分配一个新矩阵。更有效的解决方案是将值直接存储在 m 中,可以这样表示:

for i in range(10):
    for j in range(10):
        m[i,j] = i * j

但假设生成器表达式对我来说更方便,以我安排代码的方式。

我想知道的是:在上面的生成器表达式中,我是否在进行额外的矩阵分配?

您的第一个解决方案(列表理解)的问题在于它会生成一个列表列表并将其分配给 m。但是,从您的第一个语句来看,您似乎希望 m 成为一个 numpy 数组(这是在执行 scipy.zeros() 时在幕后创建的)。所以,您实际上已经创建了一个数组,然后用一个列表覆盖了它。如果您想将数据结构保持为 np.array,嵌套的 for 循环是最好的方法。

另外,你说“matrix”,但创建了一个数组。如果你想要一个实际的矩阵(例如,做矩阵数学),将你的嵌套列表理解传递给 np.matrix():

# assuming you've already run `import numpy as np`
In [5]: m = np.matrix([[i * j for j in range(10)] for i in range(10)])

In [6]: m
Out[6]: 
matrix([[ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0],
        [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9],
        [ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18],
        [ 0,  3,  6,  9, 12, 15, 18, 21, 24, 27],
        [ 0,  4,  8, 12, 16, 20, 24, 28, 32, 36],
        [ 0,  5, 10, 15, 20, 25, 30, 35, 40, 45],
        [ 0,  6, 12, 18, 24, 30, 36, 42, 48, 54],
        [ 0,  7, 14, 21, 28, 35, 42, 49, 56, 63],
        [ 0,  8, 16, 24, 32, 40, 48, 56, 64, 72],
        [ 0,  9, 18, 27, 36, 45, 54, 63, 72, 81]])

哎呀,即使你毕竟想要一个数组,像上面一样将嵌套的 listcomp 传递给数组构造函数,你就完成了。

第二个赋值(生成器)确实创建了一个新矩阵。 如果您使用 python 的 id() function,您可以看到 m 在该赋值后指向不同的位置。

例如:

>> import scipy
>> m = scipy.zeros((10, 10))
>> id(m)
4455211696
>> m = scipy.array([[i * j for j in range(10)] for i in range(10)])
>> id(m)
4478936688

让我们做一些实际的时间测试:

In [793]: timeit m=np.array([[i*j for j in range(N)] for i in range(M)])
10000 loops, best of 3: 47.8 µs per loop
In [794]: %%timeit
   .....: m=np.zeros((N,M),int)
   .....: for i in range(M):
    for j in range(N):
        m[i,j] = i*j
   .....: 
10000 loops, best of 3: 40.2 µs per loop

所以预分配和赋值稍微快一些 - 但不是那么快。

将其与向量乘法进行对比:

In [796]: timeit np.arange(M)[:,None]*np.arange(N)[None,:]
10000 loops, best of 3: 17.1 µs per loop

对更大的数组执行相同的操作:

In [797]: N,M=1000,1000
In [798]: timeit m=np.array([[i*j for j in range(N)] for i in range(M)])
1 loops, best of 3: 325 ms per loop
In [799]: %%timeit
m=np.zeros((N,M),int)
for i in range(M):
    for j in range(N):
        m[i,j] = i*j
   .....: 
1 loops, best of 3: 338 ms per loop
In [800]: timeit np.arange(M)[:,None]*np.arange(N)[None,:]
100 loops, best of 3: 12.5 ms per loop

2 次迭代保持并驾齐驱;矢量化的好多了。

我可以使用 fromiter 节省一些迭代时间,但没有像向量化的那样。

In [805]: timeit np.fromiter([i*j for j in range(N) for i in range(M)],int).reshape(N,M)
1 loops, best of 3: 235 ms per loop

这是一个常见问题,我只是懒得搜索最佳副本。 :) 通常人们声称他们的计算是一些复杂的黑盒子,只接受标量,因此无法对其进行矢量化。

有一个 np.vectorize 函数可以包装您的计算,但它旨在简化诸如广播之类的事情,并且没有声称可以加快代码速度。还是要迭代。

如果计算量小且速度快,值得关注迭代方法,但如果计算量大,在迭代机制上花费的时间比例较小,需要关注迭代的速度黑匣子。