【问题标题】:find Markov steady state with left eigenvalues (using numpy or scipy)找到具有左特征值的马尔可夫稳态(使用 numpy 或 scipy)
【发布时间】:2015-10-28 08:04:13
【问题描述】:

我需要使用一些 python 代码使用其转移矩阵的左特征向量找到马尔可夫模型的稳态。

this question 中已经确定 scipy.linalg.eig 无法提供所描述的实际左特征向量,但在那里演示了修复。官方文档像往常一样大多是无用和难以理解的。

比不正确的格式更大的问题是产生的特征值没有任何特定的顺序(没有排序并且每次都不同)。因此,如果您想找到与 1 个特征值相对应的左特征向量,您必须寻找它们,这会带来它自己的问题(见下文)。数学很清楚,但如何让 python 计算并返回正确的特征向量尚不清楚。这个问题的其他答案,比如this one,似乎没有使用左特征向量,所以这些不是正确的解决方案。

This question 提供了部分解决方案,但它没有考虑较大转移矩阵的无序特征值。所以,只需使用

leftEigenvector = scipy.linalg.eig(A,left=True,right=False)[1][:,0]
leftEigenvector = leftEigenvector / sum(leftEigenvector)

很接近,但通常不起作用,因为[:,0] 位置中的条目可能不是正确特征值的特征向量(在我的情况下通常不是)。

好的,但是scipy.linalg.eig(A,left=True,right=False) 的输出是一个数组,其中[0] 元素是每个特征值的列表(不按任何顺序),并且在位置[1] 后面跟着一个特征向量数组这些特征值的对应顺序。

我不知道如何通过特征值对整个事物进行排序或搜索以提取正确的特征向量(特征值为 1 的所有特征向量都由向量条目的总和归一化。)我的想法是获取索引的特征值等于 1,然后从特征向量数组中提取这些列。我的这个版本既慢又麻烦。首先,我有一个函数(不太好用)来查找最后一个匹配值的位置:

# Find the positions of the element a in theList
def findPositions(theList, a):
  return [i for i, x in enumerate(theList) if x == a]

然后我像这样使用它来获得与特征值= 1匹配的特征向量。

M = transitionMatrix( G )
leftEigenvectors = scipy.linalg.eig(M,left=True,right=False)
unitEigenvaluePositions = findPositions(leftEigenvectors[0], 1.000)
steadyStateVectors = []
for i in unitEigenvaluePositions:
    thisEigenvector = leftEigenvectors[1][:,i]
    thisEigenvector / sum(thisEigenvector)
    steadyStateVectors.append(thisEigenvector)
print steadyStateVectors

但实际上这不起作用。有一个 eigenvalue = 1.00000000e+00 +0.00000000e+00j 没有找到,即使另外两个有。

我的期望是我不是第一个使用 python 来查找马尔可夫模型的平稳分布的人。更精通/经验丰富的人可能有一个可行的通用解决方案(无论是否使用 numpy 或 scipy)。考虑到马尔可夫模型的流行程度,我预计会有一个库供他们执行此任务,也许它确实存在,但我找不到。

【问题讨论】:

  • 我对马尔可夫链分析不太熟悉,但听起来你或多或少已经掌握了它。您为您想要的特征值搜索的解决方案似乎很合理。如果性能是一个问题,也许发布您正在使用的代码以便有人可以查看?
  • 性能不是一个严重的问题,只要它确实有效。目前在特征值列表中搜索等于 1 的那些不起作用:它错过了其中一个。另外两个不对应于实际的稳态,因此特征向量条目的顺序也可能以文档中未说明的某种方式混乱。或者问题可能是我的矩阵有多个独立的吸引子状态。
  • 可能是精度问题?您可以尝试使用 epsilon 一些合适的小数字代替 x == a 之类的 x < a + epsilon && x > a - epsilon
  • w, vl = eig(P, right=False, left=True); tol = 1e-15; v1 = vl[:,abs(w - 1)

标签: python numpy scipy eigenvector markov-chains


【解决方案1】:

您链接到 How do I find out eigenvectors corresponding to a particular eigenvalue of a matrix? 并说它不计算左特征向量,但您可以通过使用转置来解决这个问题。

例如,

In [901]: import numpy as np

In [902]: import scipy.sparse.linalg as sla

In [903]: M = np.array([[0.5, 0.25, 0.25, 0], [0, 0.1, 0.9, 0], [0.2, 0.7, 0, 0.1], [0.2, 0.3, 0, 0.5]])

In [904]: M
Out[904]: 
array([[ 0.5 ,  0.25,  0.25,  0.  ],
       [ 0.  ,  0.1 ,  0.9 ,  0.  ],
       [ 0.2 ,  0.7 ,  0.  ,  0.1 ],
       [ 0.2 ,  0.3 ,  0.  ,  0.5 ]])

In [905]: eval, evec = sla.eigs(M.T, k=1, which='LM')

In [906]: eval
Out[906]: array([ 1.+0.j])

In [907]: evec
Out[907]: 
array([[-0.32168797+0.j],
       [-0.65529032+0.j],
       [-0.67018328+0.j],
       [-0.13403666+0.j]])

In [908]: np.dot(evec.T, M).T
Out[908]: 
array([[-0.32168797+0.j],
       [-0.65529032+0.j],
       [-0.67018328+0.j],
       [-0.13403666+0.j]])

对特征向量进行归一化(你知道它应该是真实的):

In [913]: u = (evec/evec.sum()).real

In [914]: u
Out[914]: 
array([[ 0.18060201],
       [ 0.36789298],
       [ 0.37625418],
       [ 0.07525084]])

In [915]: np.dot(u.T, M).T
Out[915]: 
array([[ 0.18060201],
       [ 0.36789298],
       [ 0.37625418],
       [ 0.07525084]])

如果您事先不知道特征值 1 的多重性,请参阅 @pv. 的注释,其中显示使用 scipy.linalg.eig 的代码。这是一个例子:

In [984]: M
Out[984]: 
array([[ 0.9 ,  0.1 ,  0.  ,  0.  ,  0.  ,  0.  ],
       [ 0.3 ,  0.7 ,  0.  ,  0.  ,  0.  ,  0.  ],
       [ 0.  ,  0.  ,  0.25,  0.75,  0.  ,  0.  ],
       [ 0.  ,  0.  ,  0.5 ,  0.5 ,  0.  ,  0.  ],
       [ 0.  ,  0.  ,  0.  ,  0.  ,  0.  ,  1.  ],
       [ 0.  ,  0.  ,  0.  ,  0.  ,  1.  ,  0.  ]])

In [985]: import scipy.linalg as la

In [986]: evals, lvecs = la.eig(M, right=False, left=True)

In [987]: tol = 1e-15

In [988]: mask = abs(evals - 1) < tol

In [989]: evals = evals[mask]

In [990]: evals
Out[990]: array([ 1.+0.j,  1.+0.j,  1.+0.j])

In [991]: lvecs = lvecs[:, mask]

In [992]: lvecs
Out[992]: 
array([[ 0.9486833 ,  0.        ,  0.        ],
       [ 0.31622777,  0.        ,  0.        ],
       [ 0.        , -0.5547002 ,  0.        ],
       [ 0.        , -0.83205029,  0.        ],
       [ 0.        ,  0.        ,  0.70710678],
       [ 0.        ,  0.        ,  0.70710678]])

In [993]: u = lvecs/lvecs.sum(axis=0, keepdims=True)

In [994]: u
Out[994]: 
array([[ 0.75, -0.  ,  0.  ],
       [ 0.25, -0.  ,  0.  ],
       [ 0.  ,  0.4 ,  0.  ],
       [ 0.  ,  0.6 ,  0.  ],
       [ 0.  , -0.  ,  0.5 ],
       [ 0.  , -0.  ,  0.5 ]])

In [995]: np.dot(u.T, M).T
Out[995]: 
array([[ 0.75,  0.  ,  0.  ],
       [ 0.25,  0.  ,  0.  ],
       [ 0.  ,  0.4 ,  0.  ],
       [ 0.  ,  0.6 ,  0.  ],
       [ 0.  ,  0.  ,  0.5 ],
       [ 0.  ,  0.  ,  0.5 ]])

【讨论】:

    【解决方案2】:

    好的,在实施 Warren 的解决方案时我必须进行一些更改,我已将这些更改包括在下面。基本上是一样的,所以他得到了所有的赞誉,但是用 numpy 和 scipy 进行数值近似的现实需要更多的按摩,我认为这对其他人将来尝试这样做会有所帮助。我还将变量名称更改为超级新手友好。

    如果我有任何问题或有进一步建议的改进(例如速度),请告诉我。

    # in this case my Markov model is a weighted directed graph, so convert that nx.graph (G) into it's transition matrix
    M = transitionMatrix( G )   
    
    #create a list of the left eigenvalues and a separate array of the left eigenvectors
    theEigenvalues, leftEigenvectors = scipy.linalg.eig(M, right=False, left=True)  
    
    # for stationary distribution the eigenvalues and vectors are always real, and this speeds it up a bit
    theEigenvalues = theEigenvalues.real                 
    leftEigenvectors = leftEigenvectors.real
    
    # set how close to zero is acceptable as being zero...1e-15 was too low to find one of the actual eigenvalues
    tolerance = 1e-10 
    
    # create a filter to collect the eigenvalues that are near enough to zero                               
    mask = abs(theEigenvalues - 1) < tolerance           
    
    # apply that filter
    theEigenvalues = theEigenvalues[mask]                
    
    # filter out the eigenvectors with non-zero eigenvalues
    leftEigenvectors = leftEigenvectors[:, mask] 
    
    # convert all the tiny and negative values to zero to isolate the actual stationary distributions    
    leftEigenvectors[leftEigenvectors < tolerance] = 0  
    
    # normalize each distribution by the sum of the eigenvector columns
    attractorDistributions = leftEigenvectors / leftEigenvectors.sum(axis=0, keepdims=True)   
    
    # this checks that the vectors are actually the left eigenvectors, but I guess it's not needed to usage 
    #attractorDistributions = np.dot(attractorDistributions.T, M).T 
    
    # convert the column vectors into row vectors (lists) for each attractor (the standard output for this kind of analysis)
    attractorDistributions = attractorDistributions.T
    
    # a list of the states in any attractor with the approximate stationary distribution within THAT attractor (e.g. for graph coloring)         
    theSteadyStates = np.sum(attractorDistributions, axis=1)  
    

    以简单的复制和粘贴格式将所有内容放在一起:

    M = transitionMatrix( G ) 
    theEigenvalues, leftEigenvectors = scipy.linalg.eig(M, right=False, left=True)  
    theEigenvalues = theEigenvalues.real                 
    leftEigenvectors = leftEigenvectors.real
    tolerance = 1e-10            
    mask = abs(theEigenvalues - 1) < tolerance 
    theEigenvalues = theEigenvalues[mask]    
    leftEigenvectors = leftEigenvectors[:, mask] 
    leftEigenvectors[leftEigenvectors < tolerance] = 0  
    attractorDistributions = leftEigenvectors / leftEigenvectors.sum(axis=0, keepdims=True)   
    attractorDistributions = attractorDistributions.T
    theSteadyStates = np.sum(attractorDistributions, axis=0)  
    

    对生成的马尔可夫模型使用此分析产生了一个吸引子(三个),其稳态分布为 0.19835218 和 0.80164782,而数学上准确的值是 0.2 和 0.8。所以这超过了 0.1% 的折扣,这对科学来说是一个很大的错误。这不是一个真正的问题,因为如果准确性很重要,那么既然已经识别了各个吸引子,就可以使用矩阵子集对每个吸引子内的行为进行更准确的分析。

    【讨论】:

    • 看起来我需要更多帮助。这在 12 月底完美运行,每次都为手动生成的马尔可夫矩阵提供相同、正确的特征向量。在 SAME MATRIX 上重新运行 SAME CODE 现在会从同一组特征值中产生几个不同和不正确的左特征向量组之一......并且它会随着运行而变化。它工作得很好,没有任何改变,现在它坏了并且不一致。这怎么可能呢?有类似经历的人吗?有关修复的任何建议?
    • 最后,如果这还不够奇怪的话,当我从我的实验室计算机上运行代码时,它每次都能正常工作,但是从我的笔记本电脑上运行它会产生上述问题。相同的代码、相同的 WinPython x64 2.7.9.4 安装文件和相同的操作系统。有人知道是什么导致了这种差异吗?
    • 您能否更新此答案以包含完整的复制案例。了解可能出了什么问题的第一步是自己运行代码,但您可以发布的内容并不完整或独立
    • 好的,代码就是上面的加上一个numpy和scipy的导入,我会在this question添加一个带有多个吸引子的特定矩阵来生成一个最小的运行示例。
    • 我认为 transitionMatrix 是主要缺失的东西。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-02-07
    • 2014-09-26
    • 1970-01-01
    • 1970-01-01
    • 2013-01-19
    • 2022-01-02
    相关资源
    最近更新 更多