我有一个非常大的吸收马尔可夫链。我想获得这个链的基本矩阵来计算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的系统中计算出吸收前预期步数的近似值。
¿是否存在类似情况?
答案 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,我得到了这个回答。如果我们按照以下等式分开:
使用Neumann系列:
如果我们将系列的每个项乘以向量 1 ,我们可以在矩阵 Q 的每一行上单独操作,并连续近似:
这是我用来计算这个的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