【问题标题】:How can I calculate the nearest positive semi-definite matrix?如何计算最近的半正定矩阵?
【发布时间】:2012-06-11 22:39:54
【问题描述】:

我从 R 开始使用 Python,并尝试使用 Python 重现我过去在 R 中所做的许多事情。 R 的 Matrix 库有一个非常漂亮的函数,称为 nearPD(),它可以找到最接近给定矩阵的半正定 (PSD) 矩阵。虽然我可以编写一些代码,但作为 Python/Numpy 的新手,如果已经存在某些东西,我不会对重新发明轮子感到太兴奋。关于 Python 中现有实现的任何提示?

【问题讨论】:

  • 你在stackoverflow.com/questions/619335/… 上查看过这个问题吗?答案显示了 numpy 的 PSD 数学。
  • ack...我正在搜索没有的python标签...我会看看,如果多余的话,我会提出我的问题并将Python标签添加到那个跨度>
  • 我看了那个问题,他们只是在不考虑“最近”的情况下将矩阵强制转换为 PSD。相比之下,这是 nearPD() 所基于的 Higham 论文:maths.manchester.ac.uk/~nareports/narep369.pdf
  • scipy函数cov_nearest不做必要的转换吗?
  • @diraria 这是对这个问题的一个很好的回答,您应该将其添加为答案,而不是评论。最初没有人回答 cov_nearestcorr_nearest 的原因是这些功能直到 2012 年 8 月才添加,而我在 2012 年 6 月提出了这个问题。

标签: python matrix numpy


【解决方案1】:

我不认为有一个库可以返回您想要的矩阵,但这里是 Higham (2000) 的近东正半正定矩阵算法的“只是为了好玩”编码

import numpy as np,numpy.linalg

def _getAplus(A):
    eigval, eigvec = np.linalg.eig(A)
    Q = np.matrix(eigvec)
    xdiag = np.matrix(np.diag(np.maximum(eigval, 0)))
    return Q*xdiag*Q.T

def _getPs(A, W=None):
    W05 = np.matrix(W**.5)
    return  W05.I * _getAplus(W05 * A * W05) * W05.I

def _getPu(A, W=None):
    Aret = np.array(A.copy())
    Aret[W > 0] = np.array(W)[W > 0]
    return np.matrix(Aret)

def nearPD(A, nit=10):
    n = A.shape[0]
    W = np.identity(n) 
# W is the matrix used for the norm (assumed to be Identity matrix here)
# the algorithm should work for any diagonal W
    deltaS = 0
    Yk = A.copy()
    for k in range(nit):
        Rk = Yk - deltaS
        Xk = _getPs(Rk, W=W)
        deltaS = Xk - Rk
        Yk = _getPu(Xk, W=W)
    return Yk

对论文中的示例进行测试时,它会返回正确答案

print nearPD(np.matrix([[2,-1,0,0],[-1,2,-1,0],[0,-1,2,-1],[0,0,-1,2]]),nit=10)
[[ 1.         -0.80842467  0.19157533  0.10677227]
 [-0.80842467  1.         -0.65626745  0.19157533]
 [ 0.19157533 -0.65626745  1.         -0.80842467]
 [ 0.10677227  0.19157533 -0.80842467  1.        ]]

【讨论】:

  • 嘿,这太棒了!谢谢。
  • 伙计,我希望更多的人认为这种东西“只是为了好玩”。但说真的,这是很酷的东西。惊人的。也许考虑将其提交给 scipy 之类的?
  • 您的输入矩阵 ([[2,-1,0,0]...]) 已经是正定的。并且您的输出矩阵从该输入具有 2.35 的 Frobenius 范数。我不太确定发生了什么,但我将 Higham 1988 年论文的 Matlab 实现移植到 Python,我将在此处留下参考:stackoverflow.com/a/43244194/500207
  • 上面答案中的代码似乎基于 Higham 2000 maths.manchester.ac.uk/~higham/narep/narep369.pdf,其目标是计算最近的 correlation 矩阵(即执行单位对角线)。这比最接近的对称半正定矩阵限制得多,这是 Higham 1988 的主题,@AhmedFasih 的代码基于该矩阵。 sciencedirect.com/science/article/pii/0024379588902236
【解决方案2】:

我将提交非迭代方法。这是从Rebonato and Jackel (1999)(第 7-9 页)略微修改的。迭代方法可能需要很长时间来处理包含数百个变量的矩阵。

import numpy as np

def nearPSD(A,epsilon=0):
   n = A.shape[0]
   eigval, eigvec = np.linalg.eig(A)
   val = np.matrix(np.maximum(eigval,epsilon))
   vec = np.matrix(eigvec)
   T = 1/(np.multiply(vec,vec) * val.T)
   T = np.matrix(np.sqrt(np.diag(np.array(T).reshape((n)) )))
   B = T * vec * np.diag(np.array(np.sqrt(val)).reshape((n)))
   out = B*B.T
   return(out)

代码是从围绕 R 中的非 PD/PSD 矩阵讨论此主题 here 修改而来的。

【讨论】:

  • 这可能是一个愚蠢的问题,但你可以转换回来,即从 PSD 转换为原始矩阵吗?
  • 不,你已经从根本上改变了矩阵。如果您需要原件,请确保保留一份副本(当然,如果内存允许)。
  • 此代码似乎有误。只需使用简单的正定对角矩阵 A = np.diag([1,2,3]) 进行测试,就会输出一个不同的结果,即恒等式。
【解决方案3】:

这可能是对 DomPazz 答案的愚蠢扩展,以考虑相关矩阵和协方差矩阵。如果您要处理大量矩阵,它也会提前终止。

def near_psd(x, epsilon=0):
    '''
    Calculates the nearest postive semi-definite matrix for a correlation/covariance matrix

    Parameters
    ----------
    x : array_like
      Covariance/correlation matrix
    epsilon : float
      Eigenvalue limit (usually set to zero to ensure positive definiteness)

    Returns
    -------
    near_cov : array_like
      closest positive definite covariance/correlation matrix

    Notes
    -----
    Document source
    http://www.quarchome.org/correlationmatrix.pdf

    '''

    if min(np.linalg.eigvals(x)) > epsilon:
        return x

    # Removing scaling factor of covariance matrix
    n = x.shape[0]
    var_list = np.array([np.sqrt(x[i,i]) for i in xrange(n)])
    y = np.array([[x[i, j]/(var_list[i]*var_list[j]) for i in xrange(n)] for j in xrange(n)])

    # getting the nearest correlation matrix
    eigval, eigvec = np.linalg.eig(y)
    val = np.matrix(np.maximum(eigval, epsilon))
    vec = np.matrix(eigvec)
    T = 1/(np.multiply(vec, vec) * val.T)
    T = np.matrix(np.sqrt(np.diag(np.array(T).reshape((n)) )))
    B = T * vec * np.diag(np.array(np.sqrt(val)).reshape((n)))
    near_corr = B*B.T    

    # returning the scaling factors
    near_cov = np.array([[near_corr[i, j]*(var_list[i]*var_list[j]) for i in xrange(n)] for j in xrange(n)])
    return near_cov

【讨论】:

    【解决方案4】:

    我知道这个线程很旧,但这里提供的解决方案对我的协方差矩阵并不满意:转换后的矩阵看起来总是与原始矩阵完全不同(至少对于我测试的情况)。所以,我在这里留下一个非常直接的答案,基于this answer 中提供的解决方案:

    import numpy as np
    
    def get_near_psd(A):
        C = (A + A.T)/2
        eigval, eigvec = np.linalg.eig(C)
        eigval[eigval < 0] = 0
    
        return eigvec.dot(np.diag(eigval)).dot(eigvec.T)
    

    这个想法很简单:我计算对称矩阵,然后进行特征分解以获得特征值和特征向量。我将所有负特征值归零并构造回矩阵,该矩阵现在是半正定矩阵。

    为了完整起见,我留下一个简单的代码来使用numpy检查一个矩阵是否是半正定的(基本上检查所有特征值是否都是非负的):

    def is_pos_semidef(x):
        return np.all(np.linalg.eigvals(x) >= 0)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-12-18
      • 2020-02-16
      • 2015-05-17
      • 1970-01-01
      • 2013-05-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多