如何计算scipy稀疏矩阵行列式而不将其变为密集?

时间:2013-10-01 03:48:08

标签: python numpy scipy linear-algebra sparse-matrix

我试图找出在python中找到稀疏对称矩阵和真实矩阵行列式的最快方法。使用scipy sparse模块,但真的很惊讶没有行列式功能。我知道我可以使用LU分解来计算行列式,但是没有看到一个简单的方法来执行它,因为scipy.sparse.linalg.splu的返回是一个对象并且实例化一个密集的L和U矩阵是不值得的 - 我可以好sp.linalg.det(A.todense()) A是我的scipy稀疏矩阵。

我也有点惊讶为什么其他人没有面对scipy中有效决定因素计算的问题。如何使用splu计算行列式?

我调查了pySparsescikits.sparse.chlmod。后者对我来说现在不实用 - 需要软件包安装,也不确定在我遇到麻烦之前代码的速度有多快。 有解决方案吗提前致谢。

4 个答案:

答案 0 :(得分:5)

解决这个问题的“标准”方法是使用cholesky分解,但是如果你不能使用任何新的编译代码,那么你就不走运了。最好的稀疏cholesky实现是蒂姆戴维斯的CHOLMOD,它是根据LGPL许可的,因此不适用于scipy本身(scipy是BSD)。

答案 1 :(得分:4)

以下是我作为答案here的一部分提供的一些参考资料。 我认为它们可以解决您要解决的实际问题:

  • notes用于Shogun图书馆的实施
  • Erlend Aune,Daniel P. Simpson:高维高斯分布中的参数估计,特别是第2.1节(arxiv:1105.5256
  • Ilse C.F. Ipsen,Dean J. Lee:行列式近似arxiv:1105.0437
  • Arnold Reusken:大稀疏对称正定矩阵行列式的逼近arxiv:hep-lat/0008007

来自幕府将军的笔记:

  

计算似然表达式中对数行列式项的常用技术依赖于矩阵的Cholesky分解,即Σ= LLT,(L是下三角Cholesky因子),然后使用因子的对角项来计算日志(DET(Σ))=2Σni= 1log(LII)。然而,对于稀疏矩阵,由于协方差矩阵通常是,Cholesky因子经常遭受填充现象 - 它们本身并不那么稀疏。因此,对于大尺寸,由于存储所有这些不相关的因子非对角系数的大量存储器需求,该技术变得不可行。虽然已经开发了订​​购技术以预先对行和列进行置换以便减少填充,例如,近似最小度(AMD)重新排序,这些技术在很大程度上取决于稀疏性模式,因此无法保证提供更好的结果。

     

最近的研究表明,使用复杂分析,数值线性代数和贪婪图着色等多种技术,我们可以将对数行列式逼近任意精度[Aune et。 al。,2012]。主要技巧在于我们可以将log(det(Σ))写为trace(log(Σ)),其中log(Σ)是矩阵对数。

答案 2 :(得分:3)

您可以使用scipy.sparse.linalg.splu来获得L分解的较低(U)和较高(M=LU)三角矩阵的稀疏矩阵:

from scipy.sparse.linalg import splu

lu = splu(M)

行列式det(M)可以表示为:

det(M) = det(LU) = det(L)det(U)

三角矩阵的行列式只是对角线项的乘积:

diagL = lu.L.diagonal()
diagU = lu.U.diagonal()
d = diagL.prod()*diagU.prod()

但是,对于large matrices underflow or overflow commonly occurs,可以通过使用对数来避免。

diagL = diagL.astype(np.complex128)
diagU = diagU.astype(np.complex128)
logdet = np.log(diagL).sum() + np.log(diagU).sum()

请注意,我调用复杂的算术来解释可能出现在对角线中的负数。现在,您可以从logdet恢复行列式:

det = np.exp(logdet) # usually underflows/overflows for large matrices

可直接从diagLdiagU计算行列式的符号(例如,在实施Crisfield的弧长方法时很重要):

sign = swap_sign*np.sign(diagL).prod()*np.sign(diagU).prod()

其中swap_sign是考虑LU分解中的置换数量的术语。感谢@Luiz Felippe Rodrigues,可以计算出它:

swap_sign = minimumSwaps(lu.perm_r)

def minimumSwaps(arr): 
    """
    Minimum number of swaps needed to order a
    permutation array
    """
    # from https://www.thepoorcoder.com/hackerrank-minimum-swaps-2-solution/
    a = dict(enumerate(arr))
    b = {v:k for k,v in a.items()}
    count = 0
    for i in a:
        x = a[i]
        if x!=i:
            y = b[i]
            a[y] = x
            b[x] = y
            count+=1
    return count

答案 3 :(得分:1)

同时使用 SuperLU 和 CHOLMOD 的 N=1e6 附近的稀疏三对角线 (-1 2 -1) 的行列式开始出现问题...

行列式应该是 N+1。

在计算 U 对角线的乘积时可能是误差的传播:

from scipy.sparse import diags
from scipy.sparse.linalg import splu
from sksparse.cholmod import cholesky
from math import exp

n=int(5e6)
K = diags([-1.],-1,shape=(n,n)) + diags([2.],shape=(n,n)) + diags([-1.],1,shape=(n,n))
lu = splu(K.tocsc())
diagL = lu.L.diagonal()
diagU = lu.U.diagonal()
det=diagL.prod()*diagU.prod()
print(det)

factor = cholesky(K.tocsc())
ld = factor.logdet()
print(exp(ld))

输出:

4999993.625461911

4999993.625461119

即使 U 的准确度为 10-13 位,这也是意料之中的:

n=int(5e6)
print(n*diags([1-0.00000000000025],0,shape=(n,n)).diagonal().prod())

4999993.749444371