将二维矩阵的每一列乘以三维矩阵的每个切片的更有效方法



我有一个 8x8x25000 数组 W 和一个 8 x 25000 数组 r。我想将每个 8x8 的 W 切片乘以 r 的每一列 (8x1(,并将结果保存在 Wres,最终将成为一个 8x25000 矩阵。

我正在使用 for 循环来实现这一点:

for i in range(0,25000):
Wres[:,i] = np.matmul(W[:,:,i],res[:,i])

但这很慢,我希望有更快的方法来实现这一目标。

有什么想法吗?

只要2 个数组共享相同的 1 轴长度,Matmul 就可以传播。从文档中:

如果任一参数为 N-D、N> 2,则将其视为驻留在最后两个索引中的矩阵堆栈并相应地广播。

因此,您必须在matmul之前执行 2 个操作:

import numpy as np
a = np.random.rand(8,8,100)
b = np.random.rand(8, 100)
  1. 转置ab,使第一个轴是 100 个切片
  2. b添加额外的维度,以便b.shape = (100, 8, 1)

然后:

at = a.transpose(2, 0, 1) # swap to shape 100, 8, 8
bt = b.T[..., None] # swap to shape 100, 8, 1
c = np.matmul(at, bt)

c现在100, 8, 1,改8, 100

c = np.squeeze(c).swapaxes(0, 1)

c = np.squeeze(c).T

最后,一行只为方便:

c = np.squeeze(np.matmul(a.transpose(2, 0, 1), b.T[..., None])).T

使用np.matmul的另一种方法是np.einsum,这可以在 1 行更短、可以说更可口的代码行中完成,无需方法链接。

示例数组:

np.random.seed(123)
w = np.random.rand(8,8,25000)
r = np.random.rand(8,25000)
wres = np.einsum('ijk,jk->ik',w,r)
# a quick check on result equivalency to your loop
print(np.allclose(np.matmul(w[:, :, 1], r[:, 1]), wres[:, 1]))
True

时机相当于@Imanol的解决方案,因此请选择两者。 两者都比循环快 30 倍。 在这里,由于阵列的大小,einsum将具有竞争力。 对于大于这些的数组,它可能会胜出,而较小的阵列可能会失败。 有关详细信息,请参阅此讨论。

def solution1():
return np.einsum('ijk,jk->ik',w,r)
def solution2():
return np.squeeze(np.matmul(w.transpose(2, 0, 1), r.T[..., None])).T
def solution3():
Wres = np.empty((8, 25000))
for i in range(0,25000):
Wres[:,i] = np.matmul(w[:,:,i],r[:,i])
return Wres
%timeit solution1()
100 loops, best of 3: 2.51 ms per loop
%timeit solution2()
100 loops, best of 3: 2.52 ms per loop
%timeit solution3()
10 loops, best of 3: 64.2 ms per loop

信用来源:@Divakar

最新更新