Python中Numpy两个矩阵部分维度相乘,有没有很快的方法?

例如,有两个三维矩阵 m1,m2,shape 都是 (10000, 2, 2)

m1 = array([x1, x2, ..., x9999])
m2 = array([y1, y2, ..., y9999])

xn, yn 都是 2x2 的矩阵。

希望得到的结果是:

res = array([dot(x1, y1), dot(x2, y2), ..., dot(x9999, y9999)])

目前我试过最快的方法是 map(循环、列表推导都试过,numpy.vectorize 没成功),但感觉还是不够快。各位有没有更好的方法?


Python中Numpy两个矩阵部分维度相乘,有没有很快的方法?

14 回复

多进程试试


帖子没给具体例子,我猜你是想对两个矩阵的某些维度做逐元素乘,而不是矩阵乘。比如一个形状是 (a, b, c) 的矩阵 A 和一个形状是 (b, c) 的矩阵 B,你想让 B 自动广播到 A 的后两个维度上做乘法。

numpy 的广播机制就行,这是最快的方法。B 的形状 (b, c) 会被自动视为 (1, b, c),然后扩展成 (a, b, c)A 逐元素相乘。代码很简单:

import numpy as np

# 假设 A 是 3维,B 是 2维,想让B广播到A的后两维
A = np.random.randn(10, 20, 30)
B = np.random.randn(20, 30)

# 直接乘,numpy自动处理广播
C = A * B
print(C.shape)  # 输出 (10, 20, 30)

如果维度不对齐,比如 B(a, c) 但你想和 A 的中间维度 b 相乘,就需要用 np.expand_dims 调整一下维度再广播:

# 另一种情况:B 形状 (a, c),想和A的中间维度b相乘?
A = np.random.randn(10, 20, 30)
B = np.random.randn(10, 30)  # 注意第一维和A相同,但没有中间维度b

# 把B中间加一个维度,变成 (10, 1, 30),然后广播到 (10, 20, 30)
B_expanded = B[:, np.newaxis, :]
C = A * B_expanded
print(C.shape)  # 输出 (10, 20, 30)

核心就是利用广播,这是 numpy 底层优化过的,速度最快。搞不清楚维度对齐的时候,就 np.newaxis 或者 reshape 调整一下。

总结:用广播机制。

Python 已经很快了底层做了很多优化,不行把矩阵拆开多进程大批量的情况下可以加快最后再组合回来。再不行试一下用 cpp。

放到 cython 里面用 prange 多线程算

https://software.intel.com/en-us/articles/numpyscipy-with-intel-mkl

试试这个,再不行就只能用 Matlab 了,不要尝试手写 cpp,几乎不可能比 MKL 更快的了

不知道是不是我理解的有问题,numpy.matmul 是可以实现多个矩阵对应元素相乘的。

按照我对 lz 的描述的理解,直接 np.matmul(m1, m2) 就可以达到目的。

https://docs.scipy.org/doc/numpy/reference/generated/numpy.matmul.html

If either argument is N-D, N > 2, it is treated as a stack of matrices residing in the last two indexes and broadcast accordingly.

意思是,如果输入大于 2d,会当做矩阵的数组 /矩阵…

所以直接乘就行了

有的有的,直接选择矩阵特定的切片元素相乘就好因为直接是矩阵计算不需要循环,性能可以的

matmul(…)
matmul(a, b, out=None)

Matrix product of two arrays.

The behavior depends on the arguments in the following way.

- If both arguments are 2-D they are multiplied like conventional
matrices.
- If either argument is N-D, N > 2, it is treated as a stack of
matrices residing in the last two indexes and broadcast accordingly.


所以 np.matmul(m1, m2) 就行了。

In [1]: a = np.random.random((100000,2,2))

In [2]: b = np.random.random((100000,2,2))

In [3]: c = np.matmul(a, b)

In [4]: d = np.array([np.dot(i,j) for i,j in zip(a,b)])

In [5]: np.allclose(c,d)
Out[5]: True

In [6]: %timeit np.matmul(a,b)
41.1 ms ± 81.6 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [7]: %timeit np.array([np.dot(i,j) for i, j in zip(a,b)])
231 ms ± 5.81 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

加上 vectorize 的:

In [28]: def mydot(a, b):
…: return np.dot(a,b)
…:
…:

In [29]:

In [29]: vmydot = np.vectorize(mydot, signature=’(n,m),(m,n)->(n,n)’)

In [30]: e = vmydot(a, b)

In [31]: np.allclose(d,e)
Out[31]: True

In [32]: %timeit vmydot(a,b)
467 ms ± 4.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

非常感谢!看来我还是学艺不精

不客气。我很久没鼓捣过这玩意儿了,我记得 numba 的各路 jit 处理这个问题是很嗨的,你要是经常遇到这种问题,去看看 numba 吧,有 GPU 加速更开心 ^_^

In [1]: from numba import guvectorize, float64

In [2]: a = np.random.random((100000,2,2))

In [3]: b = np.random.random((100000,2,2))

In [4]: c = np.matmul(a, b)

In [5]: d = np.array([np.dot(i,j) for i,j in zip(a,b)])

In [6]: ([(float64[:,:],float64[:,:], float64[:,:])], ‘(n,m),(m,n)->(n,n)’, target=‘parallel’) #target=‘cpu’,‘gpu’
…: def mydot(a,b,res):
…: for i in range(res.shape[0]):
…: for j in range(res.shape[1]):
…: tmp = 0.
…: for k in range(a.shape[1]):
…: tmp += a[i, k] * b[k, j]
…: res[i, j] = tmp
…:

In [7]: e = mydot(a, b)

In [8]: np.allclose(c,e)
Out[8]: True

In [9]: np.allclose(c,d)
Out[9]: True

In [10]: %timeit mydot(a,b)
234 µs ± 4.02 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In [11]: %timeit np.array(list(map(np.dot, a, b)))
210 ms ± 2.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [12]: %timeit np.array([np.dot(i,j) for i, j in zip(a,b)])
235 ms ± 5.87 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [13]: %timeit np.matmul(a,b)
41.1 ms ± 90 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

no.tensordot

打错,np.tensordot

回到顶部