我创造了这个玩具问题,它反映了我更大的问题:
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
有关这些结果的说明,请参阅注释。
寓意是,在为特定问题寻找最快的解决方案时,尝试生成在类型和形状方面尽可能与原始数据相似的数据。就我而言,i
比j,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
似乎是更好的选择。