假设您的内核有些平滑,请使用 low-rank approximation。
这是一个天真的例子:
import numpy as np
N=2000
input=np.random.random(N)
x=np.linspace(-1,1,N)
y=np.linspace(-2,2,N)
X,Y=np.meshgrid(x,y,sparse=True)
A = np.exp(1j*2*np.pi*X*Y)
output = np.dot(A, input)
U,S,V = np.linalg.svd(A)
# find truncation rank for given tolerance
k = np.nonzero(S < 1e-12)[0][0]
print('Truncation rank:', k)
Xk = U[:,:k].dot(np.diag(S[:k]))
Yk = V[:k,:]
output2 = Xk.dot(Yk.dot(input))
# check quality of approximation
print(np.linalg.norm(output - output2, np.inf))
%timeit np.dot(A, input)
%timeit Xk.dot(Yk.dot(input))
输出:
Truncation rank: 32
6.3535593536e-12
100 loops, best of 3: 3.9 ms per loop
10000 loops, best of 3: 61.4 µs per loop
上面所做的是计算矩阵的截断奇异值分解,并使用得到的 A 的低秩近似来加速矩阵向量乘积。所以,而不是使用A, 你使用一个近似值
A≈XkYTk,Xk,Yk∈RN×k
和k≪N.
在示例中,这会导致超过 60 倍的加速,并且只会随着 N 变大而变得更好。那是因为完整的矩阵向量乘积具有复杂性O(N2),而低秩矩阵向量积具有复杂性O(Nk). 结果并不准确,但您可以通过使用截断等级来影响准确性。
当然,计算 SVD 非常慢。相反,有更快的替代方法来计算矩阵的低秩近似,例如:快速随机 SVD,或者我推荐的自适应交叉逼近 (ACA)。后者是一种黑盒算法,用于计算低秩近似,甚至不需要计算整个矩阵 A,因此非常适合非常大的问题。它也很容易实现。
关于 ACA 的原始文献(特别是 M. Bebendorf 从 2000 年开始撰写的)有些难以获得。取而代之的是,这是一篇以基本形式描述 ACA 的随机论文:(链接)。
如果您的内核相对简单,甚至可以通过分析得出一个可分离的近似值,这直接导致低秩近似值,而不必通过算法计算它。查阅有关“多极展开”和“快速多极方法”的文献,了解许多这方面的例子。
最后,原始程序中的瓶颈实际上是计算 A,而不是矩阵向量积。ACA 将消除这一点,因为它不需要完整的矩阵。
更新:
我刚刚意识到 scipy 已经包含了一种在鲜为人知的scipy.linalg.interpolative
模块中逼近 SVD 的快速方法。这是一个例子:
import scipy.linalg.interpolative as sli
U,S,V = sli.svd(A, 1e-12)
print('Rank:', U.shape[1])
Xk = U.dot(np.diag(S))
Yk = V.conj().T
output3 = Xk.dot(Yk.dot(input))
# check quality of approximation
print(np.linalg.norm(output - output3, np.inf))
和输出:
Rank: 37
2.62815545927e-10
精确 SVD 和近似 SVD 之间的时间差异(同样,由于涉及的渐近,这将在更大的 N 下变得更好):
%timeit np.linalg.svd(A)
%timeit sli.svd(A, 1e-12)
1 loop, best of 3: 11.4 s per loop
1 loop, best of 3: 297 ms per loop
但是,这仍然需要您首先计算整个矩阵 A 并且不能摆脱该瓶颈。然而,如果您只需要计算 A 一次然后有许多矩阵向量乘积,它可能是一个合适的选择。