【问题标题】:Markov chain stationary distributions with scipy.sparse?带有scipy.sparse的马尔可夫链平稳分布?
【发布时间】:2014-01-23 12:56:42
【问题描述】:

我有一个马尔可夫链作为一个大的稀疏scipy 矩阵A。 (我已经以scipy.sparse.dok_matrix 格式构建了矩阵,但转换为其他矩阵或将其构建为csc_matrix 都可以。)

我想知道这个矩阵的任何平稳分布p,它是特征值1 的特征向量。此特征向量中的所有条目都应为正数且加起来为 1,以表示概率分布。

这意味着我想要系统的任何解决方案 (A-I) p = 0p.sum()=1(其中I=scipy.sparse.eye(*A.shape) 是恒等矩阵),但(A-I) 不会是满秩的,甚至整个系统都可能是欠定的。此外,可以生成带有负条目的特征向量,这些特征向量不能被归一化为有效的概率分布。防止p 中的负面条目会很好。

  • 使用scipy.sparse.linalg.eigen.eigs 不是解决方案: 它不允许指定附加约束。 (如果特征向量包含负条目,归一化没有帮助。)此外,它与真实结果有很大的偏差,有时会出现收敛问题,表现得比scipy.linalg.eig 更差。 (另外,我使用了 shift-invert 模式,它改进了寻找我想要的特征值类型,但不是它们的质量。如果我不使用它,那就更过分了,因为我只对一个特定的特征值感兴趣,@987654334 @.)

  • 转换为稠密矩阵并使用scipy.linalg.eig不是解决方案:除了负输入问题,矩阵太大。

  • 使用scipy.sparse.spsolve 不是一个明显的解决方案: 矩阵要么不是正方形的(当结合加性约束和特征向量条件时),要么不是满秩的(当试图以某种方式分别指定它们时),有时两者都不是。

有没有一种好方法可以使用 python 以数字方式获取作为稀疏矩阵给出的马尔可夫链的静止状态? 如果有办法获得详尽的列表(也可能是几乎静止的状态),这是值得赞赏的,但不是必需的。

【问题讨论】:

    标签: python scipy sparse-matrix markov-chains


    【解决方案1】:

    可以在 Google 学者中找到几篇包含可能方法摘要的文章,这是一篇: http://www.ima.umn.edu/preprints/pp1992/932.pdf

    下面所做的是结合上面@Helge Dietert 提出的关于首先拆分为强连接组件的建议,以及上面链接的论文中的方法#4。

    import numpy as np
    import time
    
    # NB. Scipy >= 0.14.0 probably required
    import scipy
    from scipy.sparse.linalg import gmres, spsolve
    from scipy.sparse import csgraph
    from scipy import sparse 
    
    
    def markov_stationary_components(P, tol=1e-12):
        """
        Split the chain first to connected components, and solve the
        stationary state for the smallest one
        """
        n = P.shape[0]
    
        # 0. Drop zero edges
        P = P.tocsr()
        P.eliminate_zeros()
    
        # 1. Separate to connected components
        n_components, labels = csgraph.connected_components(P, directed=True, connection='strong')
    
        # The labels also contain decaying components that need to be skipped
        index_sets = []
        for j in range(n_components):
            indices = np.flatnonzero(labels == j)
            other_indices = np.flatnonzero(labels != j)
    
            Px = P[indices,:][:,other_indices]
            if Px.max() == 0:
                index_sets.append(indices)
        n_components = len(index_sets)
    
        # 2. Pick the smallest one
        sizes = [indices.size for indices in index_sets]
        min_j = np.argmin(sizes)
        indices = index_sets[min_j]
    
        print("Solving for component {0}/{1} of size {2}".format(min_j, n_components, indices.size))
    
        # 3. Solve stationary state for it
        p = np.zeros(n)
        if indices.size == 1:
            # Simple case
            p[indices] = 1
        else:
            p[indices] = markov_stationary_one(P[indices,:][:,indices], tol=tol)
    
        return p
    
    
    def markov_stationary_one(P, tol=1e-12, direct=False):
        """
        Solve stationary state of Markov chain by replacing the first
        equation by the normalization condition.
        """
        if P.shape == (1, 1):
            return np.array([1.0])
    
        n = P.shape[0]
        dP = P - sparse.eye(n)
        A = sparse.vstack([np.ones(n), dP.T[1:,:]])
        rhs = np.zeros((n,))
        rhs[0] = 1
    
        if direct:
            # Requires that the solution is unique
            return spsolve(A, rhs)
        else:
            # GMRES does not care whether the solution is unique or not, it
            # will pick the first one it finds in the Krylov subspace
            p, info = gmres(A, rhs, tol=tol)
            if info != 0:
                raise RuntimeError("gmres didn't converge")
            return p
    
    
    def main():
        # Random transition matrix (connected)
        n = 100000
        np.random.seed(1234)
        P = sparse.rand(n, n, 1e-3) + sparse.eye(n)
        P = P + sparse.diags([1, 1], [-1, 1], shape=P.shape)
    
        # Disconnect several components
        P = P.tolil()
        P[:1000,1000:] = 0
        P[1000:,:1000] = 0
    
        P[10000:11000,:10000] = 0
        P[10000:11000,11000:] = 0
        P[:10000,10000:11000] = 0
        P[11000:,10000:11000] = 0
    
        # Normalize
        P = P.tocsr()
        P = P.multiply(sparse.csr_matrix(1/P.sum(1).A))
    
        print("*** Case 1")
        doit(P)
    
        print("*** Case 2")
        P = sparse.csr_matrix(np.array([[1.0, 0.0, 0.0, 0.0],
                                        [0.5, 0.5, 0.0, 0.0],
                                        [0.0, 0.0, 0.5, 0.5],
                                        [0.0, 0.0, 0.5, 0.5]]))
        doit(P)
    
    def doit(P):
        assert isinstance(P, sparse.csr_matrix)
        assert np.isfinite(P.data).all()
    
        print("Construction finished!")
    
        def check_solution(method):
            print("\n\n-- {0}".format(method.__name__))
            start = time.time()
            p = method(P)
            print("time: {0}".format(time.time() - start))
            print("error: {0}".format(np.linalg.norm(P.T.dot(p) - p)))
            print("min(p)/max(p): {0}, {1}".format(p.min(), p.max()))
            print("sum(p): {0}".format(p.sum()))
    
        check_solution(markov_stationary_components)
    
    
    if __name__ == "__main__":
        main()
    

    编辑:发现一个错误 --- csgraph.connected_components 还返回纯衰减组件,需要过滤掉。

    【讨论】:

    【解决方案2】:

    这是求解一个可能未指定的矩阵方程,因此可以使用scipy.sparse.linalg.lsqr 完成。我不知道如何确保所有条目都是正面的,但除此之外,它非常简单。

    import scipy.sparse.linalg
    states = A.shape[0]
    
    # I assume that the rows of A sum to 1.
    # Therefore, In order to use A as a left multiplication matrix,
    # the transposition is necessary.
    eigvalmat = (A - scipy.sparse.eye(states)).T
    probability_distribution_constraint = scipy.ones((1, states))
    
    lhs = scipy.sparse.vstack(
        (eigvalmat,
         probability_distribution_constraint))
    
    B = numpy.zeros(states+1)
    B[-1]=1
    
    r = scipy.sparse.linalg.lsqr(lhs, B)
    # r also contains metadata about the approximation process
    p = r[0]
    

    【讨论】:

      【解决方案3】:

      解决固定解不唯一且解可能不是非负的问题。

      这意味着您的马尔可夫链不是不可约的,您可以将问题拆分为不可约的马尔可夫链。为此,您希望找到马尔可夫链的封闭通信类,这本质上是对转换图的连通分量的研究(Wikipedia 建议使用一些线性算法来查找强连通分量)。此外,您可以通过所有开放的通信类,因为每个静止状态都必须在这些类上消失。

      如果您有封闭的通信类 C_1,...,C_n,您的问题有望分解成小的简单部分:每个封闭类 C_i 上的马尔可夫链现在是不可约的,因此受限转移矩阵 M_i 恰好有一个特征向量特征值为 0 且该特征向量只有正分量(参见 Perron-Frobenius 定理)。因此我们只有一个静止状态 x_i。

      整个马尔可夫链的静止状态现在都是封闭类中 x_i 的线性组合。事实上,这些都是静止状态。

      为了找到静止状态 x_i,您可以连续应用 M_i,迭代将收敛到该状态(这也将保留您的标准化)。一般来说,很难判断收敛速度,但它为您提供了一种简单的方法来提高解决方案的准确性并验证解决方案。

      【讨论】:

        【解决方案4】:

        使用幂次迭代(例如):http://www.google.com/search?q=power%20iteration%20markov%20chain

        或者,您可以使用 scipy.sparse.linalg.eig(即 ARPACK)的 shift-invert 模式来查找接近 1 的特征值。“指定”标准化不是必需的,因为您可以在之后标准化这些值.

        【讨论】:

        • 我使用了 shift-invert 模式,它提高了查找我想要的特征值的类型,但不是它们的质量。此外,仅仅归一化特征向量是行不通的,因为它们可能包含负条目,我必须通过在特征向量之间找到正确的线性组合来解决这个问题——如果特征向量不精确,就会出现问题。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-08-29
        • 2016-11-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多