计算吸收马尔可夫链基本矩阵的最佳迭代方法?

时间:2017-07-18 10:43:30

标签: algorithm math sparse-matrix markov-chains

我有一个非常大的吸收马尔可夫链。我想获得这个链的基本矩阵来计算expected number of steps before absortion。从这个question我知道这可以通过等式计算

(I - Q)t = 1

可以使用以下python代码获得:

def expected_steps_fast(Q):
    I = numpy.identity(Q.shape[0])
    o = numpy.ones(Q.shape[0])
    numpy.linalg.solve(I-Q, o)

然而,我想使用某种类似于用于计算PageRank的幂迭代方法的迭代方法来计算它。这种方法可以让我在类似mapreduce的系统中计算出吸收前预期步数的近似值。

¿是否存在类似情况?

2 个答案:

答案 0 :(得分:0)

如果您有稀疏矩阵,请检查scipy.spare.linalg.spsolve是否有效。不保证数值的鲁棒性,但至少对于琐碎的例子,它比用密集矩阵求解要快得多。

import networkx as nx
import numpy as np
import scipy.sparse as sp
import scipy.sparse.linalg as spla

def example(n):
    """Generate a very simple transition matrix from a directed graph
    """
    g = nx.DiGraph()
    for i in xrange(n-1):
        g.add_edge(i+1, i)
        g.add_edge(i, i+1)
    g.add_edge(n-1, n)
    g.add_edge(n, n)
    m = nx.to_numpy_matrix(g)
    # normalize rows to ensure m is a valid right stochastic matrix
    m = m / np.sum(m, axis=1)
    return m

A = sp.csr_matrix(example(2000)[:-1,:-1])
Ad = np.array(A.todense())

def sp_solve(Q):
    I = sp.identity(Q.shape[0], format='csr')
    o = np.ones(Q.shape[0])
    return spla.spsolve(I-Q, o)

def dense_solve(Q):
    I = numpy.identity(Q.shape[0])
    o = numpy.ones(Q.shape[0])
    return numpy.linalg.solve(I-Q, o)

稀疏解决方案的时间安排:

%timeit sparse_solve(A)
1000 loops, best of 3: 1.08 ms per loop

密集解决方案的时间:

%timeit dense_solve(Ad)
1 loops, best of 3: 216 ms per loop

就像Tobias在评论中提到的那样,我希望其他解决方案能够胜过通用解决方案,而且它们可能适用于非常大的系统。对于这个玩具示例,通用解决方案似乎运作良好。

答案 1 :(得分:0)

由于@ tobias-ribizel建议使用Neumann series,我得到了这个回答。如果我们按照以下等式分开:

t=(I-Q)^-1 1

使用Neumann系列:

t=sum_0_inf(Q^k)1

如果我们将系列的每个项乘以向量 1 ,我们可以在矩阵 Q 的每一行上单独操作,并连续近似:

t=sum_0_inf(Q*Q^k-1*1)

这是我用来计算这个的python代码:

def expected_steps_iterative(Q, n=10):
    N = Q.shape[0]
    acc = np.ones(N)
    r_k_1 = np.ones(N)
    for k in range(1, n):
        r_k = np.zeros(N)
        for i in range(N):
            for j in range(N):
                r_k[i] += r_k_1[j] * Q[i, j]
        if np.allclose(acc, acc+r_k, rtol=1e-8):
            acc += r_k
            break
        acc += r_k
        r_k_1 = r_k
    return acc

这是使用Spark的代码。此代码期望Q是RDD,其中每一行都是元组(row_id,矩阵该行的权重的dict)。

def expected_steps_spark(sc, Q, n=10):
    def dict2np(d, sz):
        vec = np.zeros(sz)
        for k, v in d.iteritems():
            vec[k] = v
        return vec
    sz = Q.count()
    acc = np.ones(sz)
    x = {i:1.0 for i in range(sz)}
    for k in range(1, n):
        bc_x = sc.broadcast(x)
        x_old = x
        x = Q.map(lambda (u, ol): (u, reduce(lambda s, j: s + bc_x.value[j]*ol[j], ol, 0.0)))
        x = x.collectAsMap()
        v_old = dict2np(x_old, sz)
        v = dict2np(x, sz)
        acc += v
        if np.allclose(v, v_old, rtol=1e-8):
            break
    return acc