如何使用 numpy 切片来表达这个复杂的表达式

计算科学 Python 麻木的
2021-12-06 00:28:20

我希望在 Python 中实现以下表达式:

xi=j=1i1kij,jaijaj,
在哪里xy是大小的 numpy 数组n, 和k是一个大小为 numpy 的数组n×n. 规模n可能高达 10000 左右,并且该函数是内部循环的一部分,将被多次评估,因此速度很重要。

理想情况下,我想完全避免 for 循环,尽管我想如果有的话,这不是世界末日。问题是我很难在没有几个嵌套循环的情况下看到如何做到这一点,这可能会使它变得相当慢。

任何人都可以看到如何使用 numpy 以一种有效且最好是可读的方式表达上述方程吗?更一般地说,处理这类事情的最佳方法是什么?

3个回答

这是 Numba 解决方案。在我的机器上,Numba 版本比没有装饰器的 python 版本快 1000 倍以上(对于 200x200 矩阵、'k' 和 200 长度向量 'a')。您还可以使用 @autojit 装饰器,它每次调用增加大约 10 微秒,以便相同的代码适用于多种类型。

from numba import jit, autojit

@jit('f8[:](f8[:,:],f8[:])')
#@autojit
def looped_ver(k, a):
    x = np.empty_like(a)
    for i in range(x.size):
        sm = 0.0
        for j in range(0, i+1):
            sm += k[i-j,j] * a[i-j] * a[j]
        x[i] = sm
    return x

披露:我是 Numba 开发人员之一。

这是一个开始。首先,对于任何错误,我深表歉意。

我尝试了几种不同的方法。我对总和的限制有点困惑 - 上限应该是i, 而不是i1?

编辑:不,问题中提供的上限是正确的。我把它留在这里是因为另一个答案现在使用相同的代码,但修复很简单。

首先是循环版本:

def looped_ver(k, a):
    x = np.empty_like(a)
    for i in range(x.size):
        sm = 0
        for j in range(0, i+1):
            sm += k[i-j,j] * a[i-j] * a[j]
        x[i] = sm
    return x

我用 numpy 切片使它成为一个循环:

def vectorized_ver(k, a):
    ktr = zeros_like(k)
    ar = zeros_like(k)
    sz = len(a)
    for i in range(sz):
        ktr[i,:i+1] = k[::-1].diagonal(-sz+i+1)
        a_ = a[:i+1]
        ar[i,:i+1] = a_[::-1] * a_
    return np.sum(ktr * ar, 1)

带有一个显式循环的 numpy 版本在我的计算机上大约快 25 倍n=5000.

然后我写了一个 Cython 版本的(更易读的)循环代码。

import numpy as np
import cython
cimport numpy as np

@cython.boundscheck(False)
@cython.wraparound(False)
def cyth_ver(double [:, ::1] k not None,
              double [:] a not None):
    cdef double[:] x = np.empty_like(a)
    cdef double sm
    cdef int i, j

    for i in range(len(a)):
        sm = 0.0
        for j in range(i+1):
            sm = sm + k[i-j,j] * a[i-j] * a[j]
        x[i] = sm
    return x

在我的笔记本电脑上,这个比循环版本快 200 倍(比 1 循环矢量化版本快 8 倍)。我相信其他人可以做得更好。

我玩的是 Julia 版本,它似乎(如果我计时正确的话)与 Cython 代码相当。

你想要的似乎是一个卷积;我认为实现它的最快方法是numpy.convolve功能。

您可能必须根据您的确切需求修复索引,但我认为您想尝试以下操作:

import numpy as np
a = [1, 2, 3, 4, 5]
k = [2, 4, 6, 8, 10]

result = np.convolve(a, k*a[::-1])