首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >把np.einsum翻译成更有表现力的东西

把np.einsum翻译成更有表现力的东西
EN

Stack Overflow用户
提问于 2022-11-03 09:43:35
回答 2查看 59关注 0票数 2

使用python/numpy,我有以下np.einsum

代码语言:javascript
复制
np.einsum('abde,abc->bcde', X, Y)

Y是稀疏的:对于每个[a,b],只有一个c == 1;所有其他的:= 0。对于轴的相对大小的例子,X.shape是按(1000, 5, 30, 30)的顺序,而Y.shape是等效的(1000, 5, 300)

这个操作非常昂贵,我想让它更有表现力。首先,einsum不是并行化的。另一方面,由于Y是稀疏的,我有效地计算了我应该执行的乘法运算数的300倍。事实上,当我用n上的循环来写等效的einsum时,我得到了大约3倍的速度。但这显然不是很好。

我该怎么做才能使这个更有表现力呢?我尝试过使用np.tensordot,但是我想不出如何从它得到我想要的东西(我仍然遇到了稀疏/密集的问题)。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2022-11-03 16:22:11

如果Y只包含1和0,那么einsum基本上是这样做的:

代码语言:javascript
复制
result = np.zeros(Y.shape[1:] + X.shape[2:], X.dtype)
I, J, K = np.nonzero(Y)
result[J, K] += X[I, J]

但这没有给出正确的结果,因为重复的j,k指数。我无法让numpy.add.at工作,但是只循环这些索引仍然非常快,至少对于给定的形状和稀疏性是这样。

代码语言:javascript
复制
result = np.zeros(Y.shape[1:] + X.shape[2:], X.dtype)
for i, j, k in zip(*np.nonzero(Y)):
    result[j, k] += X[i, j]

这是我使用的测试代码:

代码语言:javascript
复制
a, b, c, d, e = 1000, 5, 300, 30, 30
X = np.random.randint(10, size=(a,b,d,e))
R = np.random.rand(a, b, c)
K = np.argmax(R, axis=2)
I, J = np.indices((a, b), sparse=True)
Y = np.zeros((a, b, c), int)
Y[I, J, K] = 1
票数 1
EN

Stack Overflow用户

发布于 2022-11-03 11:42:21

Numba可以很容易地做到这一点。

代码语言:javascript
复制
import numba

@numba.njit('float64[:,:,:,::1](float64[:,:,:,::1], float64[:,:,::1])', fastmath=True, parallel=True)
def compute(x, y):
    na, nb, nd, ne = x.shape
    nc = y.shape[2]
    assert y.shape == (na, nb, nc)

    out = np.zeros((nb, nc, nd, ne))

    for b in numba.prange(nb):
        for a in range(na):
            for c in range(nc):
                yVal = y[a, b, c]
                if np.abs(yVal) != 0:
                    for d in range(nd):
                        for e in range(ne):
                            out[b, c, d, e] += x[a, b, d, e] * yVal

    return out

请注意,在ab上迭代顺序代码更快。也就是说,为了使代码是并行的,已经交换了循环,并在b上执行并行化(这是一个小轴)。在轴a上进行并行缩减会更有效,但不幸的是,这对于Numba来说并不容易(因为创建线程本地矩阵没有简单的方法,因此需要将矩阵分割成多个块)。

注意,可以用实际值替换ndne之类的值(即。30),因此编译器可以为这个矩阵大小生成更快的代码。

下面是测试代码:

代码语言:javascript
复制
np.random.seed(0)
x = np.random.rand(1000, 5, 30, 30)
y = np.random.rand(1000, 5, 300)
y[np.random.rand(*y.shape) > 0.1] = 0.0 # Make it sparse (90% of 0)

%time res = np.einsum('abde,abc->bcde', x, y)  # 2.350 s
%time res2 = compute(x, y)                     # 0.074 s (0.061 s with hand-written sizes)
print(np.allclose(res, res2))

这是大约32倍的,在一个10核心的英特尔Skylake处理器的。它达到38倍的速度与手写的大小。由于b轴上的并行化,它不能很好地缩放,但是使用其他轴会导致内存访问效率降低。

如果这还不够,那么最好首先转置xy,这样可以提高数据的局部性(这要归功于a轴上更连续的访问模式)和更好的缩放(通过并行化bc轴)。尽管如此,转移通常是昂贵的,所以我们当然需要优化它,以获得一个更好的速度。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/74300919

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档