【问题标题】:Numpy array and for loop: how to improve itNumpy 数组和 for 循环:如何改进它
【发布时间】:2020-04-25 16:00:33
【问题描述】:

我是 Python 新手,我有一个关于加快 for 循环的问题。

让“u”是一个维数为 (N,K) 的 numpy 数组,让“kernel_vect”是一个维数为 (K,) 的 numpy 数组,这两个数组都是 float64 数字。 我想加快以下代码的速度(例如通过消除 for 循环)

Kernel_appo = np.zeros((N**2,))
    for k in range(K):
        uk = u[:,k]
        Mat_appo = np.outer(uk,uk)
        Kernel_appo = Kernel_appo  + kernel_vect[k] * routines.vec(Mat_appo)

有什么想法吗?谢谢!

【问题讨论】:

  • routines.vec 应该做什么? NK 的典型值是什么?
  • 啊,对不起! routine.vec 将矩阵的列堆叠成一个数组(逐列)。 N 小(小于 100),K 大一个数量级或更多
  • 好的。当您说它“堆叠列”时,这意味着对于矩阵[[0, 1], [2, 3]](行主要),结果将是[0, 2, 1, 3],不是吗?如果没有,你能把这个函数的代码写短吗?
  • 是的,完全正确。我只是在使用这个函数,基本上是“np.ravel(x, order='F')”

标签: python python-3.x performance for-loop


【解决方案1】:

这里是一个更快的实现,没有对 k 的循环:

# Version 2
Kernel_appo = np.zeros((N**2,))
for n1 in range(N):
    for n2 in range(N):
        Kernel_appo[n1*N+n2] = (u[n1,:] * u[n2,:] * kernel_vect).sum()

我们可以利用 u 乘积的对称性让它更快:

# Version 3
Kernel_appo = np.zeros((N,N))
for n1 in range(N):
    for n2 in range(n1,N):
        Kernel_appo[n1,n2] = (u[n1,:] * u[n2,:] * kernel_vect).sum()
Kernel_appo = np.triu(Kernel_appo, 1) + np.tril(Kernel_appo.transpose(), 0) # make the matrix symmetric
Kernel_appo = np.ravel(Kernel_appo, order='C')

这是一个删除循环的版本:

# Version 4
Kernel_appo = np.zeros((N,N))
for n1 in range(N):
    Kernel_appo[n1,n1:N] = ((u[n1,:] * kernel_vect) * u[n1:N,:]).sum(axis=1)
Kernel_appo = np.triu(Kernel_appo, 1) + np.tril(Kernel_appo.transpose(), 0)
Kernel_appo = np.ravel(Kernel_appo, order='C')

我们仍然有一个关于 N 的循环。但是,由于 N 很小,因此保留它似乎是合理的。删除它肯定会迫使 numpy 在内存中创建巨大的矩阵,这会导致性能下降(如果 N 和 K 非常大,甚至会崩溃)。

请注意,如果 K 大得多,版本 4 可能不会那么快(因为临时 numpy 矩阵无法放入 CPU 的缓存中)。

更新:我只是发现在这种情况下可以使用很棒的np.einsum

# Version 5
Kernel_appo = np.ravel(np.einsum('ji,ki,i->jk', u, u, kernel_vect, optimize=True), order='C')

做好准备,因为这个更简单的实现也快得多(因为 numpy 能够向量化代码并并行运行)。

以下是我的机器上 N=50 和 K=5000 的性能结果:

Initial code: 58.15 ms
Version 2:    19.94 ms
Version 3:    10.11 ms
Version 4:     5.08 ms
Version 5:     0.57 ms

最终的实现现在比最初的快大约 100 倍!

【讨论】:

  • 很好的答案!最后一点:如果在我的代码中,而不是 Mat_appo = np.outer(uk,uk),我有 Mat_appo = np.outer(uk,uk.conj),我该如何更改版本 4 来处理复杂向量?
  • 我不认为Mat_appo 在这种情况下是对称的。所以你不能直接使用version 3version 4(如果你添加conj,第2版仍然可以使用)。实际上,version 3 假设对称,而version 4 就是基于它。但是,您可以从 version 2 开始,然后添加 conj 调用,然后应用 version 3 到 4 修改(包括仅处理 2D 数组而不是向量)。最后一步可以通过将u[n1:N,:] 替换为u[0:N,:] 并删除基于triu/tril 的行来完成。
  • 好的,谢谢!是的,Mat_appo 不是对称的,而是 Hermitian(即Mat_appo 等于Mat_appo.T.conj)。我会努力工作的!再次感谢!
  • 好的。我没有看到Mat_appo 是厄米特人!因此,Kernel_appo 也应该如此。所以,我认为您可以使用最后一个版本,只需在向量矩阵产品中添加一个.conj()(请注意,关于您放置.conj() 的术语,您需要在Kernel_appo 上添加一个最终的.transpose() ) 以及在np.tril(...) 通话之后。
猜你喜欢
  • 2019-08-07
  • 1970-01-01
  • 2022-11-02
  • 2020-02-15
  • 2023-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-26
相关资源
最近更新 更多