将一个数组的每个元素乘以另一个数组的每个元素

5

假设我有两个数组,

import numpy as np


x = np.array([1, 2, 3, 4])
y = np.array([5, 6, 7, 8])

如何以最快、最Pythonic等方式获得一个新的数组z,使其元素数量等于x.size * y.size,且数组中的元素是来自两个输入数组的每对元素(x_i, y_j)的乘积。

换句话说,我要找到一个数组z,其中z[k]x[i]*y[j]

获取此数组的一种简单但低效的方法如下:

z = np.empty(x.size * y.size)
counter = 0
for i in x:
    for j in y:
        z[counter] = i * j
        counter += 1

运行上述代码可以看到,在这个例子中z的值为
In [3]: z
Out[3]: 
array([  5.,   6.,   7.,   8.,  10.,  12.,  14.,  16.,  15.,  18.,  21.,
        24.,  20.,  24.,  28.,  32.])

@dbliss:可能是外积,可能是卷积,也可能是其他什么东西。外积看起来最有可能,但没有足够的信息来确定。 - user2357112
5个回答

4

虽然我对numpy没有太多经验,但是快速搜索得到了这个链接:http://docs.scipy.org/doc/numpy/reference/generated/numpy.ufunc.outer.html

>>> np.multiply.outer([1, 2, 3], [4, 5, 6])
array([[ 4,  5,  6],
   [ 8, 10, 12],
   [12, 15, 18]])

您可以将该数组扁平化以获得与您请求的相同输出: http://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.flatten.html 编辑:@Divakar的答案向我们展示了ravel将执行与flatten相同的操作,但速度更快。所以请使用它。
因此,在您的情况下,代码如下:
>>> np.multiply.outer(x, y).ravel()

奖励:您可以通过这个功能实现多维度的操作!


4
这是一种实现方法:

以下是具体步骤:

import itertools


z = np.empty(x.size * y.size)
counter = 0
for i, j in itertools.product(x, y):
    z[counter] = i * j
    counter += 1

虽然我已经去掉了其中一个循环,但是摆脱那个计数器和for循环也是不错的选择。

更新

作为一行代码,其他提供的答案比这个更好(按照我的标准,简洁性很重要)。下面的时间结果显示,@BilalAkil的答案比@TimLeathart的答案更快:

In [10]: %timeit np.array([x * j for j in y]).flatten()
The slowest run took 4.37 times longer than the fastest. This could mean that an intermediate result is being cached 
10000 loops, best of 3: 24.2 µs per loop

In [11]: %timeit np.multiply.outer(x, y).flatten()
The slowest run took 5.59 times longer than the fastest. This could mean that an intermediate result is being cached 
100000 loops, best of 3: 10.5 µs per loop

1
虽然您已经摆脱了其中一个循环,但仍在执行交叉乘法(在这种情况下使用 itertools.product),因此其复杂度与具有两个循环的情况相同:O(xy)。避免这种情况将会很困难。 - Bilal Akil
也尝试计时你的基本循环实现。包括嵌套循环和 itertools.product 的实现 :P - Bilal Akil
@BilalAkil 我宁愿不让自己尴尬 ;) - abcd

3

这里可以提出另外两种方法。

使用 np.dot 进行矩阵乘法

np.dot(x[:,None],y[None]).ravel()

使用np.einsum

np.einsum('i,j->ij',x,y).ravel()

运行时测试

In [31]: N = 10000
    ...: x = np.random.rand(N)
    ...: y = np.random.rand(N)
    ...: 

In [32]: %timeit np.dot(x[:,None],y[None]).ravel()
1 loops, best of 3: 302 ms per loop

In [33]: %timeit np.einsum('i,j->ij',x,y).ravel()
1 loops, best of 3: 274 ms per loop

@BilalAkil的答案相同,但使用ravel()替代flatten()作为更快的替代方法。
In [34]: %timeit np.multiply.outer(x, y).ravel() 
1 loops, best of 3: 211 ms per loop

@BilalAkil's answer:

In [35]: %timeit np.multiply.outer(x, y).flatten()
1 loops, best of 3: 451 ms per loop

@Tim Leathart的回答:

In [36]: %timeit np.array([y * a for a in x]).flatten()
1 loops, best of 3: 766 ms per loop

很奇怪ravelflatten更快。 (我本来以为它们应该是一样的。)你能解释一下这个时间差吗? - abcd
1
@dbliss 这与flatten()生成副本有关,而ravel()只是查看输入数组的视图,因此它更便宜。要验证这一点,您可以创建一个玩具设置:A = np.random.rand(3,4),然后使用np.may_share_memory()检查是否存在视图。因此,np.may_share_memory(A.ravel(),A)将为True,而np.may_share_memory(A.flatten(),A)将导致False。有关检查视图或否的更多信息,请参见此处:https://dev59.com/wmgu5IYBdhLWcg3wK0Gr#11524746 - Divakar

2
这是一种方法:
import numpy as np

x = np.array([1, 2, 3, 4])
y = np.array([5, 6, 7, 8])
z = np.array([y * a for a in x]).flatten()

0

我知道我来晚了,但是我想为将来阅读这个问题的任何人提供一个解决方案。使用与@Divakar相同的度量标准,我添加了一个我认为更直观的解决方案到列表中(第一个代码片段测量):

import numpy as np
N = 10000
x = np.random.rand(N)
y = np.random.rand(N)

%timeit np.ravel(x[:,None] * y[None])
635 ms ± 19.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit np.outer(x, y).ravel()
640 ms ± 16 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit np.dot(x[:,None],y[None]).ravel()
853 ms ± 57.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit np.einsum('i,j->ij',x,y).ravel()
754 ms ± 19.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

根据执行时间的相似性,numpy.outer 很可能与我的解决方案在内部完全相同,尽管你应该对这样的观察保持谨慎。

我认为它更直观的原因是,与所有其他解决方案不同,它的语法并不严格限于乘法。例如,np.ravel(x[:,None] / y[None]) 将为 x 中的每个 a 和 y 中的每个 b 给出 a / b。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接