使用 Numpy 向量化三个嵌套循环

Vectorizing three nested loops with Numpy

我有一个复数矩阵 C,尺寸为 (r, r),还有一个复数向量 r。我需要根据以下等式从 Cv 计算一个新矩阵:

其中 K 也是维度为 (r, r) 的方阵。下面是使用三个循环计算 K 的代码:

import numpy as np
import matplotlib.pyplot as plt

r = 9

#   Create random matrix
C = np.random.rand(r,r) + np.random.rand(r,r) * 1j
v = np.random.rand(r) + np.random.rand(r) * 1j

#   Original loops
K = np.zeros((r, r))

for m in range(r):
    for n in range(r):
        for i in range(r):
            K[m,n] += np.imag( C[i,m] * np.conj(C[i,n]) * np.sign(np.imag(v[i])) )

plt.figure()
plt.imshow(K)
plt.show()

使用 i 删除循环相对容易:

#   First optimization
K = np.zeros((r, r))

for m in range(r):
    for n in range(r):
        K[m,n] = np.imag(np.sum(C[:,m] * np.conj(C[:,n]) * np.sign(np.imag(v)) ))

但我不确定如何继续向量化剩余的两个循环。在这种情况下真的可以吗?

我找到了一些暂时可用的东西。但是,仍然存在一个循环,并且由于生成的矩阵是对称的,因此仍然需要进行一些优化。

我没有删除 i 循环,而是删除了另外两个循环:

K = np.zeros((r, r), dtype=np.complex128)

for i in range(r):
    K += adjointMatrix(C) @ (np.sign(np.imag(v)) * C)

K = np.imag(K)

与:

def adjointMatrix(X):
    return np.conjugate( np.transpose(X) )

这看起来像矩阵乘法:

out = np.imag((C*np.sign(np.imag(v))[:,None]).T @ np.conj(C))

或者您可以使用 np.einsum:

out = np.imag(np.einsum('im,in,i', C, np.conj(C), np.sign(np.imag(v))))

验证你的方法:

np.all(np.abs(out-K) < 1e-6)
# True

我有很多这样的问题,下面是我通常如何着手寻找解决方案来编写矢量化代码。

以下是我注意到的关于您的总结。很酷的结论是,您可能根本不需要矢量化,因为您可以将整个计算表示为二维矩阵的单一乘积。来了...

让我们首先定义以下矩阵(抱歉缺少 Latex 符号,Whosebug 不支持 Mathjax):

A_{i,j} = c_{i,j}.

B_{i,j} = c_{i,j} * sgn(Im(v_i))

那么你可以把你的总和写成:

k_{m,n} = Im( \sum_{i=1}^{r} c_{i,m} * sgn(Im(v_i)) * c_{i,n} ^* ) = Im ( \sum_{i=1}^{r} B_{i,m} * A_{i,n}^* ) = Im( \sum_{i=1}^{r} B_{m ,i}^T * A_{i,n}^* )

上面 Im(.) 内部的表达式是矩阵乘法的定义,等效于以下内容:

k_{m,n} = Im( (B^T * A^*)_{m,n} )

这意味着您的矩阵 k 可以表示为矩阵 B 的转置与矩阵 A 的乘积。在您的代码中,矩阵 A 已分配给变量 C。因此可以按如下方式进行矢量化:

C = np.random.rand(r,r) + np.random.rand(r,r) * 1j
v = np.random.rand(r) + np.random.rand(r) * 1j
k = np.imag( (C * np.sign(np.imag(v)).T @ np.conj(C) )

并且您避免了讨厌的循环和令人费解的表达式