Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/arrays/14.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python NumPy中的时间和空间高效数组乘法_Python_Arrays_Numpy - Fatal编程技术网

Python NumPy中的时间和空间高效数组乘法

Python NumPy中的时间和空间高效数组乘法,python,arrays,numpy,Python,Arrays,Numpy,给定分别具有形状(m,d)和(m,n,d)的NumPy数组R和S,我想计算一个形状(m,n)的数组p,其(I,j)-第个条目是np.dot(R[I,:],S[I,j,:]) 执行双for循环不需要任何额外的空间(除了m*n空间用于p),但不会节省时间 使用广播,我可以做p=np.sum(R[:,np.newaxis,:]*S,axis=2),但这需要额外的m*n*d空间 什么是最有效的时间和空间有效的方法? 在这些情况下,考虑 NUBA总是很好的,它可以提供最好的两个世界: import num

给定分别具有形状
(m,d)
(m,n,d)
的NumPy数组
R
S
,我想计算一个形状
(m,n)
的数组
p
,其
(I,j)
-第个条目是
np.dot(R[I,:],S[I,j,:])

执行双for循环不需要任何额外的空间(除了
m*n
空间用于
p
),但不会节省时间

使用广播,我可以做
p=np.sum(R[:,np.newaxis,:]*S,axis=2)
,但这需要额外的
m*n*d
空间


什么是最有效的时间和空间有效的方法?

在这些情况下,考虑<代码> NUBA总是很好的,它可以提供最好的两个世界:

import numpy as np
from numba import jit

def vanilla_mult(R, S):
    m, n = R.shape[0], S.shape[1]
    result = np.empty((m, n), dtype=R.dtype)
    for i in range(m):
        for j in range(n):
            result[i, j] = np.dot(R[i, :], S[i, j,:])
    return result

def broadcast_mult(R, S):
    return np.sum(R[:, np.newaxis, :] * S, axis=2)

@jit(nopython=True)
def jit_mult(R, S):
    m, n = R.shape[0], S.shape[1]
    result = np.empty((m, n), dtype=R.dtype)
    for i in range(m):
        for j in range(n):
            result[i, j] = np.dot(R[i, :], S[i, j,:])
    return result
注意,
vanilla\u mult
jit\u mult
具有完全相同的实现,但是后者是及时编译的。让我们测试一下:

In [1]: import test # the above is in test.py

In [2]: import numpy as np

In [3]: m, n, d = 100, 100, 100

In [4]: R = np.random.rand(m, d)

In [5]: S = np.random.rand(m, n, d)
好的

哎哟,是的,与广播相比,计算时间几乎增加了5倍。然而

In [8]: %timeit test.jit_mult(R, S)
The slowest run took 760.57 times longer than the fastest. This could mean that an intermediate result is being cached.
1 loop, best of 3: 870 µs per loop
很好!我们可以通过简单的JIT将运行时间减少一半!这个比例如何

In [12]: m, n, d = 1000, 1000, 100

In [13]: R = np.random.rand(m, d)

In [14]: S = np.random.rand(m, n, d)

In [15]: %timeit test.vanilla_mult(R, S)
1 loop, best of 3: 1.22 s per loop

In [16]: %timeit test.broadcast_mult(R, S)
1 loop, best of 3: 666 ms per loop

In [17]: %timeit test.jit_mult(R, S)
The slowest run took 7.59 times longer than the fastest. This could mean that an intermediate result is being cached.
1 loop, best of 3: 83.6 ms per loop
可扩展性非常好,因为广播开始因必须创建大型中间阵列而受到限制,与普通方法相比,这只需要一半的时间,但所需时间几乎是JIT方法的7倍

编辑以添加 最后,我们比较了
np.einsum
方法:

In [19]: %timeit np.einsum('md,mnd->mn', R, S)
10 loops, best of 3: 59.5 ms per loop

它显然是速度上的赢家。不过,我对它还不够熟悉,无法对空间要求发表评论。

einsum
是另一个常见的怀疑

m, n, d = 100, 100, 100
>>> R = np.random.random((m, d))
>>> S = np.random.random((m, n, d))
>>> np.einsum('md,mnd->mn', R, S)

>>> np.allclose(np.einsum('md,mnd->mn', R, S), (R[:,None,:]*S).sum(axis=-1))
True
>>> from timeit import repeat
>>> repeat('np.einsum("md,mnd->mn", R, S)', globals=globals(), number=1000)
[0.7004671019967645, 0.6925274690147489, 0.6952172230230644]
>>> repeat('(R[:,None,:]*S).sum(axis=-1)', globals=globals(), number=1000)
[3.0512512560235336, 3.0466731210472062, 3.044075728044845]
一些间接证据表明,
einsum
使用RAM不会太浪费:

>>> m, n, d = 1000, 1001, 1002
>>> # Too much for broadcasting:
>>> np.zeros((m, n, d))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
MemoryError
>>> R = np.random.random((m, d))
>>> S = np.random.random((n, d))
>>> np.einsum('md,nd->mn', R, S).shape
(1000, 1001)
>>m,n,d=100010011002
>>>#广播内容太多:
>>>np.零((m,n,d))
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
记忆者
>>>R=np.随机.随机((m,d))
>>>S=np.random.random((n,d))
>>>np.einsum('md,nd->mn',R,S).形状
(1000, 1001)

Nitpick:我不确定时间效率是否真的不同,只是使用广播方法时常数系数要低得多。@juanpa.arrivillaga我同意,但我只是说了时间效率,而不是时间复杂性。是的,你是对的,我想我使用的术语不正确。@juanpa.arrivillaga补充了一些(相当低的技术含量)有证据表明,
einsum
不能进行全面广播。啊!我以前不知道
einsum
;这是相当优雅的。谢谢
einsum
如图所示直接收缩for循环,没有生成临时变量。@Daniel您似乎对
einsum
的一切都很了解!为什么?
einsim
将索引字符串转换为
nditer
对象(capi),并对该迭代进行乘积求和。在本例中,迭代是在三维索引空间
m,n,d
上进行的。感谢您的全面回答!我从来没有听说过麻麻;我会试试看。
>>> m, n, d = 1000, 1001, 1002
>>> # Too much for broadcasting:
>>> np.zeros((m, n, d))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
MemoryError
>>> R = np.random.random((m, d))
>>> S = np.random.random((n, d))
>>> np.einsum('md,nd->mn', R, S).shape
(1000, 1001)