将高阶矩阵与 numpy 相乘



我创造了这个玩具问题,它反映了我更大的问题:

import numpy as np
ind = np.ones((3,2,4)) # shape=(3L, 2L, 4L)
dist = np.array([[0.1,0.3],[1,2],[0,1]]) # shape=(3L, 2L)
ans = np.array([np.dot(dist[i],ind[i]) for i in xrange(dist.shape[0])]) # shape=(3L, 4L)
print ans
""" prints:
   [[ 0.4  0.4  0.4  0.4]
    [ 3.   3.   3.   3. ]
    [ 1.   1.   1.   1. ]]
"""

我想尽快完成,所以使用 numpy 的函数来计算ans应该是最好的方法,因为这个操作很重,而且我的矩阵很大。

我看到了这篇文章,但是形状不同,我不明白应该使用哪种axes来解决这个问题。但是,我确信张量点应该有答案。有什么建议吗?

编辑:我接受了@ajcr的回答,但也请阅读我自己的答案,这可能会帮助其他人......

按照@ajcr的精彩回答,我想确定哪种方法是最快的,所以我使用了timeit

import timeit
setup_code = """
import numpy as np
i,j,k = (300,200,400)
ind = np.ones((i,j,k)) #shape=(3L, 2L, 4L)
dist = np.random.rand(i,j) #shape=(3L, 2L)
"""
basic ="np.array([np.dot(dist[l],ind[l]) for l in xrange(dist.shape[0])])"
einsum = "np.einsum('ijk,ij->ik', ind, dist)"
tensor= "np.tensordot(ind, dist, axes=[1, 1])[0].T"
print "tensor - total time:", min(timeit.repeat(stmt=tensor,setup=setup_code,number=10,repeat=3))
print "basic - total time:", min(timeit.repeat(stmt=basic,setup=setup_code,number=10,repeat=3))
print "einsum - total time:", min(timeit.repeat(stmt=einsum,setup=setup_code,number=10,repeat=3))

令人惊讶的结果是:

tensor - total time: 6.59519493952
basic - total time: 0.159871203461
einsum - total time: 0.263569731028

所以很明显,使用tensordot是错误的方法(更不用说在更大的例子中memory error,就像@ajcr所说的那样)。

由于这个例子很小,我将矩阵大小更改为i,j,k = (3000,200,400),翻转顺序以确保它没有效果,并设置了另一个重复次数更多的测试:

print "einsum - total time:", min(timeit.repeat(stmt=einsum,setup=setup_code,number=50,repeat=3))
print "basic - total time:", min(timeit.repeat(stmt=basic,setup=setup_code,number=50,repeat=3))

结果与第一次运行一致:

einsum - total time: 13.3184077671
basic - total time: 8.44810031351

然而,测试另一种类型的尺寸增长 - i,j,k = (30000,20,40)导致了以下结果:

einsum - total time: 0.325594117768
basic - total time: 0.926416766397

有关这些结果的说明,请参阅注释。

寓意是,在为特定问题寻找最快的解决方案时,尝试生成在类型和形状方面尽可能与原始数据相似的数据。就我而言,ij,k小得多,所以我保留了丑陋的版本,这也是在这种情况下最快的。

您可以使用

np.einsum来执行该操作,因为它允许非常仔细地控制哪些轴相乘以及哪些轴相加:

>>> np.einsum('ijk,ij->ik', ind, dist)
array([[ 0.4,  0.4,  0.4,  0.4],
       [ 3. ,  3. ,  3. ,  3. ],
       [ 1. ,  1. ,  1. ,  1. ]])

该函数将ind第一轴中的条目与dist第一轴中的条目相乘(下标 'i' )。每个数组的第二个轴也是如此(下标 'j' )。我们不是返回一个 3D 数组,而是告诉 einsum 通过从输出下标中省略它来沿轴'j'对值求和,从而返回一个 2D 数组。


np.tensordot更难应用于此问题。它会自动对轴的乘积求和。但是,我们想要套产品,但只对其中一套求和。

写入np.tensordot(ind, dist, axes=[1, 1])(如您链接到的答案)会为您计算正确的值,但返回形状为 (3, 4, 3) 的 3D 数组。如果您能负担得起更大阵列的内存成本,则可以使用:

np.tensordot(ind, dist, axes=[1, 1])[0].T

这为您提供了正确的结果,但由于tensordot首先创建一个比必要的大得多的数组,因此einsum似乎是更好的选择。

相关内容

  • 没有找到相关文章

最新更新