【问题标题】:Efficient way to perform a triangular sum with .sum over a matrix F(a[i], a[j]), given the vector a在给定向量 a 的情况下,使用 .sum 在矩阵 F(a[i], a[j]) 上执行三角和的有效方法
【发布时间】:2019-12-18 08:56:13
【问题描述】:

我有一个向量a,需要对两个索引进行求和,比如

for i in (range, n): 
    for j in (i+1, n):
        F(a[i] - a[j])

其中F 是一个函数:sum 提醒我们对数组的上三角形求和。

我阅读了Fastest way in numpy to sum over upper triangular elements with the least memory 上的有趣帖子并进行了试验:ARRAY.sum 确实是一种对上三角矩阵元素求和的非常快速的方法。

要将方法应用于我的案例,我首先需要定义一个数组,例如

A[i,j] = F(a[i],a[j])

然后计算

(A.sum() - np.diag(A).sum())/2

我当然可以通过两个 for 循环定义数组 A,但我想知道是否有更快、更简单的方法。

在另一种情况下,函数F 简单地等于

F = a[i]*a[j]

我可以写

def sum_upper_triangular(vector):
    A = np.tensordot(vector,vector,0)
    return (A.sum() - np.diag(A).sum())/2

这比直接用 sum() 求和或嵌套 for 循环要快得多。

如果F更清晰,例如

np.exp(a[i] - a[j])

我想知道最有效的方法。

非常感谢

【问题讨论】:

  • 您能分享一下您正在使用的实际F 吗?
  • @Divakar, F = a[i]*a[j] * G( a[i]-a[j]),其中 G 是以 a[i] 为参数的 Mittag Leffler 函数- 一个[j]。我想已经知道如何处理我在帖子中描述的示例函数对我很有用。事实上,我已经不确定如何最好地处理 F = a[i] - a[j]。非常感谢
  • 这个问题取决于函数 G。对于求和问题,很容易实现 Cython 或 Numba 解决方案。但是要获得良好的加速,您还必须将函数 G 实现为 Cython cdef 或使用 Numba 或包装 C 实现
  • 我理解特定的函数 G 会影响最优方法的选择。正如我所提到的,我已经满足于学习如何处理我提到的示例 np.exp(a[i] - a[j]),而不使用 sum()
  • 对此不确定,但它可能有助于存储 a[i],因此您不必重新检索该值

标签: python numpy


【解决方案1】:

如果我理解正确,您希望执行以下操作:

result = []
n = len(a)
for i in range(n-1): 
    for j in range(i+1, n):
        result.append(F(a[i] - a[j]))

对于某些功能F。此外,矩阵元素之间的运算可以是任何其他运算(例如乘法*)。下面是一种不使用 for 循环的方法:

iu = np.triu_indices(n, k=1)
A_j, A_i = np.meshgrid(a, a)
res = F(A_i[iu] - A_j[iu])  # e.g. F = np.exp

解释(n=5):

A_i = [[a[0], a[0], a[0], a[0], a[0],
       [a[1], a[1], a[1], a[1], a[1],
       [a[2], a[2], a[2], a[2], a[2],
       [a[3], a[3], a[3], a[3], a[3],
       [a[4], a[4], a[4], a[4], a[4]]

A_j = [[a[0], a[1], a[2], a[3], a[4],
       [a[0], a[1], a[2], a[3], a[4],
       [a[0], a[1], a[2], a[3], a[4],
       [a[0], a[1], a[2], a[3], a[4],
       [a[0], a[1], a[2], a[3], a[4]]

A_i[iu] = [a[0], a[0], a[0], a[0], a[1], a[1], a[1], a[2], a[2], a[3]]
A_j[iu] = [a[1], a[2], a[3], a[4], a[2], a[3], a[4], a[3], a[4], a[4]]

然后执行元素计算并应用元素 F:

F(A_i[iu] - A_j[iu]) = [ 
    F(a[0] - a[1]), F(a[0] - a[2]), F(a[0] - a[3]), F(a[0] - a[4]),
    F(a[1] - a[2]), F(a[1] - a[3]), F(a[1] - a[4]),
    F(a[2] - a[3]), F(a[2] - a[4]),
    F(a[3] - a[4])]

【讨论】:

  • 感谢您的意见。我确实接受了另一个问题,只是因为其中建议的方法结果更快。再次感谢
【解决方案2】:

您可以使用scipy.spatial.pdist 并将您想要的任何功能设置为指标。作为奖励,pdist 仅计算非对角三角形,因此您无需将其从 sum 中删除

from scipy.spatial.distance import pdist

def sum_upper_tri(arr, F = lambda x, y: x*y):
    return pdist(arr.reshape(arr.shape[0], -1), metric = F).sum()/2

如果你想要超快的东西,你需要numba:

from numba import jit

@jit
def sum_upper_tri_jit(arr, F = lambda x, y: x * y):
    out = 0
    for i in range(1, len(arr)):
        for j in range(i + 1, len(arr)):
            out += F(arr[i], arr[j])
    return out / 2

还没有找到@njit 的方法,但如果可以的话,它会快得多。

在任何情况下,每个预期 F 的专门构建的函数都会快得多。比如exp(|x-y|)的情况(提醒exp(x-y)不是对称的:x-y != y-x)

from numba import njit

@njit
def sum_upper_tri_exp(arr):
    out = 0
    for i in range(1, len(arr)):
        for j in range(i + 1, len(arr)):
            out += np.exp(np.abs(arr[i] - arr[j]))
    return out / 2

这比上面的快了大约 100 倍

如果你不想求和,你可以使用:

from numba import njit

@njit
def sum_upper_tri_exp(arr):
    out = []
    for i in range(1, len(arr)):
        for j in range(i + 1, len(arr)):
            out += [np.exp(arr[i] - arr[j])]
    return out

【讨论】:

  • 您可以在 sum_upper_tri_exp 函数中调用 F 函数之前对其进行 jit,或者使用闭包,例如。 stackoverflow.com/a/59256512/4045774stackoverflow.com/a/58752553/4045774
  • 欢迎您为赏金@max9111 做出回答,如果我尝试过,我会在那里抄袭您的答案。
  • @-Daniel,感谢您的意见。很遗憾过去3天无法登录,希望奖励积分已经获得。事实并非如此,请建议我是否有办法接受您的回复。再次感谢
猜你喜欢
  • 2021-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-16
  • 1970-01-01
相关资源
最近更新 更多