如何计算最近的正半定矩阵?

时间:2012-06-07 20:16:22

标签: python matrix numpy

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

4 个答案:

答案 0 :(得分:27)

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

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.        ]]

答案 1 :(得分:13)

我会提交一种非迭代的方法。这稍微修改了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中关于nonPD / PSD矩阵的here讨论中修改了代码。

答案 2 :(得分: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

答案 3 :(得分:0)

我知道这个线程很旧,但是这里提供的解决方案对于我的协方差矩阵并不令人满意:转换后的矩阵看起来总是与原始矩阵有很大不同(至少在我测试的情况下)。因此,根据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)