NumPy逐元素点积

15

有没有一种优雅、基于numpy的方法来对每个元素进行点积?或者如何将下面的代码翻译为更好的版本?

m0 # shape (5, 3, 2, 2)
m1 # shape (5,    2, 2)
r = np.empty((5, 3, 2, 2))
for i in range(5):
    for j in range(3):
        r[i, j] = np.dot(m0[i, j], m1[i])

预先感谢!

2个回答

26

方法 #1

使用 np.einsum -

np.einsum('ijkl,ilm->ijkm',m0,m1)

涉及的步骤:

  • 保持输入中的第一个轴对齐。

  • 在求和约简中,将 m0 的最后一个轴与 m1 的第二个轴相匹配。

  • m0m1 的其余轴通过按元素相乘并以外积方式展开/扩展。


方法 #2

如果您想要性能,并且约简的轴具有较小的长度,则最好使用单循环,并使用矩阵乘法。可以使用 np.tensordot 来实现,如下所示 -

s0,s1,s2,s3 = m0.shape
s4 = m1.shape[-1]
r = np.empty((s0,s1,s2,s4))
for i in range(s0):
    r[i] = np.tensordot(m0[i],m1[i],axes=([2],[0]))

第三种方法

现在, 对于一些二维输入,可以有效地使用np.dot来进一步提高性能。因此,通过它,虽然修改版本会稍微长一些,但希望它是最具性能的版本 -

s0,s1,s2,s3 = m0.shape
s4 = m1.shape[-1]
m0.shape = s0,s1*s2,s3   # Get m0 as 3D for temporary usage
r = np.empty((s0,s1*s2,s4))
for i in range(s0):
    r[i] = m0[i].dot(m1[i])
r.shape = s0,s1,s2,s4
m0.shape = s0,s1,s2,s3  # Put m0 back to 4D

运行时测试

函数定义 -

def original_app(m0, m1):
    s0,s1,s2,s3 = m0.shape
    s4 = m1.shape[-1]
    r = np.empty((s0,s1,s2,s4))
    for i in range(s0):
        for j in range(s1):
            r[i, j] = np.dot(m0[i, j], m1[i])
    return r

def einsum_app(m0, m1):
    return np.einsum('ijkl,ilm->ijkm',m0,m1)

def tensordot_app(m0, m1):
    s0,s1,s2,s3 = m0.shape
    s4 = m1.shape[-1]
    r = np.empty((s0,s1,s2,s4))
    for i in range(s0):
        r[i] = np.tensordot(m0[i],m1[i],axes=([2],[0]))
    return r        

def dot_app(m0, m1):
    s0,s1,s2,s3 = m0.shape
    s4 = m1.shape[-1]
    m0.shape = s0,s1*s2,s3   # Get m0 as 3D for temporary usage
    r = np.empty((s0,s1*s2,s4))
    for i in range(s0):
        r[i] = m0[i].dot(m1[i])
    r.shape = s0,s1,s2,s4
    m0.shape = s0,s1,s2,s3  # Put m0 back to 4D
    return r

时间表和验证 -

In [291]: # Inputs
     ...: m0 = np.random.rand(50,30,20,20)
     ...: m1 = np.random.rand(50,20,20)
     ...: 

In [292]: out1 = original_app(m0, m1)
     ...: out2 = einsum_app(m0, m1)
     ...: out3 = tensordot_app(m0, m1)
     ...: out4 = dot_app(m0, m1)
     ...: 
     ...: print np.allclose(out1, out2)
     ...: print np.allclose(out1, out3)
     ...: print np.allclose(out1, out4)
     ...: 
True
True
True

In [293]: %timeit original_app(m0, m1)
     ...: %timeit einsum_app(m0, m1)
     ...: %timeit tensordot_app(m0, m1)
     ...: %timeit dot_app(m0, m1)
     ...: 
100 loops, best of 3: 10.3 ms per loop
10 loops, best of 3: 31.3 ms per loop
100 loops, best of 3: 5.12 ms per loop
100 loops, best of 3: 4.06 ms per loop

哇!这完全超出了我的预期,非常感谢! - Philipp H.

-3

4
这个回答读起来像个问题。或许提供一个例子会有所帮助,或者修改原始代码使用numpy.inner(),这正是 OP 问题中所要求的。 - ad absurdum

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