【问题标题】:SciPy.sparse iterative solvers: No sparse right hand side support?SciPy.sparse 迭代求解器:没有稀疏的右手边支持?
【发布时间】:2015-12-21 09:29:16
【问题描述】:

似乎scipy.sparse.linalg 的迭代求解器不支持scipy.sparse 的稀疏矩阵数据类型作为方程组的右手边(而直接求解器支持)。考虑以下简短示例:

import numpy as np
import scipy
import scipy.sparse
import scipy.sparse.linalg

# Construct a random sparse but regular matrix A
A = scipy.sparse.rand(50,50,density=0.1)+scipy.sparse.coo_matrix(np.diag(np.random.rand(50,)))

# Construct a random sparse right hand side
b = scipy.sparse.rand(50,1,density=0.1).tocsc()

ud = scipy.sparse.linalg.spsolve(A,b) # Works as indented
ui = scipy.sparse.linalg.bicg(A,b)[0] # ValueError: A and b have incompatible dimensions

形状似乎是正确的:

print(A.shape)
print(b.shape)

返回

(50, 50)
(50, 1)

将 b 定义为密集的 ndarray 但是有效

b = scipy.sparse.rand(50,1,density=0.1).todense() 

尽管文档要求 b 的类型为 {array, matrix},但如果不支持稀疏右手边(例如 FEM 中出现),我会感到非常惊讶。

那么我在这里做错了什么或者为什么不支持?

【问题讨论】:

  • 文档说matrix or array 代表b,而不是sparse。错误由linalg.utils.make_system 发出。
  • 我知道(正如我所写的)并且它没有回答任何问题。为什么不支持稀疏右手边?
  • scipy 中的稀疏求解器都不支持多个右手边,所以 b 无论如何都必须是 rank-1。考虑到这个限制,我的猜测是开发人员认为b 不太可能太长太稀疏以至于不能表示为密集数组。更根本的是,我怀疑底层 SuperLU 求解器是否会接受 b 的稀疏矩阵。
  • 深入研究代码,我发现底层的 fortran 代码基于 netlib2,如 1993 年 netlib.org/templates/templates.pdf 中所述。它使用简单的向量bx,而A 被转换成一个LinearOperator(基本上是A.dot(X))。这就是为什么A 可以是数组、矩阵或稀疏的——任何实现dot 的东西。
  • 代码的 Python 部分可以在任何时候将todense 应用于b。但避免这种情况需要更改 Fortran 代码,将 B(数组,维度 N)更改为一对较小的数组,一个表示非零值,另一个表示它们的索引。这将是一个根本性的重写,而不是一个 hack。

标签: python numpy scipy sparse-matrix


【解决方案1】:

两部分方法:

LinearOperator 的想法简单而强大:它是一个虚拟线性运算符, 或者实际上是两个:

Aop = Linop( A )  # see below
A.dot(x) -> Aop.matvec(x)
A.T.dot(y) -> Aop.rmatvec(y)
x = solver( Aop, b ... )  # uses matvec and rmatvec, not A.dot A.T.dot

这里matvecrmatvec 可以做任何事情(在合理范围内)。 例如,他们可以线性化可怕的非线性方程 靠近任何类型的 args xy。 不幸的是,aslinearoperator 不适用于稀疏的x。 该文档提出了两种实现LinearOperator的方法,但是

只要一件事可以通过两种方式完成,就会有人感到困惑。

无论如何,下面的Linop 可以与稀疏的x 一起使用——带有修补的lsmr.py, 在gist.github.com/denis-bz 下。 其他稀疏迭代求解器?不知道。


如果你真正想做的是:
最小化 |A x - b|
并保持|x|小,也就是正则化,在 L1 或 L2 范数中

那么你一定要看看 scikit-learn 。 它针对不同的角落
速度 - 准确性 - 问题 - 人(SAPP)空间 比 scipy.sparse.isolve 。 我发现 scikit-learn 扎实、令人愉快、有据可查, 但还没有在实际问题上对两者进行比较。

你的问题有多大,有多少?您能指出网络上的测试用例吗?


""" Linop( A ): .matvec .rmatvec( sparse vecs )
http://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.linalg.LinearOperator.html
http://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.linalg.lsmr.html
"""

from __future__ import division
import numpy as np
from scipy import sparse
from scipy.sparse.linalg import LinearOperator  # $scipy/sparse/linalg/interface.py

__version__ = "2015-12-24 dec  denis + safe_sparse_dot"


#...............................................................................
class Linop( LinearOperator ):  # subclass ?
    """ Aop = Linop( scipy sparse matrix A )
        ->  Aop.matvec(x) = A dot x, x ndarray or sparse
            Aop.rmatvec(x) = A.T dot x
        for scipy.sparse.linalg solvers like lsmr
    """

    def __init__( self, A ):
        self.A = A

    def matvec( self, x ):
        return safe_sparse_dot( self.A, x )

    def rmatvec( self, y ):
        return safe_sparse_dot( self.A.T, y )

        # LinearOperator subclass should implement at least one of _matvec and _matmat.
    def _matvec( self, b ):
        raise NotImplementedError( "_matvec" )

        # not _matvec only:
        # $scipy/sparse/linalg/interface.py
        # def matvec(self, x):
        #     x = np.asanyarray(x)  <-- kills sparse x, should raise an error

    def _rmatvec( self, b ):
        raise NotImplementedError( "_rmatvec" )

    @property
    def shape( self ):
        return self.A.shape


def safe_sparse_dot( a, b ):
    """ -> a * b or np.dot(a, b) """
        # from sklearn
    if sparse.issparse(a) or sparse.issparse(b):
        try:
            return a * b
        except ValueError:  # dimension mismatch: print shapes
            print "error: %s %s  *  %s %s" % (
                    type(a).__name__, a.shape,
                    type(b).__name__, b.shape )
            raise
    else:
        return np.dot(a, b)

#...........................................................................
if __name__ == "__main__":
    import sys
    from lsmr import lsmr  # patched $scipy/sparse/linalg/lsmr.py

    np.set_printoptions( threshold=20, edgeitems=10, linewidth=100, suppress=True,
        formatter = dict( float = lambda x: "%.2g" % x ))

        # test sparse.rand A m n, x n 1, b m 1
    m = 10
    n = 100
    density = .1
    bdense = 0
    seed = 0
    damp = 1

        # to change these params in sh or ipython, run this.py  a=1  b=None  c=[3] ...
    for arg in sys.argv[1:]:
        exec( arg )

    np.random.seed(seed)

    print "\n", 80 * "-"
    paramstr = "%s  m %d  n %d  density %g  bdense %d  seed %d  damp %g " % (
            __file__, m, n, density, bdense, seed, damp )
    print paramstr

    A = sparse.rand( m, n, density, format="csr", random_state=seed )
    x = sparse.rand( n, 1, density, format="csr", random_state=seed )
    b = sparse.rand( m, 1, density, format="csr", random_state=seed )
    if bdense:
        b = b.toarray().squeeze()  # matrix (m,1) -> ndarray (m,)

    #...........................................................................
    Aop = Linop( A )
        # aslinearoperator( A ): not for sparse x

        # check Aop matvec rmatvec --
    Ax = Aop.matvec( x )
    bA = Aop.rmatvec( b )
    for nm in "A Aop x b Ax bA ".split():
        x = eval(nm)
        print "%s: %s %s " % (nm, x.shape, type(x))
    print ""

    print "lsmr( Aop, b )"

    #...........................................................................
    xetc = lsmr( Aop, b, damp=damp, show=1 )
    #...........................................................................

    x, istop, niter, normr, normar, norma, conda, normx = xetc
    x = getattr( x, "A", x ) .squeeze()
    print "x:", x.shape, x

    #     print "lsmr( A, b ) -- Valueerror in $scipy/sparse/linalg/interface.py"
    #     xetc = lsmr( A, b, damp=damp, show=1 )  # Valueerror

    safe_sparse_dot( A, b.T )  # ValueError: dimension mismatch

【讨论】:

  • 谢谢,这似乎是现在要走的路!仍然不明白为什么scipy.sparse.linalg 的直接求解器spsolve 支持稀疏右手边,而迭代求解器不支持......尽管在这种情况下问题大小实际上并没有太大而无法将其密集存储。
  • @Christoph90,对稀疏右轴的需求不足; 99% 是“不会太大而不能密集存储”。而且打过补丁的 lsmr 到目前为止不值得费心。
  • 我不是scipy.sparse.linalg 的开发人员,但我可以说明为什么它可能不受支持。从迭代求解返回的解非常可能是密集的。这些算法反复调用矩阵向量积函数。每次您进行矩阵向量乘积时,您都会填写更多非零条目,新的非零条目是通过执行广度优先搜索的单个步骤来确定的。因此,如果矩阵具有“strong-hall”属性,则可以保证在足够的迭代后得到密集输出,并且所有后续迭代都将是完全密集的。
  • @Reid.Atcheson,可以的,谢谢。但是,如果您在每次迭代后要求/保持稀疏性,x[ abs(x) &lt; tiny ] = 0 怎么办? Afaik(不是很远)L1优化器/套索做到这一点。另外,什么是“强厅”属性?谢谢
  • 我从来没有尝试过这样的阈值,所以我不知道从个人经验来看这是否可行。我可以说它是一个非线性运算,并且大多数迭代求解器 - CG、GMRES、BiCGSTAB 明确要求输入运算符的线性度。它可能适用于某些矩阵,其中逆矩阵的条目具有指数衰减特性,因此非线性运算是线性运算的小扰动。但我从未尝试过。
猜你喜欢
  • 2016-02-05
  • 1970-01-01
  • 1970-01-01
  • 2010-11-27
  • 1970-01-01
  • 2018-08-23
  • 2020-05-11
  • 2019-12-30
  • 2021-09-13
相关资源
最近更新 更多